精准测温,51单片机搭载DS18B20温度传感器
DS18B20
介绍
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点,其测温范围大约在-55°C 到 +125°C之间,它的通信接口是1-Wire(单总线),其它特征有可形成总线结构、内置温度报警功能、可寄生供电(可寄生供电即无需接VCC,只需要接数据线与GND即可完成数据通信,节省线路)
关于温度的测量,我们可以用温度传感器或者数字温度传感器,但是数字温度传感器总体会比温度传感器的精度要高一些。
本单片机的DS18B20无需设计电路,只需要通过引脚与单总线通信协议,将RAM里面的温度转化读取出来即可,因此我就不在说明里面的电路构造了
存储器
TEMPERATURE SENSOR(温度传感器)相当于内部的模拟传感器集成部分。
当发出指令让其开始温度转换时,该部分就开始工作,并将数据放入RAM中。
ALARM HIGH TRIGGER REGISTER(TH,报警高触发器)用于存储温度上限阈值,实现温度报警。
存储介质为EEPROM,掉电不丢失数据。
ALARM LOW TRIGGER REGISTER(TL,报警低触发器)用于存储温度下限阈值,实现温度报警。
存储介质为EEPROM,掉电不丢失数据。
CONFIGURATION REGISTER(配置寄存器)用于设置分辨率(精度)
——这里出厂默认最高精度为0.0625℃。
可以通过配置寄存器中两位(其他位无效)来降低精度,最低为0.5℃。
当精度降低时,温度转换速率会迅速提升。(相当于处理的数据少了,流程简化,速度就上去了)
存储介质为EEPROM,掉电不丢失数据。
8-BIT CRC GENERATOR(8位CRC生成器)中CRC为校验码,该部分将RAM之前的数据进行校验,并生成一个校验码放于后端,通讯时检测即可判断数据是否正确。(CRC为校正率较高的一种校验码)
温度存储部分:
Byte 0存储的是温度的最低有效字节,括号内为默认值。
Byte1存储的是温度的最高有效字节,括号内为默认值。
两个字节共同组成温度值,在未变动情况下,默认值为85℃。(不同单片机默认值不一样)
配置温度数据部分:
Byte 2存储为温度上限阈值,Byte 3存储为温度下限阈值,Byte 4存储为精度(分辨率)。
右边的EEPROM负责存储数据,需要读取与写入数据要先经过RAM部分,然后通过指令将数据存储进EEPROM中(当上电时,EEPROM的数据会自动放入RAM中)
Byte 5~Byte 7为保留部分,说白了,就是没有任何用处,只不过为了以后的功能添加而预留的位置。
CRC校验部分:
将前面八个字节进行运算,并生成CRC校验码。
可以读取CRC校验码,并对前面八个字节数据进行运算,如果运算结果与CRC相同,那么校验正确,反之数据出错。
单总线时序结构
初始化
由于这是单总线,所以数据传送的时间一定要严格
图中黑粗线Bus master pulling low(主机拉低)是将总线拉低;而图中黑细线Resistor pullup为上拉电阻将总线上拉(因为是弱上拉,所以利用曲线来表示弱的状态,而不是强上拉一下拉上去)
当从机存在时,从机会将总线拉低(即图中棕线DS18B20 pulling low)。最终上拉电阻拉高,总线恢复空闲状态。由于中间的操作间隔时间是范围值,没有确切的参考数据,于是取中间值来作为等待间隔时间,对于以上的操作,可以将过程分为复位与响应两个部分
发送一位
我们可以将上面的部分分为发送0与发送1两部分的时序。换句话说,就是通过时间的不同来区分数据是输入1还是0.
发送0时,拉低超过60us且不能超过120us,否则如果超过120us时,可能会变为初始化操作(至少480us),两个数据发送中间存在总线恢复时间,因此连续发送两位不能低于这个恢复时间。
图中阴影部分,表示可以在1~15us时释放总线即可,这时电阻都会将总线拉高。
从机在主线拉低30us后进行操作(如果发送0,就一直保持总线拉低;如果发送1,就在主线拉低后,将总线释放),这样就可以很清楚的确认要发送的数据了。若在主线拉低15us后进行操作,就有可能会出现一些数据传输的偏差,因为发送1的时序在15us时,正处于主线拉高的状态,我们无法准确的判断此时是否已经完成拉高的操作,因此在30us后进行最合适。
接收一位
当主机拉低总线后,从机如果需要发0,那么也拉低总线,当主机在1~15us后释放总线时,因为从机拉低着,因此总线状态为拉低状态。
当主机拉低总线后,从机如果需要发1,那么就不动总线。当主机经过1~15us后释放总线时,因为没有被拉低,因此总线状态为拉高状态。在总机拉低后15us内读取总线电平(I/O口),即可知道从机需要发送的数据。
发送与接收一个字节
DS18B20操作
首先,我们需要将DS18B20进行初始化,让从机复位,主机判断从机是否响应
然后,就是ROM的操作和功能指令的操作
ROM
对于ROM的操作,就是ROM指令+本指令需要的读写操作
SEARCH ROM就是表面意思,搜索ROM。
READ ROM就是读取ROM:先调用读取指令,再调用读取时序(接收部分),就可以将ROM读取出来。
MATCH ROM就是匹配ROM:先发送匹配指令,然后发送ROM地址,实现利用该指令匹配与哪个设备进行通信的效果。
SKIP ROM就是跳过ROM:调用跳过指令,直接将ROM部分跳过进入下一部分。但是,要注意的是,当总线上只有一个设备时使用。多个设备挂载时,使用这个指令会产生混乱,无法确定是哪个设备
ALARM SEARCH就是报警搜索:当挂载多个设备的情况下,某个设备温度超过设定温度阈值时,会产生报警,利用这个指令即可快速搜索出超过温度阈值的设备。
功能指令
CONVERT T(温度变换):调用该指令时,温度传感器部分会将温度数据的模拟信号转化为数字信号,并将数据放在RAM中,从而达到RAM里面的温度数据与外界温度几乎同步的状态。
WRITE SCRATCHPAD(写暂存器):调用该指令后,再接着写入时序(发送一个字节部分),那么该字节就会被写入RAM中的配置温度数据部分。
READ SCRATCHPAD(读暂存器):调用该指令后,再接着接收时序(接收一个字节部分部分),那么DS18B20就会依次将RAM里面内容读取出来(直到CRC)。至于代码的实现只需要读取前两个字节(温度数据)即可。
COPY SCRATCHPAD(复制暂存器):调用该指令后,DS18B20就会把RAM中配置温度数据部分写入EEPROM中(实现掉电不丢失)。
RECALL E2(重调E2暂存器):调用该指令后,DS18B20就会把EEPROM中数据放入RAM中(覆盖之前的数据)。
READ POWER SUPPLY(读取设备供电模式):调用该指令后,紧跟着读取一位时序(接收一位字节部分),就会返回当前供电状态(独立供电或寄生供电),因为进行操作(如数据变换)时,需要高电能补充;因此在寄生供电状态下,需要调强上拉模式;如果在独立供电时,就不需要调;
DS18B20数据帧
以上是温度变换与读取的相关操作。
温度存储的原理
这里的小数部分依靠二分法的机制进行补位,最小分度为0.0625,即每次数据加一,即温度增加0.0625℃。这里数据的存储方式以二进制补码的形式存储。即正数保持不变,负数则将对应的正数每个位取反,最后加一。
代码
温度读取代码
主函数代码
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "OneWire.h"
#include "DS18B20.h"
float T;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Temperature:");
while(1)
{
DS18B20_ConverT();
T=DS18B20_ReadT();
if(T<0)
{
LCD_ShowChar(2,1,'-');
T=-T;
}
else
{
LCD_ShowChar(2,1,'+');
}
LCD_ShowNum(2,2,T,3);
LCD_ShowChar(2,5,'.');
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//这样便可以将小数部分进行显示了
}
}
部分模块代码
#include <REGX52.H>
sbit OneWire_DQ=P3^7;
/**
* @brief 单总线初始化
* @param 无
* @retval 从机响应位,0为响应,1为未响应
*/
unsigned char OneWire_Init(void)
{
unsigned char i;
unsigned char AckBit;
OneWire_DQ=1;
OneWire_DQ=0;
i = 247;while (--i); //Delay 500us
OneWire_DQ=1;
i = 32;while (--i); //Delay 70us
AckBit=OneWire_DQ;
i = 247;while (--i); //Delay 500us
return AckBit;
}
/**
* @brief 单总线发送一位
* @param Bit 要发送的位
* @retval 无
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ=0;
i = 4;while (--i); //Delay 10us
OneWire_DQ=Bit;
i = 24;while (--i); //Delay 50us
OneWire_DQ=1;
}
/**
* @brief 单总线接收一位
* @param 无
* @retval 读取的位
*/
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
OneWire_DQ=0;
i = 2;while (--i); //Delay 5us
OneWire_DQ=1;
i = 2;while (--i); //Delay 5us
Bit=OneWire_DQ;
i = 24;while (--i); //Delay 50us
return Bit;
}
/**
* @brief 单总线发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
OneWire_SendBit(Byte&0x01<<i);
}
}
/**
* @brief 单总线接收一个字节
* @param 无
* @retval 接收的一个字节
*/
unsigned char OneWire_ReceiveByte(void)
{
unsigned char i;
unsigned char Byte=0x00;
for(i=0;i<8;i++)
{
if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
}
return Byte;
}
#include <REGX52.H>
#include "OneWire.h"
#include "LCD1602.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCARTCHPAD 0xBE
/**
* @brief DS18B20开始温度变换
* @param 无
* @retval 无
*/
void DS18B20_ConverT(void)
{
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
/**
* @brief DS18B20读取温度
* @param 无
* @retval 温度数值
*/
float DS18B20_ReadT(void)
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCARTCHPAD);
TLSB=OneWire_ReceiveByte();
TMSB=OneWire_ReceiveByte();
Temp=(TMSB<<8)|TLSB;
T=Temp/16.0;
return T;
}
温度警报器代码
主函数代码
#include <REGX52.H>
#include "AT24C02.h"
#include "Delay.h"
#include "I2C.h"
#include "LCD1602.h"
#include "Key.h"
#include "DS18B20.h"
#include "OneWire.h"
#include "Timer0.h"
float T,TShow;
char TLow,THigh; //定义温度上限与下限阈值
unsigned char KeyNum;
void main()
{
DS18B20_ConverT(); //使得温度数据不是默认值
Delay(1000);
THigh=AT24C02_ReadByte(0); //读取掉电前的数据
TLow=AT24C02_ReadByte(1);
if(THigh>125||TLow<-55||THigh<=TLow)
{
THigh=20;
TLow=15;
}
LCD_Init();
LCD_ShowString(1,1,"T:");
LCD_ShowString(2,1,"TH:");
LCD_ShowString(2,9,"TL:");
LCD_ShowSignedNum(2,4,THigh,3); //上限阈值
LCD_ShowSignedNum(2,12,TLow,3); //下限阈值
Timer0_Init();
while(1)
{
KeyNum=Key();
/*温度读取及显示*/
DS18B20_ConverT();
T=DS18B20_ReadT();
if(T<0)
{
LCD_ShowChar(1,3,'-');
TShow=-T;
}
else
{
LCD_ShowChar(1,3,'+');
TShow=T;
}
LCD_ShowNum(1,4,TShow,3);
LCD_ShowChar(1,7,'.');
LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);
/*阈值判断及显示*/
if(KeyNum) //在按键按下时才进行检查,减少扫描时间
{
if(KeyNum==1)
{
THigh++;
if(THigh>125){THigh=125;}
}
if(KeyNum==2)
{
THigh--;
if(THigh<=TLow){THigh++;}
}
if(KeyNum==3)
{
TLow++;
if(TLow>=THigh){TLow--;}
}
if(KeyNum==4)
{
TLow--;
if(TLow<-55){TLow=-55;}
}
LCD_ShowSignedNum(2,4,THigh,3);
LCD_ShowSignedNum(2,12,TLow,3);
AT24C02_WriteByte(0,THigh); //保存上限下限的阈值数据
Delay(5);
AT24C02_WriteByte(1,TLow);
Delay(5);
}
if(T>THigh)
{
LCD_ShowString(1,13,"OV:H");
}
else if(T<TLow)
{
LCD_ShowString(1,13,"OV:L");
}
else
{
LCD_ShowString(1,13," ");
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=20)
{
T0Count=0;
Key_Loop(); //在较短的时间内不断判断按键的状态
}
}
部分模块代码
#include <REGX52.H>
#include "Delay.h"
unsigned char Key_KeyNumber;
unsigned char Key(void)
{
unsigned char Temp=0;
Temp=Key_KeyNumber;
Key_KeyNumber=0;
return Temp;
}
unsigned char Key_GetState()
{
unsigned char KeyNumber=0;
if(P3_1==0){KeyNumber=1;}
if(P3_0==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==0){KeyNumber=4;}
return KeyNumber;
}
void Key_Loop(void)
{
static unsigned char NowState,LastState;
LastState=NowState; //由于按键按下时的前后状态会不同,就可以用判断的方法去确定是否按下按键
NowState=Key_GetState(); //这样做就可以避免调入死循环中
if(LastState==1&&NowState==0)
{
Key_KeyNumber=1;
}
if(LastState==2&&NowState==0)
{
Key_KeyNumber=2;
}
if(LastState==3&&NowState==0)
{
Key_KeyNumber=3;
}
if(LastState==4&&NowState==0)
{
Key_KeyNumber=4;
}
}
unsigned char OneWire_Init(void)
{
unsigned char i;
unsigned char AckBit;
EA=0; //关闭定时器0
OneWire_DQ=1;
OneWire_DQ=0;
i = 247;while (--i); //Delay 500us
OneWire_DQ=1;
i = 32;while (--i); //Delay 70us
AckBit=OneWire_DQ;
i = 247;while (--i); //Delay 500us
EA=1; //打开定时器0
return AckBit;
}
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
EA=0; //关闭定时器0
OneWire_DQ=0;
i = 4;while (--i); //Delay 10us
OneWire_DQ=Bit;
i = 24;while (--i); //Delay 50us
OneWire_DQ=1;
EA=1; //打开定时器0
}