
有一次我帮一个朋友所在的博物馆库房做环境巡检发现他们用的那台温湿度记录仪白天数据一切正常但纸质文献还是出现了轻微发脆的迹象。后来我把记录仪的数据导出来一帧一帧翻才发现空调早上启动的那半小时里湿度从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%RHI2C良好连续运行漂移小约15元本项目推荐BME280温湿度气压±1.0℃ / ±3%RHI2C/SPI良好约20元多参数一体场景BH1750光照分辨率1luxI2C长期强光下会老化约5元光照监测SGP30VOC/CO2TVOCCO2等效值I2C需要年度校准约40元空气质量监测GXHTC3温湿度±0.3℃ / ±2%RHI2C优秀约30元高精度展柜微环境最终我选了SHT30作为温湿度主传感器。理由很直接I2C接口能省IO口测量精度在文物环境控制精度要求温度±2℃、湿度±5%RH的范围内留足了余量价格也适中。更重要的是SHT30出厂带NIST可追溯校准实际连续通电一个月后与标准表对比偏差仍能控制在0.2℃以内这对需要长期无人值守的库房场景太关键了。光照通道用BH1750它的余弦修正特性在反射光较多的环境里表现尚可。空气污染监测通道用SGP30但它有个必须知道的坑——上电后需要预热12小时才能输出稳定值所以代码里要加一个“预热期不上报告警”的逻辑。2.2 I2C总线设计中的电平匹配和上拉电阻多个传感器挂在同一条I2C总线上首先要处理地址冲突。SHT30的I2C地址是0x44BH1750的地址是0x23SGP30的地址是0x58三者默认不冲突这是选型时的一个隐藏加分项。如果后续要加第二路SHT30可以通过ADDR引脚将其地址改为0x45。PCB布局时就要知道哪些引脚需要预留跳线或焊接点不然后期改地址要飞线非常狼狈。上拉电阻的选择也是关键。3.3V供电时我一般先在每个传感器模块上用4.7kΩ上拉然后根据实际线缆长度调整。如果传感器通过排线引出线长超过30cm4.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设备ID1字节0x01多设备组网时区分节点数据类型1字节0x01温湿度 / 0x02光照 / 0x03气质数据长度1字节后续数据的字节数数据N字节实际数据统一为整型放大10倍传输CRC162字节Modbus CRC16低字节在前帧尾1字节0x55CRC16用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比如AT24C25632KB容量擦写寿命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:0019.855.218库房正常状态12:0022.358.725外界升温明显17:0020.553.415空调重新启动22:0019.250.12闭馆后无人值守上面是某一天系统实测记录的一部分能看到光照数值在闭馆后降到个位数温湿度整体平稳这套系统在正常库房里的表现还是相当可靠的。6. 做这套系统我的几点真实体会6.1 别把系统做成“能跑”就完事这套系统从表面功能看并不复杂——采集、存储、显示、报警——但真正要稳定连续运行几个月不出问题靠的全是上面那些细节Flash磨损均衡、看门狗超时、传感器故障识别、断点补传、滞回比较。每一个都是“能跑”之外的活儿。如果你在做类似的毕业设计我的建议是不要只停留在能演示的程度把这些边界场景完整地做进去就形成了一条完整的技术链路答辩时可以讲的内容比单纯堆传感器丰富得多而且每一个点都有实际工程价值。6.2 设备维护便捷性要提前考虑博物馆的监测设备通常装在库房角落或展柜里运维人员不太可能带着烙铁去换元件。所以我在硬件设计上做了模块化处理所有传感器都通过排线接口做成插拔模块蜂鸣器和继电器驱动单独放在一块小板上主控板和传感器板通过接插件连接哪个模块故障就换哪个模块不用整机送修。这个习惯我在后来自建的其他项目里也一直保留对实际交付的幸福感提升是巨大的。6.3 安装位置对数据的影响比选型更大这是最后想说的一条经验。我见过有人把温湿度传感器装在空调出风口附近测出来的数据永远在“正常”范围因为出风口附近是空气循环最快的区域几乎实时达到设定温湿度而真实库房里远离气流的角落可能早就超限了。正确的做法是把传感器装在文物保存的实际区域离墙体至少30cm避免阳光直射和热源高度大约在1.2m左右和文物存放高度保持一致。把这个原则写进部署手册比固件里的任何算法都重要——因为传感器测到的物理量永远只代表它所在那个位置的真实环境而不是整个库房的平均值。