一、概述
DS18B920数字温度传感器bit到12bit用户可编程的摄氏温度测量精度和非易失性,具有温和低温触发报警功能。DS18B20采用的1-Wire即单总线通信方式,即只使用数据线与微控制器通信。该传感器的温度监测范围为-55℃至 125℃,温度超过-10℃至85℃还有 -0.5℃的精度。此外,DS18B20不需要外部电源就可以直接由数据线供电。(本文重点介绍了传感器最困难的部分是工作顺序,同时与您分享程序和编程中的坑,帮助您尽快避免弯路,不追求功能完美,所以本文模拟只能实现正整数温度值的显示,对于小数四舍五入显示)
二、重要特征
- 独特的1-wire总线接口只需要一个管脚通信
- 每个设备的内部ROM上都烧写了一个独特的64位序列号
- 多路采集能力使分布式温度采集应用更加简单
- 不需要外围元件
- 可采用数据线供电;供电范围为3.0V至5.5V
- 可测温度范围-55℃至 125℃(-67℉至 257℉)
- 温度超过-10℃至85℃还有 -0.5℃的精度
- 用户可以自定义内部温度采集精度9bit至12bit(上电默认12bit)
- 温度转换时间为12bit最大值为750ms
- 用户自定义非易失性的报警设置
三、工作指令
- 温度转换指令:0x44(即44H),启动Ds18b20启动转换温度
- 读暂存器指令:0xBE(即BEH),在临时存储器中读取九字节数据
- 零:0写临时存储器x4E(即4EH),将数据写入临时存储器TH、TL
- 赋值临存器:0x48(即48H),暂存器中的TH、TL写入EEPROM中
- 读电源供电方式:0xB4(即B4H):启动Ds18b20.发电供电方式
- 重调EEPROM:0xB8(即B8H):把EEPROM中的TH、TL读至暂存器
四·、单总线访问DS18B20的顺序
- 初始化
- ROM操作指令
- 存储操作命令
- 执行/数据
五、工作时间
(一)初始化(复位操作)
在初始化序列中,总线上的主设备通过降低1-wire总线超过480us来发送(TX)复位脉冲。然后主设备释放总线,进入接收模式(RX)。当总线释放时,5KΩ上拉电阻将在1左右-wire总线拉到高电平。当DS18B20检测到上升沿后,等待15us至60us后通过1-wire总线拉低60us至240us来是实现发送一个存在脉冲。

复位操作的子函数可根据上述描述及时序图写出:
void Init_Ds(void)//DS18B20初始化 { Bus=//主动拉低480-960us(此处选择600us) Delay600us(); Bus=1;//释放总线,传感器15-60us后拉低总线 while(Bus);//等待传感器降低; while(!Bus);//度过传感器被拉低的时间(60-240us)后主动拉高 Bus=1;//主动拉高 }
(二)控制器的写操作(先写低后写高)
写时段有两种情况:写1时段和写0时段。控制器通过写一个时段来向。DS18B写逻辑120,写0时段DS18B逻辑0写入20。每个写作时间必须至少60个us连续时间和堵路的写作时间至少应为1us的恢复时间。两个写作时间由控制器通过-wire初始化是先拉低的(详见图5).2)。
为形成写1时段,将1-wire总线拉低后,主设备必须在15us释放总线。当总线释放时,5KΩ上拉电阻拉高总线;为形成写0时段,将1-wire总线拉低后,控制器必须在整个时间内拉低总线(至少60us)。
控制器初始化写时段后,DS18B20将会在15us至60us时间窗对1-wire总线采样。如果总线在采样窗口期间电平较高,则写入逻辑1DS18B20;如果总线低电平,则写入逻辑0DS18B20。
根据上述描述及时序图,可以写出写操作的子函数:
/********************************向DS18B20写入一字节***********************/ void Write_Ds(uchar com)//从低位开始写入 { uchar mask; for(mask=0x01;mask!=0;mask<<=1) { //该位为0,先拉低,15us通过延迟,整个周期是60us //该位为1,先拉低,15us内(此处选5us)拉高,延迟使整个周期600us Bus=0; _nop_();_nop_();_nop_();_nop_();_nop_();//先拉低5us if((com&mask)==0)//该位为0 { Bus=0; } else//该位是1 { Bus=1; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();;//延时60us _nop_();_nop_();_nop_();_nop_();_nop_(); Bus=1;//拉高 _nop_();_nop_()//写两个位置之间至少有1us间隔(此处选2us) } }
(三)控制器的读操作(先读低后读高)
仅在阅读期间DS18B数据可以传输到主设备。因此,主设备执行完读暂存寄存器[BEh]或读取供电模式[B4h]之后,必须及时生成阅读时间,以便DS18B提供所需所需数据。此外,主设备可在执行温度转换[44h]或拷贝EEPROM[B8h]为了获得命令后生成读取期DS18B章节中提到的20功能命令操作信息。
每个阅读期必须至少60个us持续时间和独立写作时间之间的间隔至少为1us。阅读时间通过控制器将总线拉低超过1us再释放总线实现初始化(详见图5).3)。当控制器初始化时,DS18B0或1将发送到总线。DS18B20拉高总线发送逻辑1,拉低总线发送逻辑0。发送逻辑0后,DS18B通过上拉电阻将总线释放到高电平的闲置状态。从DS18B初始化读时间后,20中输出的数据只有15个us有效时间。因此。重新开始改读时间后的15个控制器us总线必须总线,并对总线进行采样。
根据上述描述及时序图,可以写出读操作的子函数
/********************************从DS18B20读出一字节***********************/ uchar Read_Ds(void)///先读低,整个阅读周期至少为60us,但控制器采样应为15us内完成,至少相邻位之间的间隔是1us { uchar value=0,mask; for(mask=0x01;mask!=0;mask<<=1) { Bus=0.//先把总线拉低超过1us(此处选择2us)后释放 _nop_();_nop_(); Bus=1; _nop_();_nop_();_nop_();_nop_();_nop_();_nop_()//延迟6us后读总线数据 if(Bus==//如果这个位置是0 { value&=(~mask); } else { value|=mask; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us()us,凑够至少60us的采样周期 _nop_();_nop_(); Bus=1; _nop_();_nop_()//写两个位置之间至少有1us的间(此处选择2us)
}
return value;
}
六、注意事项(我踩过的坑)
1. 关于延时问题
DS18B20最大的优势之一就是单总线通信,我们通过一根数据线就可以完成诸多操作,但作为代价的是,DS18B20的工作时序十分复杂,因此对定时精度要求极高。平时大家操作定时精度要求不高的传感器可能会养成一个习惯,比如我们已经有了一个1ms且0误差的延时函数,当我们遇到一个20ms的延时需求时,可能会通过for/while循环将延时为1ms的延时函数执行20次。实际上,这样的方式所达到的延时时间的远大于20ms的,但对于定时精度要求不高的传感器,毫秒级的误差不会带来影响,但对于该传感器则不可。所以,在这款传感器的操作中,即使已经有一个10us的延时函数而需要一个20us的延时时,也要重新写一个20us的延时函数,不可将10us的延时函数循环执行两次。
2. 关于总时序问题
该传感器中的所有操作都要遵循“初始化-ROM命令-DS18B20功能命令”的总时序。比如,测量温度的操作要先后经过“初始化-跳过ROM命令-转换温度命令”与“初始化-跳过ROM命令-读取温度命令”这两大步。常犯的错误为“初始化-跳过ROM命令-转换温度命令-读取温度命令”,也就是说认为初始化与ROM命令在操作传感器的最初执行一次即可,这种想法是错误的。
3. 关于编程细节
在自己编程的过程中,遭遇了一个细节性的bug,即将命令值com与掩码mask相与是否为0作为进入if语句内部的判断条件的过程中,判断条件是这么写的if(com&mask==0),而实际应该写为if((com&mask)==0),即com&mask需要用括号括起来作为一个整体,否则会出错。
七、完整例程(例程均为自己编写且验证成功)
/*所用单片机型号为AT89C52,晶振为12MHz,显示模块采用LCD1602液晶屏*/
#include<reg52.h>
#include<intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit Bus=P3^0;//数据单总线
sbit RS=P3^3;
sbit RW=P3^4;
sbit E=P3^5;
void Delay10us(void);//10us延时函数
void Delay600us(void);//600us延时子函数
void Delay(uint n);//LCD1602中延时子函数
void Delay1ms(uint t);//t毫秒延时子函数
void Init_Ds(void);//DS18B20初始化
void Write_Ds(uchar com);//向DS18B20写入一字节
uchar Read_Ds(void);//从DS18B20读出一字节
uint Get_Tem(void);//获取温度值
void Change(uint x);//把整型数值x转换为字符串
void Write_com(uchar com);//写命令子函数
void Write_dat(uchar dat);//写数据子函数
void Init_1602(void);//LCD1602初始化子函数
void Show(uchar x,uchar y,uchar *str);//LCD1602显示子函数
uchar str[4];//储存转换值对应的字符串
void main()
{
unsigned int temp;
Init_1602();
temp=Get_Tem();
Change(temp);
Show(1,1,"T:");
Show(1,3,str);
while(1);
}
/***************************************延时函数体**************************/
void Delay10us(void)//10us延时函数
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=1;a>0;a--);
}
void Delay600us(void)//600us延时函数
{
unsigned char a,b;
for(b=119;b>0;b--)
for(a=1;a>0;a--);
}
void Delay(uint n)//LCD1602中延时函数
{
uint x,y;
for(x=n;x>0;x--)
for(y=110;y>0;y--);
}
void Delay1ms(uint t)//t毫秒延时函数
{
unsigned char a,b;
uint i;
for(i=0;i<t;i++)
for(b=199;b>0;b--)
for(a=1;a>0;a--);
}
/********************************DS18B20初始化函数*************************/
void Init_Ds(void)//DS18B20初始化
{
Bus=0;//主动拉低480-960us(此处选择600us)
Delay600us();
Bus=1;//释放总线,传感器15-60us后拉低总线
while(Bus);//等待传感器拉低;
while(!Bus);//度过传感器被拉低的时间(60-240us)后主动拉高
Bus=1;//主动拉高
}
/********************************向DS18B20写入一字节***********************/
void Write_Ds(uchar com)//从低位开始写入
{
uchar mask;
for(mask=0x01;mask!=0;mask<<=1)
{
//该位为0,先拉低,15us后在拉高,并通过延时使整个周期为60us
//该位为1,先拉低并在15us内(此处选择5us)拉高,并通过延时使整个周期为60us
Bus=0;
_nop_();_nop_();_nop_();_nop_();_nop_();//先拉低5us
if((com&mask)==0)//该位是0
{
Bus=0;
}
else//该位是1
{
Bus=1;
}
Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();;//延时60us
_nop_();_nop_();_nop_();_nop_();_nop_();
Bus=1;//拉高
_nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us)
}
}
/********************************从DS18B20读出一字节***********************/
uchar Read_Ds(void)//先读的是低位,整个读周期至少为60us,但控制器采样要在15us内完成,相邻“位”之间至少间隔1us
{
uchar value=0,mask;
for(mask=0x01;mask!=0;mask<<=1)
{
Bus=0;//先把总线拉低超过1us(此处选择2us)后释放
_nop_();_nop_();
Bus=1;
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();//再延时6us后读总线数据
if(Bus==0)//如果该位是0
{
value&=(~mask);
}
else
{
value|=mask;
}
Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();//再延时52us,凑够至少60us的采样周期
_nop_();_nop_();
Bus=1;
_nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us)
}
return value;
}
/**********************************获取温度值函数***************************/
uint Get_Tem(void)
{
uint temp=0;
float tp;
uchar LSB=0,MSB=0;
Delay1ms(10);//延时10ms度过不稳定期
Init_Ds();//Ds18b20初始化
Delay1ms(1);
Write_Ds(0xcc);//跳过ROM寻址
Write_Ds(0x44);//启动一次温度转换
Delay1ms(1000);//延时1s等待转化
Init_Ds();//Ds18b20初始化
Delay1ms(1);
Write_Ds(0xcc);//跳过ROM寻址
Write_Ds(0xbe);//发送读值命令·
LSB=Read_Ds();
MSB=Read_Ds();
temp=MSB;
temp<<=8;
temp|=LSB;
tp=temp*0.0625;
temp=tp;
if(tp-temp>=0.5)
{
temp+=1;
}
return temp;
}
/******************************把整型数据转换为字符串**********************/
void Change(uint x)
{
str[0]=x/100+48;
str[1]=(x/10)%10+48;
str[2]=x%10+48;
str[3]='\0';
}
/********************************写命令函数体****************************/
void Write_com(uchar com)
{
RS=0;
P2=com;
Delay(5);
E=1;
Delay(5);
E=0;
}
/********************************写数据函数体****************************/
void Write_dat(uchar dat)
{
RS=1;
P2=dat;
Delay(5);
E=1;
Delay(5);
E=0;
}
/*****************************LCD1602初始化函数体*************************/
void Init_1602()
{
uchar i=0;
RW=0;
Write_com(0x38);//屏幕初始化
Write_com(0x0c);//打开显示 无光标 无光标闪烁
Write_com(0x06);//当读或写一个字符是指针后一一位
Write_com(0x01);//清屏
Write_com(0x80);//设置位置
}
/*******************************显示内容函数体**************************/
void Show(uchar x,uchar y,uchar *str)
{
unsigned char addr;
if (x==1)
{
addr=0x00+y-1; //从第一行、第y列开始显示
}
else
{
addr=0x40+y-1; //第二行、第y列开始显示
}
Write_com(addr+0x80);
while (*str!='\0')
{
Write_dat(*str++);
}
}
八、Proteus仿真图
左肩理想右肩担当,君子不怨永远不会停下脚步!