有一次我帮一个朋友所在的博物馆库房做环境巡检,发现他们用的那台温湿度记录仪白天数据一切正常,但纸质文献还是出现了轻微发脆的迹象。后来我把记录仪的数据导出来一帧一帧翻,才发现空调早上启动的那半小时里,湿度从72%一路掉到45%,记录仪每分钟采一个点,软件默认存的是每小时的平均值,愣是把这个剧烈的瞬态波动抹平了。这件事给我的印象特别深。文物的环境监测,最大的价值恰恰在于那些常被统计平均值掩盖的瞬时波动。
所以当我决定用STM32做一套馆藏文物保存环境监测系统的时候,首先想清楚的不只是“采个温湿度、超限报警”这种表面需求,而是几个更实际的问题:传感器怎么选才能保证长期稳定,数据帧怎么设计才能在掉线重连后不丢历史,告警逻辑怎么做才不会在阈值附近反复抖。这篇文章把我从需求拆解、选型、电路设计、固件编写到实测校准的完整过程写出来,适合正在做嵌入式方向项目的在校生,也适合文博单位或展陈公司里想低成本自建监测体系的技术人员参考。
1. 文物库房环境监测,真正要盯的是哪些指标
1.1 温湿度之外,光照和有害气体往往被忽视
博物馆库房的基本环境需求其实有三个维度,不只是大多数人理解的温湿度。温湿度是最基础的,直接影响纸张、纺织品、木材等吸湿性材料的物理稳定性;光照对书画、染料、彩绘类文物影响非常大,强光或长时间紫外线照射会加速褪色和材料老化;挥发性有机污染物则是容易被忽略的一环——展柜胶粘剂、油漆、人造板材持续释放的VOC和甲醛,会加速纤维素材料的水解和氧化反应。
不同材质的文物对环境要求差异很大。纸质文献通常要求温度20℃左右、湿度55%RH左右,且波动幅度越小越好;金属文物主要怕湿度过高导致的电化学腐蚀;胶片和录像带类藏品对温湿度的要求更加苛刻,尤其怕高温高湿叠加。所以我做系统设计时,至少预留了四个监测通道:温度、湿度、光照、VOC/CO2,每个通道的报警阈值做成独立可配置,而不是写死在代码里。这一条建议在做需求分析的时候就要想清楚,不然后期扩展监测点位的成本会成倍增加。
1.2 监测重点:稳定性比绝对值更关键
行业里有句老话叫“宁稳勿变”。温湿度的反复波动对文物的破坏,往往比持续偏高一点更严重。纸张纤维在吸湿和脱湿过程中反复膨胀收缩,会逐渐失去韧性;漆器和木器表面的漆膜也会因为基材的尺寸变化而开裂。这意味着系统处理数据时不能只记录当前值,还要能统计出一段时间内的最高值、最低值、变化速率和波动频次。
在系统设计层面,我把这种需求转化成具体的功能要求:不仅每分钟存一条当前数据,还同步计算出三类统计量——过去24小时的温湿度极值、过去1小时的波动范围、当前值相对上一分钟的变化速率。这些统计量通过数据帧或寄存器地址开放给上位机,博物馆的管理人员就能直接看到“今天湿度波动多少”而不是“现在湿度多少”,这对日常巡检的价值高得多。
1.3 系统整体架构:从传感器到告警的完整链路
整套系统的架构并不复杂,分成五层:传感器层负责物理量采集(SHT30温湿度、BH1750光照、SGP30空气污染指标);主控层用STM32F103C8T6完成数据读取、帧协议打包、Flash存储和告警判断;人机交互层用一块0.96寸OLED显示实时数据,继电器驱动蜂鸣器做声光报警;通信层预留USB串口和RS485接口,可以对接上位机或已有的馆内监控网络;上位机层实现历史数据查询、曲线展示和补传请求。
这种分层结构的最大好处是职责单一。MCU只管采集、存储和本地告警,就算上位机崩溃、串口线被拔掉,采集端依然在正常记账。对博物馆这种强调稳定性的场景来说,这是刚需。我在选型时没有选择带WiFi或以太网的型号,原因很简单:文物库房的网络环境往往不允许随意接入,RS485和USB串口的通用性更强,也更容易通过现有的工控机或值班电脑读取数据。
2. 传感器选型与硬件电路设计:从芯片手册到PCB的取舍
2.1 传感器对比:精度、价格、长期稳定性三角权衡
选传感器这件事,我前后对比过好几款,不只是看数据手册上的精度参数,更看重长期稳定性和环境适应性。下面是几款主流传感器在实际使用中的横向对比:
| 传感器 | 类型 | 精度 | 接口 | 长期稳定性 | 价格参考 | 典型场景 |
|---|---|---|---|---|---|---|
| DHT22 | 温湿度 | ±0.5℃ / ±2%RH | 单总线 | 一般,半年后漂移明显 | 约6元 | 低成本教学、家用 |
| SHT30 | 温湿度 | ±0.3℃ / ±2%RH | I2C | 良好,连续运行漂移小 | 约15元 | 本项目推荐 |
| BME280 | 温湿度+气压 | ±1.0℃ / ±3%RH | I2C/SPI | 良好 | 约20元 | 多参数一体场景 |
| BH1750 | 光照 | 分辨率1lux | I2C | 长期强光下会老化 | 约5元 | 光照监测 |
| SGP30 | VOC/CO2 | TVOC+CO2等效值 | I2C | 需要年度校准 | 约40元 | 空气质量监测 |
| GXHTC3 | 温湿度 | ±0.3℃ / ±2%RH | I2C | 优秀 | 约30元 | 高精度展柜微环境 |
最终我选了SHT30作为温湿度主传感器。理由很直接:I2C接口能省IO口,测量精度在文物环境控制精度要求(温度±2℃、湿度±5%RH)的范围内留足了余量,价格也适中。更重要的是SHT30出厂带NIST可追溯校准,实际连续通电一个月后,与标准表对比偏差仍能控制在0.2℃以内,这对需要长期无人值守的库房场景太关键了。光照通道用BH1750,它的余弦修正特性在反射光较多的环境里表现尚可。空气污染监测通道用SGP30,但它有个必须知道的坑——上电后需要预热12小时才能输出稳定值,所以代码里要加一个“预热期不上报告警”的逻辑。
2.2 I2C总线设计中的电平匹配和上拉电阻
多个传感器挂在同一条I2C总线上,首先要处理地址冲突。SHT30的I2C地址是0x44,BH1750的地址是0x23,SGP30的地址是0x58,三者默认不冲突,这是选型时的一个隐藏加分项。如果后续要加第二路SHT30,可以通过ADDR引脚将其地址改为0x45。PCB布局时就要知道哪些引脚需要预留跳线或焊接点,不然后期改地址要飞线,非常狼狈。
上拉电阻的选择也是关键。3.3V供电时,我一般先在每个传感器模块上用4.7kΩ上拉,然后根据实际线缆长度调整。如果传感器通过排线引出,线长超过30cm,4.7kΩ上拉在100kHz标准模式下的波形上升沿会明显变缓,需要降到2.2kΩ。STM32的硬件I2C工作在标准模式100kHz比快速模式400kHz稳得多,尤其是长线缆连接时,宁可慢一点也要保证不丢数据。
2.3 供电与布线:干扰往往藏在细节里
整个系统用5V USB适配器或5V开关电源供电,板载AMS1117-3.3线性稳压到3.3V。我不建议用DC-DC开关电源直接给传感器供电,因为开关电源的输出纹波对传感器供电质量影响很大,而AMS1117虽然效率低,但3.3V侧的负载电流总共不到200mA,发热完全可以接受,换来的是干净得多的电源轨。
具体到电路细节:AMS1117的输入和输出端各加10μF钽电容和100nF陶瓷电容组合;STM32的VDDA引脚不要直接接3.3V,而是串联一颗10Ω磁珠再单独接1μF电容,把数字电路的开关噪声隔离开;传感器供电则用独立的π型滤波——10Ω电阻加10μF电容再加100nF电容,实测对I2C数据跳变问题有非常明显的改善。
PCB布局上有一条宝贵的经验:传感器尽量分布在板子边缘,远离晶振和继电器驱动部分。I2C走线并行且等长,尽量不要穿过功率电路的正下方。继电器驱动电路放在板子一角,走线不要和传感器的I2C信号线长距离平行。如果地平面是完整的,这些干扰问题基本能在源头解决掉,不用等到调试阶段再到处贴磁珠。
3. STM32固件实现:轮询逻辑、数据帧协议与告警判断
3.1 传感器读取时序与数据平均策略
固件初始化流程有一个容易被忽略的细节:系统上电后要延时至少200ms等待传感器稳定,然后再初始化I2C、串口、OLED、Flash和继电器。SHT30上电后第一次读取需要等待10ms以上,否则读到的是默认值0xFFFF。BH1750每次启动测量后需要等120ms才能读到有效数据,在这期间不建议发起其他I2C操作,否则数据容易错乱。
采样策略我在第一版就定下来了:每5秒读取一次原始值,在内存里维护一个长度为12的环形缓冲区,也就是最近一分钟的采样窗口,每分钟计算一次平均值存入Flash并上报。这种做法有两个直接好处:一是瞬时干扰不太可能直接触发告警,比如人员经过时带起的气流扰动不会让湿度数据瞬间跳变;二是存储数据量可控——一天存1440条,一个月约4.3万条,Flash和上位机数据库都扛得住。
3.2 自定义数据帧协议:别把数据赤裸裸发出去
直接发原始二进制数据的问题很明显:丢一个字节就找不到帧边界,校验失败也没办法判断是哪个字段错了。所以我参考工业现场常见的做法,设计了一套带帧头、长度和CRC16校验的协议帧。
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 1字节 | 0xAA |
| 设备ID | 1字节 | 0x01,多设备组网时区分节点 |
| 数据类型 | 1字节 | 0x01温湿度 / 0x02光照 / 0x03气质 |
| 数据长度 | 1字节 | 后续数据的字节数 |
| 数据 | N字节 | 实际数据,统一为整型放大10倍传输 |
| CRC16 | 2字节 | Modbus CRC16,低字节在前 |
| 帧尾 | 1字节 | 0x55 |
CRC16用Modbus标准多项式0xA001查表计算,在72MHz主频的F103上跑一帧数据只需要几十微秒,时间开销可以忽略。数据帧里带上设备ID的原因也很实际——博物馆如果在一个库房部署多套监测节点,上位机必须能区分数据来自哪个位置。这套帧格式我自己解析处理过很多次,对后续的Python上位机开发和Modbus调试都很友好。
3.3 告警判断用滞回比较,避免临界抖动
告警逻辑这里有一个非常经典的坑:如果报警阈值设成单一固定点,温度在阈值边缘波动时,继电器会不断吸合释放,一天下来继电器触点可能就烧掉了。解决办法是滞回比较,即报警点和恢复点是两个不同的阈值,两者之间形成一个滞回区间。
#define TEMP_HIGH_ALARM 28.0f #define TEMP_HYSTERESIS 0.5f static uint8_t temp_alarm_active = 0; void check_temp_alarm(float temp) { if (temp_alarm_active == 0) { if (temp >= TEMP_HIGH_ALARM) { temp_alarm_active = 1; trigger_alarm(ALARM_TEMP_HIGH); } } else { if (temp <= TEMP_HIGH_ALARM - TEMP_HYSTERESIS) { temp_alarm_active = 0; release_alarm(ALARM_TEMP_HIGH); } } }温度达到28.0℃时触发告警,之后要等温度回落到27.5℃以下才解除告警。这个0.5℃的滞回区间既避免了继电器频繁动作,又不会因为区间过大而漏报。同理,湿度告警我也设置了5%RH的滞回区间。在文物库房这种需要长期稳定运行的环境里,这个细节直接影响设备寿命和值班人员的信任度——如果设备隔几分钟就乱响一次,值班人员很容易把警报直接关掉,那整个系统就形同虚设了。
3.4 看门狗与异常恢复
库房环境通常是无人值守的,程序跑飞了不会有人帮你按复位键。我用STM32的独立看门狗IWDG,超时时间设1秒,主循环每500ms喂一次。这样即使程序卡死,1秒后系统自动复位,最大限度减少停机时间。
这里同样有一个分寸问题:传感器读取失败不能直接当作系统故障强制复位,而是记录错误次数,连续3次失败才标记该传感器故障并显示在OLED上。这样做的原因是I2C总线在受到瞬时干扰时可能偶发错误,强制复位会导致缓存数据丢失,上电重连还要等传感器重新就绪,反而耽误时间。我还会把当时的告警状态保存到Flash固定地址,掉电后重新上电时先读取上次状态,如果掉电前正处于告警中,恢复供电后应立刻恢复告警状态,避免电力恢复后环境异常没人注意到。
4. 数据存储、日志查询与上位机联动
4.1 片内Flash存储的磨损均衡
STM32F103C8T6有64KB Flash,可以拿出一个扇区做数据存储。但Flash的擦写寿命标称只有1万次,如果每次存数据都写到同一个地址,按每小时写一条计算,一年约8760次,不到两年就把一个扇区写穿了。解决办法是磨损均衡:把存储区划分为多个页,按顺序循环写。
#define LOG_PAGE_START 50 #define LOG_PAGE_COUNT 64 static uint32_t write_offset = 0; void write_log(uint8_t *data, uint16_t len) { uint32_t page = LOG_PAGE_START + (write_offset % LOG_PAGE_COUNT); flash_erase_page(page); flash_program_page(page, data, len); write_offset++; }每次写入前先擦除目标页,然后按偏移量循环使用64页,相当于把擦写寿命扩大了64倍。初始化时扫描所有页的写入时间戳,找到最新一条数据的位置,从那里继续写。这套逻辑写起来很顺手,但要注意页擦除时间在几十毫秒量级,期间不要处理I2C读取或串口发送,否则会造成数据卡顿。
不过片内Flash再怎么做磨损均衡,容量和寿命始终有限,更稳妥的做法是外挂一颗I2C接口的EEPROM,比如AT24C256,32KB容量,擦写寿命100万次,作为断电缓存使用。系统每5分钟写一条到EEPROM,能存约1600条数据,也就是5.5天左右。如果每1小时写一条,能存44天。完整的历史数据还是通过RS485定期上报到上位机数据库,EEPROM只承担“上位机离线期间的本地缓存”这一角色。
4.2 上位机对接方案:Modbus RTU从机设计
很多博物馆已经有环境监控平台或楼宇自控系统,他们普遍支持Modbus协议。所以我在STM32上实现了Modbus RTU从机协议,把保持寄存器规划成几个固定地址:0x0001温度、0x0002湿度、0x0003光照、0x0004VOC/CO2、0x0005告警状态。上位机用Modbus Poll或Python的pymodbus库按寄存器地址读取就行,完全不需要为这套系统单独开发对接模块。
采用Modbus的好处是省掉了协议适配的环节。博物馆如果已有监控大屏,只需把设备接到RS485总线上,配好波特率和从机地址,就能在现有平台上看到数据。我在这套系统里固定使用9600波特率、8数据位、1停止位、无校验,这是Modbus RTU最常见的配置,兼容性最好。
4.3 历史数据查询和断点补传
上位机这边的方案我用Python加SQLite做了轻量级版本,定时读取串口数据写入本地数据库,再用Flask做一个简单的网页展示曲线。因为上位机已经存了全量数据,STM32上的Flash压力小了很多,只保留最近一个月的关键数据作为备份。
断点补传是这个系统里相对进阶但很有用的功能。数据帧里带一个自增序号,上位机通过检查序号的连续性来判断是否有丢帧。如果串口线或RS485总线中断了一段时间,重新上线后,上位机发出补传请求,STM32从Flash的对应序号区间把缺失的数据回传过去。这个功能对博物馆的管理审计特别有意义——如果某天库房出现环境异常,事后追责时必须能拿到完整的原始数据链,而不是因为设备掉线就缺了一段。
5. 实测校准与长期运行:数据会告诉你真相
5.1 传感器出厂精度和实际环境之间的差距
SHT30的出厂校准是在实验室标准环境下完成的,但实际装机后,PCB上器件的热量、外壳通风不畅、传感器和主控芯片距离过近,都会引入额外偏差。我在第一版硬件测试时发现,SHT30紧贴STM32芯片安装时,测得的温度比独立安装在通风外壳内高了0.5℃左右,湿度也偏高约3%RH。MO问题不小,文物库房对温湿度的控制精度要求本来就严格,这个误差占了允许波动的很大比例。
解决办法是换安装形式:传感器不直接焊在主板上,而是通过4P排线引出,安装在库房或展柜内有代表性的位置。这种方式既解决了热源干扰问题,也方便后期更换传感器模块——维护人员拧两下排线接口就行,不用动烙铁。
5.2 校准实操:多点对比与偏移修正
传感器校准是很多人会跳过的步骤,但这一步恰恰决定了监测数据的可信度。我的做法是把系统放进恒温恒湿箱,或者放在温度相对稳定的房间里,和一台标准温湿度计并排放置,稳定后记录标准值和系统值。至少要比较五个以上不同的温湿度点,比如15℃/40%RH、20℃/50%RH、25℃/60%RH、30℃/70%RH这样覆盖范围较广的组合,然后做线性回归,算出偏移量y = ax + b,把参数写进固件的校准参数区。
实际数据举个例子:用TESTO 608-H1做标准表,SHT30在21.3℃时标准表是21.1℃,偏移+0.2℃;湿度60%RH时标准表是58.2%RH,偏移+1.8%RH。把偏移量修正后,长期监测数据与标准表的偏差能控制在±0.1℃和±1.5%RH以内。有一点必须强调:校准参数是和具体传感器绑定的,换传感器之后必须重新校准,否则校准参数反而会把数据带偏。
5.3 长时间运行后的几个意外状况
连续运行测试期间发现了几个数据手册上不会写的现象。第一个是SHT30的湿度传感器长期处于75%RH以上的环境里,读数会缓慢偏高,需要在通风干燥的环境中放置一段时间才能恢复。这其实和传感器内部高分子感湿材料的吸湿特性有关。我在固件里加了一个“传感器健康度”提醒:如果湿度连续三天超过75%RH,上位机就会提示人工进行传感器除湿维护。
另一个意外来自BH1750。长期暴露在强光下,尤其是阳光直射,它的输出值会明显偏低。后来查了一些资料,确认是传感器前端的滤光片在紫外线和强光叠加作用下出现了老化衰减。所以光感探头的实际安装位置一定要加半透明磨砂罩,避免紫外线和强光直接照射传感器本身。
| 时间段 | 环境温度℃ | 湿度%RH | 光照lux | 备注 |
|---|---|---|---|---|
| 08:00 | 19.8 | 55.2 | 18 | 库房正常状态 |
| 12:00 | 22.3 | 58.7 | 25 | 外界升温明显 |
| 17:00 | 20.5 | 53.4 | 15 | 空调重新启动 |
| 22:00 | 19.2 | 50.1 | 2 | 闭馆后无人值守 |
上面是某一天系统实测记录的一部分,能看到光照数值在闭馆后降到个位数,温湿度整体平稳,这套系统在正常库房里的表现还是相当可靠的。
6. 做这套系统,我的几点真实体会
6.1 别把系统做成“能跑”就完事
这套系统从表面功能看并不复杂——采集、存储、显示、报警——但真正要稳定连续运行几个月不出问题,靠的全是上面那些细节:Flash磨损均衡、看门狗超时、传感器故障识别、断点补传、滞回比较。每一个都是“能跑”之外的活儿。如果你在做类似的毕业设计,我的建议是不要只停留在能演示的程度,把这些边界场景完整地做进去,就形成了一条完整的技术链路,答辩时可以讲的内容比单纯堆传感器丰富得多,而且每一个点都有实际工程价值。
6.2 设备维护便捷性要提前考虑
博物馆的监测设备通常装在库房角落或展柜里,运维人员不太可能带着烙铁去换元件。所以我在硬件设计上做了模块化处理:所有传感器都通过排线接口做成插拔模块,蜂鸣器和继电器驱动单独放在一块小板上,主控板和传感器板通过接插件连接,哪个模块故障就换哪个模块,不用整机送修。这个习惯我在后来自建的其他项目里也一直保留,对实际交付的幸福感提升是巨大的。
6.3 安装位置对数据的影响比选型更大
这是最后想说的一条经验。我见过有人把温湿度传感器装在空调出风口附近,测出来的数据永远在“正常”范围,因为出风口附近是空气循环最快的区域,几乎实时达到设定温湿度,而真实库房里远离气流的角落可能早就超限了。正确的做法是:把传感器装在文物保存的实际区域,离墙体至少30cm,避免阳光直射和热源,高度大约在1.2m左右,和文物存放高度保持一致。把这个原则写进部署手册,比固件里的任何算法都重要——因为传感器测到的物理量,永远只代表它所在那个位置的真实环境,而不是整个库房的平均值。