所有自学嵌入式:51单片机系列的操作相关器件的代码都汇总到了:https://gitee.com/qin-ruiqian/Qin51
- DS18B20操作流程
- DS18B20数据帧
- 温度存储格式
- 与DS18B20通信的流程图
- 代码实现
- DS18B20.h
- DS18B20.c
- main.c
- 测试效果
DS18B20操作流程
- 初始化:从机复位,主机判断从机是否响应
- ROM操作:ROM指令+本指令需要的读写操作
- 功能操作:功能指令+本指令需要的读写操作
| ROM指令 | 功能指令 |
|---|---|
| SEARCH ROM[F0h] | CONVERT T[44h] |
| READ ROM[33h] | WRITE SCARTCHPAD[4Eh] |
| MATCH ROM[55h] | READ SCRATCHPAD[BEh] |
| SKIP ROM[CCh] | COPY SCRATCHPAD[48h] |
| ALARM SEARCH[ECh] | RECALL E2[B8h] |
| READ POWER SUPPLY[B4h] |
- MATCH ROM[55h],匹配地址,和某个设备单独通信
- SKIP ROM[CCh],由于只和51MCU通信,所以直接跳过ROM,但是如果有多个设备,跳过ROM,就会让所有从机都响应
- ALARM SEARCH[ECh],多设备相连时,如果有设备出现温度报警,它会搜索是哪个设备发出的温度报警
- CONVERT T[44h],温度变换,读取温度之前要进行温度变换,就是将温度数据写入暂存器
- WRITE SCARTCHPAD[4Eh],写暂存器,它会把数据写入到暂存器的Byte2,Byte3,Byte4(TH寄存器,TL寄存器,设置寄存器)
- READ SCRATCHPAD[BEh],读暂存器,当发送指令调用接收字节这个信号时序的时候,那DS18B20会依次地把暂存器的字节全部读出来,一直读到CRC冗余码,如下图
![image]()
但是我们本节只想读温度,所以读两个字节就不再继续执行了。 - COPY SCRATCHPAD[48h],复制暂存器,将暂存器的Byte2-Byte4写入EEPROM,如下图:
![image]()
- RECALL E2[B8h],调用这个指令,从机DS18B20会把EEPROM存储的温度,存入暂存器Byte2-Byte4
- READ POWER SUPPLY[B4h],读取供电模式,读取1位,会分辨到底是直接供电还是寄生供电。
![image]()
DS18B20数据帧

温度存储格式

LS BYTE是低字节,MS BYTE是高字节
如果把高字节放到低字节前面,构成16位数据
前5位是符号位,如果是负数就都是1,如果是正数就都是0,后四位是小数位,BIT 3如果是1,代表0.5,BIT2 0.25……以此类推取一半,实际上它是用补码格式来表示的,(正数补码是其本身,负数补码是除了符号位按位取反再加1)
DS18B20 输出的温度是 16 位二进制数据,其数值格式定义为:
- 最高位(第 15 位)是符号位(0 为正,1 为负);
- 剩下的 15 位中,整数部分占高 11 位,小数部分占低 4 位(分辨率为 1/16℃,最小的小数位表示的是\(2^{-4}\))。
例如:
- 若原始数据为0x01A0(二进制0000 0001 1010 0000),十进制值为416,实际温度为416 / 16 = 26.0℃;
- 若原始数据为0xFFF0(二进制1111 1111 1111 0000),十进制值为-16(按补码存储,补码转回原码还是除了符号位按位取反加1,也就是先除了符号位按位取反得1111 1000 0000 1111,再除了符号位加1得1111 1000 0001 0000),实际温度为-16 / 16 = -1.0℃。
这也就是为什么后面代码中,读取温度的DS18B20_ReadT函数最后转换浮点数的时候要除16.0,因为要读取的是10进制的温度,也就是当前存储的补码实际上是实际温度向左移了4位的结果,所以我们要存储成浮点数,就得除16.0右移回来(见代码实现的DS18B20_ReadT函数的T = Temp / 16.0;语句),如果是小数,按浮点数除法,也能将小数位保存下来。
【补】IEEE浮点数表示
\[V = (-1)^S\times M\times2^E \]float是单精度浮点数,float在C语言(51单片机中)也是4个字节,32个二进制比特位,它被划分为3个字段来解释,如下图:
最高位31位表示浮点位s,s位是0表示整数,s位是1表示负数,exp字段与阶码相关,frac字段与尾数相关
浮点数分为3类
- 规格化的值(Normalized Values)
- 非规格化的值(Denormalized Values)
- 特殊值(Special Values)
浮点数的阶码值决定了它属于哪一类,当阶码字段的二进制数不全为0且不全为1时,此时表示的是规格化的值;当阶码字段的二进制位全为0时,此时表示的数值是非规格化的值,当阶码字段的二进制位全为1时,表示特殊值,特殊值分为两类:(1)无穷大/无穷小;(2)不是数字
阶码字段的取值是从1到254,如下图:
阶码的值并不等于图中e(8个二进制位)所表示的值,而是e的值减去一个偏置量,偏置量的值与阶码字段的位数是相关的。\[E=e - \text{bias} \]如果是单精度浮点数,偏置为\(\text{bias}(\text{float})=2^{8-1}-1=127\)
对于单精度浮点数,阶码的最小值是-126,最大值是127
尾数\(M=1+f\),如下图:
假设DS18B20传来的是1111 1111 1111 0000,通过除16.0不丢失精度,编译器首先确定1111 1111 1111 0000是补码,其原码是-16,然后将其按IEEE标准转换为-16.0,即:
要将 16 位 int 的二进制1111 1111 1111 0000转换为 32 位 float(IEEE 754 单精度浮点数),需按以下步骤推导:步骤 1:确定 16 位 int 的实际值
16 位有符号 int 的二进制
1111 1111 1111 0000是补码形式:
- 最高位为 1(符号位),表示负数;
- 补码转原码:先减 1 得
1111 1111 1110 1111,再取反(符号位不变)得0000 0000 > 0001 0000(十进制 16);- 因此,该 int 的值为 -16(十进制)。
步骤 2:将 - 16 转换为 IEEE 754 单精度浮点数(32 位)
IEEE 754 单精度浮点数格式为:
1位符号位(S) + 8位指数位(E) + 23位尾数位(M)。
- 符号位(S)
负数的符号位为 1,故
S = 1。
- 指数位(E)
- 将 - 16 表示为二进制科学计数法:
-16 = -1.0 × 2^4(因为16 = 2^4,即1.0 × 2^4);- 指数部分为 4,IEEE 754 单精度的指数偏移量为 127,因此指数位值 = 4 + 127 = 131;
- 131 的二进制为
10000011,故E = 10000011。
- 尾数位(M)
科学计数法的有效数字为
1.0,IEEE 754 规定 “隐藏整数位 1”,仅存储小数部分。此处小数部分为 0,因此 23 位尾数位全为 0,即M = 00000000000000000000000。最终 32 位 float 的二进制序列
拼接符号位、指数位、尾数位,结果为:
11000001100000000000000000000000
最后除16.0得到结果
与DS18B20通信的流程图

代码实现
(注:关于LCD1602和单总线通信的相关代码可在我的文章开头提到的开源仓库找到)
DS18B20.h
#ifndef __DS18B20_H__
#define __DS18B20_H__void DS18B20_ConvertT(void); // 转换温度
float DS18B20_ReadT(void); // 读取温度#endif
DS18B20.c
#include <REGX52.H>
#include "OneWire.h"#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE//转换温度
void DS18B20_ConvertT(void)
{OneWire_Init(); //不做确认帧处理OneWire_SendByte(DS18B20_SKIP_ROM); //发送SKIP ROM,因为就一个设备,简单测试一下OneWire_SendByte(DS18B20_CONVERT_T);
}//读取温度,4位小数
//51单片机对浮点数运算特别慢
//但是此处只是读取温度,这点时间还是耗得起
float DS18B20_ReadT(void)
{unsigned char TLSB, TMSB;int Temp; //有符号16位int做中间变量float T; //最终的温度OneWire_Init();OneWire_SendByte(DS18B20_SKIP_ROM);OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//发送完这个指令,就交给从机//开始接收从机发来的指令//传温度的数据,两个字节,16位TLSB = OneWire_ReceiveByte(); //Temperature LSBTMSB = OneWire_ReceiveByte(); //Temperature MSBTemp = (TMSB<<8)|TLSB; //强制转换为有符号的16位int,把TMSB左移8位到高位,然后TLSB和低8位0做或运算,放到低位//除16,向右移4位,见本文的温度存储格式章节T = Temp / 16.0;return T;
}
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"float T; //温度void Delay(unsigned int xms) //@11.0592MHz
{unsigned char i, j;while(xms--){i = 2;j = 199;do{while (--j);} while (--i);}
}void main()
{//上电之前先开启一次温度转换,防止出现默认值温度DS18B20_ConvertT();Delay(1000); //上电后读取温度需要750ms左右,延迟1秒LCD1602_Init();LCD1602_ShowString(0,0, "Temperature:");while(1){DS18B20_ConvertT();T = DS18B20_ReadT();//把浮点数拆分if(T<0){LCD1602_ShowChar(1,0, '-');T= -T; //转换为正数,后面好处理}else{LCD1602_ShowChar(1,0, '+');}LCD1602_ShowNumWithLength(1,1,T,3); //显示整数部分LCD1602_ShowChar(1,4,'.');//把小数部分提取出来,对T乘10000再强制转换为无符号长整数(已经超过无符号整数表示范围)按位取余数就行LCD1602_ShowNumWithLength(1,5,(unsigned long)(T*10000) % 10000, 4);}
}
测试效果
零上温度

零下温度没条件测,放了个冰块在DS18B20旁边,确实温度下降了。







