仓库环境这东西,平时没人管,一旦出事就是大事。梅雨季节墙面结露、袋装粮食发霉、设备引脚氧化、粉尘堆积,这些问题我都在实地仓库里见过。去年帮朋友改造一个存放电子元件的周转仓库,我搭了一套基于STM32的环境控制系统,核心就做三件事:实时监测温湿度和粉尘浓度,根据阈值自动控制通风和除湿设备,数据通过ESP8266上传云平台,手机随时能看。这套系统做完之后,我把它整理成了一个比较完整的方案,从传感器选型、STM32驱动、继电器控制到ESP8266上云全链路都有涉及。这篇就按我实际的开发顺序一步步拆开讲,适合正在做STM32相关毕业设计的人,也适合想给自家小仓库、地下室或者设备间做环境监控的嵌入式爱好者参考。不同基础的人都可以按自己的情况,直接抄其中一部分来用。
1. 方案选型与整体架构
1.1 为什么主控选STM32而不是Arduino或者51
很多人在做仓库环境监控时第一反应是用Arduino,因为它上手快,传感器库一大堆,几行代码就能读出DHT11的数据。但真正放到仓库这个场景里,Arduino有几个硬伤。首先是I/O和定时器资源不够灵活,你要同时驱动温湿度传感器、粉尘传感器的PWM采样、继电器、蜂鸣器、OLED屏幕、ESP8266串口通信,Arduino Uno那点外设要来回切换模拟功能,代码写起来非常别扭。其次是抗干扰和稳定性问题,仓库里往往有电机、继电器这类感性负载,电源波动大,Arduino板子的电源管理相对简单,容易出现复位。
STM32F103C8T6这个型号是我比较推荐的。它主频72MHz,GPIO和复用功能非常丰富,有3个USART、2个I2C、2个SPI、多个定时器和12位ADC,外设之间可以独立工作。最关键的是,它的定时器可以输出精确到微秒级别的PWM和延时,这对粉尘传感器的脉冲采样特别重要。很多人觉得STM32难入门,其实用标准库或者HAL库开发,配置一个GPIO输出和ADC采样并不比Arduino复杂多少,只是需要多花一点时间理解时钟树和引脚复用。对于这个项目来说,STM32的性价比和扩展性是最合适的。
1.2 系统整体架构与数据流向
整个系统的架构可以分成三层来看。第一层是感知层,包括温湿度传感器(DHT11或AM2301)和粉尘传感器(GP2Y1010AU0F),负责采集环境原始数据。第二层是控制层,STM32读取传感器数据,经过滤波、阈值比较之后,决定是否打开排风扇、除湿机或蜂鸣器报警。第三层是数据层,STM32把处理后的数据通过串口发送给ESP8266,ESP8266连接WiFi后上报到云平台。
数据流向是这样一个链路:传感器 → STM32 ADC/GPIO → 数据处理(滑动平均滤波、阈值判断) → 本地显示(OLED)和执行器(继电器) → ESP8266串口透传 → 云平台。
提醒一个架构层面的坑:不要把所有的控制逻辑都放到云平台去处理。我在最初设计时曾想过让ESP8266直接把数据扔到云上,由云端下发命令来控制继电器。这个想法看着很先进,实际上依赖网络稳定性,一旦WiFi断线,整个仓库环境控制就瘫痪了。正确做法是:STM32本地做完整的自动控制逻辑,云端只负责展示和记录。网络断了,本地照样能自动通风除湿,这才是仓库环境控制系统该有的样子。
2. 传感器选型、电路连接与采样细节
2.1 温湿度传感器:DHT11还是AM2301
温湿度传感器市面上最常用的是DHT11和AM2301(也叫DHT21),两者都是单总线协议,但是精度和稳定性差别很大。DHT11精度是±2℃和±5%RH,对于仓库这种场景勉强够用,但它的湿敏电阻长期在粉尘环境下容易漂移,而且采样频率不能超过1Hz,两次读取之间必须间隔至少1秒,否则会读到无效数据。
AM2301精度是±0.5℃和±2%RH,响应时间更快,价格也就贵几块钱。如果预算允许,我建议直接上AM2301或者AHT20这类数字传感器。我在项目中用的就是AM2301,单总线协议和DHT11几乎一样,只是时序细节略有不同,程序稍微改一下即可。
接线上要注意:必须接一个4.7kΩ到10kΩ的上拉电阻,把数据线拉到3.3V。很多新手直接用杜邦线连接传感器和STM32,忘了加上拉电阻,结果读到的一直是0或者乱码。DHT11和AM2301的数据引脚是开漏输出,没有上拉电阻根本无法正确输出高电平。
2.2 粉尘传感器GP2Y1010AU0F的采样原理
粉尘浓度检测我用的是夏普GP2Y1010AU0F,这个传感器在空气净化器里用得很多,原理是红外LED照射空气中的颗粒物,光敏晶体管接收反射光,颗粒物浓度越高,输出电压越高。它内部有一个LED驱动电路,需要外部提供一个脉宽0.32ms、周期10ms的脉冲信号来点亮红外LED,然后在LED点亮后的0.28ms附近采样ADC值。这个采样时序非常关键,采样点早了或晚了,电压值都会不准。
这个传感器的工作电压是5V,但模拟输出引脚最大电压可能超过3.3V。STM32的ADC参考电压是3.3V,如果直接把传感器输出接进去,理论上没问题,因为典型输出在0.9V左右,但高浓度粉尘时输出电压会明显抬高,为了保险,我加了一个电阻分压(10kΩ和5.1kΩ),把最大电压缩到3.3V以内。不要省略这一步,否则烧了ADC引脚就麻烦了。
驱动LED的PWM由STM32定时器产生,我用TIM2的CH1输出PWM,频率100Hz(周期10ms),占空比3.2%(0.32ms/10ms),同时开启ADC注入采样,在PWM输出的第0.28ms处触发采样。在实际项目中,更稳妥的做法是用定时器计数加延时控制,在主循环里先启动PWM,然后延时0.28ms立即读取ADC,实测误差在可接受范围内。
2.3 执行器与继电器驱动电路
执行器包括排风扇和除湿机,都是220V交流设备,必须通过继电器控制。继电器我选的是5V松乐SRD-05VDC-SL-C,一路继电器控制排风扇,另一路控制除湿机插座。STM32 GPIO输出3.3V,直接驱动继电器是驱动不了的,必须通过三极管放大,同时需要加续流二极管来吸收继电器线圈断电时的反向电动势。
推荐电路是:STM32 GPIO → 1kΩ限流电阻 → NPN三极管(S8050)基极,三极管集电极接继电器线圈一端,线圈另一端接5V,线圈两端并联一个1N4007二极管(负极接5V)。GPIO为高电平时,三极管导通,继电器吸合;GPIO为低电平时,继电器释放。如果系统里继电器数量多,可以考虑用ULN2003驱动芯片,一个芯片能驱动7路继电器,自带续流二极管,电路会更干净。
有一个经验要强调:继电器动作瞬间会产生火花和电磁干扰,可能导致STM32复位或ADC读数跳动。解决方法有三个,一是继电器电源和MCU电源分开,用单独的5V电源给继电器供电;二是继电器的COM和NO触点连线远离传感器信号线;三是在STM32的VDD和GND之间加一个100uF电解电容和一个104陶瓷电容,增强抗干扰能力。
3. ESP8266上云的三条路线
3.1 串口AT指令的基本玩法
ESP8266在这个系统里扮演的角色是“网络透传模块”,STM32通过串口2与ESP8266通信,发送AT指令控制其连接WiFi和TCP服务器。这是最经典、资料最多的方式,非常适合初学者。
先把ESP8266用AT固件恢复到出厂状态,接好串口线,在电脑上用串口助手测试基础AT指令,比如发送AT,返回OK;发送AT+CWMODE=1(Station模式)返回OK;发送AT+CWJAP="WiFi名","密码"连接路由器。确认能联网之后,再接到STM32上。
STM32发送AT指令时有一个坑:必须等待模块回复“OK”或者“ERROR”之后才能发送下一条指令,否则模块会丢指令。我在代码里写了一个简单的AT指令发送函数,带超时等待回应,循环发送AT+CWJAP时,如果长时间没收到OK,自动重发并计数,超过3次则报警提示WiFi异常。
3.2 低成本上云方式:TCP透传+HTTP协议
如果你用的是基础AT固件,最简单上云方案是让ESP8266连接TCP服务器,STM32按照HTTP协议格式发送数据。比如把数据上报到OneNET云平台,可以先建立TCP连接:AT+CIPSTART="TCP","183.230.40.40",80,然后通过AT+CIPSEND进入透传模式,发送一串HTTP POST请求。
HTTP请求格式大致是这样的:
POST /devices/设备ID/datapoints?type=3 HTTP/1.1 Host: api.heclouds.com api-key: 你的APIKey Content-Length: 数据长度 {"datastreams":[{"id":"temp","datapoints":[{"value":26.5}]},{"id":"hum","datapoints":[{"value":63}]}]}这个方式的优点是固件不用刷,缺点是要自己拼HTTP报文并计算Content-Length,一旦数据里有中文或者转义字符很容易出错。对于只需要简单展示和控制来说,这个方案足够稳定,但代码量不小。
3.3 更省心的方式:刷支持MQTT的AT固件
如果不想拼HTTP报文,我建议直接给ESP8266刷一个支持MQTT协议的AT固件。刷好之后,模块本身就有了MQTT客户端能力,可以通过一组AT指令完成连接云平台和发布消息。比如连接MQTT服务器可以这样设置:
AT+MQTTUSERCFG=0,1,"设备ID","设备密钥","",0,0,"" AT+MQTTCONN=0,"broker.emqx.io",1883,1 AT+MQTTPUB=0,"topic名称","{"temp":26.5,"hum":63}",0,0这种方式最大的好处是,STM32只需要维护几个简单的AT指令序列,不用关心TCP保活、心跳包、报文封装这些底层的细节,稳定性也更好。MQTT是物联网场景里最常用的协议,设备断线重连、消息推送都是现成的,后面扩展手机App或者小程序都很方便。
我在项目里用的是巴法云或者EMQX的公共Broker,先在网页上建好主题,ESP8266通过MQTT发布数据,网页端订阅就能看到实时曲线。调试时可以用MQTTX这种客户端工具直接查看消息流,省去了自己搭服务器的麻烦。
4. STM32端软件核心逻辑设计
4.1 主循环与状态机设计
STM32程序不复杂,但也不能全写在while循环里。我按照功能拆成了模块:AM2301驱动、GP2Y1010驱动、OLED显示、继电器控制、ESP8266通信、蜂鸣器报警。主循环是一个状态机,每个状态执行完一个任务后立即切换,避免阻塞式延时导致传感器采集频繁失败。
核心状态大概是:初始化 → 采集温湿度 → 采集粉尘(分多次ADC采样) → 数据滤波和阈值判断 → 控制继电器 → 刷新OLED → 发送数据给ESP8266 → 回到采集。每个状态都设置了超时保护,比如AM2301读取如果2秒内没有响应,则进入错误状态并重试。
有一个经验:不要在主循环里加入delay(1000)这种长延时,会导致MCU无法及时响应继电器动作和串口数据。我用的方式是用定时器3作为系统心跳,每100ms置一个标志位,主循环检测到标志位后执行相应任务。
4.2 数据滤波:滑动平均与去毛刺
粉尘传感器ADC读出来的原始数据波动很大,因为气流变化会导致颗粒物分布不均匀。我分别采集5组数据去掉最大值和最小值,再取中间3个值的平均,得到的结果稳定很多。
温湿度读取也要做合理性检查,比如温度在-10℃到60℃范围外、湿度在0到100%RH范围外,直接判定为传感器异常,沿用上次有效值,避免瞬时错误数据触发继电器误动作。这个合理性判断很简单,但能救你很多次。
4.3 自动通风除湿控制策略
控制逻辑是整套系统的灵魂。我设定的核心逻辑是:如果粉尘浓度超过0.15mg/m³,立即打开排风扇;如果湿度超过70%RH且持续5分钟,打开排风扇和除湿机;如果温度超过28℃,打开排风扇强制通风;任何一项恢复正常值并持续10分钟后,关闭对应设备。加持续时间判断是为了防止传感器单次波动就频繁启停设备,继电器寿命会长很多。仓库里的设备频繁启停不仅费电,还容易坏。
继电器控制还加了一个安全逻辑:除湿机和排风扇不能同时长时间开启到无意义状态。比如温度低于5℃时,除湿机的除湿效果会大幅下降,反而可能结霜,此时即使湿度高也不启动除湿机,只开启排风扇。这个细节是从实际使用反馈里总结出来的。
4.4 OLED显示与本地交互
本地显示我用的是0.96寸OLED SSD1306屏幕,I2C接口,两线连接就能显示温度、湿度、粉尘浓度、继电器状态和WiFi连接状态。调OLED驱动时注意I2C地址,通常是0x3C,也有0x3D的,读取失败时先检查这个。
为了方便现场调试,我还加了一个按键,短按切换显示页面,长按进入阈值设置模式,可以上下调节湿度和粉尘报警阈值。阈值数据保存到STM32内部Flash,掉电不丢失。一开始我用的EEPROM模拟,后来发现直接用Flash的最后一个扇区存储两个浮点数更简单,注意擦写次数限制,别频繁写就行。
5. 完整调试流程与实测记录
5.1 分模块调试的顺序
不要一上来就把所有模块都焊在一起调试,否则问题定位会非常痛苦。我的调试顺序是这样的:先单独调AM2301,通过串口打印温湿度数值;再单独调GP2Y1010,用点着的香靠近传感器,看ADC值和换算后的浓度值是否上升;然后调继电器控制逻辑,用按键手动开合继电器,确认电路工作正常;最后才接上ESP8266,调AT指令和MQTT上报。
每个模块调通之后,再整合到一起烧录完整程序,用串口log观察状态切换是否正常。串口打印建议开启复用,用USART1发调试信息,USART2用于和ESP8266通信,两者不要混用。
5.2 粉尘浓度的换算与实际测试
GP2Y1010AU0F的ADC原始值和浓度之间是一个近似线性关系,我用电压值进行换算。传感器输出电压和粉尘浓度的关系可以近似看成一个通过原点的直线,但最好用香烟或者标准粉尘源校准一次。我测试时用点着的檀香放在传感器进风口附近,观察ADC值变化,记录不同距离下对应的数据,然后反推一个近似系数写进程序里。
实测数据大致是:正常环境ADC原始值在200到250左右;靠近香烟烟雾时能跳到600到800;在密闭房间点燃一支香等待几分钟,能到1000以上。通过分压电阻换算后,我用一个简单的线性公式计算出mg/m³浓度的相对值。如果想要精确的绝对浓度值,需要有标准粉尘源校准,但仓库预警场景下,相对变化趋势已经足够判断是否需要通风。
5.3 整机连续运行测试
系统组装完成之后,我放在一个模拟仓库环境的小房间里跑了整整72小时。测试条件设定为:温度报警阈值28℃,湿度报警阈值70%RH,粉尘浓度阈值0.15mg/m³。前两天用加湿器把房间湿度从60%慢慢升到90%,系统在湿度超过70%后约1分钟自动开启了排风扇,除湿机也在湿度超过75%后启动,之后湿度回落到65%左右时自动停机,控制逻辑工作正常。
WiFi断线测试也做了一下:直接拔掉路由器电源,系统本地控制不受影响,继电器照常动作,ESP8266在WiFi恢复后自动重新连接并补发数据。这里有个小经验,ESP8266断线重连策略要写成指数退避,第一次3秒,第二次6秒,第三次12秒,上限60秒,否则路由器恢复后大量设备同时重连会导致拥堵。
6. 常见问题与排查速查表
6.1 高频问题与解决方案
表里是这套系统开发过程中我遇到过的典型问题和解决方式:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| AM2301读到的温度始终是0℃ | 数据线上拉电阻缺失或接触不良 | 补上4.7kΩ上拉电阻,检查杜邦线 |
| 粉尘浓度数据剧烈跳动 | PWM采样时序没对齐 | 用示波器检查PWM波形,确保在LED点亮0.28ms后采样 |
| ESP8266连接WiFi一直超时 | 供电不足,模块反复重启 | 给ESP8266单独供电,使用AMS1117-3.3V并加大电容 |
| 继电器动作导致STM32复位 | 电源共地干扰严重 | 继电器电源独立,加续流二极管,MCU电源加去耦电容 |
| 串口发送AT指令后无响应 | 引脚接错或固件异常 | 检查TX/RX交叉连接,重新刷AT固件,确认波特率115200 |
| 因串口回显干扰AT解析 | AT指令回显未关闭 | 发送ATE0关闭回显,解析时只用OK或ERROR作为判断依据 |
6.2 几个容易被忽略的坑
第一个坑是ESP8266的GPIO0和GPIO2引脚默认状态问题。模块上电时如果GPIO0被拉低会进入下载模式,导致固件不跑,所以连接STM32时不要让GPIO0悬空接低电平,最好悬空或者通过电阻拉高。第二个坑是STM32的PA9和PA10是USART1默认引脚,但如果用PA9做普通GPIO去控制继电器,会发现在初始化串口之后引脚功能被复用,导致继电器无法控制。解决方法是使用引脚重映射,或者换其他GPIO控制继电器。第三个坑是ADC采集的参考电压不一定是精确的3.3V,如果直接用ADC值反推电压,误差可能在5%左右,对于粉尘浓度换算影响不大,但如果需要精确测量,要用内部基准电压校准。
还有一个跟上位机联调相关的坑:ESP8266发云平台的数据如果包含小数,STM32用printf直接格式化浮点数,由于标准库默认不开浮点支持,打印出来的可能是空字符串,需要重写printf相关函数,或者在编译选项里勾选Use MicroLIB。这个坑在Keil MDK里几乎必踩,我花了一个多小时才定位到。
7. 优化扩展建议与心得
这套系统跑稳定之后,我给它加过几个扩展功能。一个是本地数据记录,用SPI接口的SD卡模块,每天存一张CSV文件,记录温湿度、粉尘浓度、设备启停时间,方便仓库管理追溯。另一个是增加了第二个粉尘传感器,放在排风扇出风口,用来检测通风效果,如果通风半小时后出风口粉尘浓度仍然高,说明滤网堵塞或者风扇故障。还可以把温度传感器换成DS18B20防水探头,放到仓库不同位置,做一个多点温度监测,避免只测一个点导致误判。
如果后续想接触摸屏,STM32F103C8T6的Flash只有64KB,如果程序太大装不下,可以考虑换STM32F103RCT6(256KB Flash),或者把一些数据存储逻辑移到ESP8266那边处理。
我个人实际做下来最深的体会是:系统本身技术难度不算大,真正花时间的都是细节。比如粉尘传感器采样时序差100微秒,测出来的数据就是另一回事了;比如继电器和MCU共用一个电源,刚开始时总复位,我怎么都没想到是电磁干扰;比如ESP8266固件版本太老不支持MQTT AT指令,刷完新固件之后需要重新测试所有AT指令。做这种整套系统,耐心比天赋重要,遇到问题分模块排查,总能解决的。
这套系统断断续续调了两周,最后稳定运行三个月没出大问题。如果你也要做类似的项目,我建议你从最小系统开始,先把传感器数据读出来,再考虑继电器控制和上云,一步一步来,不要急着一步到位。做完之后你会发现自己对STM32外设、串口协议、物联网通信的理解会完全不一样。