简介:基于单片机多点温度检测系统设计资料,面向电子、自动化等专业的课程设计与单片机入门学习者,完整呈现以AT89C51为核心的二路DS18B20温度采集与1602LCD显示方案。压缩包内为1个doc文档,大小约199KB,虽为单文件但结构完整,涵盖绪论、系统设计、硬件模块解析(主控制器/显示/温度采集)、DS18B20内部结构与测温原理、软件设计、系统仿真、设计总结及参考文献,并在文档末尾附有源程序代码,可直接用于方案验证与二次开发。内容包含原理框图、存储器映像、温度数据格式等关键图表,对DS18B20的64位ROM、高速暂存存储器、配置寄存器分辨率设置、温度补码换算等均有详细说明,便于对照数据手册理解单总线通信时序与多点测温实现思路。该资料已有1650人浏览学习,适合毕业设计、课程报告或项目预研时作为整体参考。
1. 多点温度检测:从“一个探头走天下”到看懂每个角落的温度
做温度监测的人,十有八九被同一个问题坑过:现场布置了十几个测点,电脑屏幕上却只有一个温度值,或者各测点的数据相互矛盾,今天这个点偏高,明天那个点偏低,排故排到怀疑人生。我在帮某工厂做设备间温控改造时,也踩过这个坑——最开始时省事,只在门口挂了一个探头,结果设备发热集中区域温度超限了半小时,值班员都没察觉。基于单片机多点温度检测系统设计(含代码与原理图)要解决的就是这件事:用一个单片机、一根总线、一串数字温度传感器,把分散在设备间、仓库、管道或实验室各个角落的温度统一读回来,并给每个测点配上独立的数值与告警。它适合正在做课程设计的高校学生、想给老旧设备加温度监测的现场工程师,也适合准备小批量做智能温控产品的开发者。下面我按自己做过的方案,把原理、电路、代码和坑一条条讲清楚。
2. 先定总线拓扑,再选单片机:多点测温的方案骨架
2.1 为什么是单总线:线最少、距离够、成本最低
多点温度测量的技术方案不止一种。最常见的有模拟量方案(热电阻或热电偶加变送器再进ADC)、I2C总线方案、SPI总线方案,以及1-Wire单总线方案。在真正动手画原理图之前,这个选择会决定整块板的复杂度和后续排故难度,所以得先讲清楚。
模拟量方案测量精度高、抗干扰强,但成本也高:每路都需要一套信号调理和ADC采样前端,线缆要求也高,12个测点就得拉12对线,现场施工量不小。I2C和SPI是板级总线的思路,适合传感器离主控很近的场合,一旦要拉线到几米、几十米外,I2C的地址冲突和SPI的片选信号线膨胀问题就会让工程变复杂。单总线(1-Wire)是Dallas半导体定义的协议,只需要一根数据线,所有传感器并联在这根线上,主机通过时序寻址访问每个设备。它的突出优势是线缆少、成本低、拓扑灵活,现场布线就是一根两芯或三芯线沿着测点走一圈,非常省事。
在具体器件选择上,多点测温基本绕不开DS18B20这个数字温度传感器。它的测温范围是-55℃到+125℃,在-10℃到+85℃区间精度标称±0.5℃,12位分辨率下温度分辨率为0.0625℃。更关键的是,每个DS18B20出厂时都烧录了一个64位的全球唯一序列号(ROM码),主机通过这个码就能区分总线上不同的传感器。一条总线上理论上最多可以挂几十个DS18B20,工程上我一般控制在15到20个以内,这样时序余量和供电余量都更充裕。
所以选型的结论是:测点分散、线缆距离在几米到几十米以内、刷新周期只要几秒到十几秒的场合,单总线方案在物料成本、施工难度和代码复杂度上都是最优解。如果你的测点超过30个、距离超过50米,或者现场有大功率变频器等强干扰源,我才会考虑分区域拉多条总线,或者换模拟量方案——后者是另一个话题,这里不展开。
2.2 主机选型:不是所有单片机都适合做1-Wire时序
多点测温系统对主控的要求,和普通的“读个传感器”项目不太一样。核心瓶颈在于1-Wire协议的时序是以微秒计的:复位脉冲480μs,写时隙60μs,读时隙60μs,采样窗口15μs。这要求单片机在干等时序期间不能被中断频繁打断,IO翻转速度也要够快。所以选单片机时我主要看三个指标:主频是否够高、定时器资源是否充裕、IO是否支持开漏模式。
我在这类项目里基本在两个方向上选:一个是经典的增强型51内核单片机(比如国内某系列),主频能到24MHz以上,指令周期快,开发环境成熟,非常适合作课程设计和样机验证;另一个是ARM Cortex-M0/M3内核的国产单片机(比如某系列),主频48~72MHz,定时器和串口资源丰富,适合后续要加显示屏、按键、联网模块或者RS485通信的场景。两个方向我都实际用过,这里给一个选型对比表:
| 选型维度 | 增强型51 | ARM Cortex-M0/M3 | 选择依据 |
|---|---|---|---|
| 主频 | 24~40MHz | 48~72MHz | 1-Wire时序要求微秒级精确,主频越高余量越大 |
| 定时器数量 | 3~5个 | 5~8个 | 一路做轮询调度,一路做看门狗,一路做串口波特率生成 |
| Flash/RAM | 8~64KB / 1~4KB | 64~128KB / 8~20KB | 决定能否缓存全部测点历史数据 |
| 开漏IO | 部分支持 | 普遍支持 | 1-Wire总线是线与结构,必须开漏输出 |
| 开发成本 | 低,直接写寄存器 | 中等,可用寄存器或库 | 课设和验证选前者,量产选后者 |
我个人的习惯是:课程设计、毕业设计、样机验证,用增强型51就足够了,几块钱一片,下载调试方便;如果这个系统将来要作为产品交付,带RS485、离线记录、蓝牙配网,那就直接上Cortex-M0,别在51上硬堆外设,后面维护成本会翻倍。
还有一条硬件前提必须强调:单片机的IO要配置成开漏模式(准双向口)。因为1-Wire总线是线与结构,主机拉低表示写0,释放总线靠外接上拉电阻恢复高电平。如果IO配置成推挽输出,传感器应答拉低总线时,主机还在持续输出高电平,轻则通信失败,重则长时间灌电流损坏端口。这个细节我在原理图阶段就会标注好,代码里也会在初始化函数里显式配置。
3. 原理图设计:传感器接口和电源电路直接决定测量结果
3.1 传感器接口电路:上拉电阻、ESD保护和连接器的取值
原理图是整个系统最先出问题的地方,而且很多问题不是逻辑错误,是硬件本底噪声。多点测温系统的原理图核心是一根数据线的接口电路。我先把最小电路连接方式描述出来,照着连就行:数据线(DQ)从单片机开漏IO引出,接一个上拉电阻到电源(3.3V或5V),然后串联一个100Ω电阻,再接一个ESD保护二极管到地和电源,最后到接线端子。每个DS18B20的DQ引脚直接并联在这根总线上,VDD和GND从端子单独引出。
这个电路里三个器件各有讲究。首先是上拉电阻的取值,为什么默认4.7kΩ而不是1kΩ或10kΩ?因为1-Wire总线的数据传输靠总线电容和上拉电阻构成RC充放电回路。上拉电阻越大,总线恢复高电平越慢,时序窗口越危险;上拉电阻太小,长线反射和过冲都会变大。我的经验是:短距离(小于2米)用4.7kΩ最稳妥;中距离(2到10米)改成2.2kΩ;超过10米先用1kΩ试,同时软件里把通信速率降低——具体做法是拉长时隙延时,后面代码部分会讲。
串联的100Ω电阻很多人会漏掉。它的作用有两个:一是限制传感器引脚上的瞬态电流,尤其热插拔的时候;二是和总线分布电容一起形成低通滤波,滤掉高频干扰。如果现场有变频器或继电器这类干扰源,这个100Ω电阻还可以换成磁珠,高频抑制效果更好。ESD保护二极管选双向或单向都行,注意结电容要小于50pF,否则会把总线时序拖垮。
连接器这里,我不建议用2.54mm排针了事。多点测温要拉线出去,现场施工可能反复插拔,接线端子至少选3.81mm间距的螺钉端子,保证牢靠。如果传感器分布范围很大,建议在每个测点的端子旁加一个100pF电容到地,这个电容能有效抑制总线反射——很多入门设计都不会放它,但长线场景它比多加一个上拉电阻更管用。
3.2 电源电路:寄生供电还是外部供电,以及压降怎么算
原理图阶段第二个大坑是供电方式。DS18B20支持两种供电模式:寄生供电(两根线:DQ+GND)和外部供电(三根线:VDD+DQ+GND)。寄生供电的好处是少一根线,但前提是主机在温度转换期间要给总线提供足够强的上拉,否则转换到一半,传感器内部电容掉电,转换结果直接乱掉,表现为“读出来是85℃”或“偶尔读出错误值”。我明确建议:所有超过3个测点、线长超过2米的多点系统,一律用外部供电,三根线,别省这一根。
外部供电电路上还有两个细节。第一,每个DS18B20的VDD和GND之间需要就近放一个100nF的去耦电容。原理图上只有三个引脚,很多人就省了。但实际上总线上几十米线缆的分布电感很大,传感器转换时内部电流突变,会在VDD上产生毛刺,这个毛刺会耦合到DQ线上,导致读取数据偶尔错位。加个100nF电容,毛刺直接被吸收掉。
第二是电源线的压降计算。我举个例子:24AWG线大约0.2平方毫米,直流电阻约每米0.08Ω;一个DS18B20正常工作时电流约1.5mA,转换期间约1mA。如果挂16个测点、总线长度20米,电源回路的长度要算40米(去+回),总电阻约3.2Ω。16个传感器同时转换时总电流约24mA,压降约3.2Ω×24mA=77mV。这个值看着不大,但如果线更细、测点更多,或者供电电压本身就是3.3V,再叠加母线上的纹波,传感器供电就已经在临界边缘了。所以我的原则是:多点系统的电源不要从单片机板上的3.3V取,单独用一路5V稳压,每个传感器端的电压落在3.0~5.5V范围内。如果距离长,就把供电线加粗到18AWG,压降能降一个数量级。
原理图画到这个程度,硬件部分的方案就定型了。接下来进入代码环节——先跑通单总线时序,再做ROM扫描和轮询调度。
4. 代码实现:单总线时序、ROM扫描与轮询调度
4.1 最小驱动:复位、读时隙、写时隙的C代码
硬件板子回来以后,第一步是用一个最小工程验证总线能通。我给出一段基于增强型51内核单片机的C代码,逻辑本身在普通51和ARM Cortex-M0上通用。这段代码能实现1-Wire最底层的三个动作:复位、写一位、读一位。
#include "stc8.h" // 替换为你实际使用的单片机头文件 #include <intrins.h> #define DQ_PIN P3_2 // 数据线接到P3.2 // 微秒级延时,按主频24MHz调整 void delay_us(unsigned short us) { while (us--) { _nop_(); _nop_(); _nop_(); } } // 总线引脚模式切换:开漏输出/高阻输入 void DQ_OUT(void) { P3M1 &= ~(1 << 2); P3M2 &= ~(1 << 2); // 设置为准双向口(开漏) } void DQ_IN(void) { P3M1 |= (1 << 2); P3M2 &= ~(1 << 2); // 设置为高阻输入 } // 复位:主机拉低480us以上,释放后检测存在脉冲 unsigned char ds18b20_reset(void) { unsigned char presence = 0; DQ_OUT(); DQ_PIN = 0; delay_us(500); // 复位脉冲,至少480us DQ_PIN = 1; // 释放总线 delay_us(20); // 等待传感器应答窗口 DQ_IN(); presence = DQ_PIN; // 存在脉冲时读到0 delay_us(60); // 读完后延时,补足复位时序 DQ_OUT(); return (presence == 0) ? 1 : 0; } // 写一位:主机拉低,根据bit决定何时释放 void ds18b20_write_bit(unsigned char bit) { DQ_OUT(); DQ_PIN = 0; if (bit) { delay_us(6); // 写1:短拉低后释放 DQ_PIN = 1; delay_us(60); // 保持整个时隙 } else { delay_us(60); // 写0:全程拉低 DQ_PIN = 1; delay_us(6); } } // 读一位:主机拉低1us后释放,在采样窗口读取 unsigned char ds18b20_read_bit(void) { unsigned char bit = 0; DQ_OUT(); DQ_PIN = 0; _nop_(); // 约1us DQ_PIN = 1; DQ_IN(); _nop_(); // 约1us后采样 bit = DQ_PIN; delay_us(60); // 读完整个时隙 DQ_OUT(); return bit; }这段代码里几个关键参数要按实际主频调整。delay_us(500)对应复位脉冲至少480μs,留了余量;delay_us(20)是释放总线后等待传感器拉低总线的时间,DS18B20的存在脉冲在复位脉冲结束后15~60μs内出现,取20μs比较居中;写1时隙拉低6μs后释放,写0时隙全程拉低60μs——这些时序窗口和器件手册是对得上的。编写时最好把整个时序放在关中断的临界区里,防止定时器中断插入后把微秒级时序拉长,这一点后面避坑章节还会细讲。
把上述代码编译烧录后,在main函数里循环调用ds18b20_reset(),正常接好总线会返回1。如果返回0,大概率是硬件问题:上拉电阻没焊、传感器极性接反、或者IO没配置成开漏模式。先解决这个,后面什么都好说。
4.2 ROM搜索算法与多点温度轮询
单总线上每个DS18B20的ROM码都不同,主机要读某个传感器的温度,得先知道它完整的64位ROM码。最可靠的方式是器件手册里的ROM搜索算法:对每一位读两次,判断该位是0还1,同时用状态记录冲突分支,通过多轮遍历枚举出全部设备。这个算法代码量不大,但状态变量要理清楚。我给一个常见的实现骨架:
#define MAX_DEVICES 16 unsigned char rom_codes[MAX_DEVICES][8]; // 存储每个设备的64位ROM unsigned char device_count = 0; // 搜索总线上所有设备,返回设备数量 unsigned char ds18b20_search_all(void) { unsigned char last_discrepancy = 0; unsigned char done = 0; unsigned char branch_point = 0; unsigned char dev_idx = 0; unsigned char bit_idx = 0; unsigned char byte_idx = 0; while (!done && dev_idx < MAX_DEVICES) { ds18b20_reset(); ds18b20_write_byte(0xF0); // SEARCH ROM命令 branch_point = 0; for (bit_idx = 0; bit_idx < 64; bit_idx++) { unsigned char bit_a = ds18b20_read_bit(); unsigned char bit_b = ds18b20_read_bit(); unsigned char direction = 0; if (bit_a == 0 && bit_b == 0) { // 该位存在冲突:两个设备在此位值不同 if (bit_idx < last_discrepancy) { direction = (rom_codes[dev_idx][bit_idx / 8] >> (bit_idx % 8)) & 1; } else if (bit_idx == last_discrepancy) { direction = 1; } else { direction = 0; branch_point = bit_idx; } } else if (bit_a == 0 && bit_b == 1) { direction = 0; } else if (bit_a == 1 && bit_b == 0) { direction = 1; } else { return 0; // 总线异常,无设备响应 } if (direction) { rom_codes[dev_idx][bit_idx / 8] |= (1 << (bit_idx % 8)); } else { rom_codes[dev_idx][bit_idx / 8] &= ~(1 << (bit_idx % 8)); } ds18b20_write_bit(direction); } // 后8位是CRC校验,通过才登记设备 if (crc8_check(&rom_codes[dev_idx][0], 8)) { dev_idx++; } else { return 0; } last_discrepancy = branch_point; if (last_discrepancy == 0) done = 1; } device_count = dev_idx; return dev_idx; }这个搜索算法的核心是last_discrepancy和branch_point。第一次遍历全部冲突位选0方向,记录最后遇到的冲突位;第二次遍历到相同冲突位时选1方向,这样不断回溯,最终枚举完所有设备。实际使用中要注意:如果总线上某个传感器接触不良,CRC校验会失败,这个时候我建议直接返回0而不是跳过——因为很有可能有设备在总线上处于不稳定状态,先处理硬件再继续。
ROM扫描通过后,多点温度轮询就简单了。流程是:对某个ROM码发匹配命令(0x55),再发启动转换命令(0x44),等待转换完成(12位分辨率约750ms),然后发读暂存器命令(0xBE),把9字节数据读回来做CRC校验和温度换算。下面是读取一个测点的完整代码:
// 读取指定ROM码对应传感器的温度,单位0.0625℃ int read_temp_by_rom(unsigned char *rom) { unsigned char data[9] = {0}; unsigned char crc = 0; unsigned char i; int16_t raw; int temp; ds18b20_reset(); ds18b20_write_byte(0x55); // MATCH ROM for (i = 0; i < 8; i++) { ds18b20_write_byte(rom[i]); } ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 12位分辨率典型转换时间 ds18b20_reset(); ds18b20_write_byte(0x55); // 再次匹配同一ROM for (i = 0; i < 8; i++) { ds18b20_write_byte(rom[i]); } ds18b20_write_byte(0xBE); // 读暂存器 for (i = 0; i < 9; i++) { data[i] = ds18b20_read_byte(); // 读9字节 } crc = crc8_check(data, 8); // 校验前8字节 if (crc != data[8]) { return -1000; // 错误码:CRC校验失败 } raw = (data[1] << 8) | data[0]; // 拼接原始12位数据 if (data[4] & 0x80) { // 符号位判断 raw = (raw & 0x07FF) - 1; // 负温度取补码 temp = (int)(~raw & 0x07FF); temp = temp * 625; temp = -temp; // 负温度,单位0.0625°C } else { temp = raw * 625; // 正温度,单位0.0625°C } return temp; }这里几个细节要说明:delay_ms(750)是12位分辨率下从发转换命令到读取暂存器的最小等待时间,如果改成9位或10位分辨率可以缩短到几百毫秒,但温度分辨率也会变粗,我一般默认12位不动。返回的temp单位是0.0625℃,比如返回1600表示100.0℃。用-1000作为错误返回码,能有效区分真实的0℃读数。data[4]是配置寄存器,第6、5位决定分辨率,读出来可以确认传感器当前精度设置。
轮询调度的主循环建议这样组织:先把所有测点的ROM码遍历一遍,逐个发启动转换命令,全部发完后再统一等待750ms,最后逐个读取结果。这样总耗时从“测点数×750ms”变成“750ms+读取时间”,16个测点能从12秒缩短到3秒以内。这里有个和直觉相反的点:给所有传感器同时发转换命令,比逐个发逐个读更快,而且不会增加总线冲突风险——因为MATCH ROM已经指定了目标设备,其他传感器不会响应。
5. 避坑与排查:多点测温最常见的5个坑
5.1 上电读出来全是85℃,换了好几个传感器都一样
现象:系统一上电,串口打印温度,所有测点全是85.0℃,过几秒也不变化,换传感器也没用。
原因:85℃是DS18B20上电后暂存器里的默认值。寄存器复位值为0x0550,换算出来就是85.0℃。这其实不是故障,而是主机在传感器还没有完成第一次温度转换前就把暂存器读了出来。常见于代码里发完0x44后没有延时,或者延时时间不够,又或者上电后第一条命令就是读温度而没发转换命令。
解决:保证每次读温度前都按“复位→匹配ROM→0x44→延时750ms→复位→匹配ROM→0xBE”的顺序执行。另外检查上电后第一次复位是否稳定,如果数据线上电平不稳,可以在上电后加一个200ms延时再执行第一次复位。至少等一轮转换周期,别在while循环里连续读。
5.2 总线只挂了12个点,却有3个测点温度跳动
现象:系统跑一段时间后,个别测点的温度值乱跳,用示波器看数据线发现波形边缘有很多毛刺,重新插拔一下传感器能好一会儿。
原因:大概率是传感器用了寄生供电,或者外部供电但线缆过长导致转换期间供电电压跌落。跳动通常发生在温度转换发起的那一刻——传感器内部对电容充电,电流突变耦合到DQ线上,把时序波形搞乱了。
解决:改成外部供电,每个传感器端加100nF去耦电容。如果已经是外部供电,就把电源线加粗或提高供电电压到5V。软件上也可以加滤波,连续读三次取中位数,但这是治标,别把软件滤波当成硬件问题的后悔药。
5.3 同一个ROM码在总线上出现了两次
现象:更新固件后重新扫描总线,发现设备数比实际挂载数量少,枚举结果里有重复的ROM码。
原因:一些成本更低的兼容传感器芯片,ROM码并不是严格全球唯一,不良品可能重复。也可能是用了拆机件,内部EEPROM被改过。
解决:第一次扫描时除了做CRC校验,还要做ROM码去重。如果发现重复,只能换传感器,没有软件解法。采购时尽量选正规货源,收到货先全量扫描一遍,把重复的挑出来,这一步能省后面大量排查时间。
5.4 加了定时器中断后,所有测点都读不到了
现象:代码加了按键扫描定时中断或串口接收中断后,1-Wire通信立即失效,去掉中断就正常。
原因:1-Wire时序对微秒级窗口敏感,定时器中断服务函数可能占用几十上百微秒,正好把写时隙或读时隙打断,时序错乱。
解决:把ds18b20_reset()、read_bit、write_bit这几个底层函数放在关中断临界区内,进入轮询时序前关中断,完成后开中断。注意临界区要覆盖整个时序操作,而不是只保护单片命令。如果中断来源很重要,比如串口通信,就在中断里置标志位、缓冲数据,等总线操作完再处理。
5.5 改了上拉电阻后通信反而更差
现象:把4.7kΩ上拉换成1kΩ后,短距离出现偶发通信失败,示波器看到波形过冲。
原因:上拉电阻减小,总线恢复速度快了,但阻抗降低导致反射和过冲增大,1-Wire的输入阈值可能被高频振铃穿破。特别是在线缆分布电容较大的情况下,RC网络的品质因数变了。
解决:中等距离优先改2.2kΩ,只有超过10米才降到1kΩ,同时软件里把时隙延时长一点。如果还是不行,在总线末端加一个100pF电容到地吸收反射。改完一定要用示波器看总线波形,确认上升沿过冲不超过电源电压20%再继续。
6. 进阶:CRC校验、掉线标记与上下位机联调
多点测温系统走到能稳定读数,只是第一步。我一般在交付前还会加一层可靠性和可维护性的设计,这个环节直接决定这套系统值不值得长期用。
第一个进阶项是给每个测点加上掉线标记。读取温度时如果连续三次CRC校验失败,就把该点标记为“掉线”,在显示界面上用特殊符号提示,而不是显示一个错乱的温度值。这一步成本很低但价值很大:现场维护的人一眼就能看出哪个探头坏了,不用拿万用表逐点量。实现方式就是在读温度函数的错误码处做计数器累积,连续失败才确认掉线,避免瞬时干扰误报。
第二个进阶项是把“启动转换”和“读取结果”拆成两个阶段。前面提过,不要在read_temp_by_rom函数里又发转换又读结果,而是先给所有测点统一发0x44启动转换,然后统一等待750ms,再逐个读取暂存器。这样可以让单片机在750ms的转换窗口期内干别的事情,比如刷新OLED、处理按键、发送状态帧。我实测过,16个测点的主循环周期从12秒缩短到3秒以内,系统的实时感完全不一样。
第三个进阶项是上下位机的通信协议设计。如果系统要接触摸屏或上位机,建议在串口输出里用固定帧格式:帧头0xA5、功能码、测点数、每个测点的温度值高字节和低字节、状态字节、最后两个字节CRC16校验。温度值放大10倍传输,比如25.4℃发254,避开浮点数,51单片机上省内存,上位机也省解析时间。联调时先用串口助手看裸数据,确认帧格式对,再对接上位机,不要上来就灌给协议库,那样排查效率太低。
最后分享一个我自己的习惯:每次拿到一批传感器,第一件事不是直接焊到系统板上,而是用一块万能板搭最小测试电路,把整批传感器逐个扫描ROM、连续读三遍温度,把数据记录下来再上系统。这一步能筛掉绝大多数体质差的器件,也顺便验证了采购批次的一致性。吃过一次亏以后就把这个流程固定下来了——那批传感器里有三个ROM码重复,上系统后怎么排查都少一个点,最后逐个扫ROM才发现是器件本身的问题。从那以后,每次新批次传感器都先过一遍这个测试流程,基本没再遇到过因器件本体导致的大型排查事故。希望帮到你。
本文还有配套的精品资源,点击获取