资讯详情

DS18B20型温度传感器的使用详解及Proteus仿真(附源码)

一、概述

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
  • 用户自定义非易失性的报警设置

三、工作指令

  1. 温度转换指令:0x44(即44H),启动Ds18b20启动转换温度
  2. 读暂存器指令:0xBE(即BEH),在临时存储器中读取九字节数据
  3. 零:0写临时存储器x4E(即4EH),将数据写入临时存储器TH、TL
  4. 赋值临存器:0x48(即48H),暂存器中的TH、TL写入EEPROM中
  5. 读电源供电方式:0xB4(即B4H):启动Ds18b20.发电供电方式
  6. 重调EEPROM:0xB8(即B8H):把EEPROM中的TH、TL读至暂存器

四·、单总线访问DS18B20的顺序

  • 初始化
  • ROM操作指令
  • 存储操作命令
  • 执行/数据

五、工作时间

(一)初始化(复位操作)

在初始化序列中,总线上的主设备通过降低1-wire总线超过480us来发送(TX)复位脉冲。然后主设备释放总线,进入接收模式(RX)。当总线释放时,5KΩ上拉电阻将在1左右-wire总线拉到高电平。当DS18B20检测到上升沿后,等待15us至60us后通过1-wire总线拉低60us至240us来是实现发送一个存在脉冲。

图5.1 操作时序图复位

复位操作的子函数可根据上述描述及时序图写出:

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。

图5.2 操作时序图写

根据上述描述及时序图,可以写出写操作的子函数:

/********************************向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总线必须总线,并对总线进行采样。

图5.3 “读”操作时序图

根据上述描述及时序图,可以写出读操作的子函数

/********************************从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仿真图

图8.1 仿真图

 

 

左肩理想右肩担当,君子不怨永远不会停下脚步!

 

 

 

 

 

 

 

 

 

 

标签: tp3231温度传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台