☰
基于STM32的仓库环境监测与智能控制方案实战
2026/10/4 18:06:58 网站建设 项目流程

仓储环境最怕的就是闷、潮、粉尘。以前去仓库巡检,拿个手持温湿度计挨个点位记录,又慢又容易漏,更别说半夜湿度过大没人发现,一仓库纸箱受潮直接损失惨重。所以我自己搭了一套基于STM32的仓库环境控制系统,把温湿度、粉尘监测、自动通风除湿和ESP8266上云一次做齐了。这套方案成本不高、原理清晰,既能跑起来当实用系统,又能拆开当毕业设计或者竞赛项目做,非常适合电子爱好者、自动化专业学生和做物联网方案的人参考。

1. 项目整体设计与方案选型

1.1 为什么选STM32F103C8T6做“大脑”

主控这块我几乎没有犹豫,直接选了STM32F103C8T6。很多朋友问我,既然都要上云了,干脆用ESP32,一个芯片既能采集又能联网,多省事。这话理论上没错,但从分工角度看,数据采集、逻辑控制、继电器驱动这些实时性要求高的活,和网络协议栈这种容易阻塞的任务放在同一个芯片上,容易互相干扰——比如你要精确读取DHT11的时序,结果WiFi中断一来,时序被打乱,读取直接失败。

STM32F103C8T6在这个场景里有几个实打实的优势:72MHz主频、64KB Flash、20KB RAM,跑一个采集加控制的逻辑绰绰有余;有足够多的GPIO和定时器,能同时驱动多个传感器;ADC是12位的,接粉尘传感器输出足够用;USART资源也够,跟ESP8266通信完全不用省。更关键的是,资料极其丰富,标准库和HAL库都能写,遇到问题搜一圈全是答案。从项目稳定性和可维护性来讲,STM32做主控、ESP8266只负责透传上云,就是最合理也最不容易翻车的组合。

功耗上我实测过整机,正常运行电流大概在300mA左右,主要是继电器和风扇的功劳,如果做电池供电版本,可以加睡眠模式控制。这块后面在软件部分会细说。

1.2 传感器选型:温湿度和粉尘监测的搭配逻辑

温度湿度我用了DHT11,这个传感器虽然精度一般,温度误差±2°C,湿度误差±5%RH,但胜在便宜、接口简单、资料海量。如果对精度有更高要求,可以直接换成DHT22或者SHT30,代码改动量不大——我把DHT11的读取函数封装成了独立模块,就是考虑到后面要替换。

粉尘检测用的是夏普GP2Y1010AU0F,这是一颗红外光电式粉尘传感器,能检测PM2.5左右的颗粒物浓度。它内部有一个红外发射二极管和一个光电晶体管,空气经过检测腔时,粉尘颗粒会散射红外光,光电管接收到的信号强度变化就反映了粉尘浓度。输出电压和粉尘浓度近似线性关系,灵敏度大约0.5V/(100μg/m³)。需要注意的是,这颗传感器需要周期性脉冲驱动——红外LED不能一直点亮,必须按照datasheet要求给脉冲信号,采样时机也要卡准,这个我放在第三章讲。

还有个方案是用PMS5003这类激光散射传感器,直接输出串口数据,还能直接给出PM2.5和PM10数值。但我没选它,一来价格高出好几倍,二来功耗大,三来它对风道有要求,需要加风扇抽气,结构上麻烦一些。GPY1010AU0F输出模拟量,接STM32的ADC引脚就行,结构简单,仓库这种大空间的监测够用。

1.3 ESP8266上云方案:AT指令还是固件二次开发

ESP8266接入云端,我最终选了AT指令方案。原因很简单:稳定、可靠、好调试。ESP8266刷原厂AT固件,STM32通过USART发AT命令控制它连接WiFi、建立TCP连接、发送MQTT消息,整个流程清晰明了。你不需要在ESP8266上写复杂逻辑,它就是个“无线透传模块”,所有业务逻辑都集中在STM32这边。

网上也有人给ESP8266刷MicroPython或者NodeMCU固件,然后直接在ESP8266上写采集逻辑。这种方案适合快速原型验证,但问题是ESP8266的ADC只有一个,你还得外接传感器,逻辑一复杂就很容易内存爆炸。更像我这种既要采集又要控制还带云交互的场景,STM32+AT指令是体验最好的。

有朋友推荐过用ESP8266的NonOS SDK做二次开发,直接在8266上写联网逻辑。这个方案性能确实更好,但开发门槛高,调试也费劲,如果不是为了学习SDK本身,我建议还是避开。后面第四章我会专门讲AT指令方式下一些容易踩的坑,包括粘包、回显处理、超时判断这些。

2. 硬件电路设计与搭建要点

2.1 供电系统设计:三路电压轨一次搞定

这套系统里面有12V的直流风扇、5V的ESP8266和继电器、3.3V的STM32和传感器,所以电源设计是整个硬件能不能稳定运行的关键。我用了一个12V/2A的适配器作为总输入,然后做两级降压。

12V到5V用AMS1117-5.0,注意这玩意儿是线性稳压,压差7V,功耗主要是电流乘以压差。系统电流如果到300mA,AMS1117上要吃掉约2W热量,虽然是SOT-223封装,但时间久了会烫手。所以我在输入端加了一个散热焊盘,同时把5V的负载尽可能控制在合理范围——继电器和风扇都直接从12V取电,不经过AMS1117,这样5V轨只负载ESP8266和逻辑电路,电流大约80-100mA,AMS1117勉强扛得住。

5V到3.3V用AMS1117-3.3,这级压差只有1.7V,散热压力小很多,可以安心用。每路输出都加了10uF电解电容和0.1uF瓷片电容组合,避免负载突变时电压跌落。

这里有个非常容易被忽略的问题:ESP8266是WiFi模块,它瞬间发射电流能达到200-300mA,如果3.3V给ESP8266供电的走线太细、电容不够,WiFi一发射电压就掉,表现就是掉线重启、TCP连接失败。用AMS1117-3.3单独给ESP8266供电,同时靠近ESP8266的VCC引脚加一个470uF电解电容,这样实测基本稳如老狗。如果是做电源板集成,直接用MP1584或者LM2596这类DC-DC模块,效率更高,发热更小,但要注意电感布局,噪声稍微大一点,对ADC采集有影响。

2.2 传感器接口与继电器驱动电路

DHT11接口很简单,DATA引脚接STM32一个GPIO,加上一个4.7kΩ到10kΩ的上拉电阻,单总线协议,时序后面细讲。接线距离超过20cm的话,用屏蔽线或者尽量缩短,否则读出来的湿度值容易跳变。

GPY1010AU0F有6个引脚,VCC和LED电源接5V,LED引脚需要接一个150Ω电阻,然后LED控制信号引脚(接STM32的GPIO输出脉冲),A0是模拟输出接STM32的ADC输入。需要注意的是V-LED引脚应该通过一个150Ω电阻接5V,但我看到很多人的原理图直接把5V串150Ω就接到LED引脚上了,这其实也是可以工作的,但不严谨。完全按datasheet来接更稳。

继电器驱动我用的ULN2003,这个芯片内部达林顿管,集成了续流二极管,直接驱动12V继电器非常合适——如果你用三极管驱动,千万别忘了那个续流二极管,不然继电器断电瞬间线圈产生的反向电动势能烧掉三极管甚至打到STM32芯片上,这个坑我身边不止一个朋友踩过。每个继电器线圈电流大约70mA,ULN2003一路能承受500mA,完全没问题。

还有一个值得注意的点:继电器和STM32之间要有隔离,最简单的方式是用光耦,比如PC817或者EL357N。我用的是光耦加ULN2003的组合给继电器做隔离,输入侧是3.3V逻辑接光耦二极管,输出侧是12V接继电器线圈。这样一来STM32和高压侧完全隔离,即使继电器出问题也不容易殃及主控板。

2.3 接线与布局的实操笔记

具体接线我给一套我验证过的方案,照着连大概率一次成功:

  • STM32F103C8T6最小系统板,PA0接DHT11的DATA。
  • PA1接GPY1010AU0F的A0模拟输出,PA2接GPY1010AU0F的LED控制引脚(输出脉冲)。
  • PA9和PA10接ESP8266的RXD和TXD,交叉连接,注意波特率设置。
  • 继电器1控制12V排风扇,继电器2控制除湿机或加热器,我用的PA6和PA7输出去控制光耦输入端。
  • OLED显示屏(I2C,0.96寸)接PB6和PB7,实时显示状态,调试的时候非常方便。

PCB布线的话,最好把数字地和模拟地分开铺,然后单点连接。GPY1010AU0F的模拟输出离开关电源的GND远一点,否则ADC采样出来的数据会有很大的毛刺。我是先用杜邦线飞线调试跑通了整个系统,再画PCB,不然硬件问题很难跟软件问题区分开来。

3. 软件架构与核心代码实现

3.1 外设初始化:时钟、GPIO、ADC、定时器一锅端

软件我用的标准库(也有HAL版,但标准库是真的清晰直观,功能简单场景下省事很多)。整个工程按模块划分了几个文件:main.c、dht11.c、dust_sensor.c、control.c、esp8266.c、oled.c、cloud.c。各模块独立封装,这样调试哪个坏点一目了然。

系统时钟配置为72MHz,PLL倍频9倍,外部8MHz晶振。这一步不懂原理的话直接照抄ST的SystemInit函数就行,但记得检查RCC配置正确后主频是72MHz,否则后面延时函数全部不准确。

GPIO配置按功能区分:PA0配置为上拉输入(DHT11),PA1配置为模拟输入(ADC),PA2配置为推挽输出(粉尘传感器LED脉冲),PA6和PA7推挽输出(继电器),USART1的PA9、PA10复用推挽,I2C的PB6、PB7开漏输出加上拉。

ADC用的是ADC1的通道1,即PA1,采样时间尽量拉长,我设置的采样周期是239.5周期,这样内阻高的信号源也能准确采样。定时器方面,我开了TIM2做全局延时基准,TIM3用来产生40kHz左右的定时中断——做粉尘传感器LED的脉冲驱动,后面讲。

3.2 DHT11采集:时序卡不准,啥都白搭

DHT11是单总线协议,一次完整数据传输是40bit,包含湿度整数、湿度小数、温度整数、温度小数、校验和。逻辑非常吃时序,主机MCU必须在微秒级别精确控制电平高低。我封装了一个读取函数,核心逻辑是这样的:

// 简化代码,重点看时序 uint8_t DHT11_Read_Bit(void) { while(GPIO_ReadInputDataBit(port, pin) == SET); // 等待低电平结束 while(GPIO_ReadInputDataBit(port, pin) == RESET); // 等待低电平后的高电平开始 delay_us(40); // 延时40us后判断电平 if(GPIO_ReadInputDataBit(port, pin) == SET) { return 1; } else { return 0; } }

细节在于:DHT11在主机发送起始信号后,会在总线上先拉低80us再拉高80us,然后开始传输数据。每个bit是从低电平开始,高电平的时间长短决定数据0还是数据1——高电平26-28us是0,高电平70us是1。所以读到高电平之后延时40us再采样,就能稳定区分。用示波器看波形是这个项目调试中最有效的步骤,我强烈建议有条件的朋友接个逻辑分析仪,不然时序问题只能靠猜,效率非常低。

还有一个容易忽视的坑:连续两次读取DHT11之间,最好间隔1秒以上,因为DHT11本身的采样频率有限,你读太快它内部还没更新,返回的还是旧数据,尤其做快速循环控制系统的时候,明明传感器没坏但就是数据不变化,容易误判成硬件故障。

3.3 粉尘传感器采集:模拟量不是简单读ADC

GPY1010AU0F的输出特性决定了它不能像光敏电阻那样一直接着读。红外LED需要脉冲驱动,datasheet上给的参考波形是:周期10ms,脉冲宽度0.32ms,然后在脉冲开始后的0.28ms时刻采样输出电压。也就是说,MCU要在每个周期内做两件事:先输出一个0.32ms的高电平给LED控制引脚,再延时0.28ms,立刻读取ADC值。

这个时序用软件延时可以做,但精度不高。我是用TIM3定时器产生10ms中断,在中断服务函数里控制PA2电平翻转,同时触发一次ADC转换。读取的原始ADC值经过滤波算法后再和电压-浓度曲线做换算。换算公式建立在输出电压和粉尘浓度的线性关系上,大致是浓度(μg/m³) = (Vout - 0.6V) / 0.5V × 100,实际每个传感器零点有差异,所以我在代码里保留了一个校准偏置变量,通过标定实验填进去。我这里用了一个简易中值滤波加滑动平均的双重滤波,ADC原始值连续采5次取中值,再对中值做滑动平均,这样比单纯读一次稳得多。实际测试下来,在粉尘浓度变化剧烈的时候,采集曲线依然比较平滑,不会出现跳变的毛刺。

粉尘传感器还有一个坑是它容易被灰尘堵塞,如果在粉尘浓度特别高的环境中长时间运行,检测腔会积灰,导致输出信号衰减。我当时给传感器进风口加了一层过滤棉,定期拆下清洗,实测运行一个月后输出下降了不到5%,效果可以接受。

3.4 控制逻辑:阈值判断加迟滞,防止继电器疯狂抖动

控制逻辑是最能体现工程思维的地方。如果你写的判断逻辑是“湿度大于70%就开风扇,小于70%就关风扇”,那系统运行起来会非常频繁地开关继电器——因为湿度在阈值附近波动时,风扇一开湿度下降,一下降就关,关了湿度又回升,于是又开。继电器频繁吸合,触点寿命会急剧缩短。

我设计的是迟滞控制:

  • 湿度 ≥ 75%RH:开启排风扇、除湿机
  • 湿度 ≤ 65%RH:关闭排风扇、除湿机
  • 温度 ≥ 35°C:开启排风扇散热
  • 温度 ≤ 30°C:关闭排风扇散热
  • 粉尘浓度 ≥ 150μg/m³:开启排风扇通风
  • 粉尘浓度 ≤ 100μg/m³:关闭排风扇通风

这个75%和65%之间、35°C和30°C之间的差值就是“迟滞带”,它能让执行机构在阈值附近变“迟钝”,避免反复开关。每个阈值我都预留了修改宏定义,方便根据不同仓库的实际需求调整。控制周期我做了个1秒的扫描循环,每1秒采集一次并刷新比较,给执行机构足够的时间响应,又不会太频繁地做判断。

实际跑下来,这套控制逻辑最大的好处是继电器动作次数大幅减少。我之前做过一个不做迟滞的版本,运行一天继电器动作超过300次,加了迟滞之后,一天不到30次,触点和执行机构寿命都能明显延长。另外,我还在控制逻辑里加了保护机制——如果排风扇连续运行超过2小时,系统会自动暂停10分钟,这叫“循环保护”,主要是防止风机长时间运行过热。

3.5 ESP8266 AT指令联网:初始化序列与粘包处理

ESP8266通过AT指令跟STM32通信,波特率我统一设置为115200。模块上电后,STM32要先做一套“标准动作”:关闭回显(ATE0)、设置Station模式(AT+CWMODE=1)、连接WiFi(AT+CWJAP="SSID","PASSWORD")、开启透传模式(AT+CIPMUX=0,AT+CIPMODE=1),然后才能建TCP连接或者MQTT连接。

提示:AT指令每一条发完一定要等模块返回“OK”或者“ERROR”再去发下一条。很多人栽在这上面——模块还没处理好上一条指令就发了下一条,结果模块直接返回乱码。我写了一个带超时等待的函数,最多等5秒,超过就重发,重发3次还失败就记录错误并重启模块。

粘包问题也要提前处理。ESP8266在透传模式下,云端返回数据可能一次来一大段,也可能分几次来。我的接收解析用了环形缓冲区加状态机,每次USART中断只塞进缓冲区,主循环再解析完整数据帧。如果你用简单的“收到一个字节就处理一个字节”,在MQTT大量数据交互的时候很大概率丢包或者解析错位。

3.6 上云接入:巴法云MQTT发布订阅一次打通

我用的云平台是巴法云,理由就三个:免费、免备案、接入文档简单。MQTT协议在这套方案里的角色是发布订阅,STM32通过ESP8266作为客户端连到云端,发布主题里带传感器数据,订阅主题接收控制指令。

MQTT协议在STM32上的实现,网上有很多移植好的MQTT库,比如paho MQTT嵌入式C库,可以在STM32上跑。但我为了降低复杂度,直接用AT指令调用ESP8266内置的MQTT透传能力,只要把云平台地址、端口、client ID、username、password这些参数按AT指令格式发给ESP8266,模块自己就完成了MQTT协议交互。我这边只做了两件事:一是按固定格式拼数据串,二是解析云端下发到订阅主题里的控制命令。实测数据上报频率设为每10秒一次,云端能稳定收到数据,没有出现掉线断连的情况。

数据格式我用的是JSON,比如:{"temp":26.3,"hum":68.5,"pm25":45.2}。巴法云控制台会自动解析展示,不用自己再去写后台和数据可视化面板。想扩展的话,还可以在巴法云设置告警规则,比如温度超过阈值就推送消息到手机。

我给数据上报设计了一种带序号的数据帧结构,方便排查丢包问题:每帧数据里加一个自增序号,通过比对云端连续收到的序号就能知道有没有漏报。同时做了一个心跳机制,每60秒发一个短消息,如果云端超过3次心跳没收到,就判定这条链路有问题,触发本地重启WiFi模块重连。

4. 常见问题与排查技巧实录

4.1 DHT11数据老是读不出来,怎么定位时序问题

这应该是最多人卡住的地方。DHT11读取失败,不外乎三个原因:上下拉电阻没接、延时函数不准、读取间隔太短。我的排查顺序是:先用逻辑分析仪抓DHT11引脚的波形,如果波形只有一簇密集的脉冲,说明DHT11根本没应答主机——大概率是起始信号时序不对,或者上拉电阻忘接了。如果是波形就像一片毛刺,电平翻转时间完全是乱的,多半是MCU主频配置错了,延时函数因此全部失真。

我自己踩过最大的坑是用软件延时函数算错指令周期。标准库环境下,一个简单的循环嵌套延时容易因为编译器优化导致时间完全不等,所以我后来统一改用SysTick做延时,用定时器来计时,这样延时精度才能有保障。网上很多帖子的软件延时函数是拿旧版编译器测试出来的,你自己换了个编译器之后可能就完全不对了。

解决方法是给DHT11读取函数加超时保护,如果40bit数据在某个bit上等了超过200us还没翻转,就直接判定失败返回错误码,主循环里做重试。这样系统不会死等一个传感器卡住整个控制流程。

4.2 粉尘传感器读数一直偏高或跳变,怎么办

粉尘传感器输出飘高或者乱跳,先排除电源质量问题。我的测试经验是:如果供电纹波大,ADC读出来的粉尘浓度会整体偏高且波动剧烈。用示波器看AMS1117-3.3的输出,纹波超过50mV就需要加滤波电容或者改DC-DC方案。

零点漂移是另一个常见问题。GPY1010AU0F在无尘环境下输出电压理论上是0.6V左右,实际会有偏差。我系统里做了一个校准流程:开机后先让传感器运行5分钟预热,然后采100次数据取均值作为零点偏置,之后每次换算都减掉这个偏置值。这个做法简单实用,比手动填一个固定偏置强得多。

另外脉冲采样的时序直接影响数值。LED脉冲的周期和宽度必须严格按datasheet来,我遇到过用普通delay写的采样函数,采样时刻从0.28ms漂到0.2ms,读数直接少了近一半。如果发现数值整体偏小,先检查采样点是否准了。

4.3 ESP8266连不上云平台或频繁掉线

连不上云平台,先做分步排查:先看ESP8266是否连上路由器(AT+CWJAP?查询状态),再检查TCP/MQTT端口和地址是否填对,最后看云端账号密码是否有问题。很多人卡在这一步,其实是云平台里的用户私钥和密码填反了,或者topic拼写错误,导致订阅不到消息。

频繁掉线,我遇到最多的是供电问题。ESP8266发射瞬间电流大,如果3.3V电源拉胯,就会在发射时电压跌落,模块自动复位。这个在2.1节已经重点讲过了,再强调一次:去耦电容必须靠近模块引脚。还有就是WiFi信号问题,如果ESP8266离路由器太远或者隔着货架金属,信号弱会导致TCP超时掉线。我用了一个简单的心跳保活机制,如果连续5次心跳无响应就重启ESP8266重新初始化,实测可靠性提升明显。

AT指令粘包问题在调试助手端也会出现。有些AT指令返回结果是多行的,比如AT+CWJAP连接成功后会有“WIFI CONNECTED”、“WIFI GOT IP”,如果程序里用一条strstr去匹配“OK”,就可能被中间多出来的字符串搞混。正确做法是匹配关键状态字段,比如“WIFI GOT IP”才算连上了WiFi。

4.4 继电器频繁吸合或者动作不响应

继电器不动作大概率是驱动电路问题,用万用表测ULN2003输出端是否拉低。如果输入有控制信号但输出不拉低,检查光耦输入端电流够不够,3.3V串联限流电阻阻值是否太大导致LED电流过小。我之前遇到过一次输出端加继电器负载后完全没反应,查了半天原因是ULN2003输入端的限流电阻选太大,输入电流只有不到2mA,达林顿管没有完全导通。

继电器频繁吸合是迟滞没做好,这个在3.4节已经讲过。如果加了迟滞还是频繁动作,可能是传感器数据的噪声太大导致阈值比较时频繁跨越边界。解决办法是加滤波,对湿度、温度、粉尘浓度三个数据都做一阶低通滤波,比如新值 = 上次值 × 0.7 + 本次值 × 0.3。这样即使原始数据有波动,进入判决逻辑的数据依然平滑,继电器自然安静了。

4.5 数据上报到云端,但控制指令下发的实时性不够

有一次我在测试中发现,数据能稳定上报,但从云端下发控制指令到系统执行,延迟总在5秒以上。排查后发现不是网络问题,而是我的STM32只监听了ESP8266透传数据,但主循环里串口解析的优先级太低,被其他任务拖住了。我把串口数据解析放进了一个独立状态机,并且在下发控制指令时使用高优先级串口中断打断当前循环,延迟立刻降到了1秒以内。

实话说,这套系统做完之后,我自己有个很深的感受:最花时间的往往是那些“理论之外”的细节,比如一个上拉电阻的位置、一个滤波参数的调整、一次供电纹波的排查。把这些问题逐一解决掉,整套系统才真正从“能跑”变成了“稳定跑”。

另外提一句,这套系统后续我觉得还可以往两个方向扩展:一是把所有传感器换成RS485总线版本,比如Modbus RTU协议,这样就可以接更远的点位,实现仓库多点位分布式监测,一台主机带几十个传感器子节点;二是加4G模块作为备选通信链路,因为仓库里WiFi信号毕竟不是随时都有保障。如果你正准备做类似的毕设或者实际改造,建议从一开始就留好这两个接口的扩展位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询