蓝桥杯单片机学习9——单总线协议&DS18B20实现可调温度报警器
单总线协议时序介绍及代码、DS18B20介绍,使用方法和函数实现、通过DS18B20实现可调温度报警器
上期我们学习了串口通信,这次我们来学习DS18B20的基本使用,在此基础上实现一个简易的可调温度报警器
蓝桥杯单片机学习9——单总线协议&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;
}
完结撒花😘😘😘😘😘😘😘😘😘💕💕💕💕💕💕💕💕💕💕💕💕
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)