1. 为什么“长时段”是温度记录的一道分水岭
先说说我对 Temperature Logger 这个项目最直接的感觉:很多朋友做温度记录,上来就用现成的 USB 温度计插电脑上,开个软件就跑了。测 10 分钟、2 小时,顶多一晚上,这个玩法完全够用。但一旦把“长时段”三个字加进去,整套思路就得换一遍。我说的长时段,指的是至少 24 小时起步,按周、按月甚至跨季度连续跑的那种场景。
我自己最早踩坑是给一个老式发酵间做温度曲线记录,想着用笔记本电脑加 USB 温度探头连着跑三天,结果第三天凌晨系统自动更新重启了,软件没设开机自启,日志断了一整晚。后来我看到同事用的独立式温度记录仪,才意识到问题不在探头准不准,而在整个系统能不能在一个无人值守、无干预的周期内稳定、连续、不丢点地工作。所以这篇博文,我打算把这类长时间温度记录设备从硬件选型到软件逻辑,从采样策略到数据校验完整拆一遍,给正要动手的朋友一个可以直接抄作业的参考方案。
这个内容适合谁?三类人:一是搞环境监测、冷链运输、农业养殖、实验室稳定性测试的,需要合规、可靠的数据留存;二是玩 3D 打印、恒温箱、数据中心散热优化的,需要长时间监控温度变化趋势;三是纯粹想做个靠谱的个人项目练手,为以后做物联网数据采集打基础的。三类需求都围绕同一个核心——长时间跑,不丢数据,出来的结果能被人信任。
说白了,长时段温度记录的本质不是“测温度”,而是“证明某个温度在某个时间段内一直在某个范围里”。这句话我在后面会反复提到,因为它决定了采样、存储、供电甚至外壳设计的每一个细节。
2. 硬件选型:长跑选手看的是稳定性和耐力,不是爆发力
2.1 传感器选择,别只看精度和分辨率
很多新手选温度传感器,上来就看精度:±0.1℃?±0.5℃?觉得数字越小越牛。但长时段记录里,精度只是其中一环,更关键的是长期漂移、响应速度、供电方式和抗干扰能力。
我常用的几类传感器做一下对比:
| 传感器类型 | 典型型号 | 精度 | 优势 | 长时段部署的短板 |
|---|---|---|---|---|
| 热敏电阻 NTC | 10K NTC,B=3950 | 0.1℃~1℃(需校准) | 便宜、响应快、电路简单 | 非线性严重,需要查表或公式换算;长期老化漂移需定期校准 |
| 数字温度芯片 | DS18B20 | ±0.5℃(-10~+85℃) | 一线协议、可直接读数字量、支持寄生供电 | 测温速度慢(750ms 左右),时序要求严格,线长时需上拉电阻 |
| I2C 温湿度 | SHT30 / SHT31 | ±0.2℃~±0.3℃ | 自带校准、功耗低、稳定性好 | 湿度传感器部分怕凝露,长期高湿环境容易失效 |
| 热电偶 | K 型热电偶 | ±1.5℃~±2℃(不含冷端补偿) | 测温范围大、探头小巧 | 需要冷端补偿,接线复杂,信号是毫伏级,抗干扰要求高 |
| 铂电阻 PT100/PT1000 | PT100 Class A | ±0.15℃ | 精度高、长期稳定性最好 | 贵、需要变送器或高精度 ADC,功耗和体积都偏大 |
长时段项目里我最推荐的首选是 DS18B20 和 SHT30 的组合。DS18B20 抗干扰能力强,一根线可以挂多个探头,适合多点测温;SHT30 适合对环境温度和湿度一起关心的场景。PT100 精度最高但成本也高,一般是实验室验证级场景才值得上。
这里有个经验之谈:如果你测的是物体表面温度,探头固定方式比传感器精度影响更大。散热片盖着和探头悬空,读数能差好几摄氏度。长时段记录的数据好坏,很大程度取决于现场部署的人懂不懂热传导。
2.2 主控和时间基准,稳定是第一优先级
主控的选择,很多人随手拿 Arduino Uno 或者 STM32 最小系统板就开搞。我做过的项目里,主控方案其实取决于一件事:要不要联网。
如果完全离线,用 Arduino Uno/Nano 加 SD 卡模块就够了,代码简单,功耗还能压得很低。但要注意 Uno 的 USB 转串口芯片在断电后可能会从 USB 口往回偷电,导致系统“关机了但没完全关”,长时间电池供电时电会被慢慢放干。如果做电池供电,建议用 Pro Mini 那种不带 USB 芯片的板子,或者干脆用 ESP32 的 deep sleep。
如果考虑远程查看数据,ESP32 是性价比之王,自带 WiFi,可以做定时上报。我现在的长时段记录仪主力就是 ESP32-C3,功耗比经典 ESP32 低不少,deep sleep 电流能到 10μA 左右,配合锂电池能跑很久。
时间基准这个细节必须单独说。温度记录如果没有可靠的时间戳,数据几乎等于废的。Arduino 的 millis() 在掉电重启后归零,SD 卡里的日志时间会乱掉。所以长时段记录仪必须有 RTC 模块,比如 DS3231,它自带温补晶振,年误差一般在 1~2 分钟内,比 DS1302 那种芯片靠谱太多。DS1302 我测过一批,有些一个月能偏出好几分钟,做长时段记录根本不能用。
2.3 存储和供电,长跑选手的体力管理
存储方案上,主流选择是 MicroSD 卡加 SPI 模块。但要留意一个坑:SD 卡模块很多是 3.3V 逻辑,而 Arduino Uno 是 5V 输出,直接接会出问题。要么用带电平转换的模块,要么给 SD 卡模块单独供电。第二个坑是 SD 卡文件系统,Arduino 的 SD 库对 FAT32 支持得最稳,FAT16 在部分卡上会异常。还有一点,卡容量不是越大越好,8GB、16GB 的卡足够跑好几年,32GB 以上的卡在 SPI 模式下兼容性反而容易出问题。
供电这块,我需要重点讲一下功耗估算。以我的方案为例:ESP32-C3 每 30 秒唤醒一次读温度然后写 SD 卡,唤醒工作时间大约 400ms,工作电流约 80mA,deep sleep 电流约 20μA。算下来平均电流大概是 80mA × 0.4s / 30s + 0.02mA ≈ 1.09mA。用一块 18650 锂电池(容量 2600mAh)理论续航约 100 天。如果采样间隔改成 5 分钟,平均电流降到约 0.13mA,一颗电池能跑两年。这个计算很简单,但很多人忽略,直接导致做到一半发现两天就没电了。
注意:锂电池供电的话,尽量选带保护板的电芯,防止过放损坏电池。另外,低功耗代码里记得把板载 LED 全部关掉,LED 一个就是十几毫安,对整机功耗影响非常大。
3. 软件逻辑与采样策略,长时段记录的灵魂
3.1 采样策略:真不是越密越好
长时段记录最容易犯的错误,是把采样间隔设得很短,比如每秒一次。表面看数据量大、曲线精细,但实际副作用很多:存储碎片多、功耗高、数据分析时噪声大、文件增长过快。我见过一个冷链项目,设置每 5 秒采一次,结果一天的 CSV 文件就有几十兆,后期处理起来非常痛苦。
合理的做法是“事件驱动 + 周期采样”结合。正常状态下按基础频率采样,比如 1 分钟一次;当检测到温度变化速率超过某个阈值(比如每分钟变化超过 0.5℃),就自动加密到 10 秒一次,直到温度稳定后再降回慢速。这个逻辑跟示波器的自适应采样是一个思路,既能捕捉关键瞬态,又不会积累大量冗余数据。
还有一个细节:采样间隔必须是采样周期的整数倍,而且启动时刻要对齐整分钟。这样后期日志分析的时候,不同点位之间可以按时间轴直接对齐,不用做插值。
// 自适应采样间隔的伪代码逻辑 const uint32_t FAST_INTERVAL = 10000; // 10秒快速采样 const uint32_t SLOW_INTERVAL = 60000; // 60秒慢速采样 const float TEMP_THRESHOLD = 0.5f; // 温度变化阈值 float lastTemp = readTemperature(); bool isFastMode = false; void loop() { float currentTemp = readTemperature(); float changeRate = abs(currentTemp - lastTemp) / (intervalMinutes / 60.0f); if (changeRate > TEMP_THRESHOLD) { if (!isFastMode) { isFastMode = true; } // 使用 FAST_INTERVAL 进入下一秒判断 } else { if (isFastMode) { isFastMode = false; } // 使用 SLOW_INTERVAL } // 记录数据到日志 writeLog(millis(), currentTemp, isFastMode); lastTemp = currentTemp; delay(isFastMode ? FAST_INTERVAL : SLOW_INTERVAL); }3.2 数据写入方式:别一条一条磨卡
把数据写入 SD 卡,看起来简单,但里面的坑不少。SD 卡写操作是有寿命的,虽然现在卡耐写到几十万次擦写,但在长时段记录下,写入频率高的话,一两年后卡就可能出现坏块。更关键的是,每次单条写入都会产生一次文件系统操作,如果恰好写到一半断电,很可能损坏文件系统。
我的做法是内存里开一个 FIFO 缓冲区,先攒数据,攒够一定条数或者到固定时间再批量写入。比如设置 16 条记录或者 1 分钟批量写一次。这样写卡次数大幅降低,卡寿命长,对功耗也有帮助。
另一个极其重要的点是:每次写入前先打开文件、写入、关闭文件。有些人的代码在 setup 里 open 一次文件就不关了,程序运行中如果断电复位,文件句柄没正常关闭,SD 卡上会出现损坏的目录项,有时整只卡的文件系统都会崩。我有一次跑了一周的数据,就因为这种写法全部丢了。
文件命名也建议按日期来,比如log-20240615.csv,这样一天一个文件,即使某个文件出问题,也只影响当天数据,不会影响历史数据。
3.3 掉电保护与断点续传,不能拿到什么算什么
长时段记录里最怕的就是掉电。很多现成方案掉电后就把内存里的数据丢了,只留下已经写卡的部分。这个问题可以从两方面解决。
硬件层面加一个大电容或者小锂电池,在检测到主电源掉电时,给主控几百毫秒的余量,让它完成“收尾”动作——把缓冲区数据写入 SD 卡,关闭文件,保存最后一条日志的时间戳。我们实测一个 1F 超级电容就能让 ESP32-C3 在 5V 掉电后多跑大约 1.5 秒,足够完成收尾。
软件层面的细节是,每次开机后先读上次保存的最后一条记录时间,如果发现有一条记录的时间戳和当前时间对不上,或者文件结尾异常,就自动在日志中加入一条“设备重启,检测到上次非正常结束”的标记。这样后期分析数据时,你能知道哪些数据之间存在着设备重启的空窗期,避免误判。
4. 完整实操过程:从零搭一台七天不死的温度记录仪
4.1 硬件清单与接线
先列一下我实际在用的这套配置:
- ESP32-C3 开发板(或者其他低功耗 ESP32 板子)
- DS18B20 防水探头(10 米线长度以内没问题)
- DS3231 RTC 模块
- MicroSD 卡模块 + 16GB 闪迪卡
- 18650 锂电池 + 充放电保护模块(带 5V 升压输出)
- I2C SSD1306 OLED 显示屏(0.96 英寸,可选)
- 10K 电阻一个(DS18B20 的上拉电阻)
接线很简单:DS18B20 数据线接 GPIO4,上拉电阻一端接 3.3V,一端接数据线;DS3231 的 SDA/SCL 分别接 GPIO8、GPIO9;SD 卡模块接 SPI 引脚,CS 选 GPIO10。这些都是 ESP32-C3 上常见的软件可配置引脚,具体看板子丝印和库文件里的引脚映射表。
注意:DS18B20 的数据线不要超过 10 米,超长线材会导致信号时序变形。如果非要拉长线,改用 RS485 总线的温度探头,或者中间加中继器。如果高湿环境,在 DS18B20 探头尾部接缝处打一圈热熔胶,防止水汽从引线根部渗进去。
4.2 软件配置,用 Arduino 快速实现
我用的 Arduino IDE 2.x,开发板选择 ESP32-C3,安装 esp32 开发板包后直接烧录。这个功能依赖几个库:OneWire、DallasTemperature、RTClib、SD。下面是主程序的骨架,我尽量写得贴近实用:
#include <OneWire.h> #include <DallasTemperature.h> #include <RTClib.h> #include <SD.h> #include <SPI.h> #define ONE_WIRE_BUS 4 #define SD_CS 10 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); RTC_DS3231 rtc; File dataFile; uint32_t lastWriteTime = 0; uint32_t writeInterval = 60000; // 每隔1分钟批量写一次 void setup() { Serial.begin(115200); sensors.begin(); if (!rtc.begin()) { Serial.println("RTC not found, halting"); while (1); } // 如果有需要,可用以下代码校准 RTC 时间: // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); if (!SD.begin(SD_CS)) { Serial.println("SD card init failed, halting"); while (1); } // 打开当天的日志文件(不存在则创建) char fileName[32]; generateFileName(fileName); // log-YYYYMMDD.csv dataFile = SD.open(fileName, FILE_APPEND); if (!dataFile) { Serial.println("open file failed"); while (1); } // 写文件头 if (dataFile.size() == 0) { dataFile.println("timestamp,temperature_c,fahrenheit"); } dataFile.close(); } void loop() { sensors.requestTemperatures(); float tempC = sensors.getTempCByIndex(0); DateTime now = rtc.now(); char timeStr[20]; snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()); // 追加到缓冲区,或者直接用 File 追加(这里简化处理) if (millis() - lastWriteTime >= writeInterval) { dataFile = SD.open(fileName, FILE_APPEND); if (dataFile) { dataFile.print(timeStr); dataFile.print(","); dataFile.print(tempC, 2); dataFile.print(","); dataFile.println(tempC * 9.0 / 5.0 + 32.0, 2); dataFile.close(); } lastWriteTime = millis(); } // 进入低功耗模式,等待下一个采样周期 esp_sleep_enable_timer_wakeup(30000000); // 30秒唤醒一次 esp_deep_sleep_start(); }代码逻辑很直接,但我建议你在此基础上加上我前面说的“事件驱动变间隔”和“掉电收尾”逻辑,否则只能是基础版。另外,deep sleep 期间 USB 串口会断开,你如果需要看实时输出,得用外接 OLED 或者通过 WiFi 发到 MQTT,否则只能等数据写卡后再读取。
写完代码烧录之前,先把你 RTC 模块上的电池装好。DS3231 有个 CR1220 纽扣电池位,这个电池负责在主电源断电时维持时钟走时,没有它,RTC 每次上电都回到 2000 年 1 月 1 日,日志里的时间戳直接作废。
4.3 组装与现场部署的细节
电路搭好后,第一件事不是直接封壳,而是先做 24 小时空跑测试。我用一个纸箱模拟实际环境,把设备放进去,然后看第二天数据是否连续、时间有没有跳变、有没有出现乱码或掉线。这一步能过滤掉大部分硬件接触不良的问题。
现场部署时,探头的固定方式非常重要。测空气温度,探头应该悬空或者放在百叶箱里,避免阳光直射和热源辐射;测物体表面温度,探头要紧贴表面并覆盖隔热材料,比如保温棉,防止环境温度干扰读数。这里有个反直觉的点:很多人以为探头贴得越紧越好,但直接贴在金属表面,如果金属表面本身有热梯度,读到的反而是局部热斑的温度。正确的做法是选一个代表整体温度的点,用铝箔胶带固定探头,再在外面盖一层泡棉。
外壳建议用防水接线盒,内部灌少量电子硅胶把电路板固定住,防止运输和振动导致引脚松动。电池和电路板之间最好加一层绝缘片,防止电池鼓包导致短路。我见过一次电池漏液腐蚀板子的事故,从那以后所有长期部署的项目,我都会在电池周围涂三防漆,并且把电池和电路板用硅胶线连接,而不是直接焊死在板上,方便几年后更换电池。
5. 数据读取、校验与可视化,让日志变成结论
5.1 CSV 数据导出与时间轴对齐
设备跑完一个周期后,把 SD 卡拿出来读出 CSV 文件。每个文件一行一条记录,字段是时间戳、摄氏温度、华氏温度。长时间记录最大的问题是文件数量和行数非常多,手动看不可能。我一般会把多天的 CSV 文件合并,然后用 Python 做重采样和对齐。
时间戳对齐一个常见问题:如果设备中间重启过,或者 RTC 校时过,时间线会有空洞或跳变。处理方法很简单,读 CSV 时先解析时间戳,用 pandas 设置成 DatetimeIndex,然后重新采样成固定频率(比如 5 分钟均值),缺失的时间点会显示为 NaN,一眼就能看出设备哪些时段没在工作。
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("log-20240615.csv", parse_dates=["timestamp"], index_col="timestamp") # 重采样成5分钟均值,空缺标记为 NaN resampled = df["temperature_c"].resample("5T").mean() print(f"总记录数: {len(df)}, 时间跨度: {df.index.min()} ~ {df.index.max()}") print(f"缺失时间段: {resampled[resampled.isna()]}") # 画趋势图 plt.figure(figsize=(12, 5)) plt.plot(resampled.index, resampled.values, linewidth=1) plt.title("Temperature Trend (5-min Avg)") plt.ylabel("Temperature (°C)") plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig("temperature_trend.png", dpi=200)5.2 数据校验的四个维度
拿到数据后先别急着下结论,我一般从四个维度做校验:
- 范围校验:每个点的温度是否在传感器量程内(DS18B20 是 -55℃~125℃)?有没有跳出合理范围的野值?
- 变化率校验:相邻两个采样点的温差是否超过物理上可能的变化速率?比如 30 秒内温度跳了 10℃,大概率是传感器接触不良或电磁干扰。这个用 pandas 的 diff() 函数就能查。
- 连续性校验:时间戳是否连续?有没有漏点、重影、乱序?深度睡眠唤醒后 RTC 偶尔会读出异常值,可以做一次时间戳单调性检查。
- 双源交叉校验:如果你同一环境放了两个探头,可以把两路曲线叠加对比,偏差大于设定阈值(比如 1℃)的时间段,需要去现场排查是否存在局部热点或传感器老化。
这四个维度过滤完,数据才算“可信”,才能拿去写报告、做判断。
5.3 现场读数的实时可视化小技巧
长时段记录最好搭配一个简易的实时可视化,便于现场巡检时快速判断设备是否正常。如果设备已经部署在配电箱或者机柜里,人不方便每次都拆卡插电脑,可以考虑在 OLED 上显示最近 1 小时的最大值、最小值和当前值,再在代码里加一个心跳 LED:正常运行时 LED 每秒闪一次,异常时(卡写入失败、RTC 失效)快速闪三下。这样巡检人员不用读数据就能判断设备状态。
我做过的一个项目里,还加过一个小喇叭,温度超限时蜂鸣器响 5 秒。这算是温控报警器的功能了,但长时段记录加报警天然互补——记录仪负责留证据,报警器负责及时提醒。
6. 一个真实案例:给 3D 打印腔体做 72 小时温度曲线
我用这段时间搭的记录仪做了个实际测试,目标很简单:确认 3D 打印机在连续打印 72 小时的过程中,腔体温度是否稳定在 ABS 打印所需的 40~50℃ 区间。打印时长长、材料对温度敏感,这个场景正好需要用长时段记录仪来验证。
部署方式是:一个 DS18B20 探头悬空挂在腔体上部中间位置,避开热床直吹,另一个探头贴在模型旁边。采样间隔设 30 秒,正常 60 秒,温差变化快时自动加密到 10 秒。电池供电,断电也不怕。
跑完 72 小时回到现场,读取数据后发现两个有意思的现象:
- 打印前 2 小时腔体温度从室温逐步爬升到 42℃,之后一直稳定在 44~47℃ 之间,波动幅度不超过 2.5℃。
- 凌晨 3 点左右有一段短暂的温度下降到 39.8℃,持续约 20 分钟后回升。排查日志才发现那段时间打印机自动暂停等待耗材续料,腔门可能被打开过。
如果当时采样间隔设成 5 分钟一次,这个 20 分钟的短暂降温很容易被“平均掉”,看不出任何异常。这也让我确认了自适应采样策略的价值。当然,如果只是长期监测整体趋势而不关心瞬态事件,5 分钟间隔完全够用,还能省电省空间。
7. 常见问题排查与避坑速查表
这节直接上干货,把我在各个项目里实际遇到的高频问题列成一个速查表,每个问题后面附上排查思路和解决方案。
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 采集到的温度一直是 85℃ 或者 -127℃ | DS18B20 未正确识别,数据线断开 | 用示波器或万用表测数据线电压;单独用示例代码扫描总线地址 | 检查上拉电阻是否接好;确认 GPIO 在代码里没有配置冲突 |
| SD 卡文件打不开,或者文件损坏 | 写入过程中断电,文件句柄未正常关闭 | 用 SD Card Formatter 重新格式化 | 代码中每次写完立即 close();加超级电容做掉电收尾 |
| 时间戳乱跳,每天快/慢好几分钟 | RTC 模块质量问题,或电池没电 | 用串口打印 RTC 当前时间,对比网络时间 | 换用 DS3231;更换 CR1220 电池;定期校时 |
| 电池续航远低于理论值 | 没进 deep sleep,或外设一直供电 | 测整机电流,确认睡眠电流是否在预期范围 | 关掉板载 LED,设置 IO 口为低电平输出,断开 USB 串口 |
| 温度曲线出现许多毛刺尖峰 | 探头线缆靠近电机或变频器等干扰源 | 把用电器关掉看曲线是否恢复 | 改用屏蔽线;在数据线上加 100nF 电容滤波 |
| 多探头数据偏差大 | 探头的出厂校准偏差或接触位置不同 | 把探头绑在一起放入恒温水浴或冰水混合物中互相对比 | 确定基准探头,给每个探头加温度偏移修正 |
| 定时采样的时间点不对齐 | 用 delay() 导致时钟漂移累积 | 改用 RTC 定时中断或者基于 RTC 时间判断 | 不要依赖 millis(),直接用 RTC 当前时间判断是否到达采样点 |
我特别想提醒的坑有两个。一个是 SD 卡的 SPI 接线顺序,网上很多教程把 CS 接在 D4 或 D10,不同板子引脚定义不同,烧录后卡不识别很常见。另一个是锂电池和充电模块的搭配,很多便宜模块的升压电路会持续消耗电流,哪怕设备处于 deep sleep,整机功耗也可能偏高。买模块前先看一下静态电流参数,最好选带使能脚的低功耗升压芯片。
部署现场还有一个隐蔽问题:冬天室内外温差大的时候,SD 卡模块和电池可能在低温下性能下降。消费级电子元器件一般标注工作温度 -20℃~70℃,但锂电池在 0℃ 以下放电能力显著下降,如果设备是户外长期部署,冬季最好选耐低温电池或者加一个小功率加热垫,否则会在一月份夜里悄悄断电。
8. 数据留存与设备管理,长时段项目的最后一块拼图
记录仪跑久了,数据量会快速增长。单条 CSV 记录大约 35 字节,一分钟一条的话一年约 18MB,这个量级一般卡都能承受。但如果做秒级采样并且多探头同时记录,一年可能到 GB 级别。这时候建议按月份分子目录存储,例如/2024/06/,并结合文件名统一管理。
设备端还有定期巡检机制建议建立起来。我的习惯是每周看一次 SD 卡写入状态,每月把卡拿下来备份一次,然后重新格式化。SD 卡虽然便宜,但长期高频率写入后坏块率上升,定期换卡(比如一年一换)是成本最低的保险。备份数据放到两台电脑上,一份留原件,一份做处理,防止电脑硬盘故障把半年数据一起带走。
如果用 WiFi 版方案,数据可以定时传到 NAS 或云平台,但一定要在本地也保留一份。我见过一次网络中断导致平台上数据全丢的案例,本地存储就是从硬件上备份了最后一道防线。长时段记录项目最核心的目标是数据不丢,传输通道、存储介质、供电链路每条线都要有冗余。
这套系统做完,你可以把它轻松改造成 CO2 浓度记录仪或者气压记录仪,只要替换传感器、调整校准逻辑,其他架构完全复用。这也是我把温度记录仪做成独立小项目的原因——它是一把钥匙,打开的是整个数据采集和长时段监控的门。