ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

51单片机读取DS18B20温度传感器:单总线时序详解与C代码实现

2026/9/13 6:31:13 拓冰建站 浏览量
51单片机读取DS18B20温度传感器:单总线时序详解与C代码实现 简介面向51单片机开发者的DS18B20温度传感器源码项目适合单片机入门学习者以及正在做温控类课程设计、电子竞赛或小项目的读者参考。资源采用C语言编写压缩包中共4个文件包含2个C源文件、1个头文件和1个可直接烧录的hex固件压缩包大小仅6KB结构十分精简源码覆盖DS18B20初始化、单总线读写时序、温度转换、数据输出等关键环节主程序与底层驱动分离模块化较好既可整体烧录验证硬件连接也方便将温度获取部分独立移植到其他工程对理解51单片机与传感器交互很有帮助。代码中的函数划分清晰注释到位能帮助初学者避开DS18B20读写时序中的常见坑点缩短调试时间。目前已有9028人学习下载对于需要快速实现温度采集、入门单总线通信或复用代码的学习者而言是一份轻量且实用的参考资料。1. 51单片机读取DS18B20温度传感器代码的核心在时序拿到一块DS18B20三根线接好程序烧进去串口助手打印出来的全是0xFF换成另一份例程读数又常驻85.0。这两个现象几乎是每个做51单片机温度采集的人都会撞上的坎根因不在传感器本身而在单总线1-Wire的时序没有对上。DS18B20是数字输出直接给出12位分辨率、最小0.0625℃的温度数据不需要像NTC热敏电阻那样做查表或曲线拟合代价是数据线和时钟共用一根DQ线复位、写位、读位都有严格的微秒级窗口。51单片机GPIO速度不高反而适合驱动这类低速器件难点是把每个延时段写准。这篇不绕弯先把时序参数拆开讲再给一份能直接编译的C代码最后拆掉最常见的那几个故障现象。适合准备做课程设计或温控风扇项目的读者也适合在Proteus里调仿真却始终读不到温度的工程师。2. DS18B20时序详解初始化、写时序与读时序的15个关键参数2.1 单总线协议与51单片机的时序匹配DS18B20由Dallas Semiconductor定义1-Wire单总线协议所有数据交换都在一根DQ线上完成。51单片机上没有硬件单总线控制器必须用普通GPIO模拟但这门手艺正好落在51的舒适区单总线要求的时隙最短1us最长120us而11.0592MHz晶振下STC89C52一个机器周期约1.085usGPIO翻转速度和协议要求在一个数量级。模拟单总线通信有一个先决条件51的P1、P2、P3口是准双向IO读外部电平前必须先向引脚写1否则读到的可能被内部锁存器拉低。DS18B20的复位、写1、读时隙流程里主机本来就要释放总线拉高DQ所以代码里不会出现“先写1再读”这种额外节奏直接按协议时序走就行。2.2 复位脉冲与存在应答检测每一次完整通信都以复位开始。主机把DQ拉低480us到960us然后释放DS18B20检测到上升沿后等待15us到60us主动将DQ拉低60us到240us这段低电平就是存在应答脉冲Presence Pulse。主机在释放总线后60us左右采样读到低电平说明器件在线。复位时序里有三个参数不能写错拉低时间不能短于480us否则传感器可能还没反应过来你就松手了释放后的采样点不能太早要落在DS18B20拉低存在应答的时间窗内整个复位周期结束后DQ必须恢复高电平再留出至少1us恢复时间才能发下一条命令。2.3 写0与写1的时隙拆分写时序把每个位拆成一个写时隙Write Time Slot时隙整体长度60us到120us。写0和写1的区别在于低电平持续时间写0时主机把DQ从拉低一直保持60us到120us期间不释放再从低拉到高完成一个时隙。写1时主机只把DQ拉低1us到15us然后立刻释放靠外部上拉电阻把总线拉回高电平保持到时隙结束。DS18B20在写时隙开始后的15us到60us窗口内对DQ采样。这条规则有一个反直觉的推论写1时拉低时间一旦超过15us传感器会把“1”误判成“0”。很多人写代码时习惯用一个统一的延时函数结果写1的拉低时间被拖到30us以上读数就乱了。2.4 读时隙与15us采样窗口读时隙的细节更考验代码功力。主机先把DQ拉低至少1us然后释放DS18B20检测到这个起始沿后如果当前位是0它会主动把总线拉低60us到120us如果当前位是1它不动作总线被上拉电阻维持在高电平。关键的采样窗口在起始沿之后的15us以内。主机释放总线后如果等超过15us才去读DQ无论传感器给出的是0还是1此时总线很可能已经被上拉成高电平结果全部读成0xFF。这也是前文提到的“串口打印全是0xFF”最隐蔽的原因——传感器是好的时序窗口错过了。2.5 DS18B20时序参数速查表时序段参数最短典型最长说明复位主机拉低480us500us960us小于480us可能复位失败存在应答等待应答15us60us60us释放后在60us左右采样存在应答应答低电平60us-240us采样点为低电平说明在线写时隙时隙总长60us-120us前后两个时隙间至少恢复1us写0低电平保持60us-120us全程保持写1低电平保持1us-15us超过15us会被误判为0读时隙低电平预置1us--主机拉低后立即释放温度转换12位分辨率--750ms9位时93.75ms10位187.5ms11位375ms参数表不是背下来就完事。它对应到代码里就是每个函数的延时量后面第4章的代码会逐行对应回来。记住一句话优先于所有细节写1拉低不超过15us读时隙采样不晚于15us复位拉低不少于480us。3. 硬件连接与最小系统引脚分配、上拉电阻与Proteus仿真要点3.1 DS18B20引脚定义与51单片机接线表DS18B20常见封装是TO-92三脚平面朝向自己时从左到右分别是GND、DQ、VDD。如果是买来的小模块通常已经焊好上拉电阻和电源滤波电容只引出VCC、GND、DQ三根线接线更简单。DS18B20引脚功能接51单片机GND电源地GNDDQ数据线/1-Wire总线P3.7可换任意IOVDD电源正极 3.3V~5.5VVCC与单片机同电源我一般把DQ固定放P3.7理由只有一个P3口在STC89C52的DIP-40封装上靠外侧走线方便。实际选哪个引脚都可以但选定之后代码里sbit DQ P3^7;要同步改这个低级错误出现频率远比你想象的高。注意VDD必须和51单片机共地供电取自同一路5V不要用两个电源分别供。3.2 4.7k上拉电阻为什么必须接DS18B20的DQ引脚是漏极开路结构只能主动拉低不能主动输出高电平。总线上的高电平完全依靠外部上拉电阻提供。没有这个电阻复位时主机释放总线后DQ处于悬空状态电平不确定存在应答和读时隙全部无法工作。常规取4.7k范围1k到10k都行。连接线短、干扰小时可以取大一点连接线超过半米、工作环境有电机或开关电源时建议取1k到2.2k并适当缩短走线。上拉电阻一端接DQ另一端接VCC这是必须的不能省。DS18B20还支持寄生供电模式VDD引脚直接接地依靠DQ线上的高电平给内部电容充电。51单片机驱动寄生供电没问题但转换期间电流尖峰明显电源纹波大的时候容易造成转换结果漂移。做课程设计或温控项目建议直接外部供电省掉一类排查麻烦。3.3 晶振选择、引脚复用和Proteus仿真注意事项晶振选择直接牵涉到后面代码能不能跑通。推荐直接上11.0592MHz这个频率下51的串口能精确产生9600bps波特率而DS18B20对主机时钟并不敏感两个场景共用一个晶振是最省心的方案。如果你坚持用12MHz串口9600会引入约2.1%误差短帧能通长帧偶尔乱码。Proteus仿真有个坑仿真模型对漏极开路和上拉电阻的建模比实物宽松DS18B20模型不上拉电阻也可能出正常读数导致你把仿真搬到实物后第一个故障就是全0xFF。不要因为仿真能跑就略过上拉电阻实物接线永远按“必须有4.7k上拉”来画图。仿真里晶振、器件选型随意但实测时以第4章代码里注释的晶振和延时关系为准。4. 51单片机读取DS18B20温度的C语言代码实现4.1 代码框架与延时函数的设计方式完整的测温流程分四段初始化复位存在检测、跳过ROM寻址0xCC、启动温度转换0x44、转换结束后读暂存器0xBE。单器件接线时ROM命令可以固定用0xCC跳过不涉及搜索ROM和匹配ROM。延时是这套代码的灵魂。空循环延时的实际时间受Keil优化级别影响你把Optimization从0级调到9级同样一段while(t--);可能快出几倍。稳妥写法是加入_nop_()并统一晶振为11.0592MHz#include reg52.h #include intrins.h #define uchar unsigned char #define uint unsigned int sbit DQ P3^7; // DS18B20数据线可换其他IO // 11.0592MHz晶振下单条空循环约1us到2us void delay_us(uint t) { while (t--) { _nop_(); } } void delay_ms(uint t) { uchar i; while (t--) { for (i 0; i 120; i); } }这段代码里延时单位不是绝对精确只能保证量级正确。DS18B20时序的容差范围很宽复位480us到960us、写时隙60us到120us量级对了就能工作。真要精确到每个微秒需要在Keil里关闭优化或使用#pragma OPTIMIZE(1)再用逻辑分析仪校准。4.2 复位、写入和读取的时序函数实现下面是协议层的核心函数。注意读位函数里采样点位置这段代码比网上很多版本更强调“释放后立即采样”// 复位并检测存在脉冲返回1表示检测到DS18B20 bit DS18B20_Reset(void) { bit ack; DQ 0; delay_us(500); // 拉低500us满足480us~960us DQ 1; // 释放总线 delay_us(60); // 等待60us后采样 ack DQ; // 采样存在应答低电平为有效 delay_us(420); // 补足整个复位周期 return ~ack; } // 写一个位val为1时走写1时隙val为0时走写0时隙 void DS18B20_WriteBit(bit val) { DQ 0; _nop_(); // 拉低保持1us if (val) { DQ 1; // 写1快速释放让上拉电阻拉高 } delay_us(60); // 时隙总长60us DQ 1; _nop_(); } // 读一个位采样点必须靠近起始沿 bit DS18B20_ReadBit(void) { bit val; DQ 0; _nop_(); // 拉低1us _nop_(); DQ 1; // 释放总线 _nop_(); // 等待约2us val DQ; // 在15us窗口内采样 delay_us(60); // 占满整个读时隙 return val; } // 写一个字节低位在前 void DS18B20_WriteByte(uchar dat) { uchar i; for (i 0; i 8; i) { DS18B20_WriteBit(dat 0x01); dat 1; } } // 读一个字节低位在前 uchar DS18B20_ReadByte(void) { uchar i, dat 0; for (i 0; i 8; i) { dat 1; if (DS18B20_ReadBit()) { dat | 0x80; } } return dat; }DS18B20_Reset返回值用了取反技巧存在应答为低电平ack DQ读到0表示有设备函数返回~ack即1让调用处的if(!DS18B20_Reset())语义更直白。读写字节都坚持LSB first这是单总线协议的规定顺序反了会得到完全错误的温度值。读位函数里两个_nop_()约等于2us到3us释放后只等一次_nop_()就采样这个节奏是读时序不出问题的关键。4.3 温度转换主流程与负数补码处理温度寄存器的低字节是TL、高字节是TH合成一个16位值。正数时直接乘0.0625就得到摄氏度负数时高字节最高位为1需要取反加一得到绝对值再加负号。为避免浮点运算工程上更常用的做法是把返回值放大10倍或100倍。下面代码返回十分之一摄氏度比如256表示25.6℃// 启动一次温度转换单设备场景跳过ROM void DS18B20_Convert(void) { DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM单设备不需要匹配64位序列号 DS18B20_WriteByte(0x44); // 启动温度转换 } // 读取温度值返回十分之一摄氏度 // 设备未应答时返回0x7FFF用于主函数区分故障 int DS18B20_ReadTempX10(void) { uchar tl, th; int raw, temp10; if (!DS18B20_Reset()) { return 0x7FFF; } DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0xBE); // 读暂存器从第0字节开始 tl DS18B20_ReadByte(); // 温度低字节 th DS18B20_ReadByte(); // 温度高字节 raw (th 8) | tl; if (raw 0x8000) // 符号位为1说明是负温度 { raw ~raw 1; // 取补码得到绝对值 temp10 raw * 625 / 1000; return -temp10; } else { return raw * 625 / 1000; } }0x7FFF作为错误码是安全的DS18B20实际测温范围只有-55℃到125℃12位分辨率下最大正数不会超过2047放大10倍是1279远小于32767。实际项目里这个返回值就是“传感器不在线”的哨兵值主函数拿到它做不上电告警。乘625除1000的本质是把0.0625转化为整数运算0.0625 * 10 0.625raw * 625 / 1000就是raw * 0.625。之所以用625而不是6250除10000是为了让raw最大2047时中间乘积不超过20位51的int刚好放得下。4.4 串口输出与主循环调度转换启动后要等750ms才能读结果。有人用一个死循环在转换期间干等更合理的做法是启动转换后让主循环去做别的事下次循环再来读。不过课程设计里多数是轮询先用延时方式把链路跑通void UART_Init(void) { SCON 0x50; // 串口方式18位UART允许接收 TMOD 0x0F; TMOD | 0x20; // 定时器1为模式2 TH1 0xFD; TL1 0xFD; // 11.0592MHz下波特率9600 TR1 1; } void UART_SendByte(uchar dat) { SBUF dat; while (!TI); TI 0; } void main(void) { int t10; UART_Init(); EA 0; // 温度采集过程中不响应中断避免时序被拉偏 while (1) { DS18B20_Convert(); // 启动转换 delay_ms(760); // 等12位分辨率转换完成 t10 DS18B20_ReadTempX10(); if (t10 0x7FFF) { UART_SendByte(X); // 传感器未接或初始化失败 UART_SendByte(\r); UART_SendByte(\n); continue; } if (t10 0) { UART_SendByte(-); t10 -t10; } UART_SendByte(0 t10 / 100); // 十位 UART_SendByte(0 (t10 / 10) % 10); // 个位 UART_SendByte(.); UART_SendByte(0 t10 % 10); // 十分位 UART_SendByte(\r); UART_SendByte(\n); delay_ms(500); // 两次测温间隔给系统留出喘息余地 } }主循环里先启动转换再延时是刻意分解的。后续要做多路采集时可以把delay_ms(760)换成对DQ轮询或把等待时间交给调度器这样代码结构不用大改。关闭总中断EA 0这条值得养成习惯51没有硬件单总线控制器任何中断服务程序里的串口发送、定时器重装都会把微秒级时序打断导致采样点偏移。5. 排错、迟滞控制与CRC校验把测温代码用进温控项目5.1 51单片机测温代码的常见故障排查对照表现象常见原因处理方式串口打印全0xFF复位失败、存在脉冲没采到、DQ根本没上拉测DQ对地电压正常空闲时应为高电平补4.7k上拉电阻读数常驻85.0未发0x44转换命令或转换未完成就读取检查主循环是否调用DS18B20_Convert转换后等待时间是否≥750ms读数为0或数值乱跳读时序采样点晚于15us读到上拉高电平检查ReadBit中释放DQ后到采样之间的延时改成不超过两三个NOP温度精度退化为0.5℃分辨率被写入的配置字改成9位重新上电默认12位或写0x4E重设配置寄存器换STC15等1T单片机后全错机器周期缩短原空循环延时不成立定时器延时或按新时钟重新标定NOP循环排查时先看存在应答再看写时序最后看读时序这个顺序别反。存在应答是后面所有命令的前提复位都失败时不要去调读时隙。5.2 温控风扇迟滞比较替代直接阈值比较读温度只是第一步温控风扇或闭环温控实验才是多数人最终要做的。直接“大于32℃开风扇小于28℃关风扇”会造成继电器在临界点反复吸合解决办法是引入迟滞#define TEMP_ON 320 // 32.0℃开启 #define TEMP_OFF 280 // 28.0℃关闭 static bit fan_on 0; void Fan_Control(int t10) { if (fan_on) { if (t10 TEMP_OFF) // 开启状态下低于关断点才关 { fan_on 0; } } else { if (t10 TEMP_ON) // 关闭状态下高于开启点才开 { fan_on 1; } } FAN fan_on; // FAN接继电器或PWM驱动 }迟滞量4℃继电器吸合频率大幅降低。如果驱动的是小风扇而不是继电器可以把FAN输出换成PWM调速51用定时器输出PWM时注意把EA 0改为只在读温度期间关中断否则PWM输出和温度采集会互相干扰。5.3 用CRC校验确保DS18B20暂存器数据可靠DS18B20读暂存器命令0xBE会连续返回9个字节第9字节是前8字节的CRC校验值。工业场景或长线传输时建议校验忽略CRC的做法在读数偶尔漂移时会让你无从下手uchar OneWire_CRC8(uchar crc, uchar dat) { uchar i; crc ^ dat; for (i 0; i 8; i) { if (crc 0x01) { crc (crc 1) ^ 0x8C; // CRC-8/Dallas多项式 } else { crc 1; } } return crc; }0x8C来自DS18B20使用的Dallas CRC-8多项式x^8x^5x^41按LSB-first方式移位时异或值为0x8C。使用时把读回的9个字节中前8个依次送入OneWire_CRC8最终结果与第9字节比较不一致就丢弃本次读数。校验函数只有几行换来的是定位“传感器劣化”还是“传输干扰”的能力。手边有逻辑分析仪的话把DQ引脚波形抓下来对照第2章参数表看一遍所有时序疑点当场就能定案。本文还有配套的精品资源点击获取