1. 粮仓环境的真实痛点监测什么、为什么重要前几年帮老家一个粮库做过一次巡检系统改造当时陪我下仓的老保管员说了句话让我印象特别深“这个仓要是半夜闷热返潮一仓粮能毁掉一半等第二天早上发现神仙来了也救不回。”粮食存储这事儿最怕的不是老鼠是看不见的温湿度和突发火情。很多团队做STM32毕设或者农业物联网项目时习惯把粮仓环境监测当成“温湿度采集 OLED显示”的简化任务但真正跑过现场的人都知道粮仓环境安防远远不止读两个数据那么简单。这套系统的定位很明确用STM32做一套适合粮仓场景的本地环境监测与安防终端采集温度、湿度、烟雾浓度、火焰信号和人体入侵信号超限时声光告警同时驱动排风扇等执行设备。代码、原理图、Proteus仿真全部开源适合正在找STM32项目练手的嵌入式学习者也适合想做农业物联网课程设计或者小规模粮仓改造的工程师参考。项目本身不复杂但把“环境监测”和“安防”两套逻辑完整地揉进一个STM32F103C8T6里涉及到的单总线时序、ADC采集、多路告警调度和仿真联调都值得掰开揉碎讲一遍。1.1 温度和湿度是粮食安全的头号杀手粮食在存储过程中最核心的生命活动是呼吸作用。温度每升高10℃粮食呼吸强度大概会翻倍呼吸消耗干物质不说还会释放水分和热量让粮堆局部“出汗”。当温度掉到25℃以上、相对湿度超过70%的时候霉菌和储粮害虫的繁殖速度会进入指数区间。换句话说粮仓里最需要盯的两个指标就是温度和湿度这俩数据一旦越线霉变和虫害几乎不可避免。传统做法是保管员每天定时拿手持温湿度计逐仓巡查记录成纸质表格。这里有两个致命问题一是夜间和非工作时间出现温湿度异常根本没人知道等第二天开门已经晚了二是大仓不同部位温湿度差异很大单点手持测量覆盖不全。这套系统在每个监控节点实时采集温湿度超过阈值立刻告警等于把“人盯数据”变成了“系统盯数据”。1.2 烟雾、火焰、非法入侵安防的另一半粮仓的安防需求很容易被忽略。粮粉在特定浓度下是易燃易爆的电气线路老化、人员违规吸烟都有可能引发火情同时粮仓一般位置偏远夜间防盗压力也不小。单纯做“环境监测”不做“安防”只能算完成了一半。所以这套系统在温湿度采集之外增加了三个安防维度烟雾浓度检测通过MQ-2气体传感器实时监测烟雾发生火情时能在早期发现。火焰检测红外火焰传感器识别火光作为烟雾告警的交叉验证减少误报。人体入侵检测被动式红外热释电传感器HC-SR501感知闯入者夜间告警提醒值守人员。四路信号在STM32里统一调度用不同的告警策略区分“环境异常”和“安防事件”这样不会一有风吹草动就乱响也不会漏报真正的问题。这个需求拆解的过程恰恰是很多教程没讲透的部分——拿到原理图先别急着画板子先搞清楚系统到底要感知哪些物理量。2. 系统架构与硬件选型这套方案为什么这么配硬件选型是项目能不能顺利跑起来的第一道坎。很多初学者一上来就想上顶级芯片结果外围电路复杂、调试困难项目烂尾。这套系统选件的基本原则是在满足性能的前提下优先选资料多、成本低、容易买到的器件。2.1 主控选型STM32F103C8T6凭什么够用主控选了STM32F103C8T6这是一颗基于ARM Cortex-M3内核的芯片主频72MHz64KB Flash20KB SRAM。有人会问粮仓监测是不是得用更高端的芯片来保证可靠性实测下来的结论是没必要。从资源占用角度算一笔账资源用途占用情况GPIODHT11、LCD1602/OLED、按键、LED约15个引脚ADCMQ-2烟雾传感器模拟输出1个通道定时器系统节拍、延时延时1-2个串口调试日志输出1个把这些全部算进去Flash和SRAM的占用率都不超过一半余量足够后续扩展。而且这颗芯片可以说是STM32生态里资料最丰富、Proteus仿真支持最成熟的型号之一遇到问题搜解决方案一搜一大把非常适合作为教学和工程验证的载体。2.2 传感器与执行器选型清单整个系统的硬件清单如下模块型号/方案作用选型理由温湿度采集DHT11采集温度和相对湿度单总线协议接线简单够用烟雾检测MQ-2检测烟雾/可燃气浓度模拟数字双输出灵敏度可调火焰检测红外火焰传感器模块识别火光数字电平输出接口简单人体感应HC-SR501感知人体红外辐射被动式热释电误报率低显示LCD1602或OLED SSD1306本地显示温湿度与告警状态16x2字符显示调试直观告警有源蜂鸣器 LED声光告警有源蜂鸣器单片机直接驱动不了需要三极管输出控制继电器模块驱动排风扇隔离大负载安全可靠电源USB 5V AMS1117-3.3V系统供电稳压简单成本极低这里重点解释一下DHT11和DHT22的选择。DHT11的精度是±2℃、±5%RH在粮仓这种场景下够不够坦白说做精细化粮库监测这个精度偏粗更推荐DHT22±0.5℃、±2%RH。但DHT11的优势在于单总线时序简单、代码量小、仿真模型成熟作为项目学习和技术验证完全够用。如果需要高精度方案软件层只需要替换驱动函数上层逻辑不用动——这也是把传感器驱动和业务逻辑分开写的价值所在。2.3 整体工作流程系统上电后先自检初始化LCD显示启动界面然后进入主循环定时器产生2秒节拍触发温湿度采集DHT11的数据手册要求两次读取间隔大于1秒太频繁会读取失败。周期性读取烟雾浓度ADC值检测火焰传感器和人体感应模块的电平状态。将采集数据刷新到LCD。在告警判断函数里做阈值比较和消抖处理。超限时触发声光告警并通过继电器启动排风扇/打开应急照明。这套流程的逻辑非常清晰后面代码部分会详细展开。3. 原理图设计每个模块背后的设计逻辑原理图是这套系统最容易被“抄完就忘”的部分。很多人拿到原理图直接用但不知道为什么这里要加上拉电阻、那里要加RC滤波。把这些设计逻辑讲清楚才是真正吃透项目。3.1 电源与最小系统稳定是底线电源部分采用了USB 5V输入 AMS1117-3.3V稳压的方案。为什么不用ST-Link直接给3.3V因为MQ-2的加热回路需要5V供电DHT11和LCD1602的典型工作电压也是5V所以系统必须同时存在5V和3.3V两路电源。AMS1117-3.3的输出端要加10uF和100nF去耦电容一个负责低频稳压一个负责高频噪声滤除。STM32F103C8T6的每个VDD引脚旁边也建议就近放一个100nF去耦电容这是很多初学者容易漏掉的小细节不加也能跑但ADC采样精度和系统稳定性会打折扣。最小系统部分有几个关键字——8MHz外部晶振配两个22pF负载电容、复位电路用10k上拉电阻加100nF电容、BOOT0和BOOT1通过10k下拉电阻接地保证从Flash启动、SWD下载口引出PA13/PA14。这里特别提醒一点SWD接口一定要把GND也引出来否则下载器经常连不上目标板。3.2 传感器接口电路上拉、分压、隔离DHT11的输出引脚是漏极开路结构数据线上需要接一个4.7k-10k的上拉电阻到VCC。上拉电阻太小会让总线驱动能力不足太大会导致上升沿变缓实测4.7k最稳。MQ-2模块的AO引脚输出的是模拟电压直接接到STM32的PA1引脚即可。但注意MQ-2的AO输出范围是0-5V而STM32的ADC输入范围是0-3.3V——市售MQ-2模块上往往自带一个可调电位器和比较器模块的DO输出是TTL电平可以直接接但AO输出直接接ADC引脚是有风险的。稳妥的做法是用两个电阻做分压比如10k和20k串联将5V分压到3.3V以内或者直接购买3.3V兼容版的MQ-2模块。HC-SR501的输出是3.3V兼容的数字电平可以直接接GPIO。这个模块上有两个可调电位器分别调节感应距离0-7米和延时时间5秒-300秒用在粮仓安防场景建议把延时调到最小、灵敏度调到中间档。3.3 告警与驱动电路GPIO怎么带动大负载STM32的GPIO最大输出电流只有约20mA直接驱动蜂鸣器和继电器的线圈肯定不够必须加驱动电路。蜂鸣器用S8050 NPN三极管驱动GPIO通过1k基极电阻接三极管基极蜂鸣器接在集电极和VCC之间发射极接地。GPIO输出高电平时三极管导通蜂鸣器响起。继电器驱动用的是ULN2003达林顿管阵列这个芯片内部自带续流二极管可以直接驱动继电器线圈。这里有一个反复被问的问题为什么不用三极管因为继电器线圈是感性负载断电瞬间会产生很高的反向电动势如果不加续流二极管会击穿驱动管。ULN2003内部集成了这个保护设计上省心很多。如果是单路继电器用一个NPN三极管加一个1N4007续流二极管也完全可以。4. 代码实现驱动、调度与告警逻辑整套代码是在Keil MDK5环境下基于STM32标准外设库完成的。工程结构分为四层硬件驱动层DHT11、ADC、LCD、业务逻辑层采集任务、告警任务、调度层主循环定时器标志位、配置层时钟、GPIO、中断初始化。4.1 工程结构与代码布局建议的工程文件组织方式文件/文件夹职责core/启动文件、系统时钟配置user/main.c、stm32f10x_it.c 中断服务函数hardware/dht11.c、adc.c、lcd1602.c、alarm.c、key.csystem/系统初始化、延时函数把硬件驱动和业务逻辑分开最大的好处是换传感器或者改逻辑时不用动底层驱动。比如从DHT11换成DHT22只需要重写dht11.c里的读写时序上层调用函数接口不变。4.2 DHT11单总线时序的软件实现DHT11的时序是整个代码里最讲究的部分。单总线协议没有时钟线靠严格的延时和电平翻转来传输数据容错窗口通常在10-30us级别所以延时函数的精度直接影响通信成败。读取一次温湿度的完整流程主机把总线拉低至少18ms发出起始信号。主机释放总线并延时20-40us等待DHT11响应。DHT11拉低总线80us作为应答然后拉高80us表示即将发送数据。随后DHT11依次发送40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。每位数据的解码规则是先有50us低电平紧接着高电平。高电平持续26-28us表示逻辑0高电平持续70us表示逻辑1。核心读取代码uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for (i 0; i 8; i) { // 等待50us低电平结束 while (DHT11_PIN 0); // 延时约30us后采样电平 Delay_us(30); if (DHT11_PIN 1) { data (data 1) | 0x01; } else { data (data 1) 0xFE; } // 等待高电平结束 while (DHT11_PIN 1); } return data; }读取完整数据的流程uint8_t DHT11_ReadData(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; // 主机拉低至少18ms发送起始信号 DHT11_PIN_OUT(); DHT11_PIN_LOW(); Delay_ms(20); DHT11_PIN_HIGH(); Delay_us(30); DHT11_PIN_IN(); // 检查DHT11应答信号 if (DHT11_PIN 1) return 1; // 无应答 while (DHT11_PIN 0); // 等待应答低电平结束 while (DHT11_PIN 1); // 等待高电平结束 // 连续读取5字节含校验 for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); } // 校验和验证 if (buf[4] ! (buf[0] buf[1] buf[2] buf[3])) return 2; // 校验失败 *humi buf[0]; *temp buf[2]; return 0; }这个代码里有几个细节值得注意一是Delay_us(30)必须放在判断之前放在判断之后读到的高电平就包含了70us和26us两种可能无法区分0和1二是主循环调用DHT11的间隔不能小于1秒否则传感器无法完成内部测量会一直返回0。这也是为什么把采集放到2秒定时器节拍里。4.3 ADC采样与烟雾浓度换算MQ-2的AO输出接到STM32的PA1引脚对应的ADC通道是ADC1_IN1。配置ADC1工作在连续转换模式采样时间为55.5周期确保阻抗匹配。读取代码uint16_t ADC_ReadChannel(uint8_t channel) { ADC1-SQR3 channel; // 选择转换通道 ADC1-CR2 | (uint32_t)1 22; // 启动转换 while (!(ADC1-SR (1 1))); // 等待转换完成 return ADC1-DR; // 返回12位ADC值 }烟雾浓度的判断不能只看单次ADC值。MQ-2在刚上电的几十秒内读数会飘因为内部加热丝需要时间达到工作温度。我的做法是在开机后等待MQ-2预热完成约60秒启动时保存一个基线值然后用当前读数相对基线的偏差作为判断依据这样可以抵消不同环境下的浓度基准差异。uint8_t SMOKE_GetLevel(uint16_t baseline, uint16_t current) { // 偏差超过200个ADC码值认为有烟雾 if (current baseline 200) return 1; return 0; }4.4 告警消抖与状态机设计告警逻辑最容易犯的错误是一读到超限就立刻触发告警。传感器瞬间波动、电磁干扰、甚至灰尘飘过探测口都会造成毛刺直接触发的后果就是蜂鸣器乱叫值守人员很快就会失去信任。所以告警函数里做了一次数值消抖连续3次采样都超限才确认告警。void Alarm_Task(void) { static uint8_t confirm_count 0; uint8_t danger 0; // 条件判断 if (temp TEMP_ALARM_THRESHOLD || humi HUMI_ALARM_THRESHOLD || smoke_adc smoke_baseline SMOKE_DEVIATION || fire_detected || pir_detected) { confirm_count; if (confirm_count 3) { danger 1; } } else { confirm_count 0; } if (danger) { Alarm_On(); // 蜂鸣器响、LED闪烁、继电器动作 } else { Alarm_Off(); } }同时不同的告警源应该有不同的表现方式。温湿度超限属于“环境预警”蜂鸣器慢速间歇鸣响烟雾和火焰属于“火情告警”蜂鸣器快速长鸣人体入侵属于“安防事件”蜂鸣器短促三声循环。这样值守人员光听声音就能判断现场出了什么类型的问题不用每次跑过去看屏幕。5. Proteus仿真从工程到跑通的完整过程Proteus仿真最大的价值在于可以不焊接一块板子就把代码逻辑验证一遍。但这个环节恰恰是无数人卡壳的地方——很多人连仿真图都搭不起来。下面把从我这边实测跑通的完整流程拆开讲。5.1 仿真环境的搭建与元件准备Proteus版本建议用8.9以上对STM32F103C8T6的支持比较完善。新建工程后在元件搜索栏里依次添加搜索关键字元件说明STM32F103C8T6MCU主控在“ST Microcontrollers”分类下DHT11温湿度传感器旧版本Proteus没有需要更新库文件LM016LLCD1602液晶屏标准字符液晶模型MQ-2气体传感器部分版本用MQ-2模型或电压源替代BUZZER有源蜂鸣器需要配合三极管驱动LED-RED / LED-GREEN指示灯告警状态显示RESISTOR / CAPACITOR电阻电容按原理图参数设置BUTTON按键阈值设置/复位POT-HG电位器LCD对比度调节如果搜索不到DHT11有一个替代思路用两个电压源模拟DHT11输出或者用Proteus自带的DHT55传感器模型来验证逻辑。但这样没法验证时序代码对不对所以还是建议安装带完整元件库的版本或者从Proteus官网更新库文件。5.2 编译生成hex并加载到Proteus代码在Keil里编译生成hex文件的方法在Keil工程中点击魔术棒Options for Target。在Output选项卡勾选“Create HEX File”。编译工程在输出目录下生成.hex文件。然后在Proteus中双击STM32F103C8T6元件在弹出的属性对话框里将Program File路径指向刚生成的hex文件。这里要注意两个坑Crystal Frequency设置必须设置为8MHz与工程代码里的SystemInit配置的外部晶振一致。如果设为默认值0仿真时钟直接乱了延时函数会出错。电源引脚Proteus的STM32模型虽然会自动供电但如果仿真中使用了PA0等模拟输入引脚记得在VSSA/VDDA引脚上正确接电源否则ADC读到的数据是乱的。加载完成后点击运行。正常的现象是LCD显示当前温度和湿度调节仿真中的DHT11湿度值或温度值LCD数据跟着变化按下对应按键触发超限蜂鸣器和LED开始告警。5.3 仿真中踩到的三个坑第一个坑是DHT11仿真时序与实物不一致。Proteus的DHT11模型对延时的敏感度比实物更高实物上30us的容错窗口在仿真里可能只有10us。解决方法是微调代码里的延时参数把Delay_us(30)改成Delay_us(50)左右前提是你用的是带仿真模型的DHT11。如果怎么调都读不到数据还有一个土办法把DHT11换成两个可变电阻模拟温湿度变化用ADC读取来代替单总线读取先把整体流程跑通再回来搞时序。第二个坑是LCD1602显示乱码或全屏方块。最常见原因是仿真里LCD的对比度调节引脚Vo直接接地了没有接电位器。加上一个10k电位器把Vo电压调到1V左右LCD工作电压的10%-20%显示就正常了。第三个坑是蜂鸣器不响。Proteus里的蜂鸣器分有源和无源两种有源蜂鸣器只要通电就会发声无源需要PWM才能响。仿真里如果上电不响先检查型号是不是SOUNDER有源/无源模型不同再看驱动管是否型号匹配。这里有一个小技巧Proteus仿真的蜂鸣器很多时候是“逻辑上已经响起、但电脑声卡没输出”——看仿真里的电流表和驱动管状态就能确认。6. 从仿真到实物部署经验与扩展方向仿真跑通只是完成了一半实物搭建和现场部署才是真正检验系统可靠性的地方。这个环节有很多仿真里根本不会暴露的问题非常值得单独拿出来说。6.1 实物搭建中容易被忽略的细节DHT11在实物调试里的头号问题是IO口方向切换的时序。STM32的GPIO配置为输入和输出之间切换需要大约几个时钟周期标准外设库里的GPIO_Init调用开销比较大。一个可行的做法是把DHT11的数据线接到一个独立IO用寄存器操作快速切换方向#define DHT11_PIN_OUT() { GPIOB-CRH 0xFFFF0FFF; GPIOB-CRH | 0x00003000; } #define DHT11_PIN_IN() { GPIOB-CRH 0xFFFF0FFF; GPIOB-CRH | 0x00004000; }这样配置方向只需要两条寄存器指令比调用库函数快几十倍实测在48MHz主频下能稳定读取DHT11。第二个是供电问题。MQ-2的加热丝最大工作电流在150mA左右如果和STM32共用一组USB供电电源线上的纹波会影响到ADC采样。建议的做法是MQ-2单独从5V端取电并在模块电源引脚就近加一个100uF电解电容和一个104瓷片电容把加热丝的瞬态电流波动吸收掉。第三个是继电器带负载的问题。继电器触点驱动排风扇这种感性负载时触点闭合断开的瞬间会产生电弧长期使用会造成触点烧蚀。在继电器输出端并联一个RC吸收电路典型值100Ω 104电容串联能有效延长继电器寿命。6.2 粮仓现场的实用建议如果这套系统真的要用在粮仓环境里有几个点比代码和原理图更关键传感器的布点位置不能只放一个点。粮仓的不同高度和位置温湿度差异很大靠近门窗的位置湿度高、粮堆中心温度高。至少要在仓内东南西北四个角落各装一个节点再在粮堆表层和距表层50cm深处各埋一个测温点才能形成有效的监测网络。这套单节点方案可以作为每个点位的基础单元后续通过RS485总线把多个节点组网把数据汇总到上位机。传感器的防护很关键。粮仓内粉尘浓度高DHT11的感湿元件如果直接裸露在环境里用不了几个月就会被粉尘覆盖测量值严重偏移。建议给传感器加一个透气防尘罩或者把传感器放在一个开有小孔的塑料盒子里。MQ-2也面临同样的问题它的防爆网罩结构本身能防尘但网罩上的粉尘也需要定期清理。告警阈值不能拍脑袋定。不同的粮食品种对温湿度要求不同小麦的安全水分是12.5%左右稻谷是14%左右对应的仓储环境湿度阈值最好参考《粮油储藏技术规范》来设置。我预留了通过按键调节阈值的功能就是为了让使用者根据实际存储品种灵活调整。6.3 后续扩展方向这套系统在扩展性上留了很多接口。软件层面主循环的调度结构可以很容易地加入新传感器——只需要在硬件驱动层增加一个文件在Alarm_Task里增加一个判断条件。硬件层面如果要做远程监控可以在串口1上挂一个ESP8266模块把温湿度和告警状态通过MQTT协议上报到云平台实现手机远程查看。要做低功耗的话STM32F103C8T6可以进入待机模式配合RTC定时唤醒用外部中断唤醒做入侵检测一块18650电池可以支持几个月。如果要组网做多节点方案建议把通信链路从单总线换成RS485DHT11的数据通过传感器节点上的小MCU预处理后再通过RS485总线上传。这样既解决了长距离布线的抗干扰问题也让每个节点具备本地独立判断能力不会因为总线上某个节点故障而影响整个网络。我在实际做的过程中最大的感受是仿真和实物之间的差距就是理论学习和工程实践的差距。仿真帮你验证了逻辑但实物会告诉你什么是电源纹波、什么是时序容错、什么是接触不良。这套系统从画原理图到最终在粮库角落稳定运行前前后后调试了将近两周踩的坑远比这篇文章里写得多。但正是因为这些坑整个系统的每一行代码、每一颗电阻的取值才能讲得清楚来龙去脉。这也是我决定把它们整理成开源项目的初衷——不要让后人重复踩同样的坑。