十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

51单片机DS18B20温度采集:单总线时序、C语言驱动与Proteus仿真全流程

51单片机DS18B20温度采集:单总线时序、C语言驱动与Proteus仿真全流程 简介这是基于51单片机的DS18B20温度采集实例采用C语言编写配套Proteus仿真工程与Keil源码适合单片机初学者、电子爱好者学习也可作为课程设计或毕业设计的参考。压缩包为RAR格式共14个文件大小仅14KB内容紧凑。核心包括C语言源程序、可直接烧录的HEX文件、Proteus仿真工程uv2/opt/plg以及Keil启动文件其余为编译生成的列表、目标文件和备份文件。目前已有400人浏览学习虽然体积小巧但麻雀虽小五脏俱全。通过这份资料读者可以学习DS18B20的单总线通信时序与温度读取流程在Proteus中运行仿真还可直观观察LCD显示效果同时借助完整源码能理解51单片机C语言工程的编译、烧录与调试过程节省自行搭建环境的时间。1. 温度采集DS18B2051单片机C语言实例里最该先跑通的单总线驱动温度采集DS18B20是51单片机课程设计里最常见的一道题也是Proteus仿真中被问得最多的案例之一。DS18B20只有一根数据线却能在单总线上同时完成命令下发、温度读取和CRC校验这让它在教学上比I2C和SPI更有“时序感”。多数初学者第一次把程序烧进去后看到85℃或一长串不正常数据问题往往不在传感器本身而在C语言驱动里那几十微秒的延时估算以及Proteus和Keil之间晶振频率是否一致。下面把“DS18B2051单片机”的最小工程拆开从单总线时序原理到可编译的C语言代码再到Proteus仿真里从原理图到HEX加载的完整流程最后讲几个实物调试才会用到的参数修正技巧。2. DS18B20单总线时序与ROM命令51单片机最小接法2.1 没有时钟线的总线靠什么对齐DS18B20使用的1-Wire总线没有SCL时钟线主从设备之间靠约定好的时间片来同步。每一位传输的标准时间片大约60~120us位与位之间还需要至少1us的恢复时间。主机是总线上的唯一发起者DS18B20只能被动响应主机拉低线缆表示开始一个时间片随后根据写0、写1或读操作来决定释放还是继续拉低。和I2C对照着学会更容易理解I2C有时钟线主机在每个SCL上升沿可以稳定判断SDA数值1-Wire的时钟信息则全部编码在电平跳变和持续时长里。所以DS18B20驱动代码中最要紧的不是“发指令”而是把每次拉低和释放的持续时间卡准。对51单片机而言这些时间由延时循环和_NOP_()指令共同实现而延时是否准确又直接受编译器优化等级影响这也是后面调试时第一个要检查的地方。2.2 最小硬件接法与Proteus中的元件选择2.2.1 电源接法外部供电优先DS18B20的供电范围是3.0~5.5V51单片机开发板通常直接提供5V。最省事的接法是VDD接VCC、GND共地、DQ通过上拉电阻接VCC。上拉电阻的典型值是4.7kΩ它的作用不是把电平拉高而是在开漏结构的单总线上提供一个高电平默认态。没有这个电阻复位脉冲释放后DQ一直浮空读到的存在脉冲和温度值都会不稳定。DS18B20也支持寄生供电也就是只接DQ和GND两根线靠数据线内部电容在空闲时存电。仿真和实物调试中都不建议一上来就用寄生供电温度转换期间主机必须主动维持强上拉否则电压跌落会让转换结果错乱。等外部供电方案跑通后再尝试寄生供电不迟。2.2.2 Proteus元件库里怎么找和怎么接在Proteus的元件选择框里输入DS18B20可以得到型号为DS18B20的仿真模型引脚依次是DQ、VDD、GND。把AT89C51或STC89C52放入原理图后DQ接在P3.7这类空闲I/O上VDD接VCCGND接GND从DQ到VCC之间放一个阻值为4.7k的电阻。这个电阻在Proteus里输入RES后选择普通电阻即可不需要专门找排阻。需要注意Proteus中的单片机模型自带电源引脚默认已经连接到VCC/GND原理图上不需要额外画出VCC和GND符号。要让仿真更接近实物可以再补一个0.1uF去耦电容到单片机电源附近但这不影响DS18B20的温度逻辑。2.3 复位脉冲与存在脉冲一次握手才算通信开始DS18B20每次通信都以复位脉冲开头。主机先把DQ拉低并保持至少480us然后释放总线DS18B20检测到这个低电平后会在15~60us内把DQ拉低60~240us这个响应叫存在脉冲。主机在释放总线后等待一段时间再去读DQ读到低电平就说明总线上有设备。这个流程在C语言里至少要拆成四步拉低、延时、释放、延时后读电平。最容易出错的是释放后读取的时机如果主机释放后立刻读存在脉冲还没出现会把高电平误判为无设备。在12MHz标准模式下释放后延时30us左右再读取比较稳妥。// 复位脉冲与存在脉冲检测返回1表示检测到DS18B20 bit ds18b20_reset(void) { bit presence 1; DQ 0; delay_us(250); // 拉低约500us满足至少480us要求 delay_us(250); DQ 1; // 释放总线 delay_us(30); // 等待DS18B20拉低DQ presence DQ; // 读存在脉冲低电平应答 delay_us(200); // 等时间片结束 return presence; }这段代码里的两个delay_us(250)叠加是500us为什么不用一个500us循环因为在51单片机上单次函数调用本身也要消耗机器周期分成两段更容易在延时基准出错时单独修正。presence读取放在释放后30us这个窗口正好落在DS18B20允许的15~60us响应区间内。如果换成11.0592MHz晶振延时参数要按机器周期比例重算不能直接照抄。2.4 功能指令表与读写时间片先查指令再写驱动DS18B20的协议分两层ROM指令负责设备寻址功能指令负责温度操作。单设备场景下最常用的是跳过ROM和读暂存器两条指令多设备并联时才需要读ROM和匹配ROM。指令名指令代码典型用途跳过ROMSkip ROM0xCC总线上只有一个DS18B20时直接操作读ROMRead ROM0x33读取64位序列号用于枚举总线设备匹配ROMMatch ROM0x55多设备时先发送目标序列号再操作温度转换Convert T0x44启动一次温度转换结果存入暂存器读暂存器Read Scratchpad0xBE读取温度、告警阈值、配置字节和CRC读写一位的过程看起来简单实际卡住很多人的是“读时序”的采样点。写一位时主机先把DQ拉低约6us写1就释放让上拉电阻把总线拉高写0则继续保持低到时间片结束。读一位时主机同样先拉低但必须在释放后的15us内完成采样因为DS18B20只在这个窗口内把0拉到低电平之后总线会被上拉电阻恢复成1。// 读一位的采样窗口示意DQ为输入方向 bit read_bit(void) { bit dat; DQ 0; _nop_(); _nop_(); // 拉低持续约2us DQ 1; // 释放 _nop_(); _nop_(); // 等待2us后采样 dat DQ; delay_us(50); // 确保时间片总长60us以上 return dat; }这个示意代码把“释放后等待”放在两个NOP上保证采样发生在DS18B20仍有效驱动总线的15us窗口内。实际驱动中这几十微秒的时序还会受51单片机I/O翻转速度和Keil编译优化影响因此“能仿真跑通”和“能实物跑通”之间通常就差这几个参数后面章节会给出完整的字节级读写函数。3. 51单片机的DS18B20驱动代码C语言实现与延时参数调整3.1 引脚定义与延时基准写驱动前先确定两件事DQ接在哪个引脚、单片机晶振频率和机器周期模式是什么。下面代码默认DQ接P3^7晶振为12MHz指令周期为标准12T模式也就是一个机器周期1us。内部两个_NOP_()加一起约2usdelay_us(n)用while循环和NOP组合每轮约2us时长的循环体。#include reg51.h #include intrins.h #define uchar unsigned char #define uint unsigned int sbit DQ P3^7; /***** 延时函数n为循环次数每次约2us12MHz12T *****/ void delay_us(uchar n) { while (n--) { _nop_(); _nop_(); } } void delay_ms(uint ms) { uint i, j; for (i 0; i ms; i) for (j 0; j 120; j); }delay_us的参数是uchar最大255次循环对应约510us。如果复制到其他工程时把参数改成uint要警惕Keil在复杂表达式下的优化避免延时被裁剪。delay_ms里内层循环120次是在12MHz下粗略凑出1ms它只用于主循环节流不参与DS18B20高速时序所以精度要求不高。3.2 三个基础函数复位、写字节、读字节在上一章的示意代码基础上把位操作封装成字节函数就是驱动的主体。下面这三个函数可以直接放进工程main函数里调用读温度函数即可。/***** DS18B20复位返回0表示存在 *****/ bit ds18b20_reset(void) { bit presence; DQ 0; delay_us(250); delay_us(250); // 总低电平时间约500us DQ 1; delay_us(30); // 释放后等待存在脉冲 presence DQ; // 读到0表示DS18B20应答 delay_us(200); return presence; } /***** 写一个字节低位在前 *****/ void ds18b20_write_byte(uchar dat) { uchar i; for (i 0; i 8; i) { DQ 0; _nop_(); _nop_(); // 低电平约2us DQ dat 0x01; // 写1则释放写0则继续拉低 delay_us(60); // 保持到时间片结束 DQ 1; // 释放总线 _nop_(); dat 1; } } /***** 读一个字节低位在前 *****/ uchar ds18b20_read_byte(void) { uchar i, dat 0; for (i 0; i 8; i) { dat 1; DQ 0; _nop_(); DQ 1; // 释放后立即采样 _nop_(); if (DQ) dat | 0x80; delay_us(50); // 等待当前时间片结束 } return dat; }逻辑说明写字节函数里先看dat最低位为1就释放总线由4.7k上拉电阻抬高为0则继续保持低这正好对应写1和写0的时间片。读字节函数把dat先右移一位再判断DQ让最低位依次从bit7方向填进结果整个过程低位在前和DS18B20的数据格式一致。两个函数都加了时间片尾部的延时避免连续位操作时位间隙太短。参数说明12MHz、12T模式下_NOP_()约1us所以拉低2us满足写时序的“至少1us”。如果改用6T内核或晶振频率变化要把NOP组合改成定时器延时或查表修正尤其是写0时的60us不能靠编译器猜。参数12MHz推荐值设计依据复位低电平480~500usDS18B20要求最低480us写时间片拉低约2us满足写位前至少1us要求写0保持低60us保证从机在15~60us采样窗口内读到0读时序采样点释放后约1~2us必须在15us内完成采样3.3 温度读取主流程转换、等待、读暂存器DS18B20读一次温度需要两轮通信。第一轮发跳过ROM0xCC和温度转换0x44并等待转换完成第二轮再次复位发跳过ROM和读暂存器0xBE连续读取9个字节其中前两个字节就是温度值。单设备场景用跳过ROM省去64位ROM号匹配代码简单很多。/***** 返回温度原始值低12位有效 *****/ int ds18b20_read_temp_raw(void) { uchar tl, th; uint timeout 0; int temp; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0x44); // 启动温度转换 while (DQ 0) { // 转换期间DQ被拉低完成后恢复高 if (timeout 60000) break; // 防止DS18B20故障时死循环 } ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器从第0字节开始 tl ds18b20_read_byte(); th ds18b20_read_byte(); DQ 1; temp (th 8) | tl; // 高字节是低字节的符号扩展 return temp; }while(DQ 0)这一步值得多说DS18B20在温度转换期间会主动把DQ拉低转换完成后再释放。12位分辨率下转换时间最长750ms主循环等在这里不会有问题但严谨做法是加超时计数。上面timeout的60000次循环在仿真环境中足够判断故障实物调试时如果出现转换时间不足可以放大到200000再观察。温度换算原始值乘以0.0625就是摄氏度比如0x0191对应25.0625°C。如果只做数码管显示可以用移位和取余算出一位小数避免在51上直接引入float库。负温度时原始值以补码形式存放在两个字节中C51的int类型可以直接处理负数但数码管显示时要单独处理负号。3.4 优化等级、负温度和CRC校验三个容易被忽略的坑第一个坑是Keil的优化等级。默认优化下编译器可能把延时循环里的_NOP_()当成无副作用代码合并结果实际延时只剩不到一半。调驱动时把Options for Target里的优化等级改成Level 0再编译下载很多“仿真正常、实物乱码”的问题会直接消失。调通后再慢慢恢复优化等级。第二个坑是负温度。DS18B20在零下的原始值是0xFF90这类形式因为C51的int是16位有符号数直接读取th、tl组合后temp已经是负数补码。不少教材把temp声明成unsigned int负温度会变成65424显示时出现巨大正数这个问题在北方冬天做实物最容易遇到。第三个坑是CRC校验。读暂存器后第9个字节是CRC校验失败说明总线上数据传输有误。Proteus仿真基本不会触发CRC错误但实物线缆过长或干扰大时会读错温度。可以在驱动里加一个查表CRC函数读满9个字节后校验一遍失败就丢弃本次结果并重新发起转换。4. Proteus仿真从原理图、HEX加载到温度读数验证4.1 元器件清单与最小电路在Proteus 8 Professional里新建工程后按下表放置元件。单片机选择AT89C51DS18B20在元件搜索框输入即可找到。Proteus元件关键字数量说明AT89C51151单片机需要设置12MHz时钟DS18B201温度传感器仿真模型RES14.7kΩ上拉电阻CAP222pF晶振负载电容可选CAP-ELEC110μF电源滤波电容可选连线时把DS18B20的DQ连到P3^7VDD接VCCGND接GND。在DQ和VCC之间放电阻并设置Resistance为4.7k。如果希望看到温度值可以在P1口接一个数码管或者直接使用P1口的LED观察二进制变化后者验证驱动更直接。4.2 晶振频率与Keil工程必须一致双击AT89C51在Edit Component对话框里把Clock Frequency设为12MHz。很多仿真时序错乱的原因不是代码写错而是这里和Keil里的预设不一致。需要注意Proteus中单片机模型有时会有默认频率必须显式改成与驱动代码一致的数值不能依赖默认值。Keil工程里也要同步设置Options for Target的目标晶振Xtal(MHz)填12。这个值虽然不参与实际编译结果但Keil调试器在模拟运行时会按这个频率估算延时两边不一致单步调试时会发现时间计算对不上。4.3 生成HEX文件并加载到ProteusKeil里编译工程前先勾选Output标签页的Create HEX File。编译成功后在Proteus里双击单片机在Program File一栏选择生成的.hex文件。需要注意HEX路径不要放在中文目录下Proteus 8对中文路径的兼容性一般很多加载失败都是路径引起的。加载完点击左下角运行按钮。数码管或LCD显示当前温度前可以在main函数里只做一次读取然后把温度原始值的低字节送到P1口观察。Proteus的VIRTUAL TERMINAL组件也能直接显示字符串但51单片机串口波特率要和虚拟终端一致例如9600或4800。4.4 温度调不下来检查这四个仿真细节运行后如果读到的温度一直不变按顺序检查DS18B20是否放在原理图里并与P3^7相连、上拉电阻是否真的生效、程序是否烧录成功、时钟频率是否一致。Proteus中的DS18B20模型默认会按照环境温度返回数据点击元件后在属性窗口的Temperature一栏修改数值就能模拟升温降温这个交互对验证程序逻辑非常有用。另外Proteus仿真中不存在线缆电容和接触电阻所以代码时序只要满足理论窗口就能通过。实物移植时若出问题问题多半在延时基准和上拉电阻值而不是代码里的逻辑。若仿真里固定读到85℃说明程序没有真正完成温度转换读到的只是DS18B20上电复位后的默认暂存器值。5. 三个能让DS18B20温度采集更稳定的调试技巧5.1 在Proteus里动态改温度验证负温度和显示逻辑运行仿真后点击原理图中的DS18B20在属性窗口的Temperature一栏直接输入新的温度值。把25改成-10观察程序能不能正确区分正负。如果只改了整数部分说明换算代码没有处理符号扩展。一个稳妥的显示换算方式是先判断正负再对绝对值做temp * 10 / 16得到十分之一度单位最后用查表法把整数部分和小数位拆出来。5.2 用逻辑探头看时序波形快速定位延时基准给DQ节点接一个Logic Probe或虚拟示波器可以看到复位脉冲的低电平宽度。测量一下复位时序中的低电平是否接近500us写时序每个低电平时间片是否在60us以上。这个方法能快速分辨“程序逻辑问题”和“延时基准问题”。虚拟示波器看到的时序再完美也不能代表实物线缆上的真实波形但至少能排除一半的软件错误。5.3 实物移植时改这三个参数比改逻辑更有效上拉电阻从4.7k改成2.2k适合数据线较长或供电电压偏低的场景读时序采样点从两个NOP改成单个NOP适合I/O翻转速度较快的STC系列温度转换等待从轮询DQ改为固定延时750ms加一次查询适合后续加入多任务调度避免单总线一直占着CPU。再往后做闭环温度控制时这套驱动可以直接作为采集层把温度原始值传给PID或开环PWM模块就扩展成51系列单片机闭环温度控制实验了。一位小数查表法的实现也很短先取出temp的低4位用它查一个长度为16的常量表得到0.0、0.1、0.1、0.2、0.3、0.3……对应的十分位。再把整数部分右移4位得到十位和个位。整个换算过程只用移位和查表不调用乘除法在51单片机的中断里也能稳定执行。本文还有配套的精品资源点击获取
返回列表