通信协议是最重要的,我们之前学习了I2C通信协议,这一节我们学习一下新的通信协议,单总线通信。
一、开发板原理图
可以看出直接由P3_7口控制,但是遵循单总线协议。
单总线的电路要求
现在介绍单总线的通信协议细节:
1、Init初始化
初始化:主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线。
2、发送一个位
发送一位:主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us。
3、接收一位
接收一位:主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60u
4、发送、接收一位的功能。连续调用8次就好了
DS18B20的使用:温度变换:初始化→跳过ROM(因为本节只有一个设备) →开始温度变换
温度读取:初始化→跳过ROM(因为本节只有一个设备) →读暂存器→连续的读操作
以下是ROM指令,功能指令
ROM指令,在只有一个设备的时候直接使用SKIP就好,如果有多个,可以使用MATCH,搜索从机。
功能指令:1、CONBERT T:执行温度变换操作,读取温度值放在寄存器中。2、对寄存器写WRITE:这里写入的是温度上限、下限、分辨率。3、读取寄存器READ:将寄存器中的温度值读出来。4、COPY :从机将寄存器中主机写入的上下限、精度,存储在DS18B20的E2PROM中。5、RECALL E2P:把从机的E2PROM读取到寄存器中。6、READ POWER SUPLY:判读寄生供电、还是独立供电。
二、简单的温度读取显示
先按照原理,编写单总线的代码:
#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;
}
这里的代码,需要结合前面的原理图进行理解,其实并不难,主要是学会如何表达流程与逻辑。上面代码,应该多多理解,因为不是简单的使用逻辑判断,而是使用更合适的方法,收发0,1写在了一起。
注意这里先发出的是低位。所以收的时候也是从低位开始收的。
其中延时500us不能直接使用以前的DELAY函数,我们现在用STC-ISP自动生成。代码中我们偷懒了,没有全部使用,大致忽略了5us。
写完了底层的通信协议,我们借助通信协议去书写我们的DS18B02的函数。
#include <REGX52.H>
#include "OneWire.h"
//DS18B20指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
/**
* @brief DS18B20开始温度变换
* @param 无
* @retval 无
*/
void DS18B20_ConvertT(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_SCRATCHPAD);
TLSB=OneWire_ReceiveByte();
TMSB=OneWire_ReceiveByte();
Temp=(TMSB<<8)|TLSB;
T=Temp/16.0;
return T;
}
这里的功能就是前面讲的,初始化,跳过ROM,然后发送指令。因为温度信息在前两位,所以连读两次。
因为表示的是一个小数,存储的最低为不是2^0,而是2^-4,所以相当于我们读进来的数,大了16倍。所以最后除以16.0,必须是点0,才能得到浮点数。
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
float T;
void main()
{
DS18B20_ConvertT(); //上电先转换一次温度,防止第一次读数据错误
Delay(1000); //等待转换完成
LCD_Init();
LCD_ShowString(1,1,"Temperature:");
while(1)
{
DS18B20_ConvertT(); //转换温度
T=DS18B20_ReadT(); //读取温度
if(T<0) //如果温度小于0
{
LCD_ShowChar(2,1,'-'); //显示负号
T=-T; //将温度变为正数
}
else //如果温度大于等于0
{
LCD_ShowChar(2,1,'+'); //显示正号
}
LCD_ShowNum(2,2,T,3); //显示温度整数部分
LCD_ShowChar(2,5,'.'); //显示小数点
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示温度小数部分
}
}
因为,LCD只能显示整数,小数部分会直接省略,所以我们需要自己把小数部分计算出来。这个逻辑很简单,先把小数部分通过×10000变成整数的一部分,然后取余数,把这部分取出来就好了。
三、设置温度上下限,进行温度报警
这里写一个综合案例,用于设置温度的上下限,这一个上下限存储在AT24C02,
里面的代码都是之前写过的,就不再讲,只看main函数,书写逻辑
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
float T,TShow;
char TLow,THigh;
unsigned char KeyNum;
void main()
{
DS18B20_ConvertT(); //上电先转换一次温度,防止第一次读数据错误
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_ConvertT(); //转换温度
T=DS18B20_ReadT(); //读取温度
if(T<0) //如果温度小于0
{
LCD_ShowChar(1,3,'-'); //显示负号
TShow=-T; //将温度变为正数
}
else //如果温度大于等于0
{
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) //K1按键,THigh自增
{
THigh++;
if(THigh>125){THigh=125;}
}
if(KeyNum==2) //K2按键,THigh自减
{
THigh--;
if(THigh<=TLow){THigh++;}
}
if(KeyNum==3) //K3按键,TLow自增
{
TLow++;
if(TLow>=THigh){TLow--;}
}
if(KeyNum==4) //K4按键,TLow自减
{
TLow--;
if(TLow<-55){TLow=-55;}
}
LCD_ShowSignedNum(2,4,THigh,3); //显示阈值数据
LCD_ShowSignedNum(2,12,TLow,3);
AT24C02_WriteByte(0,THigh); //写入到At24C02中保存
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(); //每20ms调用一次按键驱动函数
}
}
使用中断检测按键按下,获取当前按下的Key_KeyNum。
在主函数的循环下,根据每次键值的不同,进行不同的操作。键值为1的时候上限加,键值为2的时候上限减。键值为3的时候下限加,键值为4的时候下限减。修改的同时显示出来,并存储在AT24C02中。
每次循环中,进行温度和上下限的对比,显示不同的结果。