☰
DS18B20单总线多点温度检测:原理、电路与调试
2026/10/15 18:40:43 网站建设 项目流程

简介:一份基于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.3V
DS18B20 连接普通 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,模式1,16位 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℃,直接判定该探头故障并报警,而不是输出一个明显错误的温度。这个方法在冷链温度记录中非常实用,能防止一个坏探头把整套系统带偏。

最后说一个我自己的教训:最早做多点测温时,我不做校准,三路探头显示相差两度,我怀疑单总线时序有问题,反复调延时调了一整天。后来才发现是三个探头出厂特性差异。从那以后,我每装一块板子都会先做冰水和温水两点校准,换探头也会重新跑一遍校准流程,再没有为这种问题浪费过时间。多点温度检测的原理图可以画得很漂亮,代码也能跑得通,但真正能不能用在现场,看的就是校准、滤波和异常处理这三件琐碎事。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询