单片机温度检测报警系统:DS18B20/NTC/PT100选型与迟滞滤波自诊断

发布时间:2026/9/19 1:12:35
单片机温度检测报警系统:DS18B20/NTC/PT100选型与迟滞滤波自诊断 简介这份基于单片机的高级温度检测报警系统设计文档面向电子信息、自动化及嵌入式课程设计的学习者围绕DS18B20智能温度传感器与AT89C2051单片机的协同应用讲解温度采集、阈值设定、声光报警、显示及RS-485串口通信的实现思路。内容涵盖硬件电路构成、单总线工作原理、DS18B20测温特性与AT89C2051资源特点并延伸到多点测温、上位机监控与数据存档等场景适合做课设、毕设或嵌入式入门参考。文档还讨论了测温范围宽、单线接口、多点组网、无需外围元件等特点以及单片机在数据处理、控制和通信中的任务分工。资源包内共1个docx文件约15KB为纯文档型资料重点呈现方案论述、器件分析与系统总结方便直接阅读和摘录关键设计要点。目前已有48人学习下载体量轻便适合快速了解温度检测报警系统的整体架构与核心器件选型。1. 从温度超了就响到能交付的高级温度检测报警系统很多人第一次做温度报警思路是三行代码读温度、跟阈值比、开蜂鸣器。实验台上它确实会响装到现场就开始出问题——探头引线一长读数就飘继电器一吸合温度跳两度开门瞬间报警器叫个不停传感器断线时反倒安静得像没事发生。标题里的高级不是堆功能而是这套系统要处理四件事把温度稳定测准信号链与滤波、把该不该报警判断可靠迟滞与状态机、把报警信息传出去存下来串口、EEPROM、历史记录、传感器自身故障时能自报家门自诊断。这也正是单片机毕业设计里最容易失分的四处。适合正在做单片机课程设计、电子系统设计竞赛或者要把实验室样机推到小批量现场的人。下面按选型、固件、报警逻辑、进阶功能、调试标定的顺序往下拆。2. 温度检测方案选型DS18B20、NTC、PT100 到热电偶2.1 四类测温方案的量程、精度与接口对比选传感器不是比谁精度高而是先看量程和现场条件。测 water tank、室温、冷链箱和测锅炉管壁、回流焊炉膛选的东西完全不在一个体系里。下面这张表是实际项目里最常落地的几档方案典型量程精度分辨率接口形式单点成本DS18B20-55~125℃±0.5℃0.0625℃单总线低NTC 10KB3950-40~125℃±1℃需标定取决于 ADC分压 ADC极低PT100 / PT1000-200~600℃±0.1~0.3℃取决于 ADC恒流 放大 ADC中K 型热电偶-200~1250℃±1~2℃取决于 ADCMAX6675/MAX31855中高TMP102 / LM75-40~125℃±0.5℃0.0625℃I2C低判断规则很直接水温、环境、食品冷链这类 0~100℃ 区间DS18B20 一根线搞定是 51 单片机和 STM32 课程设计里最稳的选择成本敏感的家电类比如电磁炉、电饭煲用 NTC 分压进 ADC工业管道、需要长期稳定性的场合上 PT100炉温、发动机排气这类几百度以上的只有热电偶。蓝桥杯单片机赛题里的温度模块基本都落在 DS18B20 和 NTC 这两档。2.2 NTC 分压电路的阻值反解与 B 值误差NTC 便宜但非线性ADC 读到的是分压点电压必须先反解出阻值再用 B 参数公式换温度。电路接法通常是 VCC → NTC → ADC 采样点 → 固定电阻 → GND。// NTC 10K B3950 分压测温上臂 NTC下臂 10K 固定电阻 #define NTC_R25 10000.0f // 25℃ 标称阻值 #define NTC_B 3950.0f // B25/85 值取自数据手册 #define T25_K 298.15f // 25℃ 对应开尔文 #define R_FIXED 10000.0f // 下臂精密电阻建议 1% 精度 #define VREF 3.3f float ntc_adc_to_celsius(uint16_t adc_raw) { float v adc_raw * VREF / 4095.0f; // 12 位 ADC if (v 0.02f || v VREF - 0.02f) return -273.15f; // 断线或短路交上层判故障 float r R_FIXED * v / (VREF - v); // 反解 NTC 阻值 float t_k 1.0f / (1.0f / T25_K logf(r / NTC_R25) / NTC_B); return t_k - 273.15f; }逻辑上先做量程保护再算阻值最后用开尔文温度反解避免出现负绝对温度这种非法值。参数上要留意三点B 值取手册里的 B25/85 而不是 B25/50两者差几十开尔文固定电阻用 1% 金属膜普通碳膜 5% 直接带进 1℃ 以上误差分压点在 25℃ 附近最好落在 VREF 的一半左右这样两个臂灵敏度最均衡。如果现场要求 ±0.3℃ 以内B 参数公式不够得换 Steinhart-Hart 三系数拟合或者直接查表插值。2.3 PT100 的四线制与 ADC 分辨率这道坎PT100 常被误以为接上就能用真正的坑在分辨率。1mA 恒流激励下0℃ 输出 100mV温度系数约 0.385mV/℃。如果直接进 STM32 的 12 位 ADC、参考 2.5V1LSB 610μV换算过来是 1.58℃/LSB——测出来的数几乎是假的。正确的做法有三条一是加仪表放大器做 10~16 倍增益把 0.385mV/℃ 抬到 ADC 能分辨的范围二是直接上 24 位 ΣΔ ADCADS1220、AD7124 这类三是用 MAX31865 专用芯片内部带 15 位 ADC 和基准电阻走 SPI 读寄存器就行。接线方式同样决定精度。两线制里引线电阻直接串进测量回路1 米铜线约 0.02Ω10 米就是 0.2Ω按 0.385Ω/℃ 折算等于 0.5℃ 的固定偏差。三线制靠第三根线补偿前提是三根线等长同材质要求最高的场合用四线制两根送恒流、两根单独采电压引线电阻被完全排除在测量回路之外。注意PT100 的自热效应不能忽略。1mA 激励下功耗约 0.1mW常规封装没问题如果为了提信噪比把电流提到 5mA自热会让读数整体偏高零点几度这时必须缩短采样窗口或改用脉冲激励。2.4 DS18B20 单总线的上拉与转换时序DS18B20 的读写全靠一根线的时序拉开常见的读不出数基本都是三件事上拉电阻没接、转换时间不够、多个器件同时转换时供电不足。总线空置时必须被 4.7k 电阻拉到高电平MCU 的 IO 配置为开漏输出。一次完整测温流程是复位脉冲拉低至少 480μs→ 存在脉冲15~60μs 内从机拉低 60~240μs→ 发送 0xCC 跳过 ROM → 发送 0x44 启动转换 → 等待 750μs12 位分辨率下的最大转换时间→ 复位 → 0xCC → 0xBE 读 9 字节暂存器。温度值取前两字节组成有符号 16 位乘以 0.0625 就是摄氏度。寄生供电模式下总线通过 4.7k 上拉给器件供电一旦挂多个探头同时转换电流不够就会读到 85℃ 这个固定异常值。现场挂三个以上探头一律改外部供电、上拉电阻保留。3. 单片机固件采样调度、数字滤波与迟滞判阈3.1 用定时器中断搭一个固定节拍的采样任务温度采样不能用delay()硬等那样主循环全被阻塞串口和按键都来不及响应。标准做法是定时器给一个 1ms 滴答在中断里累加计数产生任务标志主循环轮询执行。// Timer0 配置为 1ms 中断产生 100ms 采样节拍 volatile uint8_t g_flag_sample 0; volatile uint16_t g_uptime_s 0; void Timer0_ISR(void) interrupt 1 { static uint8_t cnt 0; TH0 0xFC; TL0 0x18; // 重装 1ms 计数值 if (cnt 100) { // 每 100 次 100ms cnt 0; g_flag_sample 1; g_uptime_s; // 系统运行秒数供超时判故障 } } void main(void) { timer0_init(); temp_sensor_init(); while (1) { if (g_flag_sample) { g_flag_sample 0; float raw temp_read(); // 读原始温度 float t filter_update(raw); // 滤波 check_state(t); // 状态机判阈 output_refresh(); // 刷新蜂鸣器/继电器 } uart_task(); // 串口收发不占用采样节拍 wdt_feed(); // 喂狗 } }节拍定 100ms 是权衡的结果DS18B20 单次转换要 750ms所以实际读取要么延长到大于 750ms要么读上一次的结果NTC 和 PT100 走 ADC 的场合100ms 足够快也给了滤波窗口数据。中断服务函数里只做计数绝不放浮点运算和串口发送这是实时性不塌的底线。3.2 中值滤波加一阶低通把跳变压下去单次采样一定会带进脉冲干扰尤其在继电器、电机附近的现场。工程上性价比最高的组合是中值滤波除脉冲 一阶低通平滑。滤波方式窗口/参数作用代价中值滤波N5剔除单点尖峰需 N 次采样缓冲一阶低通 IIRα0.25平滑随机噪声引入响应延迟滑动平均N8平滑白噪声对尖峰无效限幅滤波阈值 2℃/次挡住离谱跳变真实突变会被削#define MED_N 5 static uint16_t buf[MED_N]; static uint8_t idx 0; static float y_lpf 0.0f; #define ALPHA 0.25f // alpha dt/(taudt)dt100ms, tau300ms float filter_update(float x) { buf[idx] (uint16_t)(x * 100.0f); // 放大 100 倍存整数省浮点 idx (idx 1) % MED_N; uint16_t s[MED_N]; for (uint8_t i 0; i MED_N; i) s[i] buf[i]; for (uint8_t i 0; i MED_N - 1; i) // 冒泡取中值 for (uint8_t j 0; j MED_N - 1 - i; j) if (s[j] s[j 1]) { uint16_t t s[j]; s[j] s[j1]; s[j1] t; } float med s[MED_N / 2] / 100.0f; y_lpf ALPHA * med (1.0f - ALPHA) * y_lpf; // 一阶低通 return y_lpf; }α 的物理含义是新数据占多大权重取 0.25 对应时间常数约 300ms、截止频率约 0.5Hz。α 越小曲线越平但真实升温被拖慢α 越大响应快噪声也跟着放大。报警系统里宁可稍慢一点也不能抖0.2~0.3 是常用区间。中值窗口取 5 是折中取 3 抗干扰偏弱取 7 以上对 MCU 的 RAM 占用明显上升。3.3 迟滞窗口与三级报警状态机温度在阈值附近必然抖动用if (t 60) 报警这种写法继电器会以每秒几次的频率反复吸合触点寿命撑不过一个月。解决办法是加回差并把逻辑写成状态机。#define T_WARN_HI 45.0f #define T_ALARM_HI 60.0f #define T_HYST 2.0f // 回差报警后要降到 58℃ 才解除 #define T_FAULT_MIN (-50.0f) #define T_FAULT_MAX 150.0f typedef enum { ST_NORMAL, ST_WARN, ST_ALARM, ST_FAULT } sys_state_t; static sys_state_t st ST_NORMAL; void check_state(float t) { if (t T_FAULT_MIN || t T_FAULT_MAX) { st ST_FAULT; return; } switch (st) { case ST_NORMAL: if (t T_ALARM_HI) st ST_ALARM; else if (t T_WARN_HI) st ST_WARN; break; case ST_WARN: if (t T_ALARM_HI) st ST_ALARM; else if (t T_WARN_HI - T_HYST) st ST_NORMAL; break; case ST_ALARM: if (t T_ALARM_HI - T_HYST) st (t T_WARN_HI) ? ST_WARN : ST_NORMAL; break; case ST_FAULT: if (t T_FAULT_MIN t T_FAULT_MAX) st ST_NORMAL; break; } }三级设计让现场人员能区分注意和危险预警亮黄灯、蜂鸣器间歇响报警亮红灯、蜂鸣器常鸣并驱动继电器切断加热回路故障状态则是传感器本身有问题报警输出走另一套策略。回差 2℃ 覆盖了大多数水温、油温系统的自然波动如果是加热功率大、超调明显的系统回差可以放到 3~5℃。还有一条容易漏的规则报警触发后加最短保持时间。比如 3 秒内状态不允许从 ALARM 退回避免瞬时干扰造成一次假报警。实现方式是在进入 ALARM 时记录g_uptime_s退出条件里加时间判断。4. 报警输出驱动与串口上报蜂鸣器、继电器、自定义帧4.1 蜂鸣器和继电器的驱动电路与上电抖动单片机 IO 口的驱动能力通常在 10~20mA有源蜂鸣器工作电流 20~30mA 已经压线继电器线圈 5V 时普遍在 70mA 上下直接接 IO 必烧。标准接法是 NPN 三极管S8050或 ULN2003 达林顿阵列基极串 1k 限流电阻基极到地并 10k 下拉防止复位期间 IO 悬空导致误动作。继电器线圈两端必须反并一只续流二极管1N4148 或 1N4007方向与电源相反。少了这一只线圈断电瞬间的反电动势会打穿三极管或者通过电源耦合进 ADC表现就是继电器一吸合温度读数跳两三度。电源处理同样关键继电器用独立一路 5V或者至少在线圈附近加 100μF 电解加 0.1μF 陶瓷去耦模拟部分和数字部分单点共地要求高的场合用 PC817 光耦把控制侧和负载侧彻底隔开。这几条做完读数跳变的问题基本消失。4.2 帧格式设计与校验让上位机敢信这包数据裸发温度字符串比如printf(%.2f\r\n, t)调试够用接上位机或组网就不行了丢一个字节后面全乱。自定义二进制帧更稳下面这套格式在小型监控系统里很好用。偏移长度字段说明01帧头固定 0xAA11设备地址1~247支持多点组网21命令码0x01 上报状态31状态0 正常 / 1 预警 / 2 报警 / 3 故障42温度值温度×10小端有符号61校验前 6 字节异或71帧尾固定 0x55void send_status(uint8_t addr, uint8_t state, float temp) { int16_t t10 (int16_t)(temp * 10.0f); // 保留一位小数转整数传 uint8_t f[8]; f[0] 0xAA; f[1] addr; f[2] 0x01; f[3] state; f[4] (uint8_t)(t10 0xFF); // 小端低字节 f[5] (uint8_t)((t10 8) 0xFF); // 小端高字节 uint8_t sum 0; for (uint8_t i 0; i 6; i) sum ^ f[i]; f[6] sum; f[7] 0x55; uart_send_bytes(f, 8); }温度乘 10 转整数传输避开浮点格式在不同平台上的字节序和表示差异上位机收到除以 10 即可。异或校验只能发现奇数个比特翻转如果传输环境恶劣建议换成 CRC16-Modbus多项式 0xA001代价是多几个字节和一次查表。4.3 上位机 Python 解析与丢帧处理接收端最容易犯的错是来多少读多少串口一次read()拿到的可能是半帧。正确做法是维护缓冲区按帧头帧尾对齐。import struct, serial def parse_frame(buf: bytes): if len(buf) 8 or buf[0] ! 0xAA or buf[7] ! 0x55: return None # 帧头帧尾不对丢弃 chk 0 for b in buf[0:6]: chk ^ b if chk ! buf[6]: return None # 校验失败丢弃 addr buf[1] state {0: 正常, 1: 预警, 2: 报警, 3: 故障}.get(buf[3], 未知) temp struct.unpack(h, buf[4:6])[0] / 10.0 # 小端有符号除以 10 return addr, state, temp def read_loop(portCOM3, baud9600): ser, buf serial.Serial(port, baud, timeout0.2), bytearray() while True: buf ser.read(64) while len(buf) 8: if buf[0] ! 0xAA: # 对齐到帧头 buf.pop(0) continue frame, buf bytes(buf[:8]), buf[8:] r parse_frame(frame) if r: print(f设备{r[0]} 状态{r[1]} 温度{r[2]:.1f}℃)缓冲区里逐字节对齐帧头凑够 8 字节就切一整帧出来解析解析失败直接丢不做补齐重试这种会把错误放大的处理。波特率 9600 下每秒上报 10 帧只是 10% 的带宽占用留足了余量。5. 进阶功能阈值掉电保存、历史记录与传感器自诊断5.1 配置参数写进 EEPROM 的双备份策略现场改过的报警阈值不能一断电就回默认值。存储介质有三档可选方式容量擦写寿命接口适用STC 内部 IAP按扇区 512B约 10 万次寄存器少量配置参数24C02 外部 EEPROM256B约 100 万次I2C配置 少量记录W25Q 系列 Flash兆字节级约 10 万次/扇区SPI历史数据TF 卡 FATGB 级无固定上限SPI/SDIO长周期日志配置参数的关键是少写 双备份。阈值只在用户修改时写一次绝不能放主循环里周期性写。typedef struct { uint16_t magic; // 0x5A5A 表示这份配置有效 uint16_t version; // 结构体版本方便日后扩展字段 float t_warn_hi; float t_alarm_hi; float t_hyst; uint16_t crc; // 前若干字节的累加和 } cfg_t; #define CFG_A_ADDR 0x0000 #define CFG_B_ADDR 0x0010 void cfg_save(const cfg_t *c) { cfg_t tmp *c; tmp.magic 0x5A5A; tmp.crc cfg_checksum(tmp); /* 交替写入 A/B 两份任一份损坏都能从另一份恢复 */ static uint8_t slot 0; eeprom_write(slot ? CFG_B_ADDR : CFG_A_ADDR, tmp, sizeof(tmp)); slot ^ 1; } uint8_t cfg_load(cfg_t *out) { cfg_t a, b; eeprom_read(CFG_A_ADDR, a, sizeof(a)); eeprom_read(CFG_B_ADDR, b, sizeof(b)); uint8_t ok_a (a.magic 0x5A5A) (a.crc cfg_checksum(a)); uint8_t ok_b (b.magic 0x5A5A) (b.crc cfg_checksum(b)); if (ok_a) { *out a; return 1; } if (ok_b) { *out b; return 1; } return 0; // 两份都坏加载默认值 }A/B 交替写入把单点的擦写次数摊到两个地址上同时规避了写一半掉电把唯一副本写坏的风险。magic 和 crc 双重校验是必须的刚出厂的芯片读出全 0xFF没有校验就会把 0xFFFF 当成合法阈值加载行为完全失控。加载失败时走默认值并置一个标志位让上位机能看出这台设备配置丢了。5.2 温度记录存 TF 卡CSV 落盘与丢数据边界需要画温度曲线或者追责的场合得把数据落盘。用 FatFs 在 TF 卡上写 CSV 是最省事的方案Excel 直接打开不用写解析工具。static FIL g_log; static char g_line[64]; static uint8_t g_pending 0; void log_tick(float temp, uint8_t state) { /* 每 60 秒写一行运行秒数,温度,状态 */ if (g_uptime_s % 60 ! 0) return; int n sprintf(g_line, %lu,%.2f,%u\r\n, (unsigned long)g_uptime_s, temp, state); if (f_open(g_log, TEMP.CSV, FA_OPEN_ALWAYS | FA_WRITE) FR_OK) { f_lseek(g_log, f_size(g_log)); // 追加到文件末尾 UINT bw; f_write(g_log, g_line, n, bw); if (bw ! n) g_pending 1; // 写失败标记待重试 f_sync(g_log); // 关键立刻落盘防止拔卡丢数据 f_close(g_log); } }这里有两个必须接受的现实。一是f_sync()每次调用会阻塞几十毫秒所以写入周期不能太短60 秒一行是常见折中如果要求秒级记录得先在 RAM 里攒够一批再统一写代价是掉电最多丢一批数据。二是 TF 卡在掉电瞬间写入会损坏 FAT 表正规做法是加掉电检测电路检测到电压跌落立刻停止写入并f_sync()。如果只是记录温度、不需要在 PC 上直接打开用 W25Q 这类 SPI Flash 按扇区裸写更可靠代价是要自己写一个上位机导出工具。5.3 断线、短路与温变率让传感器故障自己说出来报警系统最危险的失效模式不是误报而是漏报——传感器断线了读数卡在某个正常值系统一声不吭。所以自诊断逻辑必须做而且要区分瞬时干扰和真故障。判断依据有四条读数超出物理合理量程DS18B20 的固定 85℃、NTC 的 ADC 满量程或 0连续 N 次读取失败DS18B20 表现为复位后无存在脉冲读数变化率异常正常物理升温速率有限1 秒内跳 20℃ 基本是干扰或接触不良同一时刻多个测点读数完全一致可能是 ADC 通道切换故障。#define FAULT_CONFIRM_N 3 static uint8_t fault_cnt 0; static float last_t 0.0f; uint8_t sensor_diagnose(float t) { if (t -50.0f || t 150.0f) { // 超出合理量程 if (fault_cnt FAULT_CONFIRM_N) return 1; return 0; } if (fabsf(t - last_t) 20.0f) { // 单次跳变过大先不计入 return 0; } fault_cnt 0; last_t t; return 0; }连续 3 次确认才置故障是为了躲开一次静电或继电器动作带来的假异常跳变判据只做观察不做判罚因为真实场景里投入高温物料确实可能瞬间升温。故障状态下报警输出走独立逻辑故障时不能简单地不报警而应该让蜂鸣器以区别于温度报警的节奏鸣叫同时通过串口主动上报故障码让值班人员知道是设备坏了不是温度正常。6. 两点标定、抗干扰与误报排查的实招6.1 冰水与沸水两点标定怎么做才准数字传感器出厂已标定NTC 和 PT100 组成的模拟通道必须自己标。实验室条件下最实用的方法是两点法冰水混合物提供 0℃ 参考沸腾的纯水提供 100℃ 参考。冰水混合物要的是冰水共存状态不是一杯冰块冰融化吸热的过程中温度才稳定在 0℃。沸水点的实际温度受气压影响海拔每升高 300 米沸点大约降 1℃海拔 1000 米的地方沸水只有 97℃ 左右按 100℃ 标定会引入 3℃ 系统误差。有条件的话用标准温度计做第三方参考。拿到两个点后用线性修正公式处理// 两点标定raw0/raw100 是 0℃ 和 100℃ 时的原始读数 #define RAW_AT_0C 1234.0f #define RAW_AT_100C 3856.0f float calib(float raw) { float k 100.0f / (RAW_AT_100C - RAW_AT_0C); // 斜率 return (raw - RAW_AT_0C) * k; }斜率和截距存进 EEPROM产线每台设备单独标一次。如果发现两点标定后中段误差仍然偏大超过 0.5℃说明通道非线性明显得改用三点标定做分段线性插值。6.2 现场干扰的典型现象与对应处理温度报警系统在现场出的问题九成以上能归到下面这几类现象可能原因处理方式读数整体偏高激励电流自热降到 0.5~1mA缩短采样窗口继电器动作时跳变电源耦合、地线共用加 100μF0.1μF 去耦分离地线光耦隔离报警反复响阈值无迟滞加 1~3℃ 回差加最短报警保持时间长引线读数漂引线电阻、分布电容改三线/四线制加屏蔽层单端接地冬季告警频繁探头位置不当挪到被测体表面避开风口和门缝夜间偶发故障电网波动、看门狗复位加电源滤波记录复位原因到 EEPROM排查顺序建议从电源查起再看接地最后才怀疑代码。因为绝大多数温度跳变其实是共模干扰从模拟地串进来的换多少滤波算法都压不住——先把模拟地和数字地单点连接、把继电器电源单独走一路很多问题会直接消失。软件层面的最后一道保险是看门狗加复位原因记录把 MCU 的复位标志写进 EEPROM返修时读出来就能判断是不是干扰导致的反复重启比在现场猜要快得多。本文还有配套的精品资源点击获取