使用单总线协议测量温度的stc89c52 DS18B20方案

一、单总线协议(1-wire)

1.定义:主机和从机用一根总线进行通信,是一种半双工的通信方式,单线=时钟线+数据线+控制线( +电源线)。理想状况下一条总线上的从器件数量几乎不受数量限制。

2.特点:这是由达拉斯半导体公司推出的一项通信技术。它采用单根信号线,既可传输时钟,又能传输数据,而且数据传输是双向的。

3.优点:单总线技术具有线路简单,硬件开销少,成本低廉,便于总线扩展和维护等。

二、单总线通信过程

所有的单总线器件要求采用严格的通信协议, 以保证数据的完整性。该协议定义了几种信号类型:复位脉冲、 应答脉冲、 写0、写1、 读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前,这一点与多数串行通信格式不同(多数为字节的高位在前)。

(1)初始化序列:复位和应答脉冲

初始化过程 = 复位脉冲 + 从机应答脉冲。

主机通过拉低单总线480 ~ 960 us产生复位脉冲,然后释放总线,进入接收模式。主机释放总线时,会产生低电平跳变为高电平的上升沿,单总线器件检测到上升沿之后,延时15 ~ 60 us,单总线器件拉低总线60 ~ 240 us来产生应答脉冲。主机接收到从机的应答脉冲说明单总线器件就绪,初始化过程完成。

(1)初始化时序

(2)写间隙

写间隙有两种,包括写0的时间隙和写1的时间隙。当数据线拉低后,在15 ~ 60 us的时间窗口内对数据线进行采样。如果数据线为低电平,就是写0,如果数据线为高电平,就是写1。主机要产生一个写1时间隙,就必须把数据线拉低,在写时间隙开始后的15 us内允许数据线拉高。主机要产生一个写0时间隙,就必须把数据线拉低并保持60 us。

写时间隙时序图如下所示:

(3)读时间隙 

当主机把总线拉低是,并保持至少1 us后释放总线,必须在15 us内读取数据。单总线器件仅在主机发出读时隙时,才向主机传输数, 所以, 在主机发出读数据命令后,必须马上产生读时隙,以便从机能够传输数据。所有读时隙至少需要 60us, 且在两次独立的读时隙之间至少需要 1us的恢复时间,每个读时隙都由主机发起, 至少拉低总线 1us (图5 所示)。在主机发起读时隙之后,单总线器件才开始在总线上发送 0 或1。 若从机发送1,则保持总线为高电平;若发送 0, 则拉低总线 。当发送 0 时,从机在该时隙结束后释放总线 。由上拉电阻将总线拉回至空闲高电平状态。从机发出的数据在起始时隙之后,保持有效时间 15us ,因而,主机在读时隙期间必须释放总线 ,并且在时隙起始后的 15 us 之内采样总线状态。

 

三、DS18B20温度传感器模块

 

 它一共有3个角,分别是:
        GND(接地)
        DQ(数据总线,与单片机的一个IO口相连)
        Vdd(电源供应)

 DS18N20的电路原理图

                                                                                                    

DS18B20要想实现温度测量,必须先复位初始化:  

 复位初始化时序图

首先总线为高电平持续约几微秒,拉低总线,持续480us—960us再释放总线(拉高总线),等待15-60us,如果检测到DS18B20存在端口会返回一个低电平脉冲信号,持续60–240us,总的检测时间最小为480us,所以再检测低电平脉冲信号(240us)后,释放总线(拉高总线)240us(480-240=240);

   复位代码实现 

#include "reg52.h"
#include <intrins.h>
sbit DS = P3^7;

int DS_Reset()
{
    unsigned char i;
    DS = 1;
    _nop_();
    DS = 0;//拉低最少480us,480us----960us(最上边单总线协议的初始化时序图上有最大时间为960us)
    i = 70;
    while(i--);//循环一次需要9个周期,79*9=630 再加上i=0没计算的周期,630+12=642us
    DS = 1;//拉高总线
    i = 0;
    i = 2;
    while(i--);//等待18us,时序图上是等待15us -- 60us
    if(DS){
        i = 7;//7*9 = 63us
        while(i--);//再多给63us如果还是高电平,则复位失败
        if(DS){
            return 0;
        }
    }
   return 1;//如果为低电平则返回1,表示复位成功
}

 写时序图

总线高电平维持一个_nop_(),然后拉低总线维持_nop_(),DS开始写数据,先取出最低位:temp&0x01,如果写入0的话最低位就为0,如果写1的话最低位就为1。然后延时60us(15+15+30 = 60)如果写1的话,15us后就写入1,如果写0的话60us就开始写0。

写数据代码实现

#include "reg52.h"
#include <intrins.h>

sbit DS = P3^7;

void Ds18b20WriteByte(uchar temp)
{
    unsigned char i,j;
    for(j=0;j<8;j++){
    DS = 1;
    _nop_();
    DS = 0;
    _nop_();
     DS = 1;//释放总线
    DS = temp & 0x01;//取出temp的最低位,如果temp的最低位为0,0&1=1,则DS存放0,如果temp最低位为1,1&1=1,DS,存放1
    i = 6;
    while(i--);//延时66us : 6*9+12=66us
    temp >>= 1;//temp >>= 1 ==> temp = temp>>1移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
    }
}

 读时序图

 总线高电平维持一个_nop()_,拉低总线,延时_nop_(),释放总线,开始读数据,45us后开始读下一个数据

#include "reg52.h"
#include <intrins.h>

sbit DS = P3^7;

char    Ds18b20ReadByte()
{
   unsigned char temp;//存放来自DS的数据
   unsigned int i,j;
   for(j=0;j<8;j++){
        DS = 1;
        _nop_();
        DS = 0;
        _nop_();
        _nop_();
        DS = 1;//释放总线
        temp >>= 1;
        if(DS){
            temp = temp | 0x80;//如果temp写1,那么或一个0x80,就将1读出来
        }
        if(!DS){
            temp = temp | 0x00;//如果temp写0,那么或一个0x00,就将0读出来
        }
        i=4;
        while(i--);//延时48us再读取下一位数据
    }
    return temp;
}

 此次实验所使用的是普中科技A251单片机

P3^7口作为I/O口来读写数据:

读出数据后乘以频率0.0625就的到了温度,高位全为1,则温度位负数

 

 想要检测温度必须先复位然后发送CCH来跳过ROM指令,然后发44H(每次只能运行RAM指令表和暂存寄存器两个中的一个指令表,运行后需复位再运行另一个),复位,发CCH,再发送BEH来读暂存器,读温度的低8位和高8位 

温度测量的代码实现

#include <reg52.h>
#include <intrins.h>

sbit LSA 	= P2^2;
sbit LSB 	= P2^3;
sbit LSC 	= P2^4; 
sbit DS     = P3^7;	

//转换温度启动指令
void Ds18b20ChangTemp()
{
	Ds18b20_init(); //初始化
	delayms(1);   
	Ds18b20WriteByte(0xcc); //写入跳过ROM操作命令(cc)
	Ds18b20WriteByte(0x44); //温度转换命令(44)
}

//读取温度命令
void Ds18b20ReadTempCom()
{
	Ds18b20_init();
	delayms(1);
	Ds18b20WriteByte(0xcc);   //跳过ROM操作命令
	Ds18b20WriteByte(0xbe);   //发送读取温度命令
}

int DS18b20ReadTemp()
{
    float tp;
    int i;
   	int temp = 0;  
	uchar tmh,tml;
	Ds18b20ChangTemp();      //先写入转换命令   
	Ds18b20ReadTempCom();    //然后等待转换完后发送读取温度命令
	tml = Ds18b20ReadByte(); //读取温度值共16位,先读低字节
	tmh = Ds18b20ReadByte(); //再读高字节
	temp = tmh;              //用中间值来运算,防止测量温度的时候出现问题   
	temp <<= 8;              //移位运算,高字节左移8位,变为16位
	temp |= tml;             //位运算中的或运算,组合
	if(temp <0){
      temp = temp-1;//因为得到的是补码,但是温度计算用原码计算,原码按位取反+1就是补码
      tp = ~temp;
      temp = temp*0.0625*100+0.5 //留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为                
整型的时候把小数点后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
算加上0.5,还是在小数点后面。
    }else{
        tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量,如果温度是正的那么,那么正数的原码就是补码它本身
		temp=tp*0.0625*100+0.5;
    }

    return temp;             
}
    

数码管显示温度 

本次项目使用的是普中A289c52单片机,数码管的驱动不是直接在端口,而是通过74HC138译码器进行位选

 

 通过译码器实现数码管位选和显示的代码实现

#include <reg52.h>
#include <intrins.h>

sbit BEEP = P1^7;
sbit LSA 	= P2^2;
sbit LSB 	= P2^3;
sbit LSC 	= P2^4;
 
sbit DS = P3^7;	
 
char code smgduan[13]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
											0x7f,0x6f,0x00,0x80,0x40}; //共阴:0-9数码
char display_T[8];//存放温度的数组		              不显示  .	  -


//毫秒延迟函数
void delayms(uint ms)
{
	uchar i;
	while(ms--)
		for(i=0;i<123;i++);
}	


//数码管显示温度
void ds20b18display(int temp)
{
	
	display_T[2] = temp/10000;          //显示百位
	display_T[3] = temp%10000/1000;     //显示十位
	display_T[4] = temp%1000/100;       //显示个位
	display_T[6] = temp%10/10;          //显示小数点后一位
	display_T[7] = temp%10;             //显示小数点后两位
	
	display_T[1] = 10;                 //无显示
	display_T[5] = 11;                 //显示“.”
}


//数码管显示函数
void Digdisplay()
{
	char i;
	for(i=0;i<8;i++)
	{
		switch(i)
		{
			case 0: LSC=1;LSB=1;LSA=1; break;   //显示第1位数码管
			case 1: LSC=1;LSB=1;LSA=0; break;	//显示第2位数码管
			case 2: LSC=1;LSB=0;LSA=1; break;   //显示第3位数码管
			case 3: LSC=1;LSB=0;LSA=0; break;   //显示第4位数码管
			case 4: LSC=0;LSB=1;LSA=1; break;   //显示第5位数码管
			case 5: LSC=0;LSB=1;LSA=0; break;   //显示第6位数码管
			case 6: LSC=0;LSB=0;LSA=1; break;   //显示第7位数码管
			case 7: LSC=0;LSB=0;LSA=0; break;   //显示第8位数码管 LED1
		}
		P0=smgduan[display_T[i]];  //发送数据
		delayms(1);      //延时1ms,让数码管不断显示
		P0=0x00;         //数码管消影		
	}
}

所有程序展示

#include <reg52.h>
#include <intrins.h>
#define uint unsigned int 
#define uchar unsigned char

typedef unsigned int u16;

sbit BEEP = P1^7;
sbit LSA 	= P2^2;
sbit LSB 	= P2^3;
sbit LSC 	= P2^4;
 
sbit DS = P3^7;	
 
char code smgduan[13]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
											0x7f,0x6f,0x00,0x80,0x40}; //共阴:0-9数码
char display_T[8];			//		 不显示  .	  -
											
//毫秒延迟函数
void delayms(uint ms)
{
	uchar i;
	while(ms--)
		for(i=0;i<123;i++);
}	


 
//数码管显示函数
void Digdisplay()
{
	char i;
	for(i=0;i<8;i++)
	{
		switch(i)
		{
			case 0: LSC=1;LSB=1;LSA=1; break;   //显示第1位数码管
			case 1: LSC=1;LSB=1;LSA=0; break;		//显示第2位数码管
			case 2: LSC=1;LSB=0;LSA=1; break;   //显示第3位数码管
			case 3: LSC=1;LSB=0;LSA=0; break;   //显示第4位数码管
			case 4: LSC=0;LSB=1;LSA=1; break;   //显示第5位数码管
			case 5: LSC=0;LSB=1;LSA=0; break;   //显示第6位数码管
			case 6: LSC=0;LSB=0;LSA=1; break;   //显示第7位数码管
			case 7: LSC=0;LSB=0;LSA=0; break;   //显示第8位数码管
		}
		P0=smgduan[display_T[i]];  //发送数据
		delayms(1);      //延时1ms,让数码管不断显示
		P0=0x00;         //数码管消遣		
	}
}
 
//DS18B20初始化函数
uchar Ds18b20_init()
{
	/*unsigned char i;
	DS = 0;  //将总线拉低480US~960us
	i = 70;
	while(i--); //延时642us,循环一次要9个周期,9x70=630 再加一次跳过没计算的12,就是642
	DS = 1;     //拉高总线,如果DS18B20做出反应会将在15us~60us后总线拉低
	i = 0;
	while(DS)   //等待DS18B20拉低总线
	{
		delayms(1);
		i++;
		if(i>1)   //等待大于1MS,说明总线没有被拉低,初始化失败
		{
			return 0; //初始化失败
		}
	}
	return 1;   //若为低电平,直接跳过while循环初始化成功
		*/
	 unsigned char i;
    DS = 1;
    _nop_();
    DS = 0;//拉低最少480us,480us----960us(最上边单总线协议的初始化时序图上有最大时间为960us)
    i = 70;
    while(i--);//循环一次需要9个周期,79*9=630 再加上i=0没计算的周期,630+12=642us
    DS = 1;//拉高总线
    i = 0;
    i = 2;
    while(i--);//等待18us
    if(DS){
        i = 7;//7*9 = 63us
        while(i--);//再多给63us如果还是高电平,则复位失败
        if(DS){
            return 0;
        }
    }
   return 1;//如果为低电平则返回1,表示复位成功
}
 
//写时序函数
void Ds18b20WriteByte(uchar temp)
{
	/*int i,j;
	for(j=0;j<8;j++)  //8位数据
	{
		DS = 0;             //每写入一位数据之前先把总线拉低1us
		_nop_();               
		DS = tmp & 0x01;    //然后写入一个数据,从最低位开始。位运算的与运算,只要数据中有0,则相与后为0,只有为1相与为1
		i=6;                //延时初值
		while(i--);         //延时66us,因为持续时间最少为60us 6*9+12 = 66
		DS = 1;             //然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
		tmp >>= 1;          //移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
	}*/
	  unsigned char i,j;
    for(j=0;j<8;j++){
    DS = 1;
    _nop_();
    DS = 0;
    _nop_();
     DS = 1;//释放总线
    DS = temp & 0x01;//取出temp的最低位,如果temp的最低位为0,0&1=1,则DS存放0,如果temp最低位为1,1&1=1,DS,存放1
    i = 6;
    while(i--);//延时66us : 6*9+12=66us
    temp >>= 1;//temp >>= 1 ==> temp = temp>>1移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
    }
}

 
//读时序函数
uchar Ds18b20ReadByte()
{
	uchar tmp;
	uint i,j;
	for(j=8;j>0;j--)
	{
			DS = 0;  //先将总线拉低1us
			_nop_(); 
			tmp = tmp >> 1; 
			DS = 1;  //然后释放总线
			_nop_(); 
			_nop_();    //延迟几us等待数据稳定
			if(DS){
			tmp = tmp | 0x80;    
		}
		 else if(!DS){
			tmp = tmp | 0x00;
		}
		i=4;        //4*9+12 = 48
		while(i--);//读取完之后等待48us再接着读取下一个数
	}
	return tmp;
}
 
//转换温度启动指令
void Ds18b20ChangTemp()
{
	Ds18b20_init(); //初始化
	delayms(1);   
	Ds18b20WriteByte(0xcc); //写入跳过ROM操作命令(cc)
	Ds18b20WriteByte(0x44); //温度转换命令(44)
}
 
//读取温度命令
void Ds18b20ReadTempCom()
{
	Ds18b20_init();
	delayms(1);
	Ds18b20WriteByte(0xcc);   //跳过ROM操作命令
	Ds18b20WriteByte(0xbe);   //发送读取温度命令
}
 
//读取温度
int Ds18b20ReadTemp()
{
	float tp;
	int i , beep_reg;
	int temp = 0;  
	uchar tmh,tml;
	Ds18b20ChangTemp();      //先写入转换命令   	
	Ds18b20ReadTempCom();    //然后等待转换完后发送读取温度命令
	tml = Ds18b20ReadByte(); //读取温度值共16位,先读低字节
	tmh = Ds18b20ReadByte(); //再读高字节
	temp = tmh;    
	temp <<= 8;              //移位运算,高字节左移8位,变为16位
	temp |= tml;             //位运算中的或运算,组合

	if(temp < 0)
	{
		display_T[0] = 12;   //显示“-”
		temp=temp-1; //因为读取的温度是实际温度的补码,所以减1,再取反求出原码        
		temp=~temp;
		tp=temp;
		temp=tp*0.0625*100+0.5; //0.0625是精度
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算加上0.5,还是在小数点后面。
	}
	else
	{
		display_T[0] = 10;   //无显示
		tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量
		//如果温度是正的那么,那么正数的原码就是补码它本身
		temp=tp*0.0625*100+0.5;
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算加上0.5,还是在小数点后面。
		beep_reg = temp;
		if(beep_reg>3000)		//超过30度报警,但是数值一定要写3000,因为传过来的数是不加小数点的数据4位:两位整数两位小数;其他温度同理
	 	{
			for(i=30;i>0;i--)
			{
				BEEP=~BEEP;
				}
			}
	 	}		
	return temp;             //高8位和低8位的组合
	  
}
 
//数码管显示温度
void ds20b18display(int temp)
{
	
	display_T[2] = temp/10000;          //显示百位
	display_T[3] = temp%10000/1000;     //显示十位
	display_T[4] = temp%1000/100;       //显示个位
	display_T[6] = temp%10/10;          //显示小数点后一位
	display_T[7] = temp%10;             //显示小数点后两位
	
	display_T[1] = 10;                 //无显示
	display_T[5] = 11;                 //显示“.”
}
 
 
 
void main()
{
	int temp_chek;
	BEEP =1;
	Ds18b20_init();
	while(1)
	{
		ds20b18display(Ds18b20ReadTemp());		
		Digdisplay();
		temp_chek = Ds18b20ReadTemp();
		if(temp_chek >= 30 ){
			BEEP = 0;
		}else {
			BEEP = 1;
			delayms(30);
		}
	}
}

结果展示

物联沃分享整理
物联沃-IOTWORD物联网 » 使用单总线协议测量温度的stc89c52 DS18B20方案

发表评论