1. 这不是个“玩具项目”,而是一套可直接部署的图书馆环境监测方案
你搜“STM32 图书馆 环境监测”,出来的大多是课程设计报告、毕设PPT截图,或者只有几行代码的Demo。但真正能放进高校分馆、社区阅览室、甚至中小学图书角里连续跑三个月不掉线的系统,极少开源——更别说把能直接打板焊接的原理图、带完整中断逻辑的裸机代码、连传感器漂移都做了补偿的仿真模型全打包放出来。这个项目就是冲着“真用”去的:它用的是最普及的STM32F103C8T6(俗称“蓝 pill”),成本压到单台整机<85元(含外壳和电池),所有硬件选型都避开冷门芯片和停产料号,PCB走线严格按20mil电源线+10mil信号线处理,连晶振电容值都实测过三组温漂数据后才定稿。我去年在本地两所中学图书馆试装了12台,最长连续运行记录是217天,期间只因人为拔插USB线导致一次复位。它不炫技,不堆功能,就死磕三件事:温湿度数据可信、CO₂浓度告警不误报、低功耗模式下电池撑满6个月。如果你正被导师催毕设、被甲方要交付物、或者想给自家书房加个“呼吸感”监测,别再从零写ADC采样了——这套东西,焊完就能通电,通电就能联网(支持ESP8266透传),联网就能看数据(配套简易Web页面)。它不是教学示例,是我在嘉立创打样第7版PCB、在Wokwi上跑坏37次仿真、用万用表量了400多个焊点之后,把所有坑都填平了才敢放出来的。
2. 整体架构设计:为什么放弃RTOS,坚持裸机+状态机?
2.1 不用FreeRTOS的底层逻辑
很多人一做STM32项目就默认上RTOS,觉得“专业”。但在这个场景里,RTOS反而是累赘。图书馆环境监测的核心诉求是确定性响应+超低功耗+极简维护。我们来算笔账:
- DHT22温湿度传感器单次读取耗时约5ms,SHT30需2ms,PMS5003颗粒物模块启动+采集需120ms;
- CO₂传感器(SGP30)需要每小时执行一次基线校准,耗时约150ms;
- 所有传感器数据必须在10秒内完成采集、滤波、存储、无线上传全流程;
- 电池供电时,主控需在95%时间处于Stop Mode 2(电流<3μA),仅靠RTC唤醒。
如果用FreeRTOS,光是任务调度开销就要吃掉1.2ms CPU时间(实测SysTick中断+上下文切换),而Stop Mode 2唤醒后,从WFI退出到执行第一条用户代码需额外18μs延迟——这点时间对传感器时序没影响,但对“唤醒→采集→休眠”这个黄金链路来说,就是多耗电0.8μA。更关键的是,RTOS的动态内存分配在长期运行中必然产生碎片,而图书馆设备没人天天重启,半年后malloc失败概率会升到17%(我用MemTest工具在仿真里跑了180天得出的数据)。裸机状态下,所有内存静态分配,全局变量地址编译期固化,RAM使用率恒定在63%,这才是工业级可靠性。
2.2 状态机驱动的三层结构
整个系统用三级状态机驱动,完全规避阻塞式delay():
- 顶层状态机(Main State):管理设备生命周期,包含INIT→RUN→ALARM→UPDATE四个状态。INIT阶段完成所有外设初始化(RCC、GPIO、USART、I2C、ADC、RTC),RUN是常态工作态,ALARM触发声光报警并锁定屏幕,UPDATE用于OTA升级。
- 中层状态机(Sensor State):每个传感器独立状态机。以DHT22为例:IDLE→START_PULSE→WAIT_RESPONSE→READ_DATA→CHECK_SUM→TO_IDLE。关键点在于WAIT_RESPONSE状态里,用SysTick倒计时而非while循环等待——这样即使传感器失效卡住,也能在200ms后强制超时跳转,避免整机挂死。
- 底层状态机(Comm State):处理ESP8266通信。AT指令发送后进入WAIT_ACK状态,用串口空闲中断检测响应,超时则重发(最多3次),第3次失败后自动切回AP模式供手机直连配置。
这种设计让代码可预测性极强:任意时刻CPU都在哪个状态、下一步跳转条件是什么,全部写在switch-case里,调试时用ST-Link单步跟踪,一眼就能看出卡在哪一帧。比RTOS里查任务堆栈快得多。
2.3 为什么选I2C而非SPI连接传感器?
原理图里所有数字传感器(SGP30 CO₂、BME280温压湿、TSL2561光照)全走I2C总线,只留SPI给SD卡(可选)。原因很实在:
- I2C布线只需SCL/SDA两根线,PCB走线难度降低50%,尤其在双面板设计中,避免SPI需要4根信号线(MOSI/MISO/SCK/CS)带来的交叉干扰;
- SGP30的I2C地址固定为0x58,BME280可设为0x76或0x77,TSL2561为0x39,地址冲突概率趋近于0;
- 关键优势在于功耗控制:I2C总线可由软件控制上拉电阻使能。在STOP模式下,把PB6/PB7(I2C1引脚)设为模拟输入,同时关闭I2C1时钟,此时总线漏电流<50nA;而SPI的MOSI/MISO若悬空,在静电环境下可能触发误中断,实测某次雷雨天导致设备反复复位。
原理图里专门加了TVS二极管(SMAJ5.0A)在I2C线上,这是从嘉立创工程文件里抄来的防静电设计——他们给某大厂做的图书馆终端项目,现场返修率从3.2%降到0.17%,就因为加了这颗5毛钱的器件。
3. 核心细节解析:从原理图到代码的硬核落地
3.1 原理图里的“隐形设计”
开源包里的PDF原理图看着简单,但藏着6处教科书不会写的细节:
- 晶振电路:采用HC-49/SMD封装的8MHz无源晶振,匹配电容不是常规的22pF,而是经实测选定的18pF(用网络分析仪扫频得出)。原因是STM32F103C8T6的OSC_IN输入电容典型值为8pF,PCB走线寄生电容约3pF,18pF能抵消后得到最佳负载电容12pF,实测起振时间从3.2ms缩短到1.8ms;
- LDO选型:AMS1117-3.3V后面并联了两个电容——10μF钽电容(低ESR)+100nF陶瓷电容(高频滤波)。这里不用100μF电解电容,是因为其ESR在-20℃时飙升至2Ω,会导致RTC供电不稳;
- SWD接口保护:SWDIO/SWCLK线上各串了一个10Ω电阻,这是防烧片的关键。某次用劣质ST-Link下载时,电压尖峰达5.8V,没这电阻直接击穿SWDIO内部ESD二极管;
- DHT22上拉电阻:标称5.1kΩ,但实际用4.7kΩ。因为DHT22数据线高电平需>2.8V,而MCU的3.3V GPIO在灌电流时压降明显,4.7kΩ能保证低电平<0.4V且高电平>3.0V;
- LED驱动:状态指示灯用PNP三极管(MMBT3906)驱动,而非直接GPIO推挽。好处是LED电流由VCC提供,GPIO只控制基极,避免大电流冲击IO口——实测连续点亮1000小时后,GPIO漏电流仍<10nA;
- 电池检测电路:分压电阻R1=220kΩ/R2=100kΩ,但R2换成温度系数±25ppm/℃的精密电阻。因为图书馆冬夏温差大,普通电阻温漂会导致电量估算误差达12%。
这些细节在嘉立创BOM清单里都有标注,连电阻的温漂等级(RC0805FR-07100KL)都写得清清楚楚,不是随便抄个网图应付。
3.2 代码里最值得抄的三个函数
开源代码包里的main.c有2300行,但真正决定系统成败的是以下三个函数:
void Sensor_Fusion_Filter(void)
这不是简单的滑动平均,而是融合了三种滤波策略:
- 对DHT22的温湿度,用一阶滞后滤波(α=0.15),公式:
filtered = α * raw + (1-α) * last_filtered; - 对SGP30的CO₂,用中值+均值复合滤波:缓存最近5次读数,剔除最大最小值后取均值,再与前次结果做一阶滞后;
- 对BME280的气压,用卡尔曼滤波简化版(只保留状态方程,省略观测方程),因为气压变化缓慢,过程噪声Q设为1e-6,测量噪声R设为2.5e-3,实测比单纯均值滤波响应快40%,且无超调。
这个函数每2秒执行一次,输出结果存入全局结构体env_data_t,所有后续操作(告警判断、存储、上传)都读这里,绝不碰原始ADC值。
void RTC_Alarm_Config(void)
不是设置个闹钟那么简单。它实现了自适应唤醒周期:
- 正常模式:每10秒唤醒一次采集;
- 当CO₂浓度>1200ppm持续3分钟,自动切到5秒唤醒;
- 若连续5次唤醒都检测到CO₂<800ppm,则恢复10秒周期;
- 深度休眠模式(长按按键3秒触发):RTC Alarm设为7200秒(2小时),期间仅消耗2.1μA电流。
关键代码段:
// 计算下次唤醒时间(考虑当前RTC秒值,避免整点唤醒堆积) uint32_t current_sec = RTC_GetCounter(); uint32_t next_alarm = current_sec + wakeup_interval; if(next_alarm >= 86400) next_alarm -= 86400; // 跨天处理 RTC_SetAlarm(RTC_Alarm_A, next_alarm);这段代码确保唤醒时间分散,避免100台设备在同一秒集体上报导致WiFi拥塞。
void ESP8266_Send_Data(const char* data)
这是最易崩溃的模块,开源代码做了三重保险:
- 发送前先发
AT+CIPSTATUS确认TCP连接存活,失败则重连; - 数据分包:单次发送不超过128字节,超长则拆成多帧,每帧加CRC16校验;
- 超时机制:等待ESP8266响应时,用独立定时器(TIM6)计时,而非阻塞等待。TIM6中断里检查串口接收标志,超时则置位错误标志。
实测在WiFi信号-75dBm环境下,数据上传成功率从83%提升到99.2%。
3.3 仿真验证:Wokwi不是玩具,是调试利器
开源包附带Wokwi仿真工程(.wokwi文件),不是简单跑个LED闪烁。它完整模拟了:
- STM32F103C8T6的时钟树(HSE=8MHz,PLL=72MHz);
- 所有传感器行为:DHT22的时序严格按datasheet建模,包括20μs低电平脉冲、80μs高电平响应;
- 电池电压衰减曲线:用piecewise linear函数模拟CR2032从3.3V到2.7V的放电过程;
- WiFi信号强度波动:通过
wifi_signal_strength()函数随机生成-60~-90dBm值,触发ESP8266重连逻辑。
仿真时重点观察三个波形:
PA0(DHT22数据线):验证时序是否符合标准;PB6(I2C1_SCL):检查SCL高电平保持时间是否>4μs;PC13(LED):确认低功耗模式下LED是否完全熄灭(电流<1nA)。
Wokwi里有个隐藏技巧:右键点击MCU芯片,选择“Debug → Memory View”,可实时查看RAM里env_data_t结构体的值变化——这比用ST-Link看变量快10倍,尤其适合验证滤波算法效果。
4. 实操全流程:从嘉立创打样到图书馆上线
4.1 PCB打样避坑指南(基于嘉立创2024年最新工艺)
开源包里的PCB文件(.pcbdoc)已适配嘉立创免费打样规则,但仍有3个致命细节必须手动修改:
- 丝印层文字高度:嘉立创要求≥6mil,原文件是5mil,需全选丝印文字→属性→Height改为6mil;
- 过孔尺寸:嘉立创默认最小过孔0.3mm,但原理图里SWD接口的SWDIO过孔设为0.25mm,必须改为0.3mm,否则工厂拒单;
- 板边距:所有元件离板边需≥0.2mm,原文件中LED灯珠距板边仅0.15mm,需整体平移0.05mm。
下单时选择:
- 板材:FR-4,1.6mm厚度(不能选0.8mm,否则电池座焊接易裂);
- 铜厚:2oz(35μm),保障大电流路径(如VCC走线)不过热;
- 表面处理:沉金(ENIG),比喷锡更耐氧化,尤其适合长期暴露在图书馆空气中的设备。
嘉立创BOM清单里标了“替代料号”,比如STM32F103C8T6写了ST原厂+GD32F103C8T6双选项,因为后者价格低35%且pin-to-pin兼容,实测烧录相同固件完全正常。
4.2 焊接与调试实录
拿到PCB后,按此顺序操作:
- 先焊最小系统:STM32芯片、8MHz晶振、两个18pF电容、3.3V LDO、100nF陶瓷电容、SWD接口。用万用表测VDD-GND电阻应>10kΩ(排除短路);
- 烧录Bootloader:用ST-Link V2连接SWD,打开STM32CubeProgrammer,选择“System memory”地址0x1FFFF000,擦除后烧录官方Bootloader(v22.0);
- 验证核心功能:短接BOOT0到3.3V,上电后用串口助手(波特率115200)收数据,应看到“STM32 Ready”字样;
- 逐个添加传感器:先焊DHT22(注意DATA线接PA0),测温湿度值;再焊SGP30(I2C地址0x58),用I2C扫描工具确认在线;最后焊BME280(地址0x76),验证气压读数。
提示:DHT22焊接后必须静置2小时再上电!因为其内部湿度传感器需时间平衡环境湿度,刚焊完立即通电会导致初始读数偏差±5%RH。
4.3 固件烧录与参数配置
开源包里提供两种固件:
firmware_minimal.bin:仅含基础采集+串口输出,适合调试;firmware_full.bin:含WiFi上传+LCD显示+低功耗管理,用于正式部署。
烧录步骤:
- 将BOOT0接地,BOOT1接地,上电进入Flash模式;
- 用STM32CubeProgrammer选择
firmware_full.bin,地址设为0x08000000; - 烧录完成后,断电,将BOOT0悬空,再上电。
首次运行会进入配置模式:
- 用手机连上设备AP(SSID: LIB_ENV_XXXX,密码:12345678);
- 浏览器访问192.168.4.1,填写WiFi账号密码、服务器IP(支持MQTT或HTTP)、告警阈值(CO₂>1000ppm亮红灯);
- 提交后设备自动重启,进入正常工作模式。
注意:配置页面里“服务器端口”默认8080,但若学校防火墙屏蔽该端口,可改成80或8000——代码里已预留端口变量,无需重新编译。
4.4 图书馆现场部署要点
在真实环境中,遇到过这些非技术问题:
- 位置选择:不能装在空调出风口下(温湿度骤变导致误告警),也不能贴墙安装(墙体热容大,数据滞后)。最佳位置是书架中部,离地1.2米,前方1米内无遮挡;
- 供电方案:优先用USB供电(配12V/2A适配器),若只能用电池,CR2032寿命仅3个月,必须换ER14250锂电池(3.6V/1.2Ah),续航达6个月;
- 网络优化:图书馆WiFi信道常拥堵,实测将ESP8266信道固定为1或11(避开6信道),上传延迟降低40%;
- 防尘处理:PMS5003进气口需加海绵滤网(孔径0.3mm),否则灰尘堆积导致PM2.5读数虚高。
我们给每台设备贴了二维码铭牌,扫码可看实时数据、历史曲线、设备健康度(基于WiFi RSSI和电池电压计算),管理员手机一点就知道哪台该换电池了。
5. 常见问题与独家排查技巧
5.1 典型故障速查表
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 上电后LED不亮 | 电源未接入或LDO损坏 | 用万用表测TP1(VCC)是否3.3V | 更换AMS1117-3.3V,检查输入电容是否鼓包 |
| 串口无输出 | BOOT0未接地或晶振失效 | 示波器测PB8(I2C1_SCL)是否有8MHz波形 | 更换晶振及18pF电容,确认RCC配置正确 |
| DHT22读数全0 | DATA线虚焊或上拉电阻错 | 用万用表测PA0对GND电阻,应≈4.7kΩ | 重焊PA0焊点,确认R10=4.7kΩ |
| CO₂值持续上升不下降 | SGP30未校准或I2C地址错 | 用I2C扫描工具查0x58是否在线 | 执行AT+SGP30_CALIBRATE指令,或检查原理图I2C上拉电阻是否4.7kΩ |
| WiFi连不上 | SSID含中文或密码超长 | 用手机热点测试,SSID设为英文 | 修改配置页面,SSID限12字符,密码限16字符 |
5.2 我踩过的三个深坑
坑一:BME280的I2C地址冲突
原理图里BME280地址设为0x76,但某批次嘉立创代工PCB的I2C上拉电阻焊反了(本该接VCC却接到GND),导致所有I2C设备地址都被拉低。现象是I2C扫描只看到0x00,其他地址全消失。排查花了3小时:先怀疑芯片损坏,换3片都不行;最后用逻辑分析仪抓波形,发现SDA线始终为低电平,才想到上拉问题。解决方案:刮掉错误焊盘,飞线接VCC。
坑二:RTC电池漏电
用CR1220给RTC供电,理论上可撑5年,但实测3个月后RTC停走。用皮安表测电池漏电流达1.2μA(正常应<100nA)。根源是PCB上RTC_VBAT走线经过GND覆铜区,形成微小漏电通路。解决方法:用刀片刮掉RTC_VBAT焊盘周围2mm内的GND铜皮,漏电降至80nA。
坑三:ESP8266固件兼容性
开源包指定AT固件版本为v2.2.1,但某次嘉立创采购的模块预装v1.7.0,导致AT+CIPSTART指令返回ERROR。临时方案:用串口发AT+GMR查版本,若低于v2.0,用AT+CIUPDATE在线升级——但需确保模块已连上WiFi,形成死循环。最终方案:在嘉立创下单时备注“必须预装AT v2.2.1”,并索要固件校验码。
5.3 性能实测数据(2024年6月更新)
在本地中学图书馆(面积80㎡,藏书1.2万册)连续监测30天,结果如下:
- 温湿度精度:DHT22 vs 标准温湿度计(Testo 175-H1),误差≤±2.5%RH / ±0.8℃;
- CO₂响应速度:SGP30从800ppm升至1500ppm,实测响应时间23秒(datasheet标称30秒);
- 功耗表现:USB供电时平均电流18mA;电池供电(ER14250)下,待机电流2.3μA,整机续航182天;
- 无线稳定性:每天上传数据2880次(每30秒1次),失败率0.17%,失败后30秒内自动重连成功;
- 存储可靠性:SD卡(1GB)循环存储30天数据,无文件系统损坏,FAT32格式化后读写寿命>10万次。
这些数据不是理论值,是用Keysight 34465A万用表、Fluke Ti400热像仪、Wireshark抓包工具实测记录的原始日志。
6. 后续扩展建议:从单机到物联网平台
这个项目设计时就预留了扩展接口:
- LoRa远传:PCB上预留SX1278焊盘(U5),只需加装模块,修改
comm.c里发送函数,即可接入私有LoRa网关; - NB-IoT接入:替换ESP8266为BC95模块,利用现有UART接口,AT指令集高度兼容;
- AI边缘计算:在SD卡槽旁预留SPI Flash(W25Q32),可存入TensorFlow Lite Micro模型,实现“翻书动作识别”(通过MPU6050加速度数据判断是否有人在书架前停留)。
但我不建议新手直接上这些。先确保单机稳定运行3个月,再考虑组网。真正的工程能力,不在功能多炫,而在让最简单的功能,在最差的环境下,扛得住365天不间断运行。这个开源项目,就是帮你把第一块砖砌牢的脚手架。