第3-1讲:温度传感器DS18B20
-
- 学习目的
- 了解DS18B20数字温度传感器的基本原理及其数据格式。
- 掌握STC8A8K64D4与DS18B20单总线通信的程序设计,通信步骤,数据校验等。
- 硬件电路设计
- DS18B20简介
- DS18B20主要特性
- DS18B20简介
- 硬件电路设计
DS18B20是Dallas 半导体公司推出的新一代单总线数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点。DS18B20主要特性如下:
- 测温范围:-55℃ 到 +125℃。
- 通信接口:1-Wire(单总线)。
- 内置温度报警功能。
- 可寄生供电。
- 具有唯一的序列号,在一根通信线可挂接多个,形成总线结构。
-
-
- DS18B20封装
-
-
DS18B20有多种封装,如下图所示,其中我们最常用的封装是TO-92。
图1:DS18B20封装
TO-92封装的DS18B20实物图如下
图2:DS18B20实物(TO-92封装)
市面上常见的还有一种将DS18B20元件封装起来的防水型DS18B20,如下图所示,读者在选型时可以根据实际的应用场景选择使用元件型或防水型的。
图3:防水型DS18B20
-
-
-
- DS18B20寄存器结构
-
-
DS18B20片内包含一个用于存储唯一ID的64位ROM和一个用于存储温度等数据的RAM 数据暂存器。
- ROM 只读存储器:64位,用于存放DS18B20的ID编码,其低8位是单线系列编码(DS18B20的是28H),中间的48位是芯片唯一的序列号,最后8位是以上56位的循环冗余校验(CRC)值。ROM在芯片出厂时设置,用户无法更改。
- RAM 数据暂存器
DS18B20共9个字节RAM,每个字节为8位,如下图所示,我们读取温度数据时就是从这个RAM读取的。
图4:RAM 数据暂存器
- 字节0、字节1:温度转换后的数据值。
- 字节2、字节3:用户EEPROM(温度报警值TH、TL)的镜像,在上电复位时其值将被刷新。
- 字节4:配置寄存器,用于配置温度分辨率,可配置为9、10、11、12位分辨率(DS18B20默认的是12位的分辨率)。
- 字节5、字节6、字节7:保留。
- 字节8:前8个字节的CRC码。
- 温度寄存器的格式
DS18B20测得的温度值以二进制补码的形式存放于温度寄存器中。符号位(S)表示温度是正还是负:对于正数S=0,对于负数S=1。如果DS18B20配置为12位分辨率,则温度寄存器中的所有位都将包含;如果配置为11位分辨率,位0是未定义的;如果配置为10位分辨率,比特1和0是未定义的,而对于9比特分辨率,则比特2、1和0为未定义的。
图5:温度寄存器的格式
下表给出了12位分辨率的数字输出数据和相应的温度读数的示例。主机读取数据后,先将数据补码变为原码,处理正负后再乘以0.0625即可得到温度值。
表1:12位分辨率温度示例
- 配置寄存器(Configuration Register):
配置寄存器用于确定温度值的数字转换分辨率,DS18B20 工作时按此寄存器中的分辨率将温度转换为相应精度的数值。DS18B20默认的是12位的分辨率,通常,我们无需配置该寄存器。
配置寄存器中的R1、R0位的组合决定了温度分辨率,如下表所示。
表2:温度分辨率配置
R1 | R0 | 分辨率(位) | 最大转换时间 | |
0 | 0 | 9 | 93.75ms | (tCONV/8) |
0 | 1 | 10 | 187.5ms | (tCONV/4) |
1 | 0 | 11 | 375ms | (tCONV/2) |
1 | 1 | 12 | 750ms | (tCONV) |
-
-
-
- DS18B20的时序
-
-
DS18B20的时序包括发起时序和读写时序,发起时序完成初始化,读写时序完成数据的读和写。
- 发起时序
发起时序包含发送复位脉冲和存在检测。单片机给DS18B20单总线至少480uS的低电平信号,当DS18B20接到此复位信号后则会在15~60uS后回发一个芯片的存在脉冲(60~240uS的低电平信号),这个过程称为发起时序,如下图所示。发起时序完成后,单片机即可进行读写操作。
图6:发起时序(发送复位脉冲和存在检测)
- 读写时序
DS18B20的读写时序如下图所示。
图7:读写时序
- 写时序
有两种类型的写入时隙:“写入1”时隙和“写入0”时隙。总线主机使用写入1时隙将逻辑1写入DS18B20,使用写入0时隙将逻辑0写入DS18B20。所有写入时隙的持续时间必须至少为60µs,单个写入时隙之间的恢复时间至少为1µs。这两种类型的写入时隙都是由主机将1-Wire总线拉低启动的。
要生成写入1时隙,在将1-Wire总线拉低后,总线主机必须在15µs内释放1-Wire总线。当总线被释放时,上拉电阻将把总线拉高。要生成写入0时隙,在将1-Wire总线拉低后,总线主机必须在持续时间内继续保持总线低电平时隙(至少60µs)。
DS18B20在主机启动写入时隙后持续15µs至60µs的窗口期间对单线总线进行采样。如果总线在采样窗口期间为高电平,则向DS18B20写入1。如果线路为低电平,则向DS18B20写入0。
- 读时序
DS18B20只能在主机发出读取时隙时向主机发送数据。因此,主机必须在发出读取命令后立即生成读取时隙,以便DS18B20能够提供所请求的数据。
所有读取时隙的持续时间必须至少为60µs,时隙之间的恢复时间至少为1µs。主设备将单线总线拉低至少1µs,然后释放总线,从而启动读取时隙。
在主机启动读取时隙后,DS18B20将开始在总线上传输1或0。DS18B20通过保持总线为高电平来发送1,并通过拉低总线来发送0。当发送0时,DS18B20将在时隙结束时释放总线,并且总线将被上拉电阻拉回到其高电平空闲状态。DS18B20的输出数据在启动读取时隙的下降沿后的15µs内有效,因此,主机必须释放总线,然后在从时隙开始的15µs内对总线状态进行采样。
-
-
-
- DS18B20指令
-
-
DS18B20指令如下表所示,表中粗斜体字体的指令是我们常用的指令。
表3:DS18B20指令
指令类型 | 指令 | 功能 | 描述 |
ROM指令 | F0H | 搜索ROM | 在芯片初始化后,搜索指令允许总线上挂接多芯片时用排除法识别所有器件的64位ROM。如果只挂接一个芯片,使用读ROM指令即可。 |
33H | 读ROM | 当总线上只有一个DS18B20时才可以用此指令,允许主设备读取从设备的序列号。 | |
55H | 匹配ROM | 指令后面紧跟着由主设备发出了64位序列号,当总线上有多只DS18B20时,只有与主设备发出的序列号相同的芯片才可以做出反应,其他芯片将等待下一次复位。该指令适应单芯片和多芯片挂接。 | |
CCH | 跳过ROM | 允许主设备不提供64位ROM编码就可以使用功能指令。单芯片时使用,多芯片挂接时使用此指令将会出现数据冲突,导致错误出现。 | |
ECH | 报警搜索 | 在多芯片挂接情况,报警芯片搜索指令只对符合温度高于TH或小于TL报警条件的芯片做出反应。只要芯片不掉电,报警状态将被保持,直到再一次测得温度达不到报警条件为止 | |
功能指令 | 44H | 温度转换 | 用以启动一次温度转换。温度转换指令被执行,产生的温度转换结果数据以2个字节的形式被存储在高速暂存器中,而后DS18B20保持等待状态。 |
4EH | 写暂存器 | 向DS18B20 的暂存器写入数据,开始位置在TH 寄存器(暂存器的第2个字节),接下来写入TL 寄存器(暂存器的第3 个字节),最后写入配置寄存器(暂存器的第4 个字节)。 | |
BEH | 读暂存器 | 读取DS18B20暂存器的内容(共9个字节)。 | |
48H | 拷贝暂存器 | 将TH、TL和配置寄存器的数据拷贝到EEPROM中保存。 | |
B8H | 复制EEROM | 将TH、TL和配置寄存器的数据从EEPROM拷贝到暂存器。 | |
B4H | 读电源模式 | 主设备发出此指令,然后发出一个读取时隙,以确定总线上是否有任何DS18B20正在使用寄生电源。在读取时隙期间,寄生供电的DS18B20将拉低总线,外部供电的DS18 B20将使总线保持高电平。 |
-
-
- DS18B20接口电路
-
IK-64D4开发板上设计了DS18B20温度传感器接口(该接口和DHT11温湿度传感器共用),电路如下图所示。
图8:DS18B20接口电路
DS18B20占用的STC8A8K64D4的引脚如下表:
表4:DHT11引脚分配
DS18B20 | 引脚 | 说明 |
DS18B20数据 | P7.0 | 和DAC电路的输入共用 |
-
- 软件设计
本节描述的应用步骤是针对总线挂载单个DS18B20温度传感器的情况,对于总线挂载多个DS18B20,应用的场景较少,这里不予描述。
-
-
- DS18B20应用步骤
-
使用DS18B20时,最常用的操作是读取ROM获取串号和读取温度,DS18B20读ROM流程的流程如下图所示。首先由主机发起复位并等待从机响应,由此完成DS18B20存在检测,接着主机发送读ROM指令读取64位(8个字节)数据,之后进行CRC8校验,由此判断读取的数据是否正确。
图9:读ROM流程
DS18B20读取温度的流程如下所示。读取温度数据需要执行两次工作周期,第1个周期发送温度转换指令完成温度转换,第2个周期发送读取暂存器指令读出暂存器9个字节数据,之后进行校验,校验成功后计算出温度。
读取温度时,如果我们不需要校验,也可以只读取暂存器中前2个字节的温度数据,而不必将所有数据都读出来。
图10:读温度流程
- 注意:在编写 DS18B20测温程序时,向 DS18B20发指令后,需要等待DS1820响应,这时候切记不要用死循环去等待响应,因为一旦DS18B20接触不好或断线,将没有返回信号,这会导致程序进入死循环。正确的操作是严格按照时序操作,下面是发起操作中单片机发送复位脉冲后等待DS18B20响应的代码,读者可以看一下他们的区别。
- 典型的死循环等待“while(DATA_PIN);”,如果DS18B20无响应(DATA_PIN电平一直为高电平),程序会一直在这循环查询引脚状态。
- 按照时序延时后查询引脚状态
delay_us(70); //按照时序等待70us
if(DATA_PIN) // DATA_PIN为高电平,表示DS18B20未响应
{
return false; //DS1820无响应,程序返回
}
-
-
- DS18B20温度读取实验
-
- 注:本节的实验是在“实验2-6-1:串口1数据收发实验”的基础上修改,本节对应的实验源码是:“实验2-20-1:DS18B20温度读取(串口发送温度)”。
-
-
- 实验内容
-
-
DS18B20使用默认的12位温度分辨率,上电启动后,首先读取DS18B20的64位ROM并进行校验,校验成功则通过串口输出读取的数据。之后在主循环中每秒读取一次温度,读取温度时,分别使用只读取暂存器中的温度数据(2个字节)和读取全部暂存器的内容并校验(9个字节)这两种方式,读取的温度数据计算为温度后通过串口输出。
-
-
-
- 代码编写
-
-
- 新建一个名称为“ds18b20.c”的文件及其头文件“ds18b20.h”并保存到工程的“Source”文件夹,并将“ds18b20.c”加入到Keil工程中的“SOURCE”组。
- 引用头文件
因为在“main.c”文件中使用了“ds18b20.c”文件中的函数,所以需要引用下面的头文件“ds18b02.h”。
代码清单:引用头文件
- //引用DS18B20的头文件
- #include "ds18b20.h"
- DS18B20复位和存在检测
按照前文描述的发起时序编写代码如下。主机拉低信号引脚电平并延时480uS发送复位脉冲,接着拉高信号引脚电平,延时70uS后读取信号引脚电平状态,若为低电平,则DS18B20存在,若仍然为高电平,则说明无DS18B20响应,返回存在检测失败。
存在的时间宽度至少为480uS,因此,读取后DS18B20返回的低电平后,还需要延时410us(前面已经延时了70uS)。
代码清单:DS18B20复位和存在检测
- /**************************************************************************************
- * 描 述 : DS18B20初始化:主机发送复位脉冲和设备存在检测
- * 参 数 : 无
- * 返回值 : true:DS18B20正确响应;false:DS18B20未响应
- **************************************************************************************/
- bool DS18B20_Initialize(void)
- {
- DATA_PIN = 0; //发送低电平复位信号
- delay_us(480); //延时至少480us
- DATA_PIN = 1; //释放数据线
- delay_us(70); //等待70us
- if(DATA_PIN) //检测存在脉冲
- {
- return false;
- }
- delay_us(410); //等待设备释放数据线
- return true;
- }
- 编写读、写一个字节的函数
按照前文描述的写时序编写代码如下。注意发送1和0的时候时序是不一样的,发送1时主机拉低信号引脚电平并延时1uS,接着拉高信号引脚电平并延时60uS;发送0时主机拉低信号引脚电平并延时60uS,接着拉高信号引脚电平并延时10uS。
代码清单:写1字节函数
- /**************************************************************************************
- * 描 述 : 向DS18B20写1字节数据
- * 参 数 : dat[in]:写入的数据
- * 返回值 : 无
- **************************************************************************************/
- void DS18B20_WriteByte(u8 dat)
- {
- u8 i;
- for (i=0; i<8; i++)
- {
- if (dat & 0x01) //写1
- {
- DATA_PIN = 0; //开始时间片
- delay_us(1); //延时等待
- DATA_PIN = 1;
- delay_us(60); //等待时间片结束
- }
- else //写0
- {
- DATA_PIN = 0; //开始时间片
- delay_us(60); //延时等待
- DATA_PIN = 1;
- delay_us(10); //等待时间片结束
- }
- dat >>= 1; //准备好待发送的下一个位
- }
- }
读取数据时,主机拉低信号引脚电平并延时1uS,接着拉高引脚电平延时1us后对信号引脚采样并将结果保存到变量“dat”。DS18B20要求所有读取时隙的持续时间必须至少为60us,因此,在读取一个位后,延时60uS,代码清单如下。
代码清单:读1字节函数
- /**************************************************************************************
- * 描 述 : 从DS18B20读1字节数据
- * 参 数 : 无
- * 返回值 : 读取的1字节数据
- **************************************************************************************/
- u8 DS18B20_ReadByte(void)
- {
- u8 i;
- u8 dat = 0;
- for (i=0; i<8; i++)
- {
- dat >>= 1;
- DATA_PIN = 0; //开始时间片
- delay_us(1); //延时等待
- DATA_PIN = 1; //准备接收
- delay_us(1); //接收延时
- if (DATA_PIN) dat |= 0x80; //读取数据
- delay_us(60); //等待时间片结束
- }
- return dat;
- }
- 编写读ROM函数
按照DS18B20应用步骤中读ROM的步骤,编写的读ROM代码如下所示。
代码清单:读ROM(8个字节)
- /**************************************************************************************
- * 描 述 : 读取ROM
- * 参 数 : p_dat[in]:保存读取的数据
- * 返回值 : DS18B20_SUCCESS:读取成功;DS18B20_DATA_ERR:数据校验错误
- **************************************************************************************/
- u8 DS18B20_ReadID(u8 *p_dat)
- {
- u8 i;
- if(DS18B20_Initialize() == false)
- {
- return DS18B20_NACK;
- }
- DS18B20_WriteByte(0x33); //读ROM指令
- for(i=0;i<8;i++) //因为是8个字节,所以要读取8次
- {
- p_dat[i]=DS18B20_ReadByte();
- }
- //CRC8校验
- if (CRC8(p_dat, 7) == p_dat[7])
- {
- return DS18B20_SUCCESS;
- }
- else
- {
- return DS18B20_DATA_ERR;
- }
- }
- 读取温度数据
我们编写了两个温度读取函数“DS18B20_ReadTemperature()”和“DS18B20_ReadAll ()”,他们的区别如下:
- DS18B20_ReadTemperature():仅读取暂存器中的前2个字节温度数据,无校验。
- DS18B20_ReadAll ():读取暂存器全部数据,共9个字节,读取后,进行CRC8校验。
两个温度读取函数的代码清单如下。
代码清单:读温度数据(仅读取暂存器中的前2个字节温度数据,无校验)
- /**************************************************************************************
- * 描 述 : 温度读取函数,只读取温度,即9个字节中的前2个字节
- * 参 数 : 无
- * 返回值 : 温度值
- **************************************************************************************/
- u8 DS18B20_ReadTemperature(u8 *p_dat)
- {
- if(DS18B20_Initialize() == false)
- {
- return DS18B20_NACK;
- }
- DS18B20_WriteByte(0xCC); //跳过ROM命令
- DS18B20_WriteByte(0x44); //开始转换命令
- delay_ms(755); //等待转换完成
- if(DS18B20_Initialize() == false)
- {
- return DS18B20_NACK;
- }
- DS18B20_WriteByte(0xCC); //跳过ROM命令
- DS18B20_WriteByte(0xBE); //读取温度寄存器等(共可读9个寄存器) 前两个就是温度
- p_dat[0] = DS18B20_ReadByte(); //读温度低字节
- p_dat[1] = DS18B20_ReadByte(); //读温度高字节
- return DS18B20_SUCCESS;
- }
代码清单:读取暂存器全部内容(共9个字节,第9个字节为CRC校验)
- /**************************************************************************************
- * 描 述 : 读取暂存器的全部内容(9个字节)并校验
- * 参 数 : 无
- * 返回值 : DS18B20_SUCCESS:读取成功;DS18B20_DATA_ERR:数据校验错误
- **************************************************************************************/
- u8 DS18B20_ReadAll(u8 *p_dat)
- {
- u8 i;
- if(DS18B20_Initialize() == false)
- {
- return DS18B20_NACK;
- }
- DS18B20_WriteByte(0xCC); //跳过ROM命令
- DS18B20_WriteByte(0x44); //开始转换命令
- delay_ms(755); //等待转换完成
- if(DS18B20_Initialize() == false)
- {
- return DS18B20_NACK;
- }
- DS18B20_WriteByte(0xCC); //跳过ROM命令
- DS18B20_WriteByte(0xBE); //读取温度寄存器等(共可读9个寄存器) 前两个就是温度
- for(i=0;i<9;i++) //因为是9个字节,所以要读取9次
- {
- p_dat[i]=DS18B20_ReadByte();
- }
- if (CRC8(p_dat, 8) == p_dat[8]) //CRC8校验
- {
- return DS18B20_SUCCESS;
- }
- else
- {
- return DS18B20_DATA_ERR;
- }
- }
- 温度计算
主机读取数据后,先将数据补码变为原码,根据数据中的“S位(高5位,负温是全部为1,正温时全部为0)”判断是正温还是负温,之后乘以0.0625即可得到测量的温度值。
代码清单:温度计算函数
- /**************************************************************************************
- * 描 述 : 读取的温度数据转换为温度
- * 参 数 : 无
- * 返回值 : 温度值
- **************************************************************************************/
- float DS18B20_TempConvert(u8 LSB, u8 MSB)
- {
- float temperature;
- u16 dat;
- dat = LSB | (MSB << 8);
- if (dat & 0x8000) //最高位为1,是负温
- {
- dat = ~dat + 1;
- temperature = 0.0 - (dat / 16); //DS18B20使用的是默认12位分辨率,所以乘以0.0625,即除16
- return temperature;
- }
- temperature = dat / 16;
- return temperature;
- }
- 主函数
主函数中,执行相关的初始化操作后,读取DS18B20的64位ROM,校验正确则通过串口输出。程序进入主循环后以1秒的间隔分别使用两种温度读取函数读取温度数据,计算为温度后通过串口输出,代码清单如下。
代码清单:主函数
- /**********************************************************************************
- 功能描述:主函数
- 参 数:无
- 返 回 值:int类型
- ***********************************************************************************/
- int main(void)
- {
- u8 i;
- float temp_value;
- u8 id_buf[8];
- u8 dat_buf[9];
- P2M1 &= 0xBF; P2M0 &= 0xBF; //设置P2.6为准双向口(指示灯D1)
- P3M1 &= 0xFE; P3M0 &= 0xFE; //设置P3.0为准双向口(串口1的RxD)
- P3M1 &= 0xFD; P3M0 |= 0x02; //设置P3.1为推挽输出(串口1的TxD)
- P7M1 &= 0xFE; P7M0 &= 0xFE; //设置P7.0为准双向口(DS18B20数据)
- uart1_init(); //串口1初始化
- EA = 1; //使能总中断
- //读取DS18B20 ROM,串口输出读取的ROM数据
- if(DS18B20_ReadID(id_buf) == DS18B20_SUCCESS)
- {
- printf("FAMILY CODE: 0x%.2bX\r\n",id_buf[0]); //串口打印FAMILY CODE
- printf("SERIAL NUMBER: "); //串口打印序列号
- for(i=1;i<7;i++) //因为是6个字节,所以要循环6次
- {
- printf("0x%.2bX ",id_buf[i]);//串口打印序列号
- }
- printf("\r\nCRC: 0x%.2bX\r\n",id_buf[7]);// 串口打印ROM数据中的CRC
- }
- else //读取ROM失败,闪烁LED1指示灯提示
- {
- led_toggle(LED_1);
- delay_ms(100);
- }
- while(1)
- {
- //读取温度:只读取暂存器中2个字节的温度数据,没有校验
- if(DS18B20_ReadTemperature(dat_buf) == DS18B20_SUCCESS) //数据正确
- {
- temp_value = DS18B20_TempConvert(dat_buf[0],dat_buf[1]); //计算温度
- printf("温度: %.2f℃\r\n",temp_value); //串口打印温度
- }
- else
- {
- printf("read temperature err!");
- }
- delay_ms(1000); //延时,方便观察数据
- //读取暂存器全部数据,若校验正确则计算温度
- if(DS18B20_ReadAll(dat_buf) == DS18B20_SUCCESS) //数据正确
- {
- temp_value = DS18B20_TempConvert(dat_buf[0],dat_buf[1]);//计算温度
- printf("温度: %.2f℃\r\n",temp_value); //串口打印温度
- }
- else
- {
- printf("read temperature err!");
- }
- delay_ms(1000); //延时,方便观察数据
- led_toggle(LED_1); //指示灯D1状态翻转,指示程序的运行
- }
- }
-
-
- 硬件连接
-
-
本实验需要使用DS18B20温度传感器,开发板需要将P7.0通过跳线连接到DHT11(DS18B20和DHT11复用单片机的GPIO P7.0),如下图所示。
图11:硬件连接
-
-
-
- 实验步骤
-
-
- 解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-20-1:DS18B20温度读取(串口发送温度)”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
- 双击“…\ds18b20\project”目录下的工程文件“ds18b20.uvproj”。
- 点击编译按钮编译工程,编译成功后生成的HEX文件“ds18b20.hex”位于工程的“…\ds18b20\Project\Object”目录下。
- 打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
- 电脑上打开串口调试助手,选择开发板对应的串口号,将波特率设置为9600bps。
- 程序运行后,可以在串口调试助手接收框中观察到开发板发送的ROM数据(程序启动时读取)和温度(主循环中读取),如下图所示。
图12:串口接收的温度信息