ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

51单片机驱动DS18B20:单总线时序详解与温度显示实现

51单片机驱动DS18B20:单总线时序详解与温度显示实现 简介面向51单片机初学者的完整实验资源包聚焦DS18B20数字温度传感器的采集与显示DS18B20采用单总线协议测量范围-55°C125°C精度±0.5°C无需ADC转换能有效降低系统设计复杂度。压缩包共14个文件大小约729KB包含C语言主程序、Keil工程文件、可烧录的hex文件、编译过程生成的obj/lst辅助文件、电路原理图Word文档以及DS18B20中文PDF资料能够支撑从代码阅读、工程编译到硬件验证的完整流程。目前已有2599人学习下载资源对DS18B20的初始化时序、ROM寻址、温度转换命令与数据解析过程均有完整实现配合原理图可以帮助读者理解传感器引脚连接对照PDF手册掌握单总线时序参数hex文件适合直接烧录验证实验效果main.c与工程配置则为修改和调试提供了基础。整体来看适合51单片机课程设计、电子竞赛备赛或嵌入式入门自学兼顾原理讲解与实际操作是一份可直接上手的参考方案。1. 这个实验的本质是单总线时序不是采集DS18B20 的温度采集实验很多人做完一遍只记住了“能显示温度”却说不清那根 data 线上到底发生了什么。这很可惜因为 DS18B20 是接触单总线1-Wire协议成本最低的器件采温度只是它的表面工作真正的实验目的是把一条 GPIO 上毫秒级高低电平变化读懂、写对。51 单片机没有硬件 1-Wire 控制器所有时序都得靠延时函数和位操作来抠这恰恰是它作为教学实验的价值处理器的速度不快反而让每个时隙的边界变得可控、可观测。这篇从协议层的时隙参数讲起再把驱动、显示、多传感器组网串起来最后落到示波器验证和误差补偿。不管你是刚把 Keil 装好还是已经能跑流水灯想往传感器方向走一步都能在这篇里找到可以直接下到板子上的代码和边界参数。老手也可以重点看读时序里的延时容差分析和多设备挂接时的上拉电阻选择这两处是实际项目中返工最多的地方。准备好了一个 STC89C52 或 AT89S52 开发板、一个 DS18B20 封装好的模块就能开始。2. DS18B20 单总线时序初始化、读时隙与写时隙怎么写DS18B20 的通信由主机发起所有操作都以“复位脉冲 存在脉冲”开场。理解单总线协议时不要把注意力放在“温度值”上先把三个基本动作的时序图画进脑子里复位、写时隙、读时隙。后面所有驱动代码说穿了就是把这三个动作按手册给的顺序排列组合。2.1 初始化时序主机拉低 480μs等待存在脉冲初始化是整个通信流程的第一步。主机把 data 线拉低至少 480μs然后释放并切换到输入模式DS18B20 检测到这个下降沿后会在 60240μs 内把总线拉低 60240μs 作为存在脉冲。主机必须在释放总线后的 480μs 窗口内读到这个低电平否则判定设备不在线。下面是一段可以直接用在 12MHz 晶振 51 上的初始化代码sbit DQ P3^7; unsigned char ds18b20_reset(void) { unsigned char presence; DQ 0; // 主机拉低总线 delay_us(500); // 保持至少 480usSTC 的 12T 模式 delay_us 要按实际晶振调 DQ 1; // 释放总线准备读存在脉冲 delay_us(60); // 等从机响应15~60us 窗口内判断即可 presence DQ; // 读取0 表示存在1 表示无设备 delay_us(420); // 补齐整个复位时隙至少 480us return presence; }说明三点presence DQ这条语句是实验最容易翻车的地方读到的低电平其实是从机拉低的但主机释放总线后必须靠上拉电阻把电平托起来所以开漏模式下这个读操作有效复位时序的整体长度至少要 480μs但手册没有限制上限拉长到 1ms 也能正常工作延时长一点比短一点可靠如果板子上 DQ 直接接了外部上拉电阻常见 4.7kΩ51 的 P3 口内部弱上拉其实可以省掉但接上更稳。2.2 写时隙用下降沿后的延时长度区分 0 和 1写时序是主机向 DS18B20 发送命令字节的基础。每个写时隙至少 60μs两个写时隙之间要有至少 1μs 的恢复时间。写“0”时需要把总线拉低且在整个 60μs 时隙内保持低电平写“1”时拉低 115μs 后释放剩下的时间交给上拉电阻把总线拉高。用 51 实现一个逐位的写字节函数void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; // 每个时隙都先拉低 if (dat 0x01) { // 写 1短暂拉低后释放 DQ 1; delay_us(60); } else { // 写 0全程保持低 delay_us(60); DQ 1; } dat 1; } }这里有个常见的理解偏差很多初学者以为写“1”就是直接把 DQ 置 1但单总线的写“1”时序必须从低电平开始只是低电平持续时间短不到 15μs所以代码里DQ 0放在if外面。从机靠检测下降沿之后 1560μs 时间点上总线是高还是低来判定收到的位主机释放总线的速度由 GPIO 输出转输入的延时决定51 在这个场景下性能完全够用。2.3 读时隙采样点要在 15μs 以内读时序的难点在于时序窗口短而且必须由主机主动制造一个下降沿来触发从机发送数据。主机先拉低总线 16μs然后释放从机在这之后把总线拉低表示“0”或保持高表示“1”。DS18B20 的输出数据保持时间大约是 15μs所以主机必须在这个时间内完成采样晚了读到的就是总线空闲电平永远返回 1。读字节的代码unsigned char ds18b20_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat 1; DQ 0; // 触发读时隙 _nop_(); // 约 1us适应 STC 的 IO 翻转速度 DQ 1; // 释放总线 _nop_(); _nop_(); // 这里差不多在 3~5us接近采样点 if (DQ) dat | 0x80; delay_us(60); // 等当前时隙结束 } return dat; }读取的采样点大约在 1015μs 这一区间。_nop_()的个数决定采样点位置晶振频率、单片机型号、优化等级都会影响实际耗时最可靠的办法是在线仿真里用逻辑分析仪看 DQ 波形再决定要不要多补一两个 NOP。这个读时序是 DS18B20 实验里排查问题最多的一步表现出的症状很像“设备没接线”实际上只是采样点太晚。3. 51 单片机驱动层温度换算、负温度与精度配置时序层面打通之后驱动层的编写就是把数据手册的寄存器操作翻译成函数调用。DS18B20 的温度数据在 RAM 的第 0 和第 1 字节分别是温度的低字节和高字节以 16 位有符号补码形式存储。注意一个被大多数人忽略的点温度字节里低 4 位是小数部分但高字节的符号位会扩展到整个 16 位因此必须用有符号类型接收否则零下温度会被错读成负的大数。3.1 启动转换与读取暂存器的命令序列一次完整测量需要两个步骤。第一步发送转换命令 0x44跳过 ROM然后等待转换完成转换时间取决于精度配置位默认 12 位精度下最长要 750ms。第二步发送读暂存器命令 0xBE然后连续读 9 个字节前两个字节就是温度值。常用读取函数如下float ds18b20_get_temp(void) { unsigned char tl, th; short raw_temp; // 必须是有符号 16 位 float celsius; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM单设备场景 ds18b20_write_byte(0x44); // 启动温度转换 while (!DQ); // 等待转换完成总线被从机拉低表示转换中 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 tl ds18b20_read_byte(); th ds18b20_read_byte(); raw_temp (th 8) | tl; celsius (float)raw_temp * 0.0625; // 12 位精度下的分辨率 return celsius; }while (!DQ)这一行是利用 DS18B20 的“转换中拉低总线”特性做忙等省掉固定延时。但它有个隐患如果设备没接好或已经损坏总线永远保持高电平程序会卡死在这个循环里。课堂实验这么写问题不大工程上我会加一个超时变量最多等 800ms 就退出避免系统死锁。3.2 负温度数据的符号位处理负温度在寄存器里以补码存储比如 -0.5°C 对应的 16 位十六进制值是 0xFFF8。如果raw_temp用的类型是unsigned short这个值变成 65528乘以 0.0625 得到 4095.5°C明显是错的。使用signed short接收后0xFFF8 被解释为 -8乘上 0.0625 正好是 -0.5°C。这个坑在 0°C 以下环境做实验时才会暴露很多人在 20°C 室温下写完代码就交差了恰恰印证了实验设计的局限。为了减少浮点运算在 51 上带来的代码量和时间开销可以把温度值放大 16 倍用整数表示也就是直接把原始 12 位值作为定点数使用。显示的时候拆分出整数和小数两个部分// 返回放大 16 倍的温度值整数部分为温度整数低 4 位为小数 short ds18b20_get_temp_x16(void) { unsigned char tl, th; short raw_temp; ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0x44); while (!DQ); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); tl ds18b20_read_byte(); th ds18b20_read_byte(); raw_temp (th 8) | tl; return raw_temp; }返回值的 bit11 表示整数部分最高位bit3bit0 是小数位小数精度是 0.0625°C。比如 0x0191 表示 25.0625°C整数部分是 0x19 即 25小数部分 0x1 即 1/16。显示时把低位除以 16 转成两位小数即可。这种定点数写法在 51 上编译后生成的代码量明显小于浮点库显示刷新频率也能提上去。3.3 精度配置与转换时间的关系DS18B20 的配置寄存器在第 4 个暂存器字节默认值是 0x7F即 12 位精度、转换时间 750ms。如果系统只需要 0.5°C 分辨率可以写入 0x5F 降到 11 位转换时间缩短到 375ms这在需要快速周期性刷新的应用里很划算。配置函数void ds18b20_set_resolution(unsigned char bits) { unsigned char cfg 0x1F; // bit6:bit5 是精度配置 switch (bits) { case 9: cfg | 0x00; break; // 93.75ms case 10: cfg | 0x20; break; // 187.5ms case 11: cfg | 0x40; break; // 375ms case 12: cfg | 0x60; break; // 750ms default: cfg | 0x60; break; } ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0x4E); // 写暂存器命令 ds18b20_write_byte(0x00); // TH ds18b20_write_byte(0x00); // TL ds18b20_write_byte(cfg); }精度配置是写在 DS18B20 的暂存器里掉电后不会丢失的是 EEPROM 中的值。写完配置后要发一条复制暂存器命令 0x48 才能存进 EEPROM否则下次上电又恢复成默认的 12 位。实验课里很少用到这个命令但在产品上把精度固定为 11 位并复制到 EEPROM能同时降低成本和功耗。4. 将温度显示到 LCD1602/数码管显示层与应用层分离传感器驱动写完显示是下一个分水岭。实验要求通常只是“显示温度”但实现的方式决定了这个实验能不能继续扩展成温控风扇、温度记录仪之类的课设题目。把驱动函数和显示逻辑分开是让代码能复用、能被别人读懂的最低要求。4.1 LCD1602 移植四线驱动省引脚1602 液晶模块是这类实验最常见的显示载体。为了省引脚推荐用 4 线模式只用 P0 口的 P0.0P0.3 接 DB4DB7再加上 RS、RW、E 三个控制脚总共 7 个引脚。相比 8 线模式省了 4 个 IO读写速度和 8 线模式差别不大51 的晶振频率反正也跑不出瓶颈。#define LCD_DATA P0 sbit LCD_RS P2^6; sbit LCD_RW P2^5; sbit LCD_E P2^7; void lcd_write_nibble(unsigned char n) { LCD_DATA (LCD_DATA 0xF0) | (n 0x0F); LCD_E 1; _nop_(); _nop_(); _nop_(); LCD_E 0; } void lcd_write_cmd(unsigned char cmd) { LCD_RS 0; lcd_write_nibble(cmd 4); lcd_write_nibble(cmd); delay_us(40); // 命令执行时间清屏指令需要更长延时 } void lcd_write_data(unsigned char dat) { LCD_RS 1; lcd_write_nibble(dat 4); lcd_write_nibble(dat); delay_us(40); }在 4 线模式下初始化顺序有讲究必须先发送 0x03 三次把模块从 8 位模式切换到 4 位模式再发送 0x02 设置为 4 位两行。如果直接发送功能设置命令1602 可能仍处于 8 位模式显示的全是乱码。这个初始化细节是移植 LCD 驱动时最容易漏的一步。4.2 主循环结构采集与刷新分离主程序会让采集和显示以不同的频率运行而不是一次循环做完全部。常见做法是采集周期 1s对应 750ms 转换时间显示刷新周期 200ms这样温度值不变时屏幕也不会闪烁还留给串口打印、按键扫描其他任务足够的时间。void main(void) { short temp_x16; unsigned char disp_buffer[6]; // 存放 25.06 或 -0.50 lcd_init(); lcd_show_string(0, 0, Temp: ); while (1) { temp_x16 ds18b20_get_temp_x16(); format_temp(disp_buffer, temp_x16); lcd_show_string(6, 0, disp_buffer); delay_ms(1000); // 1s 刷新一次 } } void format_temp(unsigned char *buf, short temp_x16) { signed char int_part; unsigned char dec_part; unsigned char minus_flag 0; if (temp_x16 0) { minus_flag 1; temp_x16 -temp_x16; // 取绝对值 } int_part temp_x16 4; // 整数部分 dec_part (temp_x16 0x0F) * 100 / 16; // 小数部分转两位整数 buf[0] minus_flag ? - : ; buf[1] int_part / 10 0; buf[2] int_part % 10 0; buf[3] .; buf[4] dec_part / 10 0; buf[5] dec_part % 10 0; }format_temp里的小数转换是一个值得说明的细节DS18B20 的分辨率是 0.0625也就是 1/16 度的精度小数部分的 1 个单位代表 0.0625°C。(temp_x16 0x0F) * 100 / 16是把小数部分换算成百分制显示即 0.0625 * 16 1乘以 100 再除以 16 得到的是百分位的整数。这种整型运算避免调用sprintf和浮点格式化函数对 51 的 2KB RAM 来说非常要紧。4.3 读时序期间关闭中断的必要性用定时器做按键扫描或数码管动态刷新时要注意 DS18B20 的读时序是实时性要求很高的操作。定时器中断如果恰好在读时隙的采样点附近触发中断服务程序里几次_nop_级别的延时就可以让采样点错过 15μs 窗口。最保险的做法是在每次读字节和写字节前关闭总中断完成后恢复EA 0; raw_temp ds18b20_get_temp_x16(); EA 1;这套读保护和延时设计是 51 上做 DS18B20 的通用约束。实测中我见过最多的问题是“温度显示时好时坏”排查到最后都是因为定时器中断频繁打断单总线通信。与其查那些玄学问题不如一开始就在时序敏感段落地关中断。5. 进阶多传感器组网、示波器校准时序与误差补偿单设备实验跑通只是起点如果这个实验要作为课程设计的核心功能提交通常会被问到一个关键问题如果要测两个点的温度怎么办这就是 DS18B20 单总线存在的原因——多个传感器挂一条线上按地址寻址主机一根 IO 连到底。5.1 多设备挂接用 0x33 读 ROM 拿到唯一地址再发 0x55 匹配每颗 DS18B20 出厂固化了 64 位 ROM 码其中包括 8 位家族码固定 0x28、6 字节序列号和 1 字节 CRC。接线时把所有传感器的 DQ 并在一起接一个 4.7kΩ 上拉电阻到 VCC。如果总线长度超过 3 米上拉电阻要减小到 2.2kΩ并用带屏蔽的双绞线传输 data 和地用屏蔽层单端接地。程序中先让所有从机上电发 0x33读 ROM逐个识别每颗设备的 64 位 ROM 码存到数组里。之后每次通信前发 0x55匹配 ROM再紧跟目标的 8 字节 ROM 码只跟指定设备对话。这样读二号传感器温度的流程是复位 → 0x55 → ROM[1] → 0x44 → 延时 → 复位 → 0x55 → ROM[1] → 0xBE → 读字节。多设备组网时上拉电阻选型直接影响通信稳定性电阻过小会增加低电平下拉的功耗过大则读写时序的上升沿变缓采样点后移。4.7kΩ 在 1 米内最稳2.2kΩ 适合长线不要用 10kΩ上升沿太缓基本通信不了。5.2 示波器校准时序的判定标准如果手头有示波器或逻辑分析仪别浪费在“看温度数对不对”上直接抓三个波形。复位波形抓的是 DQ 对 GND 的电压主机拉低至少 480μs 后释放随后能看到一个小幅低电平脉冲表示从机存在。存在脉冲的宽度如果在 60240μs 之间复位成功。写时序抓的是连续发送 0x00 和 0xFF 时的波形一个时隙 60μs 左右0 时隙的波形是一个完整的低电平方块1 时隙是窄低后快速拉高的尖峰。细看上升沿的斜率如果上升沿明显变缓检查上拉电阻是否太大或者总线上的分布电容是否超了。读时序抓的是读 0xFF 和 0xAA 时的波形重点看主机释放总线后的 15μs 窗口从机的数据线上如果电平在窗口内稳定为高或低就说明该读的位已经放到总线上了。采样点设置在主拉低后的 10μs 附近是安全的不要用软件延时去追赶 15μs 的极限值。5.3 零点与增益误差的补偿方法DS18B20 手册标称精度是 ±0.5°C实际上这个误差在不同温度点不是固定偏置。做温控风扇类课程设计时如果目标温度在 50°C误差看起来不明显但做恒温培养箱控制就可能把控制点带到 49.2°C 还是 50.8°C直接影响成绩。常用的校准办法是两点线性校准在 0°C冰水混合物和 100°C沸水下分别记录传感器读数然后按线性关系修正。写成代码// 校准系数cal_offset 和 cal_gain 由两点标定得出 // 实测 0°C 读数 0.2实读 100°C 读数 102.0则 gain 100 / (102.0 - 0.2) ≈ 0.982 float calibrated (raw_float - cal_offset) * cal_gain;把校准系数写在 EEPROM 或定义一个数组放代码区不要在每次上电时都重新标定。没有恒温槽的同学可以用体温和室温做个两点的粗校准只要保证两个点的原始值稳定修正后精度能到 ±0.2°C 以内。另外需要注意的是自发热会导致传感器读数偏高。DS18B20 正常工作态电流约 1mA如果有持续读取操作温度值可能比环境高 0.20.5°C。产品级做法是降低读取频率单次转换之间留出足够长的空闲时间让传感器回到与环境同温的状态再执行下一次转换。这个偏置是很多人实验报告中没写但实测存在的把结论写进报告就是加分项。本文还有配套的精品资源点击获取
返回列表