尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32的仓库环境控制系统:温湿度采集、粉尘监测与ESP8266上云实现

基于STM32的仓库环境控制系统:温湿度采集、粉尘监测与ESP8266上云实现 仓库里最怕的不是“东西多”而是“环境失控”。我这几年在工厂车间、物流仓和果蔬保鲜库见过太多类似问题——梅雨季墙壁挂水珠、滤芯积尘、温湿度超标导致货物发霉变质。很多中小仓库其实没有上PLC或工业级环境监控的预算这时候用STM32做一套低成本环境控制系统性价比就非常突出。这篇文章要聊的“基于STM32的仓库环境控制系统”核心就是围着STM32F103C8T6这个主控芯片做温湿度采集、粉尘监测、自动通风除湿再用ESP8266模块把数据传到云平台实现手机远程查看和控制。整套系统解决的核心痛点很明确仓库环境不是靠人“跑现场”盯出来的而是靠传感器24小时不间断地采集、判断、联动执行出了问题主动处理。适合正在做嵌入式毕业设计、电子设计竞赛或者给小型仓储做低成本改造的朋友参考。我写这篇文章不是翻手册念参数而是把这套系统从硬件选型到软件逻辑、从传感器怎么接到ESP8266怎么连上OneNet从头到尾过一遍重点把调试中踩过的坑和底层原理讲清楚。1. 系统整体设计与方案选型1.1 设计目标拆解不是“传感器拼板子”而是完整控制闭环先明确这套系统要干的事拆成四层来看采集层温湿度数据由DHT11或SHT30负责粉尘浓度由GP2Y1010AU0F红外粉尘传感器负责。判断层STM32不断读传感器数据和环境阈值比较判断当前环境是“正常”“偏潮湿”还是“粉尘超标”。执行层根据判断结果控制继电器打开或关闭排风扇、除湿机、新风风机。上云层ESP8266通过串口从STM32拿到数据经Wi-Fi上报到MQTT云平台。这个架构看起来简单但设计上有两个容易被新手忽略的点。第一是采集与判断要做成闭环比如排风扇启动后湿度会慢慢降下来如果只靠单次阈值判断风扇会在阈值附近反复启停。第二是上云要独立于本地控制逻辑——即使ESP8266断网仓库本地的自动通风和除湿功能也要正常工作不能因为云服务挂掉仓库环境就失控。这才是工程上真正可用的系统设计而不是演示完就扔一边的样机。1.2 主控选型为什么选STM32F103C8T6而不是51单片机STM32F103C8T6几乎成了这类项目的默认选择原因不复杂性价比高几块钱一颗芯片72MHz主频64KB Flash20KB SRAM跑传感器采集和控制逻辑绰绰有余。外设丰富多个定时器、ADC、USART刚好覆盖这套系统所有需求——用定时器产生PWM驱动粉尘传感器指示灯用ADC采集粉尘电压用串口和ESP8266通信。资料生态成熟搜“STM32温湿度检测”“STM32毕业设计”大量现成参考代码遇到问题也容易找到答案。如果是对比51单片机最直观的差异是ADC和定时器资源。51内核的STC89C52虽然也能做但ADC精度和PWM定时器资源都要靠外部扩展实现而且功耗更高开发效率低很多。对我来说STM32的优势不只是性能还有调试手段——SWD调试器可以实时看变量、中断状态而51上很多逻辑错误只能靠反复烧录去猜。1.3 传感器的取舍DHT11、SHT30、GP2Y1010选型对比温湿度传感器我先说结论如果只是毕业设计或仓库粗监测DHT11完全够如果预算允许且对精度有要求建议直接上SHT30。参数DHT11SHT30温度精度±2℃±0.2℃湿度精度±5%RH±2%RH测量范围20%-90%RH0%-100%RH通信方式单总线I2C价格约2元约6-8元软件复杂度中需精确时序低I2C读寄存器即可DHT11虽然便宜简单但精度确实一般尤其在仓库湿度接近饱和的环境下误差会被放大。SHT30温漂更小校准也更方便——它出厂做了标定直接读寄存器就是换算好的数据不需要调零。我实际验证过同一个测试环境SHT30读数和福禄克温湿度计相差不到1%RH而DHT11有时会差到5个百分点。粉尘传感器这块GP2Y1010AU0F是夏普的一款光学空气质量传感器它内部有一个红外LED和一个光电晶体管工作时LED点亮光遇到空气中悬浮颗粒后发生散射光电管检测到的散射光强度与粉尘浓度成正比。它输出是模拟电压直接接STM32的ADC输入引脚原理上不复杂但采样时序有讲究这部分后面在3.3节详细展开。1.4 上云方案选型AT指令ESP8266还是直接SDK开发ESP8266上云有两条主流路线。一条是模块里烧AT固件STM32通过串口发AT指令来控制模块比如“连接Wi-Fi”“建立TCP连接”“发送数据”操作简单直观。另一条是直接用Arduino或ESP8266 SDK开发把ESP8266当成独立MCU直接跑网络协议栈和业务逻辑。我在这套系统里推荐AT指令方案原因有两点职责分离STM32负责传感器采集和本地控制ESP8266只做透传。哪边出问题排查范围都很清晰。可靠性更好AT固件是Espressif官方维护的稳定版本网络协议栈经过大量验证比自己在SDK里折腾稳定得多。实际开发中你不需要关心ESP8266内部怎么处理Wi-Fi协议只需要掌握几条关键AT指令。这也意味着时间投入可以集中在主控逻辑上对绝大多数项目场景都更高效。2. 硬件搭建与关键电路设计2.1 传感器接线细节上拉电阻、ADC引脚和供电先把DHT11接好。DHT11是单总线通信数据线必须要外接一只4.7kΩ上拉电阻到3.3V或5V。没有这个上拉电阻你会发现读出来的数据时好时坏严重时干脆读不到应答信号。原理是单总线协议在释放总线时靠上拉电阻将电平拉高MCU和传感器都通过拉低总线来发起信号。接线方式VCC接3.3V或者5VDHT11手册说3.3-5.5V都可以我习惯用3.3V因为和STM32电平完全匹配。DATA接STM32的一个GPIO配置成开漏输出并带上拉或普通推挽输出也行DHT11通信时需要切换输入输出模式。GND直接接系统电源地。GP2Y1010粉尘传感器是6针接口区分清楚LED电源Pin 2供电端需要串联一个100Ω电阻再接5V。LED GNDPin 4粉尘传感器内部LED的接地端。SENS OUTPin 5模拟信号输出接STM32的ADC输入引脚。SENS GNDPin 6传感器接地端。GP2Y1010的手册推荐驱动方式是让LED以320Hz左右的脉冲点亮每个周期LED点亮时间约0.32ms信号采样点设置在脉冲点亮后0.28ms附近。但很多卖家给的模块已经集成了驱动电路输出直接是模拟电平接线就简化成电源、地和信号三根线。我用的是模块版本STM32的ADC引脚选择PA0或PA1配置为模拟输入即可。如果沙尘环境干扰较大最好在输出管脚加一个100nF电容做滤波减少高频毛刺。2.2 继电器输出与驱动为什么不能拿GPIO直接继电器执行设备是排风扇和除湿机都属于强电设备必须用继电器隔离。但STM32的GPIO输出电流只有几毫安直接驱动继电器线圈根本拉不动大桥。正确做法是加一级三极管或ULN2003驱动。这里给一个最常用的NPN三极管驱动电路方案GPIO通过1kΩ电阻接到S8050三极管基极。三极管发射极接地集电极接继电器线圈一端。继电器线圈另一端接12V或5V电源。继电器线圈两端反向并联一个1N4007二极管阴极接电源正极。这个二极管是续流保护二极管很多人容易漏掉。继电器线圈是感性负载在三极管关断瞬间会产生反向电动势电压可以达到几十伏甚至上百伏没有续流二极管三极管很容易被击穿。我的方案是直接用ULN2003芯片它内部集成了7路达林顿管驱动还内置了续流二极管省掉不少外围元件。驱动能力足够一路给继电器一路可以留作备用扩展性也好。对于220V交流负载的控制端继电器触点耐压通常标10A/250VAC控制风扇、除湿机完全没问题。但接线时需要注意继电器触点输出端建议经过接线端子再接强电设备不要直接焊在PCB上长时间跑大电流避免发热导致焊点老化。2.3 ESP8266供电与电平适配这两颗雷必须避开ESP8266模块的坑主要在供电和电平。供电这块ESP8266 Wi-Fi发射瞬间电流能达到300mA以上如果用的是普通AMS1117-3.3这种线性稳压芯片瞬时压降会造成模块复位或连接异常。我建议给ESP8266单独用一个输出能力不低于500mA的稳压芯片或者在5V输入和稳压器之间加一个大电解电容做储能缓冲。电平适配这块很多ESP8266模块是3.3V电平但STM32F103的串口TX如果直接配置成推挽输出输出高电平大约是3.3V和ESP8266兼容。这个还好不涉及“高到5V”的问题。不过如果你把ESP8266接在5V单片机上比如Arduino Uno那RX端就需要电阻分压否则长期运行会烧模块。还有一个容易忽略的细节ESP8266模块串口引脚的默认状态。部分模块在复位时会从串口输出一段开机日志如果这段时间STM32正好在向它发送AT指令指令可能会被日志淹没。解决办法是STM32上电后先等500ms到1s再开始发AT指令。2.4 电源树设计三种电压不打架整个系统的电源需求是STM32和DHT11、ESP8266需要3.3V。粉尘传感器模块建议5V供电输出信号接到3.3V的ADC引脚问题不大。继电器线圈和风扇、除湿机需要12V或220V。推荐电源方案是外接12V/2A适配器 → 12V直接给继电器线圈供电 → 5V降压模块如LM2596或MP1584给传感器和ESP8266 → AMS1117-3.3给STM32核心供电。在PCB布局时数字电源和模拟电源分开走线ADC采样端的参考电压要稳定。如果把ESP8266的供电和ADC采样的电源放在同一路Wi-Fi发射瞬间的电流波动会让ADC读数产生几毫伏的跳动具体反映在粉尘浓度上可能就是几个ug/m³的偏差问题不大但如果你要追求精度就按这个思路去分开。3. 下位机软件实现与核心逻辑3.1 开发环境与工程初始化流程我用的开发组合是STM32CubeMX生成初始化代码 Keil MDK编译 ST-Link下载调试。这套组合基本是STM32开发的标准流程能省掉大半手写寄存器初始化的工作。CubeMX里要做这几项配置使能PA0作为ADC1通道0用于粉尘传感器采样。配置PB0-PB2作为GPIO输出控制继电器和指示灯。配置USART1为115200-8-N-1模式用于接ESP8266。配置USART2为9600-8-N-1模式用于DHT11调试打印或者DHT11接普通GPIO模拟时序。配置一个基础定时器比如TIM2产生1ms中断作为系统时基。DHT11的数据引脚如果接普通GPIOCubeMX里把它设为开漏输出模式速度设成Low。开漏的好处是配合外部上拉电阻能实现“线与”的逻辑读数据时直接把引脚切到输入模式即可。3.2 DHT11读取时序这锅不背不行DHT11的协议是单总线时序非常严格。完整读取一次要经历这几个阶段MCU拉低数据线至少18ms然后释放并切换到输入模式这是启动信号。DHT11应答先拉低80us再拉高80us。随后DHT11连续发送40bit数据湿度整数部分、湿度小数部分、温度整数部分、温度小数部分、校验和。每bit的区分方式是低电平50us后如果高电平持续26-28us代表“0”如果高电平持续70us以上代表“1”。用STM32实现时GPIO读取之间的时间精度很关键而DHT11的时序容忍度其实并不算特别严格主要在50us和70us区间内判断即可。我写的读取示例逻辑如下uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for(i 0; i 8; i) { while(DHT11_PIN_READ() 0); // 等待电平拉高 delay_us(40); if(DHT11_PIN_READ() 1) // 40us后仍为高说明是1 data | (1 (7 - i)); while(DHT11_PIN_READ() 1); // 等待电平变低准备读下一位 } return data; }一个很实际的建议DHT11连续两次读取时间间隔必须大于1秒。它内部采样就是这么设计的读得太频繁数据完全不更新或者校验报错。调试时踩过一次最后查遍代码才发现是调用频率的问题。3.3 粉尘传感器采样与浓度换算GP2Y1010模块输出的模拟电压约在0.5V到3.6V之间电压越高说明粉尘越多。换算浓度时参考典型灵敏度粉尘浓度mg/m³≈ (输出电压 - 0.6V基准电压) / 0.5V × 0.1 mg/m³严谨一点的话不同批次传感器会有一点差异最好用标准尘源校准。在毕业设计阶段用这个近似估算就足够了。ADC采样可以配置在定时器中断里进行。我设的是每100ms采样一次连续采8次取平均这样能有效滤除环境瞬时波动比如有人走过扬起的灰尘不会立刻触发阈值误判。关键坑位预警GP2Y1010不是随时采样都准它对LED脉冲时序有要求。如果用的是裸传感器而非集成模块需要用一个定时器PWM输出频率约320Hz、占空比约32%的波形去驱动LED并把ADC采样触发点设在脉冲高电平的后半段。否则读数会因为采样时LED亮度还没稳定而大幅偏小。集成模块则不需要考虑这些直接接ADC即可。3.4 自动控制逻辑滞回区间而不是单阈值这是整套系统最容易被忽略但极其关键的设计点。如果控制系统逻辑写成“湿度超过60%就开除湿机低于60%就关”现场会出现一个现象除湿机启动后湿度缓慢下降降到59.8%时关了但因为仓库内地面还在散发潮气湿度又慢慢升回60.1%继电器再次吸合。这样反复启停继电器寿命大幅缩短除湿机本身也受不了。正确写法是设置滞回区间也叫回差控制。我实际用的是执行设备启动条件停止条件排风扇粉尘超标粉尘浓度 150ug/m³粉尘浓度 100ug/m³排风扇温度偏高温度 30℃温度 26℃除湿机湿度 65%RH湿度 55%RH启动条件比停止条件高出一截中间那个区间就是滞回带。这样执行设备不会频繁切换。我把这个逻辑写成一个状态机每个执行设备都有独立的状态typedef enum { DEVICE_IDLE, DEVICE_RUNNING } dev_state_t; // 每500ms调用一次 void EnvControl_Task(void) { if(temperature 30 || dust 150) { if(fan_state DEVICE_IDLE) { relay_on(FAN_RELAY); fan_state DEVICE_RUNNING; } } else if(temperature 26 dust 100) { if(fan_state DEVICE_RUNNING) { relay_off(FAN_RELAY); fan_state DEVICE_IDLE; } } // 除湿机同理 }这种“先判断是否在运行再决定是否切换”的方式从原点就把频繁动作的问题规避掉了。3.5 STM32与ESP8266的通信协议约定STM32和ESP8266之间如果只是简单地把字符串发过去很容易出现粘包、错位的问题。我这边用了一个固定长度的帧协议帧格式帧头0xAA, 0x55 数据长度 数据域 CRC8校验数据域内容依次是温度整数、温度小数、湿度整数、湿度小数、粉尘浓度高字节、粉尘浓度低字节、设备状态。共7个字节。uint8_t frame[11]; frame[0] 0xAA; frame[1] 0x55; frame[2] 7; frame[3] temp_int; frame[4] temp_dec; frame[5] humi_int; frame[6] humi_dec; frame[7] dust_high; frame[8] dust_low; frame[9] dev_status; frame[10] crc8(frame, 10); USART_SendArray(frame, 11);ESP8266在收到完整一帧后自动解析并组装成JSON上报云端。串口发送间隔设为2秒一次既保证实时性又不至于把ESP8266的串口缓冲挤爆。4. ESP8266接入云端AT指令流程与MQTT数据上行4.1 ESP8266配网与建立连接的指令流程先用串口调试工具确认ESP8266模块工作正常然后按顺序执行步骤AT指令说明测试AT返回OK模块正常关闭回显ATE0减少串口日志干扰设置工作模式ATCWMODE1Station模式连接Wi-FiATCWJAP你的SSID,密码路由器名不能有中文查看IPATCIFSR确认模块拿到IP开启透传ATCIPMODE1进入透传模式连接云平台ATCIPSTARTTCP,183.230.40.39,6002以OneNet为例AT指令每个都要以“\r\n”结尾STM32发送时不能只发指令内容要带上回车换行。有个必须要提的坑ATCWJAP连接路由器后模块可能需要几秒到十几秒才能获得IP。很多新手代码里发完CWJAP就马上发CIFSR结果收到一堆ERROR。代码里必须加一个等待确认机制串口解析到WIFI CONNECTED后再延时500ms发CIFSR。4.2 MQTT协议接入参数与AT序列如果直接用TCP透传加HTTP POST JSON数据到平台也可以但MQTT更省流量、稳定性更好。ESP8266的AT固件版本越高对MQTT的支持越完善新版本固件里有ATMQTTCONN、ATMQTTPUB等直接指令。在OneNet云平台创建一个MQTT产品后会得到三组关键参数产品IDproduct_id设备IDdevice_idAPIKey鉴权用这几个参数会拼进MQTT的clientId和username里。以OneNet为例MQTT连接参数格式如下服务器地址183.230.40.39端口6002clientId设备IDusername产品IDpasswordAPIKey在AT指令里连接就是ATMQTTCONN183.230.40.39,6002,设备ID,产品ID,APIKey,0,1参数含义分别是服务器IP、端口、clientId、username、password、0表示不加密、1表示开启干净会话。4.3 数据点JSON格式设计OneNet的数据流格式要求是JSON键值对格式如下{temp:25.6,humi:62.3,dust:89.5,led:1}ESP8266收到STM32的串口帧后需要把它拼成上面这个JSON字符串然后发布到相应topic。发布指令是ATMQTTPUBtopic名字,payload字符串,0,0一个和OneNet对接的坑是payload里的双引号需要转义有些AT指令解析器会把双引号当成字符串界的标识。为了少踩坑在STM32拼数据时可以直接拼成完整JSON字符串再通过ATMQTTPUB发送。测试时可以用串口助手手动发一条确认平台能收到数据再固化到程序里。4.4 云端产品创建与设备调试云平台侧的操作其实比想象中简单。以OneNet为例注册登录后进入控制台选择“多协议接入”。创建产品选择“MQTT”协议。在产品下添加设备记住设备ID。创建数据流名称和JSON里的key对应比如temp、humi、dust。之后在设备列表的“数据流”页面就能看到实时上报的数据。如果一直没数据优先检查网络端到端是否通了先本地用网络调试助手模拟设备连接服务器能连上再排查MCU侧程序。4.5 断线自动重连避免死循环卡死仓库环境控制设备要求7x24小时运行ESP8266如果断网就必须自动恢复。开发时我用了一个循环等待法结果踩到了大坑——我发ATCWJAP后循环体里用strstr找“OK”如果模块因为信号弱一直没返回“OK”程序就卡死在while里出不来了。正确做法是加超时机制int send_AT_wait_OK(char *cmd, uint16_t timeout_ms) { while(timeout_ms--) { if(USART2_ReceiveBuff contains OK) return 1; delay_ms(1); } // 超时未回应返回0进入重试逻辑 return 0; }每次都设置30秒超时超时后就重启ESP8266控制模块RST引脚拉低100ms再释放重新走一遍配网流程。实测这个机制能在掉线后60秒内自动恢复在线状态不需要人工干预。5. 调试记录与避坑实录5.1 串口调试阶段的高频问题我做过的调试记录里涉及ESP8266 AT指令的问题占比最大。集中在几个点。AT指令回显乱码波特率没对上。ESP8266默认波特率常见是115200但部分卖家模块出厂可能设成了9600。发送AT前先把波特率逐档试出来。指令返回ERROR指令参数格式有问题。要么是没加回车换行要么是参数多了空格。ATCWMODE1和ATCWMODE 1不通用后者必报错。连接路由器失败检查SSID和密码是否正确2.4G频段是否开启了AP隔离。部分路由器有“AP隔离”选项开了以后设备之间互不可见会造成ESP8266无法正常联网传输数据。MQTT连接不上先确认模块能不能ping通服务器再用TCP测试端口是否可达。之前热搜词里有一条是“esp8266恢复出厂设置(atrestore)时循环体中检测不到ok,进入死循环”。这个问题的根源就是我上面说的串口等待应答必须独立成为一个带超时函数的模块永远不要在业务代码里裸写while循环去等一个不确定的信号。改成带超时和重试的封装后这个问题的出现概率基本降到零。5.2 传感器数据偏差排查DHT11湿度偏高的问题我遇到过一次。测试环境对比之下DHT11读数是72%RH而SHT30是66%RH。排查发现是因为DHT11离排风扇出风口太近风扇吹过来的水分直接喷到传感器上。解决办法是传感器不要安装在排风口或除湿机出风口附近尽量放在仓库中部、离地1.5米左右的位置有代表性又不受局部气流影响。粉尘传感器也有个问题读数跳变幅度大。刚开始没加滤波数值从90跳到180再跳回110一看就不可信。改成连续采样8次取中位值稳定性立刻好转。中位值比平均值更能抗单点毛刺干扰如果环境粉尘浓度时高时低取中位数更接近真实值。5.3 继电器吸合噪音与干扰继电器频繁吸合除了电气寿命问题还会产生两个副产品触点打火和开关噪音。打火是因为电流突变导致的电弧长期会烧蚀触点。除了滞回控制外我还做了两件事软件上继电器切换时加500ms延时防止逻辑里多个条件同时满足导致继电器被反复交叉切换。硬件上继电器触点并联一个RC吸收电路比如100Ω电阻串联0.1uF电容吸收火花能量。另外继电器线圈和MCU电路做隔离PCB上继电器区域和单片机区域铺地分开单点连接。实测下来ADC读数受到继电器动作瞬间干扰的问题减轻了很多。5.4 仓库环境下的长期运行建议如果这套系统真的要长期放在仓库里运行有几个硬件上的坑需要提前处理三防漆潮湿仓库电路板很容易凝露PCB用完后刷一层三防漆能大幅降低短路风险。端子加固强电接线用螺丝端子压接不要用杜邦线。杜邦线在振动下容易松脱。外壳防护整体装进带防水接头的仪表箱传感器探头单独外置避免箱内热量影响温度读数。看门狗STM32开独立看门狗IWDG一旦程序跑飞就能自动复位。我在主循环里喂狗超过5秒没喂就复位相当于给系统上了保险。6. 最后的经验总结在调试这套系统过程中我印象最深的不是某个传感器怎么驱动而是整机联调时暴露出的问题大部分不是单项功能的问题而是模块与模块之间的协作关系。比如串口协议设计、超时重连机制、继电器去抖逻辑每一个都是单看都能跑、连在一起就出问题的点。如果你要复刻这套系统我的建议是先做减法第一版只做温湿度采集串口打印第二版加入粉尘和继电器控制第三版才是ESP8266上云。这样每一步的变量都少出了问题定位很快。另外从成本角度看这套系统的物料成本大约在120元左右对应的是传统环境监控系统数十分之一的价格做毕业设计或者小型仓库改造都合适。仓库环境控制系统后续还可以扩展的方向不少加烟雾报警联动消防、加OLED屏本地显示、甚至接多路DHT11做分区监测。对我来说这套系统最大的价值不只是省了人工巡检而是把“环境状态”从靠感觉判断变成了靠数据判断。
返回列表