简介一份基于AT89C51单片机与DS18B20数字温度传感器的多点温度检测系统设计资料适合正在做课程设计、毕业设计或入门嵌入式测温应用的单片机学习者。方案采用两个DS18B20采集温度通过1602LCD循环显示系统测温范围约-30℃至99℃显示精度0.1℃硬件上包含主控制器、显示模块、温度采集模块并详细解析DS18B20内部结构、测温原理、配置寄存器及温度数据格式软件部分给出系统主流程与读取温度的方法文档末尾还附有可参考的源程序代码。压缩包内含1个doc文档大小199KB报告按绪论、方案设计、硬件设计、软件设计、系统仿真、设计总结等章节组织目录清晰方便按需查阅。已有1650人学习下载可直接用于同类课程设计参考也可在其基础上扩展测温节点或改善显示交互。1. 单片机多点温度检测为什么最终都会收敛到一条总线如果直接把三个 DS18B20 挂到同一根数据线上时序稍不对返回的可能不是三个温度而是三路 85℃ 或只有第一路正常。单片机多点温度检测系统做出来不难难的是让几路温度稳定、互不干扰。很多人第一反应是一个传感器一个 IO一路一路独立读取这样三四路还能接受到了五路以上线多、器件多、故障点也多排查起来非常痛苦。常见的做法是把所有传感器接到同一条单总线靠每个传感器唯一的 64 位 ROM 地址来区分这就是俗称的一线总线多点测温。这套方案成本低、布线简单适合机房环境监测、冷链运输记录和课堂教学验证这类场景。如果你正准备搭一组三到五路的温度采集这篇笔记把我的选型思路、原理图要点、驱动代码和调试顺序完整写给你。2. 器件选型与系统结构三点测温为什么首选 DS18B20 单总线2.1 数字传感器单总线方案为什么是首选多点测温的器件选择业内绕不开 DS18B20。它把温度采集、数字转换和单总线通信做在同一个 TO-92 封装里单片机只需要一根 IO 口就能读取温度。单总线协议下每个器件出厂时烧录了唯一的 64 位 ROM 编码主机下发匹配 ROM 命令后只有地址匹配的传感器会响应。这意味着总线上的传感器数量不受 IO 口数量限制一条线就能挂多个点。对比模拟方案更清楚。如果用 NTC 热敏电阻加 ADC每个测温点至少要占用一路 ADC 通道还得逐个做分压电路、查表和非线性校准。PT100 精度高但调理电路复杂成本也上去。DS18B20 的测量范围是 -55℃ 到 125℃默认 12 位分辨率下量化步进 0.0625℃对付机房、粮仓、冷库这类场景足够。我一般会把传感器数量控制在 4 到 6 个以内再多不是不能用而是总线上某个器件响应异常时定位是哪一路反而费时间。2.2 单片机平台选型STC89C52 还是 STM32F103决定用单总线之后下一个问题是主机选什么。标题里的单片机通常默认 51 内核实际设计时我一般会在 STC89C52 和 STM32F103 之间二选一。对比项STC89C52STM32F103内核与主频8051常见 12MHzCortex-M3最高 72MHz工作电压5V3.3VDS18B20 连接普通 IO 直接接IO 配置为开漏输出并外部上拉微秒级延时靠软件循环或定时器标定定时器方式更可靠后续扩展串口、LCD、按键足够可加网络模块、GPS、多路 ADC适用场景课程设计、小型监测设备工程化产品、需要联网上报如果只是把三路温度测出来送到 LCD 显示STC89C52 是最顺手的。5V 供电可以直接喂给 DS18B20不需要做电平转换资料也多遇到问题很容易找到对照。STM32 的优势在于延时精度和扩展性比如你后面想把温度数据通过串口定时上报或者接一个 WiFi 模块上传到平台M3 的定时器资源明显更宽裕。我的建议是做验证和课程设计用 STC89C52做产品原型用 STM32F103。两个平台的单总线驱动代码结构几乎一样差别只在延时函数和 GPIO 操作方式所以代码可以复用。2.3 系统组成传感器、主机、显示与报警的完整链路一套完整的三点测温系统至少包含五部分DS18B20 传感器组、单片机最小系统、LCD1602 显示、报警输出和按键切换。模块器件作用测温DS18B20 × 3采集三路温度单总线接入主机主机STC89C52 或 STM32F103解析单总线数据处理逻辑显示LCD1602显示当前选中通道或全部通道温度报警LED 有源蜂鸣器超限时声光报警人机交互独立按键 × 2切换显示通道、确认阈值数据流是这样一个顺序三个 DS18B20 并联在总线上单片机先广播启动温度转换等待约 750ms 后用匹配 ROM 命令逐路读出暂存器里的温度值把温度值格式化后送到 LCD。按键按下时切换当前显示通道同时判断当前通道是否超限超限则拉低蜂鸣器控制引脚。这套结构的好处是模块边界清楚。测温只依赖一根总线显示和报警独立挂在单片机上任何一个外设坏了不会拖垮其他部分。3. 原理图设计最小系统、4.7k 上拉与报警电路接线3.1 最小系统晶振、复位和电源的匹配原理图是这类设计最容易翻车的地方很多人 PCB 画得漂亮上电后单片机不跑最后查到晶振虚焊或复位电容选错。STC89C52 的最小系统要注意以下几点。晶振选 12MHz两脚分别接 30pF 负载电容到地电容离晶振引脚越近越好。复位电路用 10μF 电解电容串联 10k 电阻到地上电瞬间 RST 脚保持高电平完成复位。电源部分在 VCC 和 GND 之间并一个 104 陶瓷电容去耦如果系统里有继电器或蜂鸣器还需要在电源入口加一个 100μF 电解电容。器件参数说明晶振 Y112MHz决定指令周期和延时基准C1 / C230pF晶振负载电容C310μF配合复位电阻R110k复位下拉C4104电源去耦用 STM32F103 时主频由 8MHz 晶振经 PLL 倍频到 72MHz晶振电容建议按数据手册选 20pF复位芯片可选专用复位 IC也可以用 RC 复位。注意 3.3V 供电下 DS18B20 的 VDD 也要接 3.3V避免电平不匹配。3.2 DS18B20 接线与上拉电阻取值DS18B20 只有三根引脚接线是这套设计里最需要较真的部分。VDD 接电源正极GND 接公共地DQ 接单片机 IO并在 DQ 到 VDD 之间接一个 4.7kΩ 上拉电阻。DS18B20 引脚连接目标说明VDD5V / 3.3V建议外部供电不用寄生供电DQP1.0示例单总线数据线GND系统地必须与单片机共地上拉电阻的取值需要说清楚。总线上只有一个传感器、连线很短时4.7kΩ 完全够用。如果总线上挂了三个以上传感器或者连接线超过 30cm我一般会改成 2.2kΩ因为总线上多个器件的漏电流会叠加上拉电阻太大导致高电平被拉低通信就会出现时好时坏的现象。更极端的情况比如线长达到一米以上可以考虑 1kΩ但这时要注意器件功耗和高电平驱动能力。还有一个容易忽略的细节大多数现成的 DS18B20 模块板上已经带了 4.7kΩ 上拉电阻和 104 滤波电容如果你再在单片机端加上拉相当于两个电阻并联阻值变成 2.35k不一定有问题但你在排查时心里要有数。3.3 显示与报警电路LCD1602、按键和蜂鸣器LCD1602 的接线建议用 4 位数据模式省下 4 个 IO。RS、RW、E 分别接 P2.0、P2.1、P2.2数据口 D4 到 D7 接 P2.3 到 P2.6。V0 脚接一个 10k 电位器的中间抽头用来调节对比度这是 LCD 显示清晰与否的关键。蜂鸣器驱动不建议直接用 IO 口带。无源蜂鸣器需要 PWM 驱动初学者容易绕晕有源蜂鸣器自带振荡源只要给高低电平就能响。驱动电路用一只 NPN 三极管基极串联 1k 电阻接单片机 IO发射极接地集电极接蜂鸣器负极蜂鸣器正极接电源。蜂鸣器两端反向并联一个 1N4148 二极管续流防止关断瞬间的反向电动势击穿三极管。按键接法上我习惯把按键一端接 IO另一端接地IO 内部或外部上拉。读按键时检测电平低电平表示按下。按键去抖可以用 20ms 延时也可以用定时器扫描原理图阶段只需要保证按键有明确的上下拉路径即可。4. 代码实现DS18B20 驱动、ROM 配对与多点轮询4.1 时间基准延时函数与 DS18B20 时序的关系DS18B20 是单总线时序协议所有命令都是通过拉低、释放和采样的时间长短来传输的。这意味着代码里最基础、最重要的就是延时函数。用 STC89C52 时晶振 12MHz、12T 模式下_nop_()大概执行 1μs所以很多例程会写出下面的软件延时。#include reg52.h #include intrins.h // 软件延时12MHz、12T 下约等于 us 数量级 void delay_us(unsigned int us) { while (us--) { _nop_(); } }这段代码的问题是循环里有判断和跳转实际延时比 us 参数大不少而且不同编译优化级别下差别很大。我建议把微秒级延时改成定时器方式虽然代码多几行但移植和排查更容易。// 定时器0做微秒延时12MHz、12T 下定时器频率1MHz void delay_us(unsigned int us) { TMOD 0xF0; TMOD | 0x01; // 定时器0模式116位 TH0 (65536 - us) 8; // 计算初值 TL0 (65536 - us) 0xFF; TR0 1; // 启动定时器 while (!TF0); // 等待溢出 TR0 0; TF0 0; }注意两点实际进入时序操作时应关闭总中断EA 0因为 DS18B20 的读位采样窗口很短中断服务函数一旦插入采样点就错过了定时器延时适合 60μs 以上的长延时读位用的 10μs 级延时用软件_nop_()更稳妥具体延时常数要结合自己的晶振和编译环境标定。4.2 单总线底层复位、读写位无论单点还是多点底层驱动都是三个函数复位、写一位、读一位。先在头文件里定义引脚和数据类型。sbit DQ P1^0; // 单总线数据引脚 // 复位脉冲返回1表示检测到存在脉冲 unsigned char ow_reset(void) { unsigned char presence; DQ 0; delay_us(500); // 拉低至少480us DQ 1; delay_us(70); // 释放后等待70us presence DQ; // 读取存在脉冲 delay_us(400); // 等待时序结束 return presence; } // 写一位 void ow_write_bit(unsigned char bit) { DQ 0; _nop_(); if (bit) { DQ 1; delay_us(60); // 写1拉低后释放高电平保持 } else { delay_us(60); // 写0持续拉低 DQ 1; delay_us(5); } DQ 1; delay_us(2); // 每个bit之间恢复总线 } // 读一位 unsigned char ow_read_bit(void) { unsigned char bit; DQ 0; _nop_(); DQ 1; delay_us(10); // 释放后约10us采样 bit DQ; delay_us(50); return bit; }写位的逻辑解释一下主机把总线拉低表示启动一个时隙写 1 时要在拉低后尽快释放总线靠上拉电阻把电平拉高写 0 时要保持拉低至少 60μs。读位则是主机拉低启动时隙后释放在 15μs 内读取从机拉低或放开的电平状态。参数上的关键点在于 60μs 的下限和 15μs 的采样窗口这两个数值是根据 DS18B20 数据手册来的。4.3 多点识别ROM 匹配还是总线搜索单点测温时可以直接发跳过 ROM 命令总线只有一个设备不需要地址。多点时必须先知道每个传感器的 64 位 ROM 码然后在读取温度前发匹配 ROM 命令指定要操作的传感器。获取 ROM 码的常见做法是先把某一个传感器单独接到总线上运行下面的读 ROM 程序把读到的地址记下来。// 读取当前总线上唯一传感器的ROM码通过串口打印 void dump_rom(void) { unsigned char i; ow_reset(); ow_write_byte(0x33); // 读ROM命令只支持总线上一个设备 for (i 0; i 8; i) { uart_send_byte(read_byte()); } }这里的read_byte()是从ow_read_bit()组装出来的连续读 8 位得到一个字节。完整实现是这样的。// 写一个字节低位先发 void ow_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { ow_write_bit(dat 0x01); dat 1; } } // 读一个字节低位先收 unsigned char ow_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat | ow_read_bit() i; } return dat; }把每个传感器抄出来的地址存到代码里之后的操作就简单了。我习惯把它们定义成常量表每路对应一行。#define SENSOR_NUM 3 // 依次用 dump_rom() 读出后填入实际地址必须替换 unsigned char code rom_table[SENSOR_NUM][8] { {0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, {0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02} };读某一路上温度时先复位发匹配 ROM 命令 0x55再补发 8 个 ROM 地址字节。匹配成功后就可以启动转换或读暂存器了。4.4 主循环广播转换、逐路读取与显示报警多点的效率优化点是转换命令可以广播不必每路都单独等待 750ms。流程是先复位并广播跳过 ROM 加启动转换命令让所有传感器同时转换一次性延时 750ms然后再逐路匹配 ROM 读取温度。// 启动所有传感器同时转换 void start_all_conversions(void) { ow_reset(); ow_write_byte(0xCC); // 跳过ROM广播给总线上的所有设备 ow_write_byte(0x44); // 启动温度转换 } // 按ROM表读取所有温度 void read_all_temps(float *temps) { unsigned char i, j, tl, th; int raw; start_all_conversions(); delay_ms(750); // 12位分辨率转换时间上限约750ms for (i 0; i SENSOR_NUM; i) { ow_reset(); ow_write_byte(0x55); // 匹配ROM命令 for (j 0; j 8; j) { ow_write_byte(rom_table[i][j]); } ow_write_byte(0xBE); // 读暂存器前两个字节是温度低字节和高字节 tl ow_read_byte(); th ow_read_byte(); raw (th 8) | tl; if (raw 0x0800) { // bit11为符号位负数扩展 raw | 0xF000; } temps[i] raw * 0.0625f; } }负温度处理是这里最容易被忽略的细节。DS18B20 的 12 位温度值是带符号补码0.0625℃ 是分辨率。只读到高位后直接拼接再乘 0.0625会导致负温度变成很大的正数必须先把 bit11 扩展为 16 位有符号数。主循环里做的事就是调用read_all_temps()刷新 LCD然后根据按键切换当前显示通道并判断报警。void main(void) { unsigned char sel 0; float temps[SENSOR_NUM]; LCD_Init(); while (1) { read_all_temps(temps); LCD_Clear(); LCD_ShowString(0, 0, TEMP); LCD_ShowChar(12, 0, 0 sel); LCD_ShowFloat(0, 1, temps[sel]); if (temps[sel] HIGH_LIMIT || temps[sel] LOW_LIMIT) { BEEP 0; // 蜂鸣器低电平触发 } else { BEEP 1; } if (KEY_SEL 0) { delay_ms(20); // 按键去抖 if (KEY_SEL 0) { sel (sel 1) % SENSOR_NUM; } } } }LCD 的ShowFloat需要自己实现浮点转字符串另一个常见做法是直接把温度乘以 10 转成整数再拆位显示。这样既避免了浮点格式化的麻烦显示 123.4 这种格式也足够直观。5. 单总线调不通常见问题排查与避坑这 5 点5.1 读回来的温度全是 85℃现象系统刚上电时三路温度都显示 85℃ 或第一路稳定在 85℃。原因DS18B20 上电后暂存器默认值就是 85℃如果复位命令没被执行成功传感器不会启动转换读出来永远是默认值。常见的触发因素是复位拉低和释放的时序里夹进了中断比如串口中断或定时器中断把 480μs 的低电平截成了不连续的脉冲。解决在ow_reset()、写位、读位整个临界区加上EA 0操作结束后再EA 1。注意转换等待的 750ms 期间可以开中断不能为了省事全程关中断。5.2 总线上明明挂了三个传感器只能识别到一个现象用读 ROM 命令时只打印出一个地址或者read_all_temps()只有第一路正常后面全返回 85℃。原因读 ROM 命令 0x33 只适用于总线上只有一个设备的情况多个设备同时响应时数据线冲突读出来的地址是乱的。解决不要用 0x33 去批量枚举传感器。按 4.3 的方式每次只接一个传感器到总线单独读出 ROM 并记录再全部接入。如果一定想程序自动搜地址需要用 0xF0 搜索 ROM 命令配合冲突位判断代码复杂且容易出错工程上我更推荐先单点枚举再固化 ROM 表。5.3 三路温度正常运行十几分钟后某一路消失现象系统刚上电能读到三路温度过一段时间后其中一路显示 85℃ 或一直保持上一个数值。原因大概率是供电问题。传感器用寄生供电时单总线高电平状态不够稳定总线变长或上拉电阻偏大后个别器件复位失败。解决把 DS18B20 的 VDD 直接接电源正极不依赖数据线供电上拉电阻从 4.7k 降到 2.2k在传感器供电脚旁边加 104 电容。还有一个次要原因连接线使用了过长杜邦线总线上的分布电容把高电平拉软缩短线长或用双绞线可以缓解。5.4 Proteus 仿真正常实物全部读不出来现象仿真里三路温度显示正常烧到真实板子上全部 85℃ 或乱码。原因Proteus 里的延时是理想的而真实单片机的软件延时受晶振频率、时钟模式、编译器优化级别影响。STC89C52 如果被配置成 6T 模式指令速度比 12T 快一倍同样的软件延时函数实际时长变成一半DS18B20 的时序就不达标了。解决用定时器方式重写延时函数或者用逻辑分析仪看 DQ 引脚的波形确认复位低电平时间和写时序是否符合要求。先把底层时序调通再看上层逻辑。5.5 LCD 显示残影、乱码刷新内容叠在一起现象切换通道后屏幕上旧数字没完全擦掉新数字叠在上面或者显示出现整行乱码。原因LCD1602 显示数字时只改了数字所在位置的字符没有清掉上一次显示的其他字符。比如上一路显示 25.3℃切换后变成 8.1℃8 会覆盖 2但剩下两位可能还是 5.3 的残留。解决每次更新前先在这个显示区域写两个空格或者调用一次清屏函数如果只用单行显示切换通道时清屏一次避免屏幕闪烁。另外读写 LCD 前要检测忙标志否则数据写不进去也会花屏。6. 进阶验证温度校准、软件滤波与阈值联动测温系统从能跑到能信差的往往不是代码而是校准。DS18B20 出厂精度是 ±0.5℃三路之间的误差可能到一两度现场直接用会出现各路读数不一致的情况。我的做法是做一次两点校准把三个传感器绑在一起放进冰水混合物记录零度偏差再放进 40℃ 温水中记录第二个偏差然后线性补偿。校准后把每路的修正值存成一个偏移表。float offset_table[SENSOR_NUM] {0.0f, -0.3f, 0.2f}; // 读取后叠加补偿值 for (i 0; i SENSOR_NUM; i) { temps[i] offset_table[i]; }软件滤波也很有必要。单总线数字化传输本身抗干扰能力不差但工业现场的电火花、电机启停还是会瞬间拉偏电平导致个别读数跳变。用简单的滑动平均就能压住这类毛刺。#define FILTER_N 5 float history[FILTER_N]; unsigned char hist_idx; float smooth_value(float new_value) { float avg 0.0f; unsigned char i; history[hist_idx] new_value; hist_idx (hist_idx 1) % FILTER_N; for (i 0; i FILTER_N; i) { avg history[i]; } return avg / FILTER_N; }这个滤波函数每次读入一个新值丢弃一个旧值平均值由最近五次采样决定。要注意初始化时把history数组清零否则开机最初几次平均会带上随机值。阈值联动上我常用的验证方案是三路温度同时判断高低限并且比较各路之间的差值。如果某一路与平均值的差超过 2℃直接判定该探头故障并报警而不是输出一个明显错误的温度。这个方法在冷链温度记录中非常实用能防止一个坏探头把整套系统带偏。最后说一个我自己的教训最早做多点测温时我不做校准三路探头显示相差两度我怀疑单总线时序有问题反复调延时调了一整天。后来才发现是三个探头出厂特性差异。从那以后我每装一块板子都会先做冰水和温水两点校准换探头也会重新跑一遍校准流程再没有为这种问题浪费过时间。多点温度检测的原理图可以画得很漂亮代码也能跑得通但真正能不能用在现场看的就是校准、滤波和异常处理这三件琐碎事。希望帮到你。本文还有配套的精品资源点击获取