
简介面向物联网、计算机、自动化等相关专业的课程设计与毕业设计提供完整的作业场所粉尘危害监测预警系统源码与配套文档。项目采用物联网技术、数据库及软件工程方法聚焦煤矿、建筑工地、生产企业等典型场景的呼吸性粉尘实时检测与预警可支撑高校学生的课程设计、毕业设计或项目初期立项演示。资源包共952个文件以html、css、js前端代码为主含453个png图片、json配置、字体图标等素材整体仅5.06MB代码经测试可稳定运行。当前已有181人学习浏览。资源从需求分析、系统设计、数据库建模到功能实现全流程可循目录结构清晰附带详细文档说明答辩评审平均分达96分下载后若遇运行问题可私聊咨询并支持远程指导便于二次开发、功能扩展与学习进阶。1. 基于物联网的作业场所粉尘监测预警系统这个综设项目在解决什么粉尘是作业场所最常见的职业危害之一花岗岩加工、面粉包装、木材打磨、焊接工位看似风平浪静数据一旦超标就是事故。基于物联网的作业场所粉尘危害监测预警系统就是为这类场景做一套“端—管—云”闭环粉尘传感器实时采样STM32 节点做采集与判定LoRa/Wi-Fi 传到网关云端按职业接触限值分档报警同时联动风机和声光设备。这个题目几乎年年出现在物联网工程、安全工程的专业综合设计里因为技术点都在真实项目桌上传感器选型、通信组网、嵌入式代码、云平台预警、文档说明。本文不讲课程作业的应付写法按可落地、可复现、可升级的路径拆开从硬件选型一直讲到验收演示和避坑适合物联网毕业设计和现场技改两条路线的读者。2. 系统链路与硬件选型传感器、STM32网关和两种组网方式2.1 传感器选型GP2Y1010 与 PMS5003 的取舍先谈感知端因为整个系统的精度上限由它决定。作业场所粉尘监测最常用的传感器有两类一类是红外散射原理的 GP2Y1010AU0F另一类是激光散射原理的 PMS5003 / SDS011。两者都不是“精度越高越好”而是看你想要什么GP2Y1010 输出的是模拟电压需要自己做脉冲驱动、采样时序和标定换算工作量集中在模拟前端练的是嵌入式基本功PMS5003 内部完成粒子计数和浓度计算直接通过 UART 输出 PM1.0/PM2.5/PM10 三个数值开发难度低数据更稳定但价格是前者的三到五倍。两类传感器在项目中的典型选型思路是主传感器用 PMS5003保证测量结果可信辅路再加一路 GP2Y1010 做对比和标定用同一个烟箱同时采集两组数据既能证明你理解传感器原理又能在答辩时解释“为什么红外方案会漂、激光方案为什么稳”。如果预算卡得很死全用 GP2Y1010 也可以但在文档说明里必须写明标定方法和局限否则评审会直接问“你这个电压读数换算成浓度依据是什么”。型号原理输出量程供电参考价GP2Y1010AU0F红外 LED 散射模拟电压约 0~0.8 mg/m³5V低PMS5003激光散射粒子计数UART 数字帧0~999 µg/m³5V中SDS011激光散射UART 数字帧0~999 µg/m³5V中低对于综设项目我一般建议直接以 PMS5003 为主传感器GP2Y1010 作为扩展或对比。理由是 PMS5003 的数值在演示现场更容易被“接受”你拿一支点燃的香烟靠近传感器几秒内 PM2.5 从几十跳到几百观众能直观看到这份体感是红外方案给不了的。2.2 通信组网直连 Wi-Fi 与 LoRa 集中转发的选择通信层决定了系统的拓扑也决定“物联网”这三个字落在哪一段。对于综设项目最常见且可靠的组网方式有两种。第一种是每个粉尘节点直接用 ESP8266 模块连 Wi-Fi把数据上报到 MQTT 服务器。这种结构简单、代码量小适合实验室环境问题在于部署现场往往没有覆盖良好的无线网络而且节点一多路由器压力大掉线排查很麻烦。第二种是 LoRa 节点 集中式网关每个粉尘采集节点挂一个 LoRa 模块把数据用串口透明传输方式发给网关网关负责接收所有节点数据再通过 ESP8266 或 4G 模块统一上云。这种拓扑更接近工业现场的真实部署也是“stm32 物联网网关”这个词在毕设里出现频率最高的原因。LoRa 的优势是穿墙能力和距离室内厂房几百米没有压力缺点是速率低不适合传大包但粉尘浓度本来就是一分钟一条的小数据完全够用。实际部署时网关与路由器/交换机之间要注意如果 MQTT broker 跑在服务器上网关的 ESP8266 走 Wi-Fi 接入路由器服务器在另一个网段就需要在交换机侧放行 1883 或 8883 端口如果同一台电脑既跑 broker 又跑 Web 服务就让网关和电脑接在同一个局域网段避免跨三层访问导致的不必要排错。这类网络问题看着是小事却常把项目卡在联调阶段好几天。2.3 硬件接线清单与电源设计最小系统板和关键电平下面给出一套我常用且稳定复现过的硬件清单按“能跑通、不过度设计”的原则选型部件型号/规格数量说明主控节点STM32F103C8T6 最小系统板1~N每路粉尘节点一块粉尘传感器PMS5003 或 GP2Y1010AU0F每节点 1~2 路主传感器必装无线模块E32-433T20DLoRa 串口透传每节点 1 个选 LoRa 方案时用Wi-Fi 模块ESP8266-01S每个网关 1 个数据上云用网关板STM32F407 或 NodeMCU1汇接 LoRa 数据并转发继电器5V 单路继电器模块1~2控制风机/声光显示0.96 寸 OLEDI2C1节点就地显示浓度电源12V/2A 适配器 DC-DC 降压 5V1节点和网关分开供电更稳接线中要特别注意两个电平问题。GP2Y1010 虽然用 5V 供电但模拟输出范围并不会超过 3.6VSTM32 的 ADC 可以直接读取PMS5003 的 UART 输出也是 5V TTL 电平接到 STM32 的 PA9/PA10 之前最好加一颗电平转换芯片或者用电阻分压否则长期使用容易把引脚击穿。很多项目“跑几天就挂”原因并不是代码而是这种电平细节没处理。电源设计上PMS5003 启动瞬间电流能达到 100mA 以上GP2Y1010 的 LED 脉冲也容易拉低 VCC。我的经验是传感器和 STM32 不要共用同一路 LDO至少给传感器加一颗 10µF 以上的钽电容或电解电容做缓冲否则 ADC 采样值会带着电源纹波一起晃动。3. 粉尘采集源代码落到板子脉冲时序、滤波换算与 MQTT 上报3.1 采集驱动GP2Y1010 的 LED 脉冲与 ADC 采样时序GP2Y1010 的驱动方式需要在一开始就立好规矩。这颗传感器内部有一颗 LED必须通过外部脉冲点亮输出信号才能被 ADC 正确采样不能简单地接上电源就读。数据手册给出的典型时序是周期约 10msLED 脉冲宽度约 0.32ms采样点要落在 LED 脉冲开启后的 0.28ms~0.30ms 之间。下面这段代码是我在 STM32F103 上用 GPIO 模拟脉冲并采样 ADC 的最小实现/* dust_sensor.c * 引脚约定PA0 接 GP2Y1010 的 LED 脉冲控制脚 * PA1 接 GP2Y1010 的 V0 模拟输出到 ADC1_IN1 */ #include gpio.h #include adc.h #define LED_PULSE_PIN GPIO_PIN_0 #define DUST_PERIOD_US 10000 /* 采样周期 10ms符合数据手册 */ #define LED_PULSE_WIDTH_US 320 /* LED 脉冲宽度 0.32ms */ #define ADC_SAMPLE_DELAY_US 290 /* 采样点取 LED 开启后 0.29ms */ void dust_read_raw(uint16_t *adc_val) { /* LED 脉冲拉高 */ HAL_GPIO_WritePin(GPIOA, LED_PULSE_PIN, GPIO_PIN_SET); delay_us(ADC_SAMPLE_DELAY_US); /* 启动一次 ADC 转换并等待完成 */ ADC_SoftwareStartConv(hadc1); while (HAL_ADC_GetState(hadc1) HAL_ADC_STATE_BUSY_INTERNAL); *adc_val HAL_ADC_GetValue(hadc1); /* 脉冲拉低保持到周期结束 */ HAL_GPIO_WritePin(GPIOA, LED_PULSE_PIN, GPIO_PIN_RESET); delay_us(DUST_PERIOD_US - LED_PULSE_WIDTH_US - ADC_SAMPLE_DELAY_US); }逻辑说明delay_us做的是微秒级阻塞延时采样点必须控制在 LED 脉冲宽度之内提前或拖后都会读到非峰值电压。ADC_SoftwareStartConv是单次转换模式适合每周期采一次如果改成连续采样模式反而要额外处理缓存和溢出问题没必要。参数说明DUST_PERIOD_US设为 10ms意味着每秒采 100 次对粉尘这种缓变信号来说已经冗余ADC_SAMPLE_DELAY_US是采样窗口位置对测量结果影响最大不同批次的 GP2Y1010 会有细微差异可以在 280~310µs 之间微调以输出电压最高且稳定为准。3.2 滤波与浓度换算滑动平均、标定系数与自检注意采集到的裸 ADC 值不能直接当浓度用。GP2Y1010 的输出电压与粉尘浓度近似线性但不同批次的传感器基准电压不同必须在代码里做两点标定干净环境下测基准电压然后在一个有已知浓度的烟箱里测增益。我在项目中普遍这样处理先用滑动平均滤掉单点毛刺再做标定换算。/* 滑动平均去掉最大最小值后求均值长度 10 */ #define SAMPLE_BUF_LEN 10 float dust_sliding_average(uint16_t raw) { static uint16_t buf[SAMPLE_BUF_LEN]; static uint8_t idx 0; uint16_t min_val 0xFFFF, max_val 0; uint32_t sum 0; uint8_t i; buf[idx] raw; idx (idx 1) % SAMPLE_BUF_LEN; for (i 0; i SAMPLE_BUF_LEN; i) { if (buf[i] min_val) min_val buf[i]; if (buf[i] max_val) max_val buf[i]; sum buf[i]; } /* 去掉一个最大、一个最小后除以8 */ return (float)(sum - min_val - max_val) / (SAMPLE_BUF_LEN - 2); } /* 电压换算浓度V0 基准 500mV增益按实测标定 */ float dust_voltage_to_mg_m3(float raw_mv) { float density (raw_mv - 500.0f) / 500.0f * 0.1f; if (density 0.0f) density 0.0f; return density; }逻辑说明dust_sliding_average采用“去极值 平均”比纯滑动平均更抗脉冲干扰。现场偶尔会有电机启停产生的电磁毛刺单次采样会跳出几百毫伏直接平均会把浓度拉高去掉最大最小值后再平均就不会误报。参数说明SAMPLE_BUF_LEN取 10综合了响应速度和稳定性容积越小越灵敏越大越平滑。如果你使用的是 PMS5003不需要这层换算直接用串口解析出来的 PM2.5 数值即可如果是 GP2Y1010标定系数比如等比增益 0.1必须根据实测修正不能手抄数据手册的典型值当万能公式。3.3 MQTT 上报与源代码管理Payload 设计和传输细节数据采集齐全后最重要的一环是“上云”。我习惯把节点上报协议设计成 JSON 文本因为它可读性强、容易扩展字段在文档说明里也方便给别人讲清楚。下面是构建 MQTT PUBLISH 报文的核心片段/* mqtt_client.c * 场景ESP8266 透明传输模式下STM32 直接按 MQTT 协议封装报文 */ static void mqtt_build_publish(uint8_t *packet, const char *topic, const char *payload, uint16_t *out_len) { uint8_t idx 1; /* 位置0留给固定头 0x30 */ uint16_t topic_len strlen(topic); uint16_t payload_len strlen(payload); /* 固定报文头PUBLISHQoS0不保留 */ packet[0] 0x30; /* 剩余长度的占位先写1字节后续回填 */ packet[idx] 0; uint8_t *remaining_len_pos packet[1]; /* 变量头主题长度 主题内容 */ packet[idx] (uint8_t)(topic_len 8); packet[idx] (uint8_t)(topic_len 0xFF); memcpy(packet[idx], topic, topic_len); idx topic_len; /* 载荷 */ memcpy(packet[idx], payload, payload_len); idx payload_len; /* 计算剩余长度并回填 */ packet[1] idx - 2; *out_len idx; }逻辑说明这段代码做的是在 STM32 里手工拼一个 MQTT QoS0 的 PUBLISH 报文。0x30是 PUBLISH 的固定头QoS0 不需要报文 ID省掉了后续的确认交互适合粉尘数据这种可容忍偶尔丢一条的遥测场景。你还需要在系统初始化时先发一条 CONNECT 报文建立会话语义否则 broker 会断开链接。参数说明topic 里建议带上设备 ID 和作业场所编号比如/dust/workshop_01/node_01这样云端可以按 topic 做订阅转发不用在 payload 里重复解析设备信息。payload 中至少包含pm25、timestamp、alarm三个字段。ESP8266 在透明传输模式下只要之前执行过ATCIPSTARTTCP,服务器IP,1883后续串口收到的数据就会直接送到 broker这是最简单可靠的上报链路。源代码管理方面这个项目建议按“固件工程、网关脚本、云端服务、文档说明”四层目录分开我常在固件目录里保留一份doc/子目录把引脚定义和时序图画在一张图里存入版本管理而不是只在论文里画。后续换板子、换引脚时改了代码顺手更新图就不容易出现“接线图和源代码不一致”的答辩翻车。4. 预警规则与联动控制职业接触限值、趋势告警和风机执行4.1 职业接触限值与四级阈值划分传感器把浓度数值报上来之后第二件重要的事是判断“这个值算不算超标”。判断依据不能拍脑袋应参考工作场所有害因素职业接触限值。不同粉尘类型的限值差别很大比如煤尘总尘的 8 小时时间加权平均容许浓度通常在 4mg/m³ 的量级而某些游离二氧化硅含量高的粉尘会更严格。正式项目里要以最新版标准为准但作为综设你至少要把这套“限值可配置”的机制做进系统里。我一般把预警分为四个等级便于现场人员直接理解而不是看裸数据等级判定条件处置动作正常浓度 50% 限值仅记录刷新 OLED关注50%~100% 限值加大采样频率上报趋势预警≥ 100% 限值持续 3 个采样周期声光报警启动风机紧急≥ 200% 限值推送消息至管理端强制通风注意这里“持续 3 个采样周期”这个条件很关键。粉尘传感器偶尔会有瞬时高值比如有人从传感器前走过气流扰动就能让读数跳一下。如果单点超过阈值就报警现场会天天误报最终所有人对报警置若罔闻。持续 N 次确认是一个低成本的防抖手段我在所有项目里都会加。4.2 滑动窗口趋势告警用斜率提前几分钟发现问题达标判定属于事后判断浓度已经超了才动作。另一种更实用的思路是做趋势告警如果短时间内的浓度上升斜率很大即使当前值还没超限也应该提前提醒现场人员检查除尘设备或工艺环节。这是“预警系统”区别于“记录系统”的关键功能。对于嵌入式端我使用固定窗口的线性回归来做斜率估计窗口长度取最近 5~10 分钟的数据/* trend.c * 输入最近 N 个采样点的浓度数组和对应时间戳 * 输出浓度变化斜率单位 mg/(m³·min) */ float calc_trend_slope(const float *concentration, const float *time_s, uint8_t n) { float sum_x 0, sum_y 0, sum_xy 0, sum_xx 0; float k 0; for (uint8_t i 0; i n; i) { sum_x time_s[i]; sum_y concentration[i]; sum_xy time_s[i] * concentration[i]; sum_xx time_s[i] * time_s[i]; } k (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x); return k; }逻辑说明最小二乘拟合的斜率就是单位时间内浓度的变化速率。当斜率超过设定值比如 0.05mg/(m³·min)并且当前浓度达到限值的 60% 以上时我让系统提前进入“关注”状态在 OLED 上显示向上箭头同时把趋势值通过 MQTT 上报。参数说明窗口n不建议太小取 10~30 个点对应 10~30 分钟比较合理太小则噪声主导斜率太大则响应迟钝。斜率阈值要根据作业场所的粉尘本底浓度调整不能一刀切我在代码里把它做成 Flash 可配置项。4.3 联动控制逻辑继电器时序、手动/自动切换和恢复预警如果只停留在屏幕和服务器上价值会大打折扣。真正的闭环要把报警接上执行器启动排风机、打开声光报警器、必要时关停设备。下面是我在项目里用的联动控制骨架/* control.c * 联动规则 * 等级1/关注 - 不动作 * 等级2/预警 - 间歇通风(开10s停20s) * 等级3/紧急 - 持续通风 声光报警 * 恢复条件浓度低于预警限值且持续60s */ void dust_control_task(void) { static uint32_t relay_on_until 0; static uint32_t alarm_until 0; uint8_t lvl get_alarm_level(); if (auto_mode_enabled) { if (lvl DUST_LEVEL_3) { HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(ALARM_PORT, ALARM_PIN, GPIO_PIN_SET); } else if (lvl DUST_LEVEL_2) { if (HAL_GetTick() relay_on_until) { HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_RESET); if (HAL_GetTick() alarm_until) { relay_on_until HAL_GetTick() 10000; /* 下次开10s */ alarm_until HAL_GetTick() 30000; /* 停20s */ } } } else { /* 浓度已恢复延迟60s后关闭执行器 */ HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(ALARM_PORT, ALARM_PIN, GPIO_PIN_RESET); } } }逻辑说明这段代码用系统 tick 做非阻塞延时避免delay卡死主循环。等级 2 采用间歇通风是为了避免风机持续运转带来的误能耗和噪音投诉等级 3 才持续通风并同时拉高声光报警。恢复逻辑不是浓度一降就关而是等一个 60 秒的稳定窗口防止风机在临界值附近来回启停。参数说明间歇通风的 10s/20s 参数、恢复确认的 60s 参数都应做成可调。我见过一份做得认真的综设文档把这几组参数单独列一张表放在系统设计说明里并给出“为什么选这个值”的推导答辩时这一页就能值不少分。5. 项目实施避坑与排查从传感器漂移到文档说明不一致的五个高频问题5.1 传感器输出飘零上电之后读数不动现象GP2Y1010 上电后输出电压一直停留在 500mV 附近把香烟凑到传感器进风口数值也只有轻微波动看起来像“感知失灵”。 原因多数情况下不是传感器坏了而是 LED 脉冲控制脚没有真正在动作。GP2Y1010 需要外部脉冲驱动内部红外 LED如果控制脚悬空或电平恒定为高传感器内部的发光管不点亮接收端自然没有信号变化。 解决先用万用表量控制脚是否有 10ms 周期的方波再检查该引脚的 GPIO 是否被初始化成复用功能或者被其它外设占用了。另一种常见情况是采样点太早在 LED 开启后的 100µs 内就做了 ADC 转换此时输出还没有建立起来读数自然恒低把采样延时调到 280~300µs 即可。5.2 采样值周期性跳动电源纹波和滤波不足现象读数呈现规律性波动每隔几个采样周期出现一次尖峰滑动平均也压不住报警逻辑偶尔误触发。 原因GP2Y1010 的 LED 脉冲瞬间电流接近 20mA如果整个节点用同一个 LDO 供电这一路电流会让 VCC 产生毫伏级跌落ADC 的参考电压跟着抖动转换结果就跟着跳。 解决在传感器供电脚并一颗 10µF 以上电容再把 ADC 采样值从“裸值”换成“去极值滑动平均”。如果换电容后仍跳动用示波器看 VCC 纹波超过 50mV 就说明电源容量不足该把 5V 适配器换成 12V 适配器加 DC-DC 方案。5.3 LoRa 丢包与重传网关总是缺数据现象网关串口打印接收日志A 节点每 30 秒一条数据但每天总有那么几十条缺失多发生在车间里有叉车或变频器经过的时刻。 原因E32 这类 LoRa 串口透传模块有发送缓存和接收缓存串口写入速度大于射频发送速率时数据会被内部缓存丢弃电磁干扰或同频别的节点占用信道也会造成空中碰撞。 解决发送侧在写完串口后做一次“模块忙检测”或至少延时 50ms 再发下一条避免一条一条往里塞接收侧加 CRC 校验校验失败的直接丢弃并计数。如果要更高的可靠性可以让网关周期性地下发“缺哪几条”的重传请求节点收到后再补发一次这种机制对 10 个节点以内的规模非常有效。5.4 MQTT 断线重连服务器看起来在线但数据断了现象节点侧日志显示已连接服务器端 Web 页面却长时间不更新重连后数据恢复过一会儿又断了。 原因ESP8266 的 TCP 连接在断网后不会立刻感知如果 MQTT 的 Keep Alive 设得太长或者 broker 没收到心跳连接处于半开状态数据一直发送但到达不了 broker。 解决把 MQTT Keep Alive 设为 30~60 秒并在 CONNECT 报文里带遗嘱消息。遗嘱的作用是让 broker 在客户端异常断开时帮你发布一条“该设备离线”的消息云端就能及时标记设备状态。代码里要周期查询连接状态连续两次心跳无响应就主动关闭 TCP 并重新走 CONNECT 流程不能只在启动时连一次。5.5 文档说明与代码不一致答辩前发现引脚图对不上现象打印出来的系统框图里写的是 PA0 接传感器实际源代码里却是 PB1联调时换了一条线但文档没有同步更新答辩被问到现场卡住。 原因毕业设计时间跨度长改版后经常只改代码不改文档这是最普遍也最可惜的翻车点。 解决把“每次改动引脚或协议都同步改文档”作为项目纪律。我习惯在代码仓里放一份wiring.md记录每个引脚号、电平、供电电压并用 git 管理历史最后写论文时直接导出这份文件再对照实物拍照保证文档说明里的接线图、代码和实际板子三者一致。这个习惯不花时间但能避免答辩现场最尴尬的局面。6. 验收演示与升级方向把综设项目做成可扩展的监测样板项目做到这一步代码能跑数据能看阈值能报警剩下最值得花时间的是“让别人信服”。我建议做一套标准验收流程准备一个透明收纳箱作为烟箱点燃一小段蚊香放入箱内传感器进风口接在箱子一侧演示全程记录浓度变化曲线。先把传感器置于干净环境等基线稳定然后放入烟箱观察浓度爬升到达预警阈值后确认声光报警和风机是否按预期动作最后把烟箱撤走观察浓度回落和系统自动复位。整个过程录一段视频附在文档说明里比任何文字描述都有说服力。参数验证方面用干净环境标定零点用蚊香烟箱做一次人工标定把两个标定点写入文档并注明传感器在湿度高于 70% 时的测量偏差这些内容能体现你在认真做工程而不是搭积木。对于有精力的读者涂两个升级方向其一是把节点从电池供电改成无源物联网方案用环境光能量采集给节点供电降低现场布线难度其二是把网关侧加一层轻量边缘计算在 STM32 上做浓度异常检测本地先判断再上报减少云端无效数据量。这两条路都能让综设项目从“课设”进阶到“有工程价值”的水平。我自己在粉尘监测项目上踩过最多的坑从来不是哪一个零件烧毁而是对作业场所的粉尘类型和限值一无所知就开始调阈值。先弄清楚现场是什么粉尘、限值是多少、工人最可能在哪个位置停留再决定传感器和布点系统才有真正的用途。希望这篇笔记能帮你把题目的每个环节都落地得扎实少走我当年走过的弯路祝你的项目顺利收尾。本文还有配套的精品资源点击获取