当前位置: 首页 > news >正文

【自学嵌入式:51单片机】用单总线与温度传感器DS18B20通信

所有自学嵌入式: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数据帧

image

温度存储格式

image
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个字段来解释,如下图:
image
最高位31位表示浮点位s,s位是0表示整数,s位是1表示负数,exp字段与阶码相关,frac字段与尾数相关
image
浮点数分为3类

  • 规格化的值(Normalized Values)
  • 非规格化的值(Denormalized Values)
  • 特殊值(Special Values)

浮点数的阶码值决定了它属于哪一类,当阶码字段的二进制数不全为0且不全为1时,此时表示的是规格化的值;当阶码字段的二进制位全为0时,此时表示的数值是非规格化的值,当阶码字段的二进制位全为1时,表示特殊值,特殊值分为两类:(1)无穷大/无穷小;(2)不是数字
阶码字段的取值是从1到254,如下图:
image
阶码的值并不等于图中e(8个二进制位)所表示的值,而是e的值减去一个偏置量,偏置量的值与阶码字段的位数是相关的。

\[E=e - \text{bias} \]

如果是单精度浮点数,偏置为\(\text{bias}(\text{float})=2^{8-1}-1=127\)
对于单精度浮点数,阶码的最小值是-126,最大值是127
尾数\(M=1+f\),如下图:
image
假设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)

  1. 符号位(S)

负数的符号位为 1,故 S = 1

  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
  1. 尾数位(M)

科学计数法的有效数字为 1.0,IEEE 754 规定 “隐藏整数位 1”,仅存储小数部分。此处小数部分为 0,因此 23 位尾数位全为 0,即 M = 00000000000000000000000

最终 32 位 float 的二进制序列

拼接符号位、指数位、尾数位,结果为:
11000001100000000000000000000000
最后除16.0得到结果

与DS18B20通信的流程图

MAXIM DS18B20-中文_14

代码实现

(注:关于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);}
}

测试效果

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

http://www.sczhlp.com/news/4630/

相关文章:

  • lua5.1位操作
  • 第十八章 获得大量免费流量的核心:关键词
  • 读书笔记:Oracle锁机制解析:从闩锁到死锁的实战指南
  • 大数据之路:阿里巴巴大数据实践——元数据与计算管理
  • 题解:P13080 [NOISG 2017] Best Places / 最佳选址
  • 题解:P13101 致谢 | FJCPC2025全体参赛选手赛事staff
  • bootstrap 5的渐变背景色和文字颜色
  • 《AutoCAD 给排水插件神器!T30天正 v1.0 一键布图+流量计算实操演示》
  • [笔记]中国剩余定理(CRT)和扩展中国剩余定理(exCRT)
  • [CQOI2018] 解锁屏幕
  • spring boot + vue +MySQL 项目的服务器部署
  • FDM下载神器:免费多线程下载工具,速度90+M/S,完美替代1DM!
  • J1939协议
  • 设备端语音处理技术解析
  • 对于依赖注解,@Autowired 和 @Resource 有什么不同?
  • 第三周假期进度报告(7.27 - 8.2)
  • MySQL 24 MySQL是怎么保证主备一致的?
  • centos mongodb 第十七节课 常用的操作符
  • 题解-CSPS模拟赛8 T2
  • 汽车助力转向的的助力特性曲线三维图
  • 国内用户如何用手机进行YouTube直播? - 教程
  • Lab8 Locks
  • Java练习Day1
  • 一期集训总结
  • Diffusion (DDPM、DDIM) 原理 - rzy
  • php实现一个简单的MySQL分页
  • 2.6 基本运算符
  • 2025.8.2模考
  • Spring Boot中的分布式缓存方案
  • 跨行星共识(IPC)实现区块链突破性扩展