前言
大家好,我是林白柏;
希望你看完之后,能有所收获,不足请指正!
PS:本文提到的模块都使用正点原子的stm32开发板战舰驱动,模块用的某宝现成的模块。
PS:代码为正点原子的代码,整理成自己习惯的接口
DS18B20介绍
基本情况
DS18B20是单总线接口的温度传感器,而且一个总线上可以挂多个DS18B20,通过传感器内部的64bit ROM区分不同的DS18B20(前8bit为家族码,随后48bit为序列号每个DS18B20的序列号均不相同,最后8bit是前56bit的CRC检验码),这点跟DHT11不同。
DS18B20的内部结构如下图
寄生电源电路可以使DS18B20在不接电源的情况下工作。本文演示时有外接VDD
另一个跟DHT11不同点是温度测量范围和精度,DS18B20测温范围-55_+125℃,精度±0.5℃(DHT11:0-50℃;±2℃),并且可以通过配置寄存器设置采样分辨率(温度数据的位数,9~12bits),位数越多采样需要的时间越长,对应关系如下图
温度数据读回来后不能直接用,需要根据规格书提供的表进行转换才能得到温度值(这点没有DHT11那么直观,但也不复杂),对应关系如下表
记温度值为T0,读回来的数据为T1,从表中可以得到T0 = T1 * 0.0625;
怎么通信
现在,你对DS18B20有了基本的了解,那接下来要解决的问题就是单片机怎么跟DS18B20通信了。
时序
-
初始化信号(复位信号)
主机拉低最少480us,之后将引脚设置为输入等待DS18B20(后面简称从机)15-60us后响应;从机的响应信号为拉低60-240us,之后释放总线,由上拉电阻将总线拉高;时序图如下图所示
-
读/写“0”和“1”
写“1”和写“0”都是先拉低,如果是写“1”则在1us~15us内拉高,如果时写“0”,则在60us后拉高;
读“1”和读“0”都是先拉低,然后将引脚配置为输入,在拉低的1us~15us内读取引脚电平,为高则为“1”,为低则为“0”;时序图如下图所示
通信流程
了解了初始化信号,读写“0”“1”的时序,接下来根据特定的通信流程,我们就能读出温度数据啦!
DS18B20的规格书有两页的通讯流程图,有兴趣的小伙伴可以去看下规格书下载,这里我们就用最简单的流程
-
触发传感器开始转换:
复位 -> 发 SKIP ROM 命令(0xCC) -> 发开始转换命令(0x44)
-
延时,等待转换完成
-
读取温度:
复位 -> 发送 SKIP ROM 命令(0xCC) -> 发读存储器命令(0xBE) -> 连续读出两个字节数据(即温度)
-
结束
发送命令 0xCC可跳过发送64bit ROM的环节,类似I2C的发送设备地址,因为我们只用了1个DS18B20,不用关心序列号,所以可以跳过;
发送命令0xBE可读出DS18B20的整个存储器的9bytes的数据,中间允许主机发送复位信号打断。因为我们只需要温度数据,所以只接收前2bytes的数据;读取数据时,数据是从Byte0(下图)的低位开始传输,写数据也是低位先写。更多命令请查看规格书
DS18B20的内存映射如下图所示
代码讲解
这部分只讲解部分代码,完整代码可通过文末链接获取
基本信号
- 复位信号
这部分跟DHT11的代码很像
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); //SET PG11 OUTPUT
DS18B20_DQ_OUT=0; //拉低DQ
delay_us(750); //拉低750us(大于480us即可)
DS18B20_DQ_OUT=1; //DQ=1
delay_us(15); //15US
}
复位信号之后等待DS18B20响应
u8 DS18B20_Check(void)
{
u8 retry=0;
DS18B20_IO_IN(); //SET PG11 INPUT
while (DS18B20_DQ_IN&&retry<200)//DS18B20会拉低60-240us,没拉低说明传感器不存在
{
retry++;
delay_us(1);
};
if(retry>=200)return 1;
else retry=0;
while (!DS18B20_DQ_IN&&retry<240)//等待DS18B20响应后释放总线。上面时序部分有提到,总线最后是由上拉电阻拉高
{
retry++;
delay_us(1);
};
if(retry>=240)return 1;
return 0;
}
- 写1byte
void DS18B20_Write_Byte(u8 dat)
{
u8 j;
u8 testb;
DS18B20_IO_OUT(); //SET PG11 OUTPUT;
for (j=1;j<=8;j++)
{
testb=dat&0x01;//低位先写
dat=dat>>1;
if (testb)
{
DS18B20_DQ_OUT=0; // Write 1
delay_us(2);
DS18B20_DQ_OUT=1;
delay_us(60);
}
else
{
DS18B20_DQ_OUT=0; // Write 0
delay_us(60);
DS18B20_DQ_OUT=1;
delay_us(2);
}
}
}
- 读1byte
u8 DS18B20_Read_Bit(void)
{
u8 data;
DS18B20_IO_OUT(); //SET PG11 OUTPUT
DS18B20_DQ_OUT=0;
delay_us(2);
DS18B20_DQ_OUT=1;
DS18B20_IO_IN(); //SET PG11 INPUT
delay_us(12);
if(DS18B20_DQ_IN)data=1;
else data=0;
delay_us(50);
return data;
}
u8 DS18B20_Read_Byte(void)
{
u8 i,j,dat;
dat=0;
for (i=1;i<=8;i++)
{
j=DS18B20_Read_Bit();
dat=(j<<7)|(dat>>1);
}
return dat;
}
读一帧数据
需要说明一点,前面提到温度值为T0 = T1 * 0.0625
,这样就需要返回float变量;代码中是*0.625
,得到的值为实际温度值的10倍,用户处理起来也比较方便。
举例:
- 产品需要将采集到的温度传给其他设备,如果返回
float
,用户如果直接传需要4bytes,否则得做转换;如果返回short
,则用户可以直接传,只用2bytes; - 产品需要在oled显示温度值,如果是
float
,要取小数部分就比较麻烦一点;如果是short
,直接%10
就可以得到小数部分。
uint8_t ds18b20_read_data( short * data )
{
u8 temp;
u8 TL,TH;
short tem;
DS18B20_Start (); // ds1820 start convert
DS18B20_Rst();
if( DS18B20_Check() ) {
return 1;
}
DS18B20_Write_Byte(0xcc); // skip rom
DS18B20_Write_Byte(0xbe); // read [function command]
TL=DS18B20_Read_Byte(); // 低字节
TH=DS18B20_Read_Byte(); // 高字节
if(TH>7)
{
TH=~TH;
TL=~TL;
temp=0; //温度为负
}else temp=1; //温度为正
tem=TH; //获得高八位
tem<<=8;
tem+=TL; //获得底八位
tem=(float)tem*0.625; //转换
if(temp) *data = tem; //返回温度值
else *data = -tem;
return 0;
}
代码使用方法
int main(void){
dht11_init();
while(1) {
short temp_data = 0;
if( !ds18b20_read_data( &temp_data ) ) {
_LOG_DEBUG( "[ds] temp %0.1fC\n\n", ((float)temp_data/10.0));
}
else {
_LOG_INFO("[err] ds18b20 read\n");
}
delay_ms(2000);
}
}
今天就分享到这里,希望你在这篇文章中有所收获;不变秃且变强[doge]。
代码获取
共用代码:https://gitee.com/sumoting1629/mcu-practice/tree/master/common
驱动代码:https://gitee.com/sumoting1629/mcu-practice/tree/master/component