DS18B20是美信公司的一款温度传感器,单片机可以通过1-Wire协议与DS18B20进行通信,最终将温度读出。1-Wire总线的硬件接口很简单,只需要把DS18B20的数据引脚和单片机的一个IO 口接上就可以了。
DS18B20通过编程可以实现最高12位的温度存储值,在寄存器中,以补码的格式存储。
一共 2 个字节,LSB 是低字节,MSB 是高字节,其中 MSb 是字节的高位,LSb 是字节的低位。可以看出来,二进制数字,每一位代表的温度的含义,都表示出来了。其中 S表示的是符号位,低 11 位都是 2 的幂,用来表示最终的温度。DS18B20 的温度测量范围是从-55 度到+125 度,而温度数据的表现形式,有正负温度,寄存器中每个数字如同卡尺的刻度一样分布。如下图所示:
二进制数字最低位变化 1,代表温度变化 0.0625 度的映射关系。当 0 度的时候,那就是0x0000,当温度 125 度的时候,对应十六进制是 0x07D0,当温度是零下 55 度的时候,对应的数字是 0xFC90。反过来说,当数字是 0x0001 的时候,那温度就是 0.0625 度了。
1、初始化
和I2C的寻址类似,1-Wire总线开始也需要检测这条总线上是否存在DS18B20这个器件。如果这条总线上存在DS18B20,总线会根据时序要求返回一个低电平脉冲,如果不存在的话,也就不会返回脉冲,即总线保持为高电平,所以习惯上称之为检测存在脉冲。此外,获取存在脉冲不仅仅是检测是否存在DS18B20,还要通过这个脉冲过程通知DS18B20准备好,单片机要对它下手了。
通过上图,实粗线是单片机IO口拉低这个引脚,虚粗线是DS18B20拉低这个引脚,细线是单片机和DS18B20释放总线后,依靠上拉电阻的作用把IO口引脚拉上去。
存在脉冲检测过程:首先是单片机要拉低这个引脚,持续大概480~960us之间的时间即可,在持续里持续了500us。然后单片机释放总线,就是给高电平,DS18B20等待大概15~60us之后,会主动拉低这个引脚大概是60~240us,而后DS18B20会主动释放总线,这样IO口会被上拉电阻自动拉高。
根据下面的程序:由于 DS18B20 时序要求非常严格,所以在操作时序的时候,为了防止中断干扰总线时序,先关闭总中断。然后第一步,拉低 DS18B20 这个引脚,持续 500us;第二步,IO释放总线,延时 60us;第三步,读取存在脉冲,并且等待存在脉冲结束。
2、ROM操作指令
总线上可以挂多个器件,通过不同的器件地址来访问不同的器件。同样1-Wire总线也可以挂多个器件,但是它只有一条线,如何区分不同的器件。
在每个DS18B20内部都有一个唯一的64位长的序列号,这个序列号值就存在DS18B20内部的ROM中。开始的8位是产品类型编码(DS18B20是0x10),接着的 48 位是每个器件唯一的序号,最后的 8 位是 CRC 校验码。DS18B20 可以引出去很长的线,最长可以到几十米,测不同位置的温度。单片机可以通过和DS18B20 之间的通信,获取每个传感器所采集到的温度信息,也可以同时给所有的 DS18B20 发送一些指令。
Skip ROM(跳过ROM):0xCC
当总线上只有一个器件的时候,可以跳过 ROM,不进行 ROM 检测。
3、RAM存储器操作指令
RAM读取指令,常用的有两条。
Read Scratchpad(读暂存寄存器):0xBE
这里要注意,DS18B20的温度数据是两个字节,读取数据的时候,先读取到的是低字节的低位,读完了第一个字节后,再读高字节的低位,直到两个字节全部读取完毕。
Convert Temperature(启动温度转换):0x44
当发送一个启动温度转换的指令后,DS18B20开始进行转换。从转换开始到获取温度,DS18B20是需要时间的,而这个时间的长短取决于DS18B20的精度。DS18B20最高可以用12位来存储温度,但是也可以使用11位、10位和9位一共四种格式位数越高,精度越高,9位模式最低位变化1个数字,温度就变化0.5°C,同时转换速度也要相应快一些。
其中寄存器R1和R0决定了转换的位数,出厂默认值就是11,也就是12位表示温度,最大转换时间是750ms。当启动转换之后,至少要再等750ms之后才能读取温度,否则读到的温度有可能是错误的值。
如果读取到的值为85°C,这个值要么是没有启动转换,要么是启动转换了,但还没有等待一次转换彻底完成,,读到的是一个错误的数据。
4、DS18B20的位读写时序
写时序
当要给 DS18B20 写入 0 的时候,单片机直接将引脚拉低,持续时间大于 60us 小于 120us就可以了。图上显示的意思是,单片机先拉低 15us 之后,DS18B20 会在从 15us 到 60us 之间的时间来读取这一位,DS18B20 最早会在 15us 的时刻读取,典型值是在 30us 的时刻读取,最多不会超过 60us,DS18B20 必然读取完毕,所以持续时间超过 60us 即可。
当要给 DS18B20 写入 1 的时候,单片机先将这个引脚拉低,拉低时间大于 1us,然后马上释放总线,即拉高引脚,并且持续时间也要大于 60us。和写 0 类似的是,DS18B20 会在15us 到 60us 之间来读取这个 1。
DS18B20的时序比较严格,,写的过程中最好不要有中断打断。但是在两个“位”之间的间隔,是大于1小于无穷的,在这个时间段,是可以开中断来处理其他程序的。
读时序
当要读取 DS18B20 的数据的时候,我们的单片机首先要拉低这个引脚,并且至少保持1us 的时间,然后释放引脚,释放完毕后要尽快读取。从拉低这个引脚到读取引脚状态,不能超过 15us。从上图可以看出来,主机采样时间,也就是 MASTER SAMPLES,是在 15us 之内必须完成的。
DS18B20所表示的温度值中,有小数和整数两部分。常用的带小数的数据处理方法有两种,一种是定义成浮点型直接处理,第二种是定义成整型,然后把小数和整数部分分离出来,在合适的位置点上小数点即可。
/*******************************************************************************
* 函数名 :Delayus
* 输入值 :unsigned int us
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月17日
* 功能描述:1T单片机延时指定us
* 备注 :最大形参65535,即最大延时65ms
*******************************************************************************/
void Delayus(unsigned int us)
{
do{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}while(--us);
}
/*******************************************************************************
* 函数名 :Get18B20Ack
* 输入值 :none
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月27日
* 功能描述:复位总线,获取18B20存在脉冲,以启动一次读写操作
* 备注 :
*******************************************************************************/
bit Get18B20Ack(void)
{
bit ack;
EA = 0; //禁止总中断
DS18B20_IO = 0; //产生500us的复位脉冲
Delayus(500);
DS18B20_IO = 1; //延时60us
Delayus(60);
ack = DS18B20_IO; //读取存在脉冲
while(!DS18B20_IO); //等待存在脉冲结束
EA = 1; //重新使能总中断
return ack;
}
/*******************************************************************************
* 函数名 :DS18B20Write
* 输入值 :unsigned char dat
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月27日
* 功能描述:向18B20写入一个字节
* 备注 :dat为待写入字节
*******************************************************************************/
void DS18B20Write(unsigned char dat)
{
unsigned char mask;
EA = 0; //禁止总中断
for(mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次移出8个bit
{
DS18B20_IO = 0; //产生2us低电平脉冲
Delayus(2);
if(dat & mask) //输出该bit值
DS18B20_IO = 1;
else
DS18B20_IO = 0;
Delayus(60); //延时60us
DS18B20_IO = 1; //拉高通信引脚
}
EA = 1; //重新使能总中断
}
/*******************************************************************************
* 函数名 :DS18B20Read
* 输入值 :none
* 返回值 :unsigend char dat
* 作者 :小默haa
* 时间 :2019年2月27日
* 功能描述:从18B20读取一个字节
* 备注 :返回值为读取到的字节
*******************************************************************************/
unsigned char DS18B20Read(void)
{
unsigned char mask, dat = 0;
EA = 0; //禁止总中断
for(mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次采集8个bit
{
DS18B20_IO = 0; //产生2us低电平脉冲
Delayus(2);
DS18B20_IO = 1; //结束低电平脉冲,等待18B20输出数据
Delayus(2); //延时2us
if(DS18B20_IO) //读取通信引脚上的值
{
dat |= mask;
}
Delayus(60); //再延时60us
}
EA = 1; //重新使能总中断
return dat;
}
/*******************************************************************************
* 函数名 :Start18B20
* 输入值 :none
* 返回值 :bit ~ack
* 作者 :小默haa
* 时间 :2019年2月27日
* 功能描述:启动一次18B20温度转换
* 备注 :返回值为是否启动成功
*******************************************************************************/
bit Start18B20()
{
bit ack;
ack = Get18B20Ack(); //执行总线复位,并获取18B20应答
if(ack == 0) //如18B20正确应答,则启动一次转换
{
DS18B20Write(0xCC); //跳过ROM操作
DS18B20Write(0x44); //启动一次温度转换
}
return ~ack; //ack == 0 表示操作成功,所以返回值对其取反
}
/*******************************************************************************
* 函数名 :Get18B20Temp
* 输入值 :int *temp
* 返回值 :bit ~ack
* 作者 :小默haa
* 时间 :2019年2月27日
* 功能描述:读取18B20转换的温度值
* 备注 :返回值为是否读取成功
*******************************************************************************/
bit Get18B20Temp(int *temp)
{
bit ack;
unsigned char LSB, MSB; //16bit温度值的低字节和高字节
ack = Get18B20Ack(); //执行总线复位,并获取18B20应答
if(ack == 0) //如18B20正确应答,则读取温度值
{
DS18B20Write(0xCC); //跳过ROM操作
DS18B20Write(0xBE); //发送读命令
LSB = DS18B20Read(); //读温度值的低字节
MSB = DS18B20Read(); //读温度值的高字节
*temp = ((unsigned int) MSB << 8) + LSB; //合成16bit的整数
}
return ~ack; //ack == 0 表示操作应答,所以返回值为1其取反值
}
为使显示精度也能达到0.0625°C,我们可以在程序中做如下处理。
bit res = 0;
int Temp = 0; //读取当前的温度值
int Temp_int = 999, Temp_dec = 999; //温度值的整数和小数部分
res = Get18B20Temp(&Temp); //读取当前温度
if(res) //如果读取到
{
Temp_int = Temp >> 4; //分离出温度值整数部分
Temp_dec = Temp & 0xF; //分离出温度值小数部分
Temp_dec = Temp_dec * (10000 / 16); //二进制小数部分转换为4位十进制
}
Start18B20(); //重新启动下一次转换