1. 仓库环境控制,先想清楚要控什么
做仓库环境控制系统,第一反应往往是"STM32 + 传感器 + 执行机构",网上类似的毕业设计和工程案例一抓一大把。但真把这套系统做出来、放到真实仓库里跑,你会发现最难的从来不是代码,而是你压根没想清楚"控制"这两个字意味着什么。
我这边项目的初始需求是:温湿度监测、粉尘浓度监测、自动通风除湿,数据还要通过ESP8266上传云端。对应的使用场景是电子元器件仓库——这类仓库对温湿度和洁净度都有明确要求,尤其是防潮防静电,环境失控造成的损失远比想象中大。系统最终要做到的效果是:现场有一套本地闭环控制逻辑,能独立工作不依赖网络;同时数据要上云,方便远程查看和告警。
拆解下来,整个系统其实分四个层次:
- 感知层:温湿度传感器、粉尘传感器,负责采集环境数据
- 控制层:STM32做主控MCU,跑控制逻辑,决定要不要通风、要不要除湿
- 执行层:风机、除湿机、加热器、继电器组,实施具体动作
- 网络层:ESP8266做主控的"通信外脑",把数据推到云端
很多新手项目做到最后变成"传感器读数在OLED上滚动播放",本质上就是只做了感知层和显示层,控制逻辑要么没写,要么写成了定时开关——那不能叫环境控制系统,那叫电子温度计。
这个项目里我做了一个关键决策:MCU选STM32F103C8T6,通信模块选ESP8266-01S,本地控制逻辑全跑在STM32中,ESP8266只负责和云端打交道。为什么不直接让ESP8266干所有事?因为ESP8266虽然便宜、能联网,但它的GPIO和实时性做这种多传感器采集+控制闭环并不舒服,尤其是风扇、除湿机这类执行机构的PWM控制和逻辑调度,还是STM32的强项。两者各干各擅长的,这是整体架构的核心思路。
2. 硬件选型与接线:这里最容易返工
2.1 主控与传感器的搭配逻辑
主控选了STM32F103C8T6,也就是俗称的"蓝丸板"。它便宜、资料多、引脚够用,做这种环境控制项目绰绰有余。你也许会在网上看到有人用STM32F407甚至F429做同样的功能,说实话没必要,性能严重过剩,功耗还高。
温湿度传感器是DHT22(也叫AM2302),测量范围-40~80°C、0~100%RH,精度±0.5°C和±2%RH,比DHT11靠谱太多。DHT11的精度只有±2°C和±5%RH,在仓库环境控制这种场景下,误差大了直接影响控制逻辑的判定——比如实际湿度80%RH,DHT11测出来75%RH,除湿机永远启动不了,仓库里的元器件一样受潮。所以别省那几块钱,DHT22是底线。
粉尘传感器选了GP2Y1010AU0F,夏普那款经典的红外粉尘传感器。它能测PM2.5级别的悬浮颗粒物,原理是红外LED照射空气中的颗粒物,光接收管检测散射光强度,输出一个与粉尘浓度呈正相关的模拟电压。输出是模拟量,直接用STM32的ADC采集就行。它的灵敏度用灵敏度系数换算,具体公式后面细说。
2.2 执行机构与驱动方案
执行机构里最核心的是通风和除湿。通风用的是24V轴流风机,仓库面积不大时这种风机足够换气;除湿机是成品独立除湿机,但它本身不带干接点控制接口,所以我用了继电器组来做开关控制。注意,成品除湿机改装控制时一定要看说明书,确认它支持通过断电重启来控制启停——有的除湿机断电重启后需要手动再按一次开关,那个没法用继电器直接控制,得自己改电路或者选带遥控器的型号。
继电器选的是5V供电的2路光耦隔离继电器模块,低电平触发。为什么强调光耦隔离?因为继电器后面接的是24V风机、220V除湿机,如果共地串扰,STM32的3.3V系统很容易被打挂。光耦把控制侧和执行侧隔离了,安全系数高很多。这个细节在实验室玩玩不觉得,真放到仓库现场就知道有多大价值了。
另外还加了一路PWM控制的排风扇调速——不是简单的开/关,而是根据温湿度偏差大小动态调速。这需要一个MOS管驱动电路,IRLZ44N这种逻辑电平MOS管正好合适,可以用STM32的TIM输出PWM直接控制。
2.3 接线表的详细说明
接线是整个系统最繁琐但最不能出错的部分。我按功能模块整理了接线表,你照着接基本不会乱:
| 模块 | 信号线 | STM32引脚 | 备注 |
|---|---|---|---|
| DHT22 | DATA | PA6 | 需外接4.7kΩ上拉 |
| GP2Y1010AU0F | V-LED、LED-GND | 3.3V、GND | 脉冲驱动由PWM控制 |
| GP2Y1010AU0F | VO | PA1(ADC1) | 模拟输出 |
| 继电器模块 IN1 | PA0 | 低电平触发 | 控制除湿机 |
| 继电器模块 IN2 | PA1 | 低电平触发 | 控制风机开关 |
| MOS管栅极 | PA2 | PWM输出 | 风机调速 |
| ESP8266-01S TX | PA9(USART1_TX) | 交叉连接 | 3.3V逻辑兼容 |
| ESP8266-01S RX | PA10(USART1_RX) | 交叉连接 | 3.3V逻辑兼容 |
| OLED(I2C) | PB8、PB9 | SCL、SDA | 0.96寸12864 |
有一个细节特别容易被忽略:GP2Y1010AU0F的LED驱动不是直接给高低电平的,而是需要一个周期10ms、脉冲宽度0.32ms的脉冲信号,采样点要放在脉冲开始后0.28ms处。如果直接用GPIO高低电平驱动LED,输出信号会完全不正常。我在这个项目里用TIM1的PWM输出来生成这个脉冲波形,然后ADC采样时机对齐到脉冲高电平的中段,这样读到的电压才稳定。
关于ESP8266和STM32的引脚对接,网上有大量"自锁"接法的讨论。ESP8266-01S的GPIO0和GPIO2必须保持高电平才能正常启动运行,而它的TX/RX是3.3V逻辑,和STM32的3.3V系统电平正好匹配,不需要额外的电平转换电路。不过ESP8266上电瞬间的电流峰值比较大,有时候会拉低3.3V电压导致它启动失败,这个坑我在调试时踩过,解决方案是ESP8266的供电独立用AMS1117-3.3V,不要和STM32共用同一个LDO输出——具体做法后面挑重点详细说。
3. 传感器数据采集与处理:数学比代码更重要
3.1 DHT22的时序与校验
DHT22用的是单总线协议,数据线只有一根,所有通信靠时序控制。它的采集过程需要经过"主机发送起始信号 → DHT22响应 → 40位数据输出"三个阶段。网上有现成的库函数,但我建议你还是理解一下时序本质——因为很多仓库环境下,线路长了、干扰大了,DHT22的时序会漂移,靠库函数里的死循环等待经常读到全0或校验失败。
DHT22的40位数据由16位湿度、16位温度、8位校验和组成。其中温度最高位为1时表示零下温度,用补码表示。比如温度是负数,直接按无符号数解析会解析出一个很大的错误值,这点在冬天北方仓库特别容易踩坑。校验方式是前32位数据之和的低8位等于校验和,不相等直接丢弃本次数据,重新采样。
我在读取DHT22时做了三件事:
- 连续读取失败3次才报警,避免偶发错误触发误告警
- 限幅滤波:如果温度突变超过5°C或湿度突变超过10%RH,认为数据异常,用上一次有效数据
- 定时采样而非循环采样:DHT22两次读取间隔必须大于2秒,否则传感器自身会处于忙状态,返回数据不稳定
好多人的温湿度显示跳动得很厉害,就是没有做限幅滤波,本质是读取时序不稳定或者传感器老化后输出飘了。DHT22采购时也得留个心眼,市面上很多号称DHT22的模块其实用的是国产替代芯片,虽然兼容,但时间长了稳定性差一些。有条件的话用SHT30或者AHT20这类I2C数字传感器,效果会更好——当然这是后话。
3.2 GP2Y1010AU0F的电压-浓度换算
粉尘传感器的数据处理是另一个容易翻车的地方。GP2Y1010AU0F的输出电压大约在0~3.6V之间,对应粉尘浓度大约0~0.5mg/m³,但输出电压和粉尘浓度不是线性关系。官方数据手册给出的关系近似如下:
粉尘浓度(mg/m³) ≈ (Vout - 0.6V) × 0.5V对应的浓度系数实际工程中更实用的做法是用标定曲线插值,但业余条件下标定条件不够,很多人的处理方式是直接用线性公式估:
浓度(mg/m³) = (Vout_voltage - V_0) / K其中V_0是洁净环境下的零点电压(一般0.3~0.6V之间,不同批次硬件有差异),K是灵敏度系数,大约0.15~0.2 V/(mg/m³)。你手里这块传感器的V_0和K是多少,需要通过实测得到——在洁净环境测一次零点电压,然后在已知浓度的测试环境中测一次电压,两个点就能算出K。
这在很多教程里被当成"公式一抄就行",但我建议每个人拿到传感器后都做一次零点标定,把V_0记下来。不同批次、不同模块之间V_0差异非常大,接上去直接套公式的结果就是把偏置误差带进了控制逻辑——本来0.1mg/m³算成0.25mg/m³,风机瞎转。
ADC采集的时候我一般开12位分辨率,参考电压用内部参考2.9V(F103刚好多路ADC都支持内部参考),然后软件做多次采样取平均+滑动滤波。粉尘信号本身波动很大,单次采样数值跳来跳去,如果不滤波,控制逻辑会反复启停风机——这是很多人做完之后发现"风机一会儿转一会儿停"的根本原因。
3.3 ADC与滤波策略的取舍
ADC滤波我用了两种策略的组合:
- 中位值滤波:连续采样11次,排序取中间值,滤掉意外尖峰
- 滑动平均滤波:最近5次中位值做平均,平滑输出
为什么不用简单平均?因为简单平均在遇到强干扰尖峰时,一个异常值能拉偏平均值很多。中位值滤波能彻底剔除毛刺,代价是采样速度慢一点,但环境控制系统的采样周期做几百毫秒毫无压力。
注意,STM32F103的ADC在连续采样模式下,如果你开了DMA循环,要小心数据覆盖问题。我最终采用的是定时器触发单次采样,每次取完一组数据后处理完再启动下一组,逻辑更清晰,不容易踩DMA缓冲区错位的坑。
4. 控制逻辑设计:从"读数据"到"控环境"的质变
4.1 温湿度闭环控制
控制逻辑是整个系统的灵魂。设计原则是:优先本地闭环,云端只是远程监控和数据备份。
温度控制的目标区间我设在20~26°C。这个区间主要是参考电子元器件仓库的一般要求,兼顾春季和秋季不用空调的自然温度。当温度高于26°C时启动风机通风,低于20°C时打开加热器(如果配置的话)。但通风的同时要考虑湿度——外面如果在下雨,室外空气湿度接近100%RH,这时候把外界潮湿空气抽进来,温度降了湿度却上去了,得不偿失。
所以控制逻辑必须做成环境综合判断,不能只盯单一参数。典型规则如下:
| 条件 | 动作 |
|---|---|
| 温度 > 26°C 且 湿度 < 70%RH | 启动风机通风降温 |
| 温度 > 26°C 且 湿度 ≥ 70%RH | 启动除湿机,暂不通风 |
| 湿度 > 65%RH | 启动除湿机 + 关窗(模拟) |
| 湿度 ≥ 80%RH 且持续10分钟 | 强制通风除湿(除湿机除湿效率有限时) |
| 温度 < 18°C 且 湿度 > 70%RH | 加热除湿(如有加热器) |
湿度控制我用的是双阈值滞回比较。比如湿度上限设为65%RH,下限设为55%RH,当湿度超过65%RH时启动除湿机,当湿度降到55%RH以下时才关闭除湿机。为什么用滞回?因为不用滞回会变成"超过65%就启动,降到64.9%就关闭",一天之内继电器能咔嗒咔嗒响上百次,继电器寿命很快就磨完了,除湿机压缩机频繁启停也容易损坏。这个道理和家里的空调温控是一样的,只不过很多人刚开始做系统控制时根本没往这方面想。
滞回量一般设5~10%RH左右比较合理,太小没效果,太大会导致环境波动大。
4.2 粉尘浓度联动控制
粉尘浓度与通风的联动逻辑,和温湿度类似但更简单:阈值设为0.15mg/m³,超过就启动风机,降到0.10mg/m³以下再停。同样给了滞回区0.05mg/m³。
但这里有个容易忽略的点:粉尘浓度传感器的读数受空气流速影响。如果风机排气口正好对着传感器位置,读数会明显偏低,因为气流把局部区域的粉尘吹走了;如果传感器装在墙角落,气流不流通,读数又会偏高。所以在实际安装中,传感器位置要选在仓库中部、离地面1.5~1.8米左右的位置,尽量避开直吹气流。这是纯工程经验,很多教程里不会提醒。
另外粉尘浓度和湿度也有关系——高湿度环境下颗粒物吸湿后会变得更重,沉降加快,浓度读数会比干燥环境下偏低。如果要精细控制,可以在高湿度时段对粉尘阈值做点补偿,不过大多数仓库场景没必要做这个,先跑起来再说。
4.3 定时调度与状态机设计
判断"要不要开风机/除湿机",表面看是一个比较指令,但实际系统里我建议把它设计成有限状态机。每个外设处在不同状态下,行为完全不同:
- 风机:停止 → 启动中 → 运行 → 停止中
- 除湿机:停止 → 启动延时 → 运行 → 压缩机停机延时 → 停止
之所以给除湿机设"启动延时"和"停机延时",是因为除湿机的压缩机不能频繁启停。从"运行"到"停止",压缩机不能立刻断电,否则制冷剂回流会产生液击,损坏压缩机。标准做法是:切断除湿机继电器后,至少等待3分钟再允许重新启动。
这一条很多教程都一笔带过了,实际是除湿机控制系统可靠性的关键。你直接用继电器去控制一台压缩机类设备,不做停机延时,可能一个星期内压缩机就报废了。
状态机的实现用switch-case就够了,不需要引入RTOS。整个系统的运行节奏大概是:
- 每2秒读一次DHT22
- 每1秒读一次粉尘传感器
- 每5秒做一次状态判断和阈值比较
- 任何状态切换在OLED上同步显示
这节奏下来,整个控制逻辑流畅不卡顿,同时避免了传感器数据的疯狂抖动。
5. ESP8266上云:本地控制之外的远程大脑
5.1 为什么要让ESP8266单独跑
先说说我在"让STM32自己带网络"和"ESP8266独立上网"之间做的选择。STM32F103C8T6本身不带以太网MAC,要联网就得外挂ENC28J60之类的以太网模块,或者SPI接口的W5500,但这些都是有线网络方案——在仓库现场布线麻烦,而且W5500的电路复杂度明显高于ESP8266。ESP8266自带WiFi协议栈,一个串口就能通信,从开发效率到功耗都是更好的选择。
更关键的考虑是控制逻辑的独立性。如果网络功能和控制功能挤在同一个MCU上,一旦WiFi协议栈出问题(连接卡死、缓冲区溢出),整个控制系统就瘫痪了。分开之后,ESP8266最多是连不上网,本地温湿度监控和风机控制不受影响——这个思路在工业控制领域叫"自治控制",简单说就是本地系统不要依赖外部资源才能保命。
5.2 ESP8266的AT指令配置
ESP8266-01S出厂默认就是AT固件,可以直接用串口和STM32通信。8266在项目里当一个"串口转WiFi"的透明通道来用。
我配置ESP8266的核心指令序列如下:
AT AT+CWMODE=1 // 设置为Station模式,连接路由器 AT+CWJAP="SSID","密码" // 连接WiFi AT+MQTTUSERCFG=0,1,"客户端ID","用户名","密码",0,0,"" // MQTT用户配置 AT+MQTTCONN=0,"IP地址",1883,1 // 连接MQTT服务器 AT+MQTTSUB=0,"主题/runtime/device/xxx/msg/property/post",1 // 订阅云端指令关于AT指令细节,有一个特别容易踩的坑:AT+CWSAP和AT+CWJAP的WiFi名称若包含中文或特殊字符,8266会返回ERROR。很多仓库现场的WiFi是中文SSID,这个坑会让新手卡很久。解决办法是路由器开一个2.4GHz的英文SSID专门给设备用,注意不要带空格和$这类特殊符号。
然后是MQTT连接的稳定性问题。ESP8266的AT固件和MQTT服务器之间如果长时间没数据交互,服务器会断开连接,8266还浑然不知。解决方法是每30秒发一个心跳包:
AT+MQTTPUB=0,"主题",1,"{\"status\":\"online\"}",0,0如果连接异常断开,STM32检测到"TCP连接已关闭"之类的返回后,要把ESP8266重新初始化,顺序是:AT+RST → AT+CWMODE → AT+CWJAP → AT+MQTTCONN。整个重连过程要控制好节奏,不要一失败就疯狂重试。
5.3 数据上云协议的设计
数据上云的格式我用的是JSON,字段如下:
{ "deviceId": "WH01", "temp": 23.5, "hum": 58.2, "pm25": 0.12, "fanStatus": 1, "dehumidifierStatus": 0, "timestamp": 1699833600 }esp8266 AT指令里发JSON最大的坑是AT指令本身的长度限制。经典的ESP8266-01S AT固件,单条指令长度默认限制为256字节。我这个JSON串大概120字节,刚好够。如果你的数据量大,比如要传历史数据或者带中文备注,超过了长度限制,就必须把数据分块或者改用固件允许的更长指令,比如AT+MQTTPUBRAW。
关于云平台的选择,我最终用了常见的物联网云平台MQTT接入方式,将数据实时推送到云端dashboard。有些平台还支持在网页端直接下发指令控制风机/除湿机,这个功能其实就是向MQTT主题发布下行控制指令,ESP8266订阅相关主题后解析JSON,把控制指令通过串口转发给STM32。
5.4 断线重连与异常恢复
WiFi断线重连算是我这个项目里耗时最长的一个调试点。现象是:ESP8266偶尔断开连接,而且不会自动恢复,整个系统"失联"。
排查后发现是和路由器之间长时间空闲连接被踢下线,TCP/MQTT的那个底层链路静默时间超过了路由器老化时间。加心跳后问题解决,但还不够彻底——ESP8266在处理某些AT指令时如果返回ERROR,控制逻辑可能陷入死循环。我踩过一个具体例子:循环里执行AT+CIPSTATUS检测连接状态,如果返回"ERROR"而不是"+CIPSTATUS...",解析函数会一直等待而卡死。
最终方案是把ESP8266状态检测改成超时机制:任何一条指令发送后,如果没有在2秒内收到正常的"OK"或"\r\n",就认为通信异常,重新初始化整个WiFi模块。刚开始以为这会让系统频繁重启,实际上线后运行稳定,很少触发。
关于"STM32 巴法云""esp8266 接OneNet"这类搜索热词,其实都是同一件事的不同云平台实现方式——核心就是MQTT协议,换成哪个平台都是改broker地址和主题而已。
6. 完整代码框架与关键实现片段
6.1 主函数与系统运行框架
主函数采用无RTOS的前后台结构。主循环轮询各模块状态,定时器中断处理时间基准。
int main(void) { SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM1_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); OLED_Init(); DHT22_Init(); ESP8266_Init(); while (1) { // 主循环:非阻塞轮询 DHT22_Poll(); // 2s采样周期内读取温湿度 DustSensor_Poll(); // 1s采样周期内读取粉尘 ControlLogic_Run(); // 环境状态机 OLED_Update(); // 显示刷新 ESP8266_Handle(); // 网络通信处理 delay_ms(100); } }关键设计在于所有操作都不阻塞。DHT22读取虽然有时序要求,但我在主循环里用状态机控制它在"等待2s→发起读取→等待响应→读取数据→校验"各环节切换,而不是在while循环里死等。这样做的好处是ESP8266串口数据到来时,主控不会被DHT22卡住而错过处理。
6.2 DHT22读取与校验
DHT22读取的核心代码框架如下:
uint8_t DHT22_ReadData(float* temperature, float* humidity) { uint8_t data[5] = {0}; // 主机拉低总线 >1ms 作为起始信号 DHT22_SetPinOutput(); DHT22_WriteLow(); delay_ms(1); DHT22_WriteHigh(); DHT22_SetPinInput(); // 等待DHT22拉低响应信号 uint32_t timeout = 100; while (DHT22_ReadPin() == 1 && timeout--) delay_us(1); // 等待DHT22拉高 timeout = 200; while (DHT22_ReadPin() == 0 && timeout--) delay_us(1); // 读取40位数据 for (uint8_t bitCount = 0; bitCount < 40; bitCount++) { timeout = 100; while (DHT22_ReadPin() == 0 && timeout--) delay_us(1); // 每bit起始低电平 delay_us(32); // 跳过前32us if (DHT22_ReadPin() == 1) data[bitCount / 8] |= (0x80 >> (bitCount % 8)); timeout = 100; while (DHT22_ReadPin() == 1 && timeout--) delay_us(1); } // 校验 uint8_t checkSum = data[0] + data[1] + data[2] + data[3]; if (checkSum != data[4]) return 0; uint16_t humRaw = (data[0] << 8) | data[1]; uint16_t tempRaw = (data[2] << 8) | data[3]; // 负数判断:最高位为1 if (tempRaw & 0x8000) *temperature = (int16_t)(tempRaw & 0x7FFF) / 10.0 * (-1); else *temperature = (int16_t)(tempRaw & 0x7FFF) / 10.0; *humidity = humRaw / 10.0; return 1; }读取时序里有一个细节的等效处理:delay_us(32)后判断数据位的电平,通常判断点是脉冲开始后的28~40us。因为DHT22的"0"脉冲宽度是26us左右,"1"脉冲宽度是70us左右,在32us处判断可以清晰区分。当然这里用了循环等待大幅简化,实际项目里用SysTick做us级延时。
6.3 控制逻辑状态机
控制逻辑直接看代码更清楚:
typedef enum { FAN_STOPPED, FAN_RUNNING } FanState; typedef enum { DEHUM_STANDBY, // 待机,允许启动 DEHUM_RUNNING, // 运行中 DEHUM_STOPPING // 停机等待3分钟 } DehumidifierState; void ControlLogic_Run(void) { static DehumidifierState dehumState = DEHUM_STANDBY; static uint32_t lastSwitchTime = 0; // 读取最新数据 float temp = GetLatestTemperature(); float hum = GetLatestHumidity(); float dust = GetLatestDustConcentration(); // 风机控制:温湿度综合判断 if ((temp > 26.0f && hum < 70.0f) || dust > 0.15f) { if (GetFanState() == FAN_STOPPED) { SetFanState(FAN_RUNNING); } } else if (temp <= 24.0f && hum < 60.0f && dust <= 0.10f) { if (GetFanState() == FAN_RUNNING) { SetFanState(FAN_STOPPED); } } // 除湿机控制:带滞回与压缩机保护 switch (dehumState) { case DEHUM_STANDBY: if (hum >= 65.0f) // 滞回上限 { SetDehumidifier(1); dehumState = DEHUM_RUNNING; lastSwitchTime = GetSysTick(); } break; case DEHUM_RUNNING: if (hum <= 55.0f) // 滞回下限 { SetDehumidifier(0); dehumState = DEHUM_STOPPING; lastSwitchTime = GetSysTick(); } break; case DEHUM_STOPPING: // 等待3分钟,保护压缩机 if (GetSysTick() - lastSwitchTime >= 180000) { dehumState = DEHUM_STANDBY; } break; } }这套逻辑跑起来之后,系统不会因为1%RH的波动来回折腾继电器,除湿机每次运行周期至少会持续一段时间(因为湿度从65%降到55%通常需要一段时间),压缩机得到了充分保护。
实际操作中我还会在控制逻辑里加一个最小运行时间限制——风机启动后至少持续运行5分钟,不管温度湿度降到多少,防止空调或者风机频繁启停造成的能耗浪费和机械损耗。这在仓库这类有大热容的环境中尤其重要,因为环境温度不会因为风机开1分钟就立刻降下来。
7. 云端平台与App接入实测
7.1 选型与接入流程
云端平台选型时,我对比过几种常见方案:常见物联网平台的MQTT接入、自建MQTT broker、私有化服务器。自建broker在公网环境下需要一台有公网IP的服务器,对个人项目来说成本高、维护麻烦。最终选了常见物联网平台的MQTT接入方式,理由是:
- 接入流程标准化,设备端只需配置MQTT地址、端口、clientID
- 平台自动帮你处理数据展示和存储
- 支持自定义报警规则
连接平台的核心参数如下:
MQTT服务器: broker.xxx.com 端口: 1883 ClientID: 设备唯一标识 Username / Password: 产品密钥 数据上报Topic: /productKey/deviceName/upload 指令下发Topic: /productKey/deviceName/down填入上面这些参数后,ESP8266的AT指令序列如下:
AT+MQTTUSERCFG=0,1,"deviceName","productKey","设备密钥",0,0,"" AT+MQTTCONN=0,"broker.xxx.com",1883,1设备端接入成功后,平台端就能看到设备状态"在线",然后就能配置数据流转和显示。
7.2 数据上报与App可视化
我用平台的网页端看板配置了温度、湿度、粉尘浓度三个仪表盘,设备数据上报后基本能稳定显示。这里提醒一句,平台免费版的数据上报频率限制是需要注意的,有的限制每秒1条,有的限制每分钟10条。我的系统设计每30秒上报一条数据,完全足够。如果你原来按1秒1条上报,来做实时监控,免费额度很快就烧完了。
App端展示我用的是平台的官方小程序或者App,通过设备扫码绑定后,手机上就能看到实时数据和历史曲线。平台还支持告警推送,我设置了两条规则:
- 温度高于30°C持续5分钟 → 微信告警
- 湿度高于80%RH持续10分钟 → 微信告警
实际跑起来这个功能很实用。有一次周末仓库断电,空调停了,温度很快升高,我人在外面就接到了告警,及时找人处理。
7.3 本地显示与告警机制
除了云端,现场端的OLED屏幕也在持续显示关键数据。虽然整个系统有OLED、云平台双路显示,但告警不要做成只有云端一种。我在现场加了一个蜂鸣器,当温湿度超过紧急阈值时,本地蜂鸣器也会响。为什么?因为云端告警依赖网络,万一WiFi断了,现场人员必须能第一时间知道环境异常,人工去处理。
OLED显示的内容分两屏:
- 第一屏显示温度、湿度、粉尘浓度
- 第二屏显示风机状态、除湿机状态、系统运行时间
两屏之间手动切换或者每8秒自动切换都行。OLED的刷新不要做太快,环境数据本身变化慢,1秒刷新一次就足够了。这里注意I2C OLED的显示缓冲,如果刷新过于频繁,会和ESP8266串口传输抢占USART资源,导致数据延迟。
8. 部署实测与问题修复
8.1 部署初期遇到的三类典型问题
真实仓库环境比实验室严格得多,部署后第一周问题集中爆发,归纳为三类:
问题一:DHT22数据校验频繁失败
仓库面积较大、空气流动复杂,DHT22的时序偶尔会受到干扰导致校验失败。刚开始我采用"失败3次再报警"的策略,但现场基本不会触发报警,而在日志里能看到大概每小时还会有几十次校验失败。后来排查发现是DHT22和STM32之间的线缆过长(接近3米),加上数据线上有干扰。解决方法是缩短线缆,并把数据线改成屏蔽双绞线(屏蔽层单端接地),之后校验失败率大幅下降。
问题二:ESP8266经常断线
排查过程前面提过,最后锁定两个关键点:一是路由器闲置老化导致自动踢线,二是ESP8266供电不稳。在仓库现场,3.3V LDO的输入电源来自24V DC电源降压,但24V风机启动瞬间电流冲击很大,会造成瞬间压降,进而导致ESP8266复位。最终解决是给ESP8266单独加了一路低纹波3.3V电源,并从风机供电回路上单独分出一路,做了电气隔离。这个能在设计阶段规避,但我开始时确实没想到电机启动的瞬态会拉挂WiFi模块。
问题三:粉尘传感器读数漂移
GP2Y1010AU0F放置一周后,零点电压V_0漂移了大概0.1V,导致浓度读数偏高。解决方法是增加了每周自动归零校准机制——周六凌晨2点,系统自动运行一次时长30分钟的校准流程:强制关闭风机和门窗,在相对静止的空气中测量10分钟数据取平均,作为新的零点电压。
这个归零校准在实验室里完全没必要做,但现场环境灰尘累积、传感器光路老化都是不可避免的,一周做一次能把漂移控制在可接受范围内。
8.2 用电安全与防护设计
仓库环境里还有几个实验室不需要考虑但现场必须处理的问题:
- 传感器线缆的机械防护:仓库通道会有叉车和货物搬运,线缆如果裸露在地面,迟早被压断。我把所有传感器走线都改到了墙面上方,沿线采用线槽,避免拉扯和碾压。
- 继电器输出端的保险丝:除湿机和风机虽然是成品,但继电器触点如果发生短路或者过载,必须有过流保护。继电器到执行器之间加了3A的保险丝,至少能保证故障时导线不会起火烧毁。
- 雷击浪涌和静电:粉尘传感器在干燥环境下容易积累静电,静电放电可能损坏MCU端口。我在传感器信号线上加了ESD保护二极管(BAV99),并在电源入口用TVS管吸收了线缆感应的浪涌。这是很便宜的防护器件,但能省去很多麻烦。
8.3 系统稳定性验证
系统连续运行一周后的稳定结果可以参考下面这组数据:
| 时段 | 平均温度 | 平均湿度 | 平均粉尘 | 风机日启动次数 | 除湿机日启动次数 |
|---|---|---|---|---|---|
| 白天 | 24.8°C | 58.3%RH | 0.09mg/m³ | 8次 | 3次 |
| 夜间 | 23.2°C | 61.5%RH | 0.06mg/m³ | 4次 | 4次 |
从数据看,风机日启动次数明显偏高,分析后认为最主要原因是下午时段温度经常在26°C临界值附近波动,造成频繁切换。我把滞回上限改为27°C、回差改为3°C之后,风机启动频率下降了一半,仓库内温度波动基本上还是控制在2°C以内。这套参数目前已经稳定运行了将近两个月,没有再出现明显的执行机构频繁启停问题。
9. 低成本迭代:那些可以继续优化的方向
系统虽然已经能跑,但距离"完整的产品级方案"还有不少差距。如果接下来继续做迭代,我建议按优先级关注下面几个方向。
升级传感器方案:DHT22虽然能用,但长期稳定性一般。SHT30温湿度传感器采用I2C接口,精度更高、一致性更好,而且有内置加热功能防止冷凝;粉尘传感器可以升级为激光散射式的PMS5003或PMS7003,能直接输出PM2.5和PM10的浓度数值,单位就是μg/m³,不用自己做模拟量标定换算,可靠性高一个量级。这两个替换会让整个系统的数据可信度大幅提升。
增加执行机构类型:目前系统只有风机和除湿机两个执行器。真正的仓库环境控制系统往往还需要空调联动(风机盘管)、加湿器(冬季干燥环境)、新风阀、电加热器等。每增加一种执行机构,控制逻辑会更复杂,但也更贴近真实产品。同时可以加一个风阀执行器,控制新风和回风的混风比例,让通风除湿更精细。
引入RTOS:如果控制逻辑进一步复杂,比如加入多任务调度(传感器巡检、控制计算、网络上报、人机交互),建议把裸机前后台改成FreeRTOS。STM32F103C8T6跑FreeRTOS毫无压力,任务划分会更清晰。但这个过程本身也有成本,如果系统规模不大,裸机反而更简单可靠,只是别被这个建议带着走。
增加本地数据存储:云端虽然能存储数据,但万一网络断了,数据就丢了。可以在STM32上挂一个SPI Flash(W25Q64)或者用SD卡模块,本地缓存7天数据,等网络恢复后再批量上报。这个对仓库管理来说很有价值,尤其是需要追溯环境历史的时候。
锂电池备份供电:仓库断电时,系统至少应该维持传感器采集和本地显示,断电告警才能发挥作用。加一块锂电池和充电管理电路,让控制系统在断电后独立支撑4~8小时,然后把断电事件上报云端。这个功能看着简单,实际效果和安全意义非常大。
就我个人做这个项目的体会,仓库环境控制系统的难点不在单点功能,而在多个子系统如何协同配合——传感器要耐得住现场环境的干扰,控制逻辑要保护执行机构不被频繁启停折腾,网络层要能自治恢复,云端只是一个遥控中心而不该成为系统的心脏。把这些关系理顺了,这套东西在仓库现场跑起来才能真正让人省心。
最后再分享一个调试阶段的技巧:所有传感器和执行器都先做成独立模块单独调通,再接总线联调。DHT22单独测试过、粉尘传感器单独读过电压值、继电器单独测过吸合释放,再接STM32系统联调。否则一旦系统连起来出问题,你要在传感器、接线、代码、逻辑之间来回排查,效率极低。我当年从"整体联调"切换到"模块级调试"之后,项目推进速度快了很多倍,这个习惯我现在做任何嵌入式项目都还保留着。