思路:通过DS18B20测量温度,与设定值进行比较,温度高于设定值启动直流电机风扇降温。低于设定温度时电机停止。
主要模块:DS18B20温度传感器、直流电机驱动、onewire通信协议、定时器扫描按键。
重点为DS18B20的读写,和onewire通信协议的使用,定时器扫描按键。
一. onewire通信协议的使用
1.onewire不同的时序结构
(1) 初始化时序
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平
时间至少 480us(该时间的时间范围可以从 480 到 960 微妙),以产生复位脉
冲。接着主机释放总线,外部的上拉电阻将单总线拉高,延时 15~60 us,并进
入接收模式。接着 DS18B20 拉低总线 60~240 us,以产生低电平应答脉冲,若
为低电平,还要做延时,其延时的时间从外部上拉电阻将单总线拉高算起最少要
480 微妙。初始化时序图如下:
(2)写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在 2 次
独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总
线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0
时序:主机输出低电平,延时 60us,然后释放总线,延时 2us。写时序图如下:
(3)读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出
读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少
需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时
序都由主机发起,至少拉低总线 1us。主机在读时序期间必须释放总线,并且在
时序起始后的 15us 之内采样总线状态。读时序图如下:
典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后延时 50us。
#include <REGX52.H>
#include "INTRINS.H"
sbit DQ=P3^7;
unsigned char OneWire_Init() //开始指令
{
char i,Reception;
EA=0;
DQ=1;
DQ=0;
_nop_();i = 247;while (--i); //延时
DQ=1;
_nop_();i = 32;while (--i);
Reception=DQ;
_nop_();i = 247;while (--i);
EA=1;
return Reception;
}
void Send_Bit(unsigned char Bit) //发送一位
{
unsigned char i;
EA=0;
DQ=1;
DQ=0;
_nop_();i = 4;while (--i);
DQ=Bit;
_nop_();i = 24;while (--i);
DQ=1;
EA=1;
}
unsigned char Receive_Bit() //接收一位
{
unsigned char Bit,i;
EA=0;
DQ=1;
DQ=0;
i = 2;while (--i);
DQ=1;
i = 2;while (--i);
Bit=DQ;
_nop_();
i = 24;while (--i);
EA=1;
return Bit;
}
void OneWire_SendByte(unsigned char Byte) //发送一个字节
{
unsigned char i;
for(i=0;i<8;i++)
{
Send_Bit(Byte&(0x01<<i));
}
}
unsigned char OneWire_ReceiveByte() //接收一个字节
{
unsigned char Byte=0x00,i;
for(i=0;i<8;i++)
{
if(Receive_Bit())Byte|=(0x01<<i);
}
return Byte;
}
DS18B20温度传感器
#include <REGX52.H>
#include "OneWire.H"
#define SKIP_ROM 0xCC //跳过ROM
#define Conver 0x44 // 温度更新
#define ReadT_T 0xBE //读取温度
void ConverT() //温度转换(更新温度)
{
OneWire_Init();
OneWire_SendByte(SKIP_ROM);
OneWire_SendByte(Conver);
}
float ReadT() //读取温度
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(SKIP_ROM);
OneWire_SendByte(ReadT_T);
//将数据合并并转化为浮点数
TLSB=OneWire_ReceiveByte();
TMSB=OneWire_ReceiveByte();
Temp=(TMSB<<8)|TLSB;
T=Temp/16.0;
return T;
}
三、定时器扫描按键
通过中断在相同间隔时间内对按下的按键进行扫描消抖,再通过判断两次扫描的结果判断上升沿来确定按键是否按下。
#include <REGX52.H>
unsigned char Key_KeyNumber;
unsigned char Key(void)
{
unsigned char Temp = 0;
Temp = Key_KeyNumber; // 将全局变量Key_KeyNumber的值赋给Temp,以便后续返回该值
Key_KeyNumber = 0; // 将Key_KeyNumber清零,为下一次记录按键相关情况做准备
return Temp;
}
unsigned char Key_GetState() // 函数Key_GetState用于检测按键的当前状态,根据P3端口特定引脚的电平情况来确定按下的按键对应的编号
{
unsigned char KeyNumber = 0; // 定义一个无符号字符型变量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; // NowState用于记录按键的当前状态,LastState用于记录按键的上一次状态
// 静态变量在函数多次调用之间能保持其值,便于对比前后状态变化
LastState = NowState; // 将当前状态赋值给上一次状态变量LastState,用于记录之前的状态
NowState = Key_GetState(); // 获取当前按键的状态并赋值给NowState变量
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;
}
}
四、主函数
#include <REGX52.H>
#include "LCD1602.H"
#include "Timer0.H"
#include "DS18B20.H"
#include "Delay.H"
#include "Key.H"
sbit Motor = P1^0;// 定义电机控制引脚
float T;// 用于存储温度值
char STD = 28;// 存储设定温度值,为28
char KeyNum;// 用于存储按键的键值
// 函数功能:在LCD1602上显示当前温度值
// 具体步骤包括进行温度转换、适当延时后读取温度值,然后根据温度正负情况进行相应的显示处理
// 同时也会在LCD上显示设定温度值相关的符号和数值
void Show_Temperature()
{
ConverT(); // 调用温度转换函数,启动DS18B20进行温度转换
Delay(300); // 延时300毫秒,等待温度转换完成(这个延时时间根据实际情况调整,确保转换完成)
T = ReadT(); // 读取温度传感器DS18B20转换后的温度值,并存储到变量T中
if (T < 0) // 如果温度值小于0
{
LCD_ShowChar(2, 10, '-'); // 在LCD1602的第2行第10列显示负号'-'
T = -T; // 将温度值取绝对值,方便后续显示处理(因为显示时一般正数处理更统一)
}
else
{
LCD_ShowChar(2, 10, '+'); // 在LCD1602的第2行第10列显示正号'+'
}
LCD_ShowChar(2, 14, '.');
LCD_ShowNum(2, 11, T, 3);
LCD_ShowNum(2, 15, (unsigned long)(T * 100) % 100, 2); // 在LCD1602的第2行第15列开始显示温度值的小数部分(乘以100取余得到两位小数对应的整数)
if (STD >= 0) // 如果设定温度STD大于等于0
{
LCD_ShowChar(2, 5, '+'); // 在LCD1602的第2行第5列显示正号'+'
}
if (STD < 0) // 如果设定温度STD小于0
{
LCD_ShowChar(2, 5, '-'); // 在LCD1602的第2行第5列显示负号'-'
}
if (STD >= 0) // 如果设定温度STD大于等于0
{
LCD_ShowNum(2, 6, STD, 2); // 在LCD1602的第2行第6列开始显示设定温度的整数部分,保留2位有效数字
}
else
{
LCD_ShowNum(2, 6, -STD, 2); // 在LCD1602的第2行第6列开始显示设定温度的绝对值,保留2位有效数字(因为是负数情况)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
void main()
{
//初始化
Motor = 0;
ConverT();
Delay(1000);
LCD_Init();
Timer0_Init();
LCD_ShowString(2, 8, "T:");
LCD_ShowString(2, 1, "STD:");
LCD_ShowString(1, 1, "T Control:");
while (1)
{
KeyNum = Key(); // 检测控制电机开关
if (STD < T)
Motor = 1;
else
Motor = 0;
if (KeyNum) // 如果有按键按下(即KeyNum不为0)
{
if (KeyNum == 1) // 如果按下的是按键K1
{
STD++; // 设定温度值加1,然后对125取余(防止超出温度合理范围)
STD %= 125;
}
if (KeyNum == 2) // 如果按下的是按键K2
{
STD--; // 设定温度值减1
if (STD < -54) // 如果设定温度值小于 -54,则将其重置为0(同样是范围限定处理)
STD = 0;
}
}
Show_Temperature(); // 调用函数显示当前温度和设定温度相关信息到LCD1602上
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
void Routine() interrupt 1 //用于扫描按键
{
// 用于计数定时器中断的次数
int T0Count;
TL0 = 0xCD;
TH0 = 0xD4;
T0Count++;
if (T0Count > 20)
{
// 将计数变量清零,重新开始计数
T0Count = 0;
// 调用按键扫描循环函数
Key_Loop();
}
}