资讯详情

DS18B20单总线测温与数码管动态显示:单片机实战全解

📅 2026/10/16 1:06:09 | 华诺云谱 👁 阅读
DS18B20单总线测温与数码管动态显示:单片机实战全解
搞单片机开发的人十有八九会碰这个组合DS18B20温度传感器把温度读出来再用数码管显示。看起来特别基础但我在实际带项目调试时发现越基础的项目越容易翻车——时序差几个微秒就读不到存在脉冲数码管扫描频率不够拍出来的照片全是数值重影更别提负数温度的补码换算很多人第一次做随便赋值最后显示一个刺眼的负号或者乱码。这个项目的本质是“单总线通信 时序控制 动态显示”三件事的集合。适合两类人来参考一是刚学完C语言、想做一个完整单片机项目的入门开发者把外设驱动、显示扫描、主循环调度一次打通二是做产品预研的工程师用这个方案快速验证温度采集和显示逻辑后面扩展成多路采集、继电器联动都不难。我下面按“方案设计 → 硬件电路 → 代码实现 → 调试排查”的顺序把我在实际调试中踩过的坑、验证过的参数都整理出来尽量让读者照着做就能跑通。1. DS18B20项目设计思路先想清楚这几件事1.1 需求拆解核心指标其实是两个这个需求看起来只有一句话其实隐含了两个核心指标。第一个是测温范围与精度。DS18B20能在-55℃到125℃内工作在-10℃到85℃之间的典型精度是±0.5℃。对大多数环境监测、设备热保护场景来说这个精度完全够用。默认12位分辨率下温度台阶是0.0625℃读数本身足够细腻真正要留意的是你准备用几位小数去显示。第二个是显示实时性。温度变化是缓慢信号不需要毫秒级刷新但数码管必须用不低于50Hz的频率刷新否则人眼会看到闪烁。这两个时间尺度天然矛盾——传感器转换一次温度要750ms而数码管每位点亮只能维持几毫秒。处理不好就会出现屏幕闪得厉害、温度却半天不变的效果。所以这个项目的工程量不在“读温度”这个动作本身而在把单总线协议吃透以及把数码管刷得既稳定又均匀。1.2 为什么选DS18B20而不是别的传感器现在测温方案很多热电偶、热敏电阻、I2C接口的数字传感器一大把。DS18B20能成为入门项目的“标准答案”核心有三点。其一单总线通信最少只占一个IO口。对比I2C需要两根线、SPI需要四根线单总线把时钟和数据合成一根线对引脚紧张的51单片机非常友好省下来的IO可以做按键、控制继电器。其二数字输出没有模拟信号处理的麻烦。热敏电阻要配分压电路、运放和ADC还得做非线性校准DS18B20直接把数字结果吐出来换算公式也很简单不依赖基准电压稳定性。其三单总线协议本身就值得学。你在项目里学会的高低电平时序、复位应答、读写时序后面做单总线协议的其他器件都能复用。这也是我坚持建议不要用现成模块封装板、而是自己搭一次电路的原因——网上很多DS18B20一体模块内置了上拉电阻和滤波电容插上就能用方便归方便但把底层时序藏起来之后出了问题很难排查。1.3 整体架构数据是怎么流动的整个系统的数据流向非常清晰DS18B20 → 单总线读取温度寄存器 → 换算成十进制温度值 → 拆分成十位/个位/小数位数字 → 查找段码表 → 动态扫描刷新数码管主控我用51系列芯片来做示例晶振常见11.0592MHz或12MHz两者都可以但延时函数必须按实际晶振重新核算。如果只是照搬网上的代码时序差一点温度数据就可能随机跳变。显示方案有两种选择一是用带驱动芯片的数码管模块内部集成扫描逻辑代码写起来很轻松但学不到底层二是用普通共阴/共阳数码管IO直接驱动自己写动态扫描。我强烈建议用第二种因为动态扫描是单片机开发绕不开的核心技能后面做点阵屏、LED灯带控制都能用上。2. 硬件电路设计与接线新手最爱在这里翻车2.1 器件清单与选型要点先把器件列出来这个清单基本就是标准答案。器件型号/规格数量作用温度传感器DS18B20TO-92封装1采集温度输出数字量主控芯片STC89C52或AT89C511运行单总线协议、驱动显示数码管4位共阴0.56英寸或0.5英寸1显示温度值上拉电阻4.7kΩ1单总线空闲时拉高电平限流电阻220Ω或330Ω8数码管段电流限制滤波电容104即0.1μF2DS18B20电源与主控电源滤波其他面包板/洞洞板、杜邦线若干搭建电路选型时最需要提醒的是数码管共阴还是共阳直接影响段码表。共阴的公共端接GND要给某段高电平才会亮共阳正好反过来公共端接VCC要给低电平才会亮。新手最常犯的错就是硬件选了共阳代码却用共阴的段码表结果数字显示得乱七八糟。DS18B20有三种常见封装TO-92、SOIC-8和MSOP-8用得最多的是TO-92外形和三极管一模一样。引脚从左到右分别是GND、DQ、VDD注意不是两端的GND和VDDDQ在中间。我见过有人把DQ和VDD接反折腾一晚上都读不出来最后发现是引脚顺序搞错。2.2 上拉电阻取值为什么一定是4.7kΩ单总线协议要求总线空闲时必须保持高电平但DS18B20的输出级是开漏结构它自己只能把总线拉低不能主动输出高电平。所以必须在DQ线上接一个上拉电阻到VCC否则总线浮空读出来的数据大概率是随机值。阻值选取要平衡两个因素。阻值太小总线被拉低时电流偏大。按5V供电、4.7kΩ上拉计算拉低时电流大约是1mA功耗很低如果换1kΩ电流到5mA虽然一时半会儿不会烧但会给开漏输出级增加负担时序长线线上压降也可能偏离预期。阻值太大上升沿变慢。单总线读时序要求主机释放总线后在15微秒内完成采样如果上拉电阻超过10kΩ再加上导线分布电容和芯片输入电容上升沿就会变缓采样时可能还没来得及升到高电平就被误判成0。工程上4.7kΩ是黄金值3.3V和5V系统都能直接配。如果DQ线需要拉得很长比如超过1米可以考虑降到2.7kΩ但单总线本来就不适合长距离传输真要做长线采集应该走RS485之类的差分方案而不应该硬扛单总线。2.3 数码管驱动电路设计四位数码管有静态驱动和动态扫描两种接法。静态驱动是每个数码管独立占一组IO显示稳定、代码简单但4位数码管要占32个IO对51单片机来说太奢侈通常要加译码器或移位寄存器才能玩。动态扫描把所有数码管的段选线并联在一起只把位选线分开一次只点亮一位靠快速轮换在视觉上形成“同时亮”的效果。IO占用只有8根段选线加4根位选线一共12个IO这也是我这次采用的方式。限流电阻的放置位置有个细节值得强调放在段选线上而不是位选线上。因为动态扫描时任何时候只有一个位被点亮同一时刻流过某一段的电流就是单个LED的电流如果把限流电阻放在位选线上该位同时点亮多段时总电流就是多个LED电流之和对单片机IO口压力很大。限流电阻的取值按这个公式估算R (VCC - LED压降) / LED工作电流LED压降一般按1.8V到2.0V算5V供电下想让工作电流在8mA左右电阻取330Ω或390Ω都很常见。如果发现亮度不够优先考虑提高每位点亮时长或者换高亮数码管不要盲目减小限流电阻否则LED老化快、功耗也难看。3. 代码实现DS18B20读温度、数码管显示全流程3.1 单总线时序基础复位、写、读DS18B20所有操作都靠一根IO上的时序组合完成基础操作只有四个复位、写0、写1、读位。先看复位。主机把总线拉低480到960微秒然后释放。DS18B20检测到这个下降沿后会在15到60微秒内把总线拉低60到240微秒作为存在脉冲。主机释放总线后等大约60到70微秒再去检测电平读到低电平说明传感器在线。这个过程也叫复位应答相当于总线上的“握手”。写时序是整个协议里最容易理解错的地方。想写0就把总线拉低并保持整个时隙一般60微秒期间不释放想写1就把总线拉低1到15微秒后立刻释放让上拉电阻把总线带回高电平。关键区别不是拉低时间长短而是“拉低之后是否释放总线”很多人把这两个动作搞反写出来的数据就反了。读时序对延时精度最敏感。主机先拉低总线1到15微秒然后释放在释放后的15微秒窗口内采样总线电平。传感器想传0就会在这段时间内继续拉低总线想传1总线被上拉电阻保持高电平。这个15微秒的采样窗口非常窄延时稍差就会读到错值。3.2 DS18B20驱动代码四个基础函数我用STC89C52的C51环境做示例DQ接P3.7。先定义一个IO口sbit DQ P3^7; // DS18B20数据线然后是基础延时函数。这里必须注意不同的晶振、不同的编译优化等级一个空循环的实际耗时差别很大最好用示波器实测校准。下面这个版本面向12MHz晶振写void DelayUs(unsigned int us) { while (us--) { _nop_(); _nop_(); _nop_(); _nop_(); } }复位与存在检测bit DS18B20_Reset(void) { bit presence; DQ 0; DelayUs(480); // 拉低至少480us DQ 1; // 释放总线等待存在脉冲 DelayUs(70); // 等传感器应答 presence DQ; // 读到0表示存在 DelayUs(250); // 把剩余时序走完 return presence; }写字节void DS18B20_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; // 每个时隙先拉低 if (dat 0x01) { DQ 1; // 写1短暂拉低后释放 DelayUs(60); } else { DelayUs(60); // 写0全程保持低 } DQ 1; DelayUs(1); // 时隙间隔 dat 1; } }读字节unsigned char DS18B20_ReadByte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { DQ 0; _nop_(); // 拉低约1us DQ 1; // 释放总线 _nop_(); // 约1us后采样 dat 1; if (DQ) { dat | 0x80; } DelayUs(60); // 把这个时隙走完 } return dat; }这四段代码是整套系统的地基。写完先别急着接数码管直接用一个调试串口或者LED验证读取到的原始字节确认能跑通再往下走。3.3 温度采集与数值换算DS18B20的读温流程分两步先启动温度转换再读取暂存器。启动转换的命令序列DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM单设备时用 DS18B20_WriteByte(0x44); // 启动温度转换0xCC是跳过ROM指令意思是“不比对64位序列号直接对总线上所有传感器操作”。只有单设备时可以这样偷懒如果总线上挂了多个DS18B20必须用0x55匹配ROM指令先读出每个传感器的64位ROM序列号再逐个点名。这个扩展后面单独说。转换完成后读取暂存器unsigned char low, high; int raw; float temp; DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器9个字节 low DS18B20_ReadByte(); // 温度低字节 high DS18B20_ReadByte(); // 温度高字节温度换算的核心逻辑在这里raw (high 8) | low; // 12位分辨率下低4位是小数位 // 判断负数高字节的高5位全是1 if (raw 0x8000) { raw ~raw 1; // 取补码的绝对值 temp raw / 16.0; return -temp; // 返回负温度 } return raw / 16.0;默认12位分辨率时原始值除以16就是摄氏温度。比如读取到0x0191也就是十进制401除以16等于25.0625℃和理论一致。负温度时情况特殊比如-25℃对应的原始值是0xFFE7如果不管符号位直接除以16会得到一千多度这就是负数补码没有处理的典型错误。3.4 温度值拆成数码管的显示数字假设我们显示到小数点后一位格式是“XX.X”4位数码管刚好够用。遇到负温度我习惯把最高位用来显示负号剩下三位显示整数和小数。比如-12.3℃数码管显示“-12.3”。拆数字的方法很多我推荐用放大十倍的技巧避开浮点数误差unsigned char c, d0, d1, d2, d3; int v; d0 ; // 最高位可能是符号位 if (temp 0) { v (int)(-temp * 10); d0 0x40; // 段码表中负号对应的段 } else { v (int)(temp * 10); } d1 v / 100; // 十位 d2 v % 100 / 10; // 个位 d3 v % 10; // 小数位 // 存入显示缓冲区后续由动态扫描读取 displayBuf[0] d0; displayBuf[1] d1; displayBuf[2] d2; displayBuf[3] d3;这里displayBuf是全局数组动态扫描函数会不断访问它。用整数放大计算能避免浮点数四舍五入的不一致问题实测更稳定。共阴数码管的段码表0到9和一个负号放在Flash里unsigned char code segTable[] { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x40 // 负号 - };如果是共阳数码管需要把每个段码按位取反比如0显示0xC0而不是0x3F。这一点在代码里写死之前一定要先确认硬件接的是共阴还是共阳反了就是满屏乱码。3.5 动态扫描消隐和刷新率是关键我用P0做段选线、P2低4位做位选线来示例。假设位选是低电平有效也就是选中某位时对应IO输出0void DisplayScan(void) { unsigned char pos; for (pos 0; pos 4; pos) { P0 0x00; // 第一件事消隐清除上一段的段码 P2 ~(0x01 pos); // 第二件事切换位选选中当前位 P0 segTable[displayBuf[pos]]; if (pos 3) { P0 | 0x80; // 如果第3位是小数字点亮小数点 } DelayMs(2); // 每位点亮约2ms } P0 0x00; // 扫描结束前再消隐一次防止最后一位有拖影 }消隐的顺序非常关键。必须先关段选再切位选。如果先切位选、后改段选前一位会在最后几个毫秒里显示出下一位的段码视觉上就是一层明显的重影。刷新率方面4位数码管整体刷新周期最好控制在20ms以内即刷新率大于50Hz。每位点亮时间等于总周期除以位数按2ms算总周期8ms刷新率125Hz亮度和稳定性都不错。如果每位点亮时间太长比如超过5ms人眼就能看出闪烁太短LED平均电流不够亮度明显下降。这是一个需要在实际硬件上微调平衡的参数。3.6 主循环整合先解决转换时间和显示刷新冲突温度转换要750ms动态扫描每位只能亮2ms这两个时间尺度天然冲突。如果简单地在主循环里先Delay 750ms再读温度然后扫描显示显示会一卡一卡观感很差。正确做法是“先启动转换过段时间再来读”。把“启动转换”和“读取结果”拆成两步期间持续刷新数码管。伪代码如下void main(void) { unsigned char tCnt 0; float currentTemp 0; bit converting 0; while (1) { if (!converting) { DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0x44); converting 1; } if (converting tCnt 20) { DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); low DS18B20_ReadByte(); high DS18B20_ReadByte(); currentTemp CalcTemp(low, high); UpdateDisplayBuf(currentTemp); converting 0; tCnt 0; } DisplayScan(); // 核心显示扫描不能停 tCnt; DelayMs(10); // 粗略定时 } }注意一点在转换期间绝对不要在DQ线上做任何写操作否则会中断转换过程。最好的状态是主循环里唯一干预DQ的地方就是固定的启动转换和读取数据两个时间点。tCnt的定时只是个粗糙方案更规范的做法是开一个定时器中断做时间节拍主循环只做状态切换和显示扫描。但只要把握住“转换期间不打扰、显示不能停”这两个原则这个基础轮询版本就足够稳定了。4. 调试实战我踩过的坑和排查技巧4.1 高频问题速查表把这些年做这个项目见过的问题整理成一张表照着排查快得多。现象可能原因排查方法数码管一个数字都不亮公共端接错、共阴共阳选反万用表二极管档测公共端和段位之间的压降数字乱码、算子不像段码表方向错、位选极性反单独测试一位数码管逐段点亮验证稳定显示85.0℃只读了上电默认值没跑转换检查是否执行0x44命令并等待750ms温度值乱跳读时序采样点偏晚或上拉电阻缺失用示波器看DQ波形确认采样窗口显示-0.6℃这种怪值负数补码没处理检查MSB高5位符号判断逻辑数码管闪烁明显刷新率低于50Hz缩短每位的延时或增加总扫描频率只有首个温度对后面一直不变0xCC后没重新复位或状态机卡死每次读写前后都执行一次复位4.2 避坑一上电默认值85℃第一次接上DS18B20数码管显示85.0℃这是所有人都会遇到的现象。别慌不是传感器坏了而是DS18B20上电时暂存器里存放的就是85℃这个默认值。只有正确执行了0x44转换命令并等待转换结束后再读才会拿到真实温度。很多人栽在“上电后只读一次”的写法上。只要程序里没有启动转换的步骤或者启动转换后马上读取没有等完750ms读到的就还是旧值。解决办法就是严格按照“复位→0xCC→0x44→等待→复位→0xCC→0xBE→读取”这个顺序来一个都不能少。4.3 避坑二负数温度的补码处理如果只是做室内温度计可能永远接触不到负温度。但消费电子、冷链设备、北方户外设备冬天零下是常态。负数不处理显示效果比乱码还离谱。一个常见错误是只判断最高位然后用raw/16得到一个大正数。正确的补码处理是先判断符号位负数时按位取反再加1得到绝对值除以16最后在显示端加负号。我在代码里给的是完整的负数分支建议保留下来别在调试时为了省事删掉。4.4 避坑三上拉电阻缺失和长线问题面包板自己搭电路时最容易漏的就是DQ线上的4.7kΩ上拉电阻。开发板通常在MCU侧已经集成好上拉但面包板没有漏接后总线浮空读出来的数据要么全1要么全0完全没有规律。这个问题接上电阻就能解决不必改代码。如果线太长即使接了上拉也可能因为分布电容导致上升沿过慢。这时有两种处理思路一是把采样点提前到释放总线后5到8微秒避开边缘二是把上拉降到3.3kΩ加快上升沿。但根本上单总线不适合超过1米的布线需要远距离采集时果断换方案。4.5 时序调试心得没有示波器怎么调时序问题最难排查因为肉眼看不到。我建议有条件尽量用逻辑分析仪看DQ引脚波形但如果没有也有土办法。在DS18B20_Reset函数里加一个标志位每次成功检测到存在脉冲就翻转一个LED。手握住传感器让温度升高观察LED是否保持固定频率闪烁。如果程序卡死在某个读周期LED的闪烁节奏会突然停止或不规律这时就把串口打印打开逐字节输出读到的暂存器内容定位是哪个环节出错。在读时序的采样点选择上我的经验是采样时间越早越好。我实测过把采样点延后到20微秒以上10次里有3次读到错误数据温度跳变毫无规律。后来把采样点控制在释放后的8微秒左右连续读100次基本零错误。这个窗口虽然手册给到15微秒但为了余量越靠近起始端越安全。4.6 分辨率调整与EEPROM的一个隐藏坑DS18B20默认12位精度转换时间最长750ms。如果觉得这个时间太长可以改成9位分辨率转换时间缩短到93.75ms但是分辨率降到0.5℃。写配置寄存器的命令序列是复位→0xCC→0x4E→写TH→写TL→写配置字。配置字的Bit6和Bit5控制分辨率00对应9位01对应10位10对应11位11对应12位。改完后可以执行0x48命令把配置复制到EEPROM这样断电后也会保留。这里有个隐藏坑如果改分辨率时没写EEPROM系统一旦断电芯片上电后又恢复12位默认配置你可能会疑惑“明明改了怎么又回去了”。所以改完配置后建议重新掉电再上电验证一次确认真正生效。4.7 多点测温的扩展思路这个项目做完后最顺理成章的扩展就是总线上挂多个DS18B20。单总线上每个DS18B20都有唯一的64位ROM序列号包含家族码、48位序列号和8位CRC校验。多设备时不能用0xCC跳过ROM必须先执行0x33读取每个传感器的序列号再通过0x55匹配ROM来点名访问。48位序列号决定了一条总线上理论可以挂多个设备但实际受总线电容和时序影响挂太多会让读时序边缘变得很脏。我建议一条总线上稳定挂8个以内再多就分多条总线。上位机可以通过CRC校验来确认读到的序列号是否正确这是很有价值的一个工程细节。4.8 显示部分的小技巧最后分享一个显示小技巧。如果只需要显示“XX.X”格式比如25.5℃第三位的小数点需要常亮。方法是把段码按位或上0x80共阴数码管小数点段对应的位不用单独写额外的IO控制函数。这样在动态扫描函数里加一行判断就行代码结构不会被破坏。亮度不够时优先增加每位点亮时间但总周期要控制在20ms以内也就是每位最多5ms。把点亮时间从1ms调到2ms到3ms亮度改善非常明显再往上就需要考虑用三极管或者专门的数码管驱动芯片来增强驱动能力了。这个项目做下来我最深的体会是“引脚定义和极性搞对代码就赢了一半”。DS18B20和数码管原理都不难但在实际调试中恰恰是那些看似简单的电平方向、时序宽度、共阴共阳选择决定了整个项目能不能顺利跑起来。如果现在你调试时发现温度值完全不合理先别急着改代码拿万用表从头到尾量一遍接线再对着时序图一级一级核DS18B20的读写延时大概率比闷头改代码更快见效。后面如果要做温控产品可以在现有基础上加一个阈值比较函数设置上限下限去控制继电器或风扇这个项目就是一个非常扎实的起点。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑