资讯动态

DS18B20温度采集实战:1-Wire时序与数码管动态扫描

发布时间:2026/10/16 1:28:51 来源:尧图企业网站定制
做温度采集项目时我最早用的是NTC热敏电阻加单片机自带ADC结果被标定、查表、线性化这些环节折腾得够呛。后来换成DS18B20温度传感器整个采集链路一下子清爽了一根数据线进单片机直接得到数字温度值再经过简单换算就能送到数码管显示。这篇文章就把我从硬件接线到1-Wire时序、从固件驱动到数码管动态扫描的完整做法写清楚适合正在做单片机课程设计、电子制作或者想给实际设备接温度检测功能的朋友参考文章里的代码和接线方式都可以直接照着抄。1. 为什么温度采集绕不开DS18B201.1 DS18B20解决的是“测温链路复杂度”问题很多人选择DS18B20的直觉是“数字输出省事”但真正用下去才会发现它省掉的不是一行代码而是整个测温链路的复杂度。用传统NTC方案时传感器输出的是电阻变化要经过分压、放大、ADC采集最后还得靠查表或多项式拟合把电压换算成温度。这里面每一步都会引入误差分压电阻的精度、ADC的参考电压漂移、热敏电阻本身的离散性都会让最终读数偏离真实值。如果要求高一点还得做温度校准整套流程做下来工作量远超过“读温度显示温度”这个需求本身。DS18B20把这些问题压缩进一颗三引脚芯片里。它自带模数转换和出厂校准温度结果直接以数字量输出测量范围是-55℃到125℃在-10℃到85℃区间可以做到±0.5℃精度默认12位分辨率下最小可以分辨0.0625℃。更重要的是它只需要一根数据线通信也就是常说的单总线1-Wire接口。数据线同时承担数据传输和部分供电功能硬件连接极其简单单片机任意一个普通IO口就能搞定。实际项目中我还发现一个很实用的点DS18B20有各种封装形态常用的是TO-92直插封装和带不锈钢探头的不防水/防水版本。做水温监测、烤房温度记录、设备机箱内部温度检测时直接买带探头线的那种不用自己在PCB上做防护防水头浸到液体里也稳。这种“传感器本身就能适应现场环境”的能力是很多模拟方案不具备的。1.2 好用的背后也有明显约束DS18B20不是什么场景都能完美适配理解它的约束有助于避开使用误区。第一是转换时间。12位分辨率下一次温度转换需要最长750ms。也就是说你发出转换命令之后不能立刻读数据必须等够这个时间。如果显示刷新频率要求很高或者要做快速温度闭环控制就得注意这个延迟。一般数码管显示场景只有三位数、刷新几次每秒就够750ms完全不影响体验但如果你按100ms循环去读就会拿到旧数据。第二是通信时序严格。单总线时序最小单位是微秒级写时隙和读时隙都有固定窗口程序里的延时函数必须尽量准确。这个在第三部分我会详细展开也是新手最容易翻车的区域。第三是单总线上可以挂多片DS18B20这是它的强项不过要区分每个传感器就需要读取64位ROM序列号并且匹配命令。多数简单项目用不到这个功能老老实实按照“跳过ROM再转换”的方式来做即可。第四是采样频率不要奢望太高。我见过有人想在电机控制里用DS18B20做实时过温保护实际上750ms的转换时间对于短路等快速温升场景已经偏慢。这种场景应该用热敏电阻加比较器或者专用温度传感芯片DS18B20更适合慢速、要高可靠性且维护简单的测量场景。2. 硬件搭建传感器接线和数码管驱动2.1 传感器三根引脚重点在拉高不拉低DS18B20有三种供电方式外部电源供电、寄生电源供电、用数据线供电。对于初学者和大多数项目我强烈建议直接使用外部电源供电就是VDD引脚接电源正极。寄生供电模式虽然能省一根线但总线驱动要求更严格转换期间还得靠上拉电阻供流遇到劣质面包线很容易莫名其妙读错数据。外部供电接法非常固定引脚号名称接法1GND接电源负极2DQ数据线接单片机IO口同时经上拉电阻接VCC3VDD接3.0V-5.5V电源正极DQ这根线是开漏式设计传感器自己只能把总线拉低不能主动拉高。要让总线在没有设备驱动时保持高电平必须在DQ和VCC之间接一个上拉电阻。典型阻值是4.7kΩ适用大多数3.3V和5V系统。如果数据线比较长或者线上挂的设备多可以降到2.2kΩ甚至1kΩ这个我会在后面的干扰排查部分再细说。我建议先把传感器单独接好不接数码管用单片机读一次温度并通过串口或者调试器确认数值正常再往下做显示部分。别一口气把所有模块全接上出了问题很难定位。2.2 数码管亮与灭的控制逻辑动态扫描的关键数码管在项目里常用四位数码管显示温度例如显示“25.3℃”需要四位十位、个位、小数点、小数位。首先要区分共阳和共阴两种数码管。共阳数码管是所有段的公共端接正极某一脚给低电平时对应段点亮共阴数码管则相反公共端接负极给高电平时点亮。这里我以共阳数码管为例因为很多开发板配的也是共阳而且单片机灌电流驱动段码比较方便。驱动方式上四个数码管如果每个都占用独立段线四个就需要32个IO口这在51单片机上根本不够用。所以实际做法是段选和位选分开段选控制七段码和DP点四个数码管共用一组接到单片机的P0口位选控制哪个数码管导通四个位分别接P1.0到P1.3。单片机可以同一时刻只点亮一个数码管让它显示对应的数字然后快速切换到下一位。只要切换频率超过人眼视觉暂留频率看起来就是四个位同时稳定显示。通常每位数码管停留2到5ms四位数码管一个循环20ms左右对应刷新率50Hz效果就很理想。实际接线建议在段选线上串联220Ω到470Ω的限流电阻防止过流烧段。位选部分如果直接用单片机IO口驱动公共端电流会比较紧张简单做法是加四个小功率三极管或者直接用ULN2003这类驱动芯片。用三极管时注意共阳数码管的公共端是接正极位选低电平有效所以要用PNP管基极由单片机IO口控制低电平导通高电平截止。3. 单总线通信时序先看透再写代码DS18B20的1-Wire协议看起来简单实质上是一个非常依赖时间窗口的协议。发送命令和读取数据都靠总线上的高、低电平持续不同时间来表达。如果时序不对轻则读数异常重则完全读不到存在脉冲。3.1 复位与存在检测通信的第一步每次通信开始前主机必须发一个复位脉冲。这一步的作用是让总线上所有DS18B20同步并让传感器回应一个“存在脉冲”确认自己在线。复位时序是主机先把总线拉低至少480μs然后释放总线此时上拉电阻会让总线恢复高电平。传感器在检测到总线释放后会等待15到60μs再把总线拉低60到240μs这个低电平就是存在脉冲。主机需要在释放总线后大约60μs左右去采样总线电平读到低电平说明传感器存在。这里有个常见的低级错误主机释放总线后马上就去读这时传感器还没来得及拉低总线读到的自然是高电平以为传感器没接。所以复位函数里要先延时至少60μs再采样。参考代码如下bit ds18b20_reset(void) { bit presence; DQ 0; // 主机拉低总线 delay_us(500); // 保持低电平500us满足复位要求 DQ 1; // 释放总线 delay_us(60); // 等存在脉冲出现 presence DQ; // 采样0表示存在1表示无设备 delay_us(400); // 等待整个复位周期结束 return presence; }这个函数返回0表示检测到DS18B20返回1表示失败。调试的时候可以专门做一个LED用来指示返回值能省不少排查时间。3.2 写时隙怎么区分写0和写1复位之后主机发送ROM命令和功能命令每个字节都是按从低位到高位的顺序一位一位发送的每一位需要一个写时隙。写0和写1的区别在于低电平持续的时间写0主机把总线拉低并且在整个时隙期间一直保持低电平大约60到120μs写1主机把总线拉低很短暂的时间大概1到15μs然后立刻释放总线让上拉电阻把总线拉高维持到整个时隙结束。传感器在写时隙开始后的15到60μs窗口内对总线采样如果采样到低电平就认为是0高电平就是1。因此写1的关键是拉低时间不能太长否则传感器会误判成0。很多人用延时函数时没校准把写1的低电平时间写成了几十微秒命令就完全乱了。参考实现void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; if (dat 0x01) { delay_us(10); // 短暂拉低后释放 DQ 1; delay_us(55); // 保持高电平直到时隙结束 } else { delay_us(65); // 写0持续拉低 DQ 1; delay_us(5); // 间隔 } dat 1; } }注意整个写时隙的时长最好控制在60到120μs之间上述代码在12MHz晶振下配合精细的delay_us函数基本可行。如果单片机频率不同需先把延时函数用示波器或逻辑分析仪校准一遍再跑DS18B20时序。3.3 读时隙主机必须学会放手读数据和写数据逻辑不一样。读时隙同样由主机发起主机先拉低总线1到15μs然后释放总线。释放之后总线状态由传感器掌控。如果传感器要传0它会把总线拉低并保持如果要传1它就保持高电平。主机需要在读时隙开始后大约15到30μs内采样总线电平采样太晚可能读到下一个时隙的数据。读时隙最容易犯的错误是主机释放总线之后没有把IO口配置成输入模式或者还在强行输出高电平。对于51单片机的准双向IO口读之前先要向端口写1然后才能正确采样外部电平。代码上要特别处理这个细节。unsigned char ds18b20_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat 1; DQ 1; // 先置高准备读 DQ 0; // 发起读时隙 delay_us(2); DQ 1; // 释放总线 delay_us(8); // 等传感器驱动总线 if (DQ) { dat | 0x80; // 读到高电平这一位为1 } delay_us(60); // 等待读时隙结束 } return dat; }这部分原理我建议不要死记代码而是用逻辑分析仪抓一次真实波形把写0、写1、读0、读1四种波形对比一下理解会快很多。很多“明明代码照着写就是读不到数据”的问题本质都是对这里时隙窗口理解不够。4. 固件实现读温度、换算、给数码管显示4.1 启动转换与读取暂存器的命令流DS18B20完整读一次温度命令顺序是固定的复位发送跳过ROM命令0xCC因为只有一个传感器不需要匹配64位地址发送启动温度转换命令0x44等待转换完成12位分辨率下等待750ms再次复位发送跳过ROM命令0xCC发送读暂存器命令0xBE连续读取9个字节其中第0和第1字节分别是温度值的低字节和高字节后面7个字节是告警值、配置寄存器和CRC校验值根据两个字节算出温度。很多教程在步骤4里直接用delay_ms(750)这个延时在实现温度转换时必须保留。但在整体程序里如果主循环是“读温度→刷新显示→再读温度”这样循环一次循环就要750ms数码管动态扫描会明显闪烁。最佳做法是把扫描显示放到定时器中断里主循环只负责读取温度和更新显示缓冲区。完整采集封装成函数大概长这样void ds18b20_read_temp(unsigned int *integer_x10, bit *negative) { unsigned char low, high; int raw; ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0x44); delay_750ms(); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); low ds18b20_read_byte(); high ds18b20_read_byte(); raw (high 8) | low; *negative 0; if (high 0x80) { *negative 1; raw (~raw) 1; // 补码转绝对值 } *integer_x10 (raw * 5 4) / 8; // 四舍五入到0.1℃ }变量名中integer_x10不是整数温度而是“温度×10”的整数例如25.3℃就存成253这样方便后续拆位显示。4.2 两个字节怎么换算成可显示的温度DS18B20默认12位分辨率下温度寄存器里每个最小单位代表0.0625℃也就是1/16℃。读到的16位数据是带符号补码符号占高5位有效温度占11位。换算公式是温度值 原始值 × 0.0625。几个典型例子低字节高字节十六进制合并原始值实际温度0x500x010x015033621.0℃0x910x010x019140125.1℃四舍五入到0.10xF80xFF0xFFF8-8-0.5℃如果把显示精度做到一位小数可以直接用上节代码里的(raw * 5 4) / 8公式。为什么是5和8因为0.0625℃要放大10倍显示一位小数就是乘以0.625等于乘以5再除以8。加4是四舍五入避免比如25.0625℃被截断成25.0℃四舍五入后显示25.1更合理。负数处理是低字节和高字节合起来按16位补码取反加一得到绝对值再按同公式换算。数码管显示时如果negative位为1就在最高位显示负号。4.3 动态扫描显示的具体实现共阳数码管段码表如下显示字符段码00xC010xF920xA430xB040x9950x9260x8270xF880x8090x90负号0xBF熄灭0xFF这个表是按共阳数码管设计的段码为0的位点亮。如果用共阴数码管所有值都要取反。显示缓冲用四个变量索引0到3分别对应位选从左到右的四位数。主流程中先把温度拆解到缓冲unsigned char code seg7[10] { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90 }; unsigned char disp[4]; unsigned int temp_x10; bit is_negative; void update_buffer(void) { unsigned char i, dec; unsigned int abs_temp; abs_temp temp_x10; if (is_negative) { disp[0] 0xBF; // 最高位显示负号 if (abs_temp 100) { disp[1] seg7[(abs_temp / 100) % 10]; disp[2] seg7[(abs_temp / 10) % 10]; disp[3] seg7[abs_temp % 10]; } else { disp[1] 0xFF; // 不显示十位 disp[2] seg7[(abs_temp / 10) % 10]; disp[3] seg7[abs_temp % 10]; } } else { disp[0] 0xFF; if (abs_temp 100) { disp[1] seg7[(abs_temp / 100) % 10]; } else { disp[1] 0xFF; // 高位消隐 } disp[2] seg7[(abs_temp / 10) % 10]; // 个位段码加上小数点DP共阳点亮DP是bit7写0 disp[2] 0x7F; disp[3] seg7[abs_temp % 10]; } }扫描函数在定时器中断里调用每2ms左右切换一位。切换顺序要特别注意必须先关闭所有段选再关闭所有位选然后设置下一位的段码最后打开新的位选。反过来操作就会出现拖影也就是上一个数字的残留亮影叠在下一个数字上。void scan_display(void) { static unsigned char pos 0; P0 0xFF; // 关闭所有段 P1 0x0F; // 关闭所有位 P0 disp[pos]; // 设置当前位的段码 P1 ~(1 pos) 0x0F; // 选中当前位 pos; if (pos 3) pos 0; }如果把显示扫描放在定时器中断里主循环里的750ms延时就不会影响显示刷新温度采样也只需要在每次进入读取前更新缓冲。中断方式看起来比“循环里面顺便刷一下”复杂但实际代码量差不多稳定性却好得多。5. 实测中反复踩的坑与排查链路5.1 温度永远显示85℃的根因在哪里这个现象是DS18B20最常见的“假死”表现。很多初学者接好电路、烧好固件数码管确实亮了但温度死死卡在85.0℃不动。不要先怀疑传感器坏了也不要急着换芯片。85℃这个值是DS18B20上电复位后的寄存器初始值它等于0x0550也就是0x0550 × 0.0625 85℃。换句话说芯片虽然收到复位并且回报了存在脉冲但温度转换从来没被正确触发过读回来的永远是启动值。按这条链路排查顺序很重要第一步确认复位返回正常。把复位函数的返回值接一个LED复位成功时LED点亮。如果LED不亮说明传感器物理连接就有问题先查DQ上拉电阻、GND和VDD是否接对、供电电压是否在3到5.5V之间。第二步如果复位灯正常还是显示85℃重点检查0x44转换命令是否真正写进去了。最常见原因是写1时隙的拉低时间太长传感器把1误判成0。用逻辑分析仪抓写数据时的波形把每个位对应的时隙宽度和手册对比一眼就能看出问题。第三步确认命令顺序没有漏。我见过有代码在启动转换后忘记再次复位直接发0xBE命令这也能读到数据但读到的可能就是上一次转换的旧值或者启动值。标准流程是转换完成后必须再发一次复位再发跳过ROM和读暂存器命令。还有一个很容易被忽略的细节当总线上接了多个DS18B20的时候如果跳过ROM跳过的是全部设备多个传感器会同时响应总线冲突会导致通讯完全混乱。多传感器场景必须读取每个芯片自己的64位ROM并做匹配不能用0xCC。实际调试中我给这个项目用的是一根几十块的逻辑分析仪采样率不用太高重点是看复位低电平持续时间、存在脉冲是否存在、0x44命令的位波形是否完整。硬件排障器里没有它很多问题就只能靠猜了。5.2 数码管动态显示乱成一片常见的三种现场表现动态扫描显示遇到乱码通常不是逻辑看不懂而是时序和消隐控制没做到位。我分三种情况说读者朋友对号入座。现象一数字之间有淡淡的拖影看起来像重影。原因几乎都是切换位选时没有先关闭段码。正确的切换顺序是先关段选再关位选然后给段码赋值最后开位选。顺序错肯定重影因为上一轮段码还残留在IO口上新的位选已经把下一位打开了。现象二某一位特别亮或者某一位特别暗。动态扫描里每位导通时间是均等的如果有位暗通常是位选驱动能力不足三极管没工作在饱和状态或者基极电阻太大。有位特别亮可能是那一位对应的位选IO口被外部干扰拉低了或者那一位数码管公共端和别的位短接。用万用表按住位选触点逐个量电压能比较快定位。现象三所有数字都稳定但温度小数点和预设的位置不对。这通常是段码表的DP位没有单独处理。显示“25.3”时小数点应该在个位的DP上而个位的DP是和段码的第8位共用的。段码表里默认DP灭需要显示小数时单独把这一位置0共阳极。不要试图把小数点做成另一个数码管位那会多占一位显示资源。还有一点我之前在刷新里说过不要在温度转换的750ms延时里直接塞显示扫描。主循环一旦卡住750ms动态扫描就成“静置扫描”了肉眼会看到明显的闪烁。只要把扫描挪到定时器中断每次5ms调用一次这个问题就从根上消失。5.3 长线传输、电源纹波和引脚接反的现场经验把DS18B20放到离单片机两三米远的地方是很多真实项目都会遇到的场景。这时候4.7kΩ上拉可能就不够用了因为导线本身有电容上拉电阻越大电平恢复越快吗答案是越慢。上拉电阻大、导线电容大总线在高电平和低电平之间切换的上升沿就会变缓DS18B20采样时可能还没到达有效电平。解决手段有几个一是把上拉电阻降到2.2kΩ甚至1kΩ二是数据线尽量和电源线一起走双绞或者用屏蔽线屏蔽层单端接地三是在传感器端的VDD和GND之间并一个0.1μF的陶瓷电容。这三个动作对稳定性提升非常明显。接反引脚也是我踩过真实的坑。TO-92封装的引脚朝上时从左到右依次是GND、DQ、VDD但是很多人凭印象把中间当GND接反之后轻则读不到数据重则损坏芯片。我用过一个笨办法第一次接传感器时先用调压板把电源限制在5V、限流100mA就算接反也不会烧然后把能正常读取的一端方向记在万用表旁边。这个习惯在后面做一批传感器时帮我省了很多芯片。在环境温度变化慢的场合DS18B20的读数滞后通常在数秒级别这是因为传感器自带的不锈钢外壳和环氧封装有一定热阻。如果你要把传感器埋入液体尽量留出足够长的浸入段不要让探头金属部分露在空气中。我实际测过同一颗探头在水里的响应速度比在空气中快得多这里不是芯片的问题是封装外壳的问题。整套从接线到数码管显示的流程走下来最深刻的一条经验是DS18B20这种芯片只要硬件供电和上拉正确软件时序严格按手册来失败率非常低。大部分异常其实都出在“读时序时没有让出总线”和“写1时隙时间过长”这两个点上。如果你第一次上手建议先只接传感器和一块LED指示灯把复位、读ID这些底层动作全部验证干净再接数码管做显示。一次接一个模块比整体点亮之后抓瞎要高效得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑