一、DS18B20数据手册解读
首先我们知道DS18B20使用的是单总线传输,默认情况下读出来的温度是12位的,我们这里只讨论外部电源供电这种情况。
有这张图片我们知道,12位温度的最小分辨率是10^-4次方,因此就是0.0625.我们只需要将最后读到的温度,乘以0.0625即可得到我们测量到的温度。
通过单总线端口访问DS18B20的协议是:首先进行初始化、ROM操作命令、存储器操作命令、执行/数据,这4步来完成的。接下来我们一步一步的来看
(1)初始化
首先我们需要知道,所有的执行都是从一个初始化序列开始的,我们需要控制总线先产生一个复位脉冲,一个最少保持480微秒的低电平信号,然后释放总线,进入到接收状态,当DS18B20检测到I/O引脚上的上升沿之后,DS18B20等待15~60微秒,然后发送一个存在脉冲,也就是一个60~240微秒的低电平信号。具体编程实现如下:我这里加了一些返回参数,用来保证初始化的成功
uchar ds18b20_init(void)
{
uchar i = 0;
DS18B20_BUS = 1; //将数据线拉高方便产生下降沿
_nop_();
DS18B20_BUS = 0; //产生复位脉冲
delay500us(); //最短为480微秒的低电平信号
DS18B20_BUS = 1; //释放掉总线并进入接收状态
_nop_();
while (DS18B20_BUS) //循环检测ds18b20发送过来的存在信号
{
i++;
delay20us(); //每20微秒检测一次
if (i > 7)
{
return 1; //如果等了140微秒还没有就返回1,表示失败
}
}
delay500us(); //如果没有失败,就延时至少480微秒,等待ds18b20将总线释放
return 0;
}
(2)ROM操作命令
我们想要进行ROM指令的操作,就必须得有一定的方法,给DS18B20发送数据,和接收数据。接下来我们先实现DS18B20对于一个字节数据的发送和接收。我们首先来看发送函数,也就是给DS18B20写数据:
我们主要是根据上面的时序图来进行程序的编写,首先先对时序图做一些解释:当主机把数据线从逻辑高电平拉高到逻辑低电平的时候,写时序开始,有两种写时序,一种是写0,一种是写1.所有的些时间时序必须最少持续60微秒,两个写周期之间至少间隔1微秒。数据线上的电平变低以后,DS18B20在15~60微秒的窗口内对数据线进行采样,如果线上是高电平就写1,如果线上是低电平就写0,我这里的编程思路是:首先将数据线拉低持续1个微秒,然后赶紧释放总线,然后将我们想要发送的0或者1放在数据线上,然后持续至少60微秒的延时。最后在释放总线,具体的编程实现如下:
//给ds18b20写入数据
void ds18b20_write(uchar cmd)
{
uchar i = 0, temp = 0;
for (i=0; i<8; i++)
{
DS18B20_BUS = 0; //首先将总线拉低,产生写时序
_nop_(); //延时一个微秒
DS18B20_BUS = 1; //释放总线
temp = (cmd & 0x01);
DS18B20_BUS = temp; //发送0或者1
delay60us(); //凑够至少60微秒的时间
DS18B20_BUS = 1; //释放总线
cmd >>= 1; //连续两个写周期间隙应该大于1us
}
}
接下来我们就需要进行读字节时序了,还是一样先来看时序图:
读时序和写时序差不都,只不过我们需要注意主机的采样时间,当主机把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持1微秒,然后我们就要将数据线释放掉,方便DS18B20传输数据,从DS18B20输出的数据在读时序开始后的15微秒内有效,也就是说我们必须在这个时间间隔内完成采样。所有的读时序都必须最少60微秒,两个读周期至少1微秒的恢复时间。具体编程实现如下:
//从ds18b20里面读取数据
uchar ds18b20_read(void)
{
uchar date = 0, i = 0, temp = 0;
for (i=0; i<8; i++)
{
DS18B20_BUS = 0; //拉低总线产生读时序的起始信号
_nop_(); //产生1us的延时
DS18B20_BUS = 1; //释放掉总线
delay6us(); //延时6us 这里也可以是其他的延时只要保证15us以内
temp = DS18B20_BUS; //采集ds18b20上面的数据
date = (date | (temp << i));//采集到的数据从低位到高位一次放好
delay60us(); //等待ds18b20释放总线后,准备下一次的就收
}
return date;
}
当我们有了这两个函数之后我们就可以进行ROM指令的操作了。接下来我们来看一下,我们常用的ROM指令:一般来讲我们的设备上就只有一个DS18B20,也就是说,我们只需要一条指令,跳过ROM这一条指令就可以了,也就是发送cch就可以了。具体下图:
(3)存储器操作指令
我们一般也就用到 温度转换命令、读取暂存器命令。其他的不太需要:在我们发送出去读存储器的指令之后,我们只要连续的不停的读,他就会一直读到最后一个字节,也就是说当我们发送读存储器命令之后,我们读第一次就是再读,下面存储器映射表的字节0, 读第二次就是读字节1。如果我们只是想获取读到的温度值,我们只需要,发送完读,存储器命令之后读两次就可以了。
二、DS18B20读取温度
首先我们得知道,所有的操作都包含4步,1、初始化, 2、ROM操作指令,3、暂存器操作指令 4、读数据(这一步可以都很多次都可以)
首先我们需要给DS18B20发送一个温度转换的指令,具体编程如下:我这里发送完命令之后直接给他足够的时间,让他完成温度的转换。
//发送检测温度的命令
void ds18b20_change_temperature(void)
{
ds18b20_init(); //首先进行初始化
ds18b20_write(0xcc); //发送ROM指令
ds18b20_write(0x44); //发送温度转换指令
delay1s(); //等待温度转换完毕
}
温度转换完成之后就是,读温度了,DS18B20将温度转换完毕后,他不会向我们主动发送,而是将转换完后的温度,放到暂存器里面,因此我们只需要都暂存器就可以了。具体代码实现如下:在使用的时候,我们只需要连续调用这两个函数就可以啦。
//读取转换完成的温度
//注意这里温度转换过程中将温度结果放大了1000倍
uint ds18b20_read_temperatur(void)
{
uint temp = 0;
uchar temp1 = 0, temp2 = 0;
ds18b20_init(); //首先进行初始化
ds18b20_write(0xcc); //发送ROM指令
ds18b20_write(0xbe); //读取暂存器
temp1 = ds18b20_read(); //读取暂存器第0个字节 温度的低8位
temp2 = ds18b20_read(); //读取暂存器第1个字节 温度的高8位
temp = ((temp2 << 8) | temp1);
temp = temp * 62.5; //将温度进行转换
return temp;
}
我这里还专门为这个读取温度的函数,写了一个串口发送函数,实现如下:需要注意串口发送一个字节的函数需要自己实现。
//串口发送温度专用函数
void uart_send_num1(uint num)
{
uchar a[10] = 0, i = 0;
do
{
a[i] = num % 10 + '0';
num /= 10;
a[i+1] = '\0';
i++;
}while (num);
while (i--)
{
uart_send_byte(a[i]);
if (i == 3)
{
uart_send_byte('.');
}
}
}
三、总线上有两个DS18B20
我们前面讨论的是,总线上只有一个DS18B20的情况,接下来我们讨论总线上有2个DS18B20的情况,具体目标是让两个DS18B20能独立地采集温度。并将温度打印在串口:
首先我们需要知道我们手中的DS18B20内置ROM的唯一64位编码,具体实现如下:需要注意的是我们先一个一个的将DS18B20放到总线上然后运行下面的程序,这样我们就可以在串口上面看到这个DS18B20的唯一64位编码了。
ds18b20_init();
ds18b20_write(0x33);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
temp = ds18b20_read();
uart_send_byte(temp);
接下来我们就可以将2个DS18B20的总线连接到一起,然后通过ROM指令里面的匹配ROM,来查找我们需要查找的DS18B20,然后再和他进行通讯,发送温度转换命令,然后读取温度即可,具体实现如下:
ds18b20_init();
ds18b20_write(0x55);
ds18b20_write(0x28);
ds18b20_write(0xaf);
ds18b20_write(0x0d);
ds18b20_write(0x85);
ds18b20_write(0x00);
ds18b20_write(0x00);
ds18b20_write(0x00);
ds18b20_write(0xd2);
ds18b20_write(0x44);
delay1s();
temp = ds18b20_read_temperatur1();
uart_send_byte('a');
uart_send_num1(temp);
ds18b20_init();
ds18b20_write(0x55);
ds18b20_write(0x28);
ds18b20_write(0xb4);
ds18b20_write(0xfc);
ds18b20_write(0x43);
ds18b20_write(0xd4);
ds18b20_write(0xe1);
ds18b20_write(0x3c);
ds18b20_write(0x01);
ds18b20_write(0x44);
delay1s();
temp = ds18b20_read_temperatur2();
uart_send_byte('b');
uart_send_num1(temp);
需要注意的是,在温度读取函数里面我们也必须针对不同的DS18B20有着不同的温度读取函数,我这里写的是原始的处理方式,以上的代码也可以简化,将每个DS18B20的64位编码放到一个数据里面,我们使用循环的方式发送就可以了。这里就不做进一步修改了,感兴趣的可以自己修改。
uint ds18b20_read_temperatur1(void)
{
uint temp = 0;
uchar temp1 = 0, temp2 = 0;
ds18b20_init(); //首先进行初始化
ds18b20_write(0x55); //发送ROM指令
ds18b20_write(0x28);
ds18b20_write(0xaf);
ds18b20_write(0x0d);
ds18b20_write(0x85);
ds18b20_write(0x00);
ds18b20_write(0x00);
ds18b20_write(0x00);
ds18b20_write(0xd2);
ds18b20_write(0xbe); //读取暂存器
temp1 = ds18b20_read(); //读取暂存器第0个字节 温度的低8位
temp2 = ds18b20_read(); //读取暂存器第1个字节 温度的高8位
temp = ((temp2 << 8) | temp1);
temp = temp * 62.5; //将温度进行转换
return temp;
}
uint ds18b20_read_temperatur2(void)
{
uint temp = 0;
uchar temp1 = 0, temp2 = 0;
ds18b20_init(); //首先进行初始化
ds18b20_write(0x55); //发送ROM指令
ds18b20_write(0x28);
ds18b20_write(0xb4);
ds18b20_write(0xfc);
ds18b20_write(0x43);
ds18b20_write(0xd4);
ds18b20_write(0xe1);
ds18b20_write(0x3c);
ds18b20_write(0x01);
ds18b20_write(0xbe); //读取暂存器
temp1 = ds18b20_read(); //读取暂存器第0个字节 温度的低8位
temp2 = ds18b20_read(); //读取暂存器第1个字节 温度的高8位
temp = ((temp2 << 8) | temp1);
temp = temp * 62.5; //将温度进行转换
return temp;
}