蓝桥杯单片机学习9——单总线协议&DS18B20实现可调温度报警器

上期我们学习了串口通信,这次我们来学习DS18B20的基本使用,在此基础上实现一个简易的可调温度报警器

DS18B20

关于DS18B20温度传感器的相关知识,我这里就不在过多赘述了,主要介绍一些关键的信息,大家可以自行去网上看一看相关的资料。本人强烈推荐小蜜蜂老师的 《单总线数字温度传感器DS18B20的基本原理及开发要点》 ,通俗易懂,老少皆宜。

单总线协议

DS18B20使用的是单总线协议通信(onewire),顾名思义,只有一个数据线,可以极大的节省IO口。在比赛的时候,官方会提供对应的库函数供我们使用,我们先来看一下DS18B20(单总线协议)的一些具体操作:

1. 初始化/复位时序

在这里插入图片描述

【1】微处理器(单片机)首先将总线(数据线)拉低480us以上,然后释放总线。
【2】总线释放,将总线拉高。
【3】DS18B20检测到上升沿,在等待15~60us后,拉低总线,表示应答。
【4】微处理器在DS18B20应答期间,读取总线上的电平,如果是低电平则表示复位成功。
【5】DS18B20在产生60~240us的应答信号后,释放总线。

官方提供代码如下:

//DS18B20设备初始化
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;			//DQ是数据线,用于单总线通信
  	Delay_OneWire(12);	//将DQ拉高
  	DQ = 0;
  	Delay_OneWire(80);	//将总线拉低480us以上
  	DQ = 1;				//释放总线,将总线拉高
  	Delay_OneWire(10); 	//等待DS18B20应答
    initflag = DQ;      //读取DS18B20是否应答,
  	Delay_OneWire(5);
  
  	return initflag;	//返回应答状态,对初始化函数的返回值进行判断,为低电平(0)边表示应答成功,复位完成
  						//由于单片机和DS18B20直接连接,不存在多机通信,复位一般都会成功,所以亦可以不对返回值进行检验
}

2.写字节时序

在这里插入图片描述
【1】微处理器(单片机)将总线拉低10~15us。
【2】在接下来的15~45us直接,根据逻辑1或逻辑0,控制总线的高低电平。
【3】释放总线。

注意:

  • 写0和写1的最短时间都必须大于60us,最长时间小于120us
  • 在两个相邻的写入操作直接必须要有1us以上的时间间隔

官方提供代码如下:


//通过单总线向DS18B20写一个字节
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;		//将总线拉低10~15us
		DQ = dat&0x01;	//写入逻辑0或逻辑1
		Delay_OneWire(5);	//等待DS18B20采集数据
		DQ = 1;				释放总线
		dat >>= 1;
	}
	Delay_OneWire(5);		
}

3.读字节时序

在这里插入图片描述

【1】微处理器先将总线拉低1us,然后释放总线。
【2】微处理器读取总线上的电平。
【3】微处理器读取电平后,延时约45us。
注意:

  • 读数据的最短时间都必须大于60us
  • 读取数据必须要在在单片机释放总线后的15us内完成
  • 在两个相邻的读取数据操作直接必须要有1us以上的时间间隔
  • 在读取数据之前必须要写入相应的指令,常用的是读出数据指令:0xBE

官方提供代码如下:

//从DS18B20读取一个字节
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;	//将总线拉低1us
		dat >>= 1;
		DQ = 1;	//释放总线
		if(DQ)	//在总线拉低后的15us内,读取数据为0还是1
		{
			dat |= 0x80;	//为1则在对应位写入1
		}	    
		Delay_OneWire(5);	//	//延时45us以上,保证最短时间大于60us
	}
	return dat;
}

DS18B20使用的基本流程

1.DS18B20复位
2.写入ROM指令
3.写入功能指令
4.执行相应指令

常见的DS18B20指令:

0xCC:跳过ROM指令。忽略64位ROM地址,直接向DS18B20发起各种执行指令。
0x44:温度转换指令。启动DS18B20进行温度转换。
0xBE:读取暂存器指令。DS18B20收到该指令后,会逐个输出高速暂存器中字节0到字节9的内容。如果要停止读取,必须进行复位操作。如果只需要读取温度数据,那么,在读完第0个字节和第1个字节数据后,不再理会DS18B20后面发出的数据即可。

DS18B20读取温度的基本流程

【1】DS18B20复位。
【2】写入字节0xCC,跳过ROM指令。
【3】写入字节0x44,开始温度转换。
【4】延时700~900ms。
【5】DS18B20复位。
【6】写入字节0xCC,跳过ROM指令。
【7】写入字节0xBE,读取高速暂存器。

【8】读取暂存器的第0字节,即温度数据的LSB。
【9】读取暂存器的第1字节,即温度数据的MSB。

【10】DS18B20复位。,表示读取数据结束。
【11】将LSB和MSB整合成为一个16位数据。
【12】判断读取结果的符号,进行正负温度的数据处理。

注意: 第五步是为了等待温度转换完成,温度转换最大时间为750ms,在使用过程中可以跳过这一步,避免长时间延时导 致的其他模块无法正常工作,但有可能导致读取温度错误。

DS18B20读取温度代码如下:

//DS18b20获取温度,
void DS18B20_Get_Tempreature()      
{
    unsigned char LSB,MSB;
    init_ds18b20();     //复位
    Write_DS18B20(0xCC);    //跳过ROM指令
    Write_DS18B20(0x44);    //开始温度转换
    //Delayxms(750);
    init_ds18b20();         //复位
    Write_DS18B20(0xCC);    //跳过ROM指令
    Write_DS18B20(0xBE);    //读取温度
    LSB = Read_DS18B20();   
    MSB = Read_DS18B20();
    init_ds18b20();         //复位
    
    DS18B20_Tempreature = (MSB<<8) | LSB;       //对读取的温度进行处理
    if((DS18B20_Tempreature & 0xF800) == 0x000) //判断温度为正
    {
  		  //这种计算方式时错误的,虽然我也不知道为什么,实践告诉我的,求解。。。。。。
//       DS18B20_Tempreature = ((DS18B20_Tempreature >> 4) *10) + ((LSB & 0x0F)*0.0625);	

        DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;           //将读取的温度放大十倍,假设为26.5度,则读出的温度为265
        DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.625; 
    }

}

LSB和MSB寄存器的数据存储格式

LSB和MSB是DS18B20内部的两个存储器,分别存储着温度数据的低八位/高八位。在写入指令0xBE后,DS18B20会将LSB/MSB中的数据发送出来,可以通过读单个字节函数读出先后读出LSB/MSB寄存器的内容,其数据存储格式如下:
在这里插入图片描述
两个寄存器中的数据以二进制补码的格式读出,其中:
低四位为小数部分,当温度数字转换分辨率为12(默认值是12)时,最小分辨率可以达到 1/12 = 0.0625 度
中间七位为整数部分,
高五位表示温度的正负,全为0时温度为正,全为1时温度为负。

                                                                至此,DS18B20相关内容介绍完毕,以下是实践部分

可调温度报警器

1.任务要求


  • 某蔬菜大棚为给蔬菜提供适合的成长为温度,现需要制作一个可调温度报警器,具体要求如下:

1.通过DS18B20获取室内温度,并且再数码管后四位上显示当前温度,具体温度保留一位小数,显示单位为摄氏度

2.在数码管的前四位显示报警温度,单位为摄氏度,当室内温度超过报警温度时蜂鸣器和LED以1s为周期闪烁和鸣叫

3.要求可以通过按键修改报警温度,当按键S5按下时,报警温度增加,S4按下时,报警温度减小,每次按下,增加/减小0.1摄氏度

示例:
在这里插入图片描述

2.具体思路

在这里插入图片描述

3.代码实现:

1.main.c
#include <STC15F2K60S2.H>
#include "LS138.h"
#include "onewire.h"
#include "Interrupt.h"

//这个数组为数码管显示函数对应的数组,可在SEG_Show()函数中显示对应的内容,每一行共十个元素
								  /*0    1    2    3     4    5    6    7    8    9 */
unsigned  char code  NixieTube1[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,
                                   /*0、  1、   2、  3、  4、  5、  6、   7、  8、  9、 */
                                    0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
								  /* A   B     C    D    E    F    H    L    N    P */
								   0x88,0x83,0xC6,0xA1,0x86,0x8E,0x89,0xC7,0xC8,0x8C,
								  /* U    -   ' ' */
								   0xC1,0xBF,0xFF};
unsigned int DS18B20_Tempreature = 0;       //存放DS18B20测得的温度
unsigned int Timer0_Count = 0;              //用于定时器0计数
unsigned char Warming_Flag = 0;             //温度警报标志位,超过预期温度时为1
unsigned int Tempreature_Max = 210;         //设置温度警报标志

//DS18b20获取温度,
void DS18B20_Get_Tempreature()      
{
    unsigned char LSB,MSB;
    init_ds18b20();     //复位
    Write_DS18B20(0xCC);    //跳过ROM指令
    Write_DS18B20(0x44);    //开始温度转换
    //Delayxms(750);
    init_ds18b20();         //复位
    Write_DS18B20(0xCC);    //跳过ROM指令
    Write_DS18B20(0xBE);    //读取温度
    LSB = Read_DS18B20();   
    MSB = Read_DS18B20();
    init_ds18b20();         //复位
    
    DS18B20_Tempreature = (MSB<<8) | LSB;       //对读取的温度进行处理
    if((DS18B20_Tempreature & 0xF800) == 0x000) //判断温度为正
    {
//       DS18B20_Tempreature = ((DS18B20_Tempreature >> 4) *10) + ((LSB & 0x0F)*0.0625);
        DS18B20_Tempreature = (DS18B20_Tempreature >> 4) *10;           //将读取的温度放大十倍,假设为26.5度,则读出的温度为265
        DS18B20_Tempreature = DS18B20_Tempreature + (LSB & 0x0f)*0.625; 
    }

}
//数码管显示温度和温度最大值
void SEG_Show(unsigned int Tempreature,unsigned int Tempreature_Max)
{
    unsigned char i=0;
    unsigned char arr[4];   //存放温度的十位、个位、小数点后一位、单位
    unsigned char arr1[4];  //存放温度最大值的十位、个位、小数点后一位、单位
    arr1[0] = Tempreature_Max/100%10;
    arr1[1] = Tempreature_Max/10%10 +10;
    arr1[2] = Tempreature_Max%10;
    arr1[3] = 22;       //显示字符'c'
    
    
    arr[0] = Tempreature/100%10;
    arr[1] = Tempreature/10%10 +10;
    arr[2] = Tempreature%10;
    arr[3] = 22;        //显示字符'c'
    for(i=0;i<4;i++)    //前四位数码管显示温度最大值
    {
        LS138_Clear();
        LS138_Set(7);   //选择Y7
        P0 = 0xFF;      //消隐
        P0 = NixieTube1[arr1[i]];   //写入数码管段码
        LS138_Clear();
        LS138_Set(6);               //选择Y6
        P0 = 0xFF;
        P0 = 0x01 << i;             //写入需要亮起的数码管
        LS138_Clear();
        Delayxms(2);
        P0 = 0xFF;
    }
    
    for(i=0;i<4;i++)    //后四位数码管显示当前温度
    {
        LS138_Clear();
        LS138_Set(7);   //选择Y7
        P0 = 0xFF;      //消隐
        P0 = NixieTube1[arr[i]];    //写入数码管段码
        LS138_Clear();
        LS138_Set(6);               //选择Y6
        P0 = 0xFF;
        P0 = 0x10 << i;             //写入需要亮起的数码管
        LS138_Clear();
        Delayxms(2);
        P0 = 0xFF;
    }
}
void main()
{
    LS138_Init();   //LS138初始化,包括LED、数码管、蜂鸣器、继电器的初始化
    IT0_Init();     //外部中断0初始化
    IT1_Init();     //外部中断1初始化
    DS18B20_Get_Tempreature();
    Timer0_Init();  //定时器0初始化
	while(1)
	{
        SEG_Show(DS18B20_Tempreature,Tempreature_Max);      //数码管显示温度
    }
}
//外部中断0服务函数,按下S5,温度最大值增加0.1度,最大值为99.9度
void External_Hander0() interrupt 0
{
    if(Tempreature_Max<1000)
    {
        Tempreature_Max++;  
    }
    else
    {
        Tempreature_Max = 999;
    }
}

//外部中断1服务函数,按下S4,温度最大值减小0.1度,最小值为0度
void External_Hander2() interrupt 2
{
    if(Tempreature_Max>0)
    {
        Tempreature_Max--;
    }
    else
    {
        Tempreature_Max = 0;
    }
} 
//定时器0服务函数
void External_Hander1() interrupt 1
{
    static unsigned char i =1;
    Timer0_Count++;
    if(Timer0_Count>=1000)
    {
        Timer0_Count=0;
        DS18B20_Get_Tempreature();  //每隔一秒测一次温度
        if(DS18B20_Tempreature >= Tempreature_Max )      //判断温度是否超出最大温度
        {
            Warming_Flag = 1;      
        }
        else
        {
            Warming_Flag = 0;   
        }
    }
    if(Timer0_Count%500 == 0 && Warming_Flag == 1)  //如果超出最大温度,LED和蜂鸣器以1s为周期,进行闪烁和鸣叫
    {
        LED_Contrl(i%2);
        BEEP_Contrl(i%2);
        i++;
    }
    if(Timer0_Count%500 == 0 && Warming_Flag == 0)  //  如果温度回到正常,关闭蜂鸣器和LED。
    {
        LED_Contrl(0);
        BEEP_Contrl(0);
        i=0;
    }
    
}

基本功能的代码实现都在mian.c中,中间使用的函数在之前以及介绍,这里不做过多赘述,值得一体的的是DS8B20温度读取哈函数:DS18B20_Get_Tempreature() ,
在这里例子中,我们要求对温度保留一位小数,因此,可以定义一个整型变量tempreature ,将读取的问温度放大十倍后赋值给tempreature,如果我们要求的是保留两位小数,则可以将读取的温度放大100倍后赋值给empreature,后再进行处理显示;反之,如果只需要保留整数部分,则可以略去小时部分,直接赋值,

下面是官方提供的单总线协议代码:

2.onewire.c
/*
  程序说明: 单总线驱动程序
  软件环境: Keil uVision 4.10 
  硬件环境: CT107单片机综合实训平台(外部晶振12MHz) STC89C52RC单片机
  日    期: 2011-8-9
*/
#include "STC15F2K60S2.h"

sbit DQ = P1^4;  //单总线接口

//单总线延时函数
void Delay_OneWire(unsigned int t)  //STC89C52RC
{
		unsigned char i;
	while(t--){
//		for(i=0;i<12;i++);  这句再官方提供的代码中是不存在的,但由于官方提供的是工作在12T下的8051单片机编写的
							//而我们竞赛的板子是1T工作模式下的,速度比8051单片机快12倍,因此需要作此改动
							//顺便吐槽一句,2022年的比赛,竟然还给我们提供2011年的源代码,这也太懒了吧。。。。。
		for(i=0;i<12;i++);
	}
}

//通过单总线向DS18B20写一个字节
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	Delay_OneWire(5);
}

//从DS18B20读取一个字节
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
	return dat;
}

//DS18B20设备初始化
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(12);
  	DQ = 0;
  	Delay_OneWire(80);
  	DQ = 1;
  	Delay_OneWire(10); 
    initflag = DQ;     
  	Delay_OneWire(5);
  
  	return initflag;
}


完结撒花😘😘😘😘😘😘😘😘😘💕💕💕💕💕💕💕💕💕💕💕💕
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想写代码的我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值