一,器材目录
51单片机最小系统板,DHT11温湿度传感器三个(求取平均温湿度),继电器,水泵,排气扇,加热片,PNP三极管8550(驱动继电器工作),LCD1602显示温湿度值,发光二极管。
二,实现要求
三个温湿度传感器DHT11采集温湿度显示到LCD1602上,通过四个按键实现屏幕切换设置恒温值,温度上下限,湿度上下限,K1具有设置和确定功能,K2增加,K3减少,K4返回,如果温度低于设定恒温值时,调用PID算法控制加热设备,温度高于设定恒温值时,停止加热,如果当前温湿度高于或者低于所设置的温湿度上下限时,蜂鸣器报警提示,并且开启相应的除湿或加湿设备工作,当按键设定完恒温值,温湿度上下限时,可以随时查看所设定值的大小。
三,PROTEUS仿真电路图
由于在仿真中找不到水泵,排气扇,加热片这些器件,直接用发光二极管替代!
四,DHT11温湿度传感器接线图
DHT11 器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。单总线通常要求外接一个约 5.1kΩ 的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结极,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。
五,DHT11数据采集分析
1.数据总时序
用户主机(MCU)发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11 发送响应信号,送出 40bit 的数据,幵触发一次信采集。
2.主机发送起始信号
单片机连接DHT11的DATA引脚的I/O口输出低电平,且低电平保持时间不能小于 18ms,然后等待 DHT11 作出应答信号。
3.检测从机应答信号
DHT11 的 DATA 引脚检测到外部信号有低电平时, 等待外部信号低电平结束, 延迟后 DHT11 的 DATA引脚处于输出状态,输出 80 微秒的低电平作为应答信号,紧接着输出 80 微秒的高电平通知外设准备接收数据。
4.接收数据
(1)数据判定规则
位数据“0”的格式为: 50 微秒的低电平和 26-28 微秒的高电平,位数据“1”的格式为: 50 微秒的低电平加 70微秒的高电平。
接收数据时可以先等待低电平过去,即等待数据线拉高,再延时60us,因为60us大于28us且小于70us,再检测此时数据线是否为高,如果为高,则数据判定为1,否则为0。
(2)数据格式
一次传送 40 位数据,高位先出。
8bit 湿度整数数据 + 8bit 湿度小数数据+8bit 温度整数数据 + 8bit 温度小数数据+8bit 校验位。
(3)数据校正
判断“8bit 湿度整数数据 + 8bit 湿度小数数据+8bit 温度整数数据 + 8bit 温度小数数据”的结果是否等于8bit 校验位。如果等于则数据接收正确,否则应该放弃这一次的数据,重新接收。
六,LCD1602操作时序
(1),RS,RW操作时序
(2),指令集
LCD_1602 初始化指令小结:
0x38 设置 162 显示,57 点阵,8 位数据接口
0x01 清屏
0x0F 开显示,显示光标,光标闪烁
0x08 只开显示
0x0e 开显示,显示光标,光标不闪烁
0x0c 开显示,不显示光标
0x06 地址加 1,当写入数据的时候光标右移
0x02 地址计数器 AC=0;(此时地址为 0x80) 光标归原点,但
是 DDRAM 中断内容不变
七,程序代码
#include
#include
#include
#define uchar unsigned char
#define uint unsigned int
sbit RS=P2^7;//LCD1602 RS口
sbit RW=P2^6;//LCD1602 RW口
sbit EN=P2^5;//LCD1602 EN口
sbit AddHeat=P2^2;//加热控制口
sbit AddHum=P2^3;//加湿控制口
sbit DelHum=P2^4;//除湿控制口
sbit Data1=P1^0;//1号DHT11 数据输出口
sbit Data2=P1^1;//2号DHT11 数据输出口
sbit Data3=P1^2;//3号DHT11 数据输出口
sbit Buzzer=P1^3;//蜂鸣器控制口
sbit SetSure=P1^4;//设置和确认键
sbit Add=P1^5;//数据加
sbit Sub=P1^6;//数据减
sbit Back=P1^7;//返回键
uchar table[6];//存入三个传感器的温湿度值
uint FinData[2];//温湿度平均值
uchar table1[]={" NowTem: "};
uchar table2[]={" NowHum: "};
uchar high_time,low_time;//定义调节时间变量
uchar ConTem=0;//温度恒定值
uchar TemH=0;//温度上限值
uchar TemL=0;//温度下限值
uchar HumH=0;//湿度上限值
uchar HumL=0;//湿度下限值
uchar count=0,time=0;//设定值变量,记录按键次数变量
uchar count1=0;//定时器中使用
uchar Flag1=0,Flag2=1;//标志位
void FirDHT11_receive();//声明函数
void SecDHT11_receive();
void ThDHT11_receive();
struct PID
{
uint SetPoint;// 设定目标
uint Proportion;// 比例常数
uint Integral;// 积分常数
uint Derivative;// 微分常数
uint LastError;//当前误差
uint PrevError;//前次误差
uint SumError;//误差和
};
struct PID spid;//定义PID变量
unsigned int rout;//累计误差值
void PIDInit(struct PID*pp)//PID初始化
{
memset(pp,0,sizeof(struct PID)); //PID参数初始化全部设置为0
}
uint PIDCalc(struct PID*pp,uint NextPoint)//PID误差计算
{
uint dError,Error ;
Error=pp->SetPoint-NextPoint;// 偏差
pp->SumError+=Error;// 积分
dError=pp->LastError-pp->PrevError;//当前微分
pp->PrevError=pp->LastError ;
pp->LastError=Error ;
return(pp->Proportion*Error+pp->Integral*pp->SumError+pp->Derivative*dError);//比例积分项微分项
}
void compare_temper()//温度比较
{
uchar i ;
float temper=FinData[1]/10;//当前温度值
float set_temper=ConTem;//设定温度值
if(set_temper>temper)
{
if(set_temper-temper>1)
{
high_time=100 ; //大于1°不进行PID运算
low_time=0 ;
}
else
{ //在1°范围内进行PID运算
for(i=0;i<10;i++)
{
FirDHT11_receive();
SecDHT11_receive();
ThDHT11_receive();
FinData[1]=(table[1]+table[3]+table[5])/3*10;
rout=PIDCalc(&spid,FinData[1]/10); //执行PID运算
}
if(high_time0;x--)
for(y=110;y>0;y--);
}
void LCD_WriteCom(uchar com)//LCD1602写指令函数
{
RS=0;
P0=com;
delay(5);
EN=1;
delay(5);
EN=0;
}
void LCD_WriteData(uchar dat)//LCD1602写数据函数
{
RS=1;
P0=dat;
delay(5);
EN=1;
delay(5);
EN=0;
}
void LCD_Init()//LCD1602初始化
{
EN=0;
RW=0;
LCD_WriteCom(0x38);
LCD_WriteCom(0x0c);
LCD_WriteCom(0x06);
LCD_WriteCom(0x01);
}
void DHT11_delay_us(uchar n)//微妙延时函数
{
while(--n);
}
void DHT11_delay_ms(uint z)//毫秒延时函数
{
uint i,j;
for(i=z;i>0;i--)
for(j=110;j>0;j--);
}
void FirDHT11_start()//1号DHT11起始信号
{
Data1=1;//数据口先拉高
DHT11_delay_us(2);//短暂延时
Data1=0;//数据口拉低
DHT11_delay_ms(30); //延时18ms以上
Data1=1;//重新拉高
DHT11_delay_us(30);//延时
}
uchar FirDHT11_rec_byte()//接收一个字节
{
uchar i,dat=0;
for(i=0;i<8;i++)
{
while(!Data1);//等待50us低电平过去
DHT11_delay_us(8);//延时60us左右
dat<<=1;
if(Data1==1)//此时数据口仍然为1,则数据为1
dat+=1;
while(Data1);//等待数据口重新拉低
}
return dat;//返回8位数据
}
void FirDHT11_receive()//数据接收函数
{
uchar R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise;
FirDHT11_start();//发送起始信号
if(Data1==0)//如果为0,则DHT11应答
{
while(Data1==0);//等待80us低电平过去
DHT11_delay_us(40); //等待80us高电平过去
R_H=FirDHT11_rec_byte();//湿度整数8位
R_L=FirDHT11_rec_byte(); //湿度小数8位
T_H=FirDHT11_rec_byte(); //温度整数8位
T_L=FirDHT11_rec_byte(); //温度小数8位
revise=FirDHT11_rec_byte(); //校验位
DHT11_delay_us(25);
if((R_H+R_L+T_H+T_L)==revise)//校验验证
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
}
table[0]=RH+RL/10*0.1+RL%10*0.01;//温湿度存入数组中
table[1]=TH+TL/10*0.1+TL%10*0.01;
}
}
void SecDHT11_start()//2号DHT11原理与1号相同
{
Data2=1;
DHT11_delay_us(2);
Data2=0;
DHT11_delay_ms(30);
Data2=1;
DHT11_delay_us(30);
}
uchar SecDHT11_rec_byte()
{
uchar i,dat=0;
for(i=0;i<8;i++)
{
while(!Data2);
DHT11_delay_us(8);
dat<<=1;
if(Data2==1)
dat+=1;
while(Data2);
}
return dat;
}
void SecDHT11_receive()
{
uchar R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise;
SecDHT11_start();
if(Data2==0)
{
while(Data2==0);
DHT11_delay_us(40);
R_H=SecDHT11_rec_byte();
R_L=SecDHT11_rec_byte();
T_H=SecDHT11_rec_byte();
T_L=SecDHT11_rec_byte();
revise=SecDHT11_rec_byte();
DHT11_delay_us(25);
if((R_H+R_L+T_H+T_L)==revise)
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
}
table[2]=RH+RL/10*0.1+RL%10*0.01;
table[3]=TH+TL/10*0.1+TL%10*0.01;
}
}
void ThDHT11_start()//3号DHT11原理与1号相同
{
Data3=1;
DHT11_delay_us(2);
Data3=0;
DHT11_delay_ms(30);
Data3=1;
DHT11_delay_us(30);
}
uchar ThDHT11_rec_byte()
{
uchar i,dat=0;
for(i=0;i<8;i++)
{
while(!Data3);
DHT11_delay_us(8);
dat<=2)
Flag2++;//Flag2表示所要设置的对象
}
while(SetSure==0);//等待按键松开
}
if(Add==0)//按键加按下
{
delay(2);//延时消抖
if(Add==0)//再次确定按键按下
{
count++;//数值加1
if(Flag2==1) ConTem=count;//Flag2为1,表示设置对象为恒温值
else if(Flag2==2) TemH=count;//Flag2为2,表示设置对象为温度上限
else if(Flag2==3) TemL=count;//Flag2为3,表示设置对象为温度下限
else if(Flag2==4) HumH=count;//Flag2为4,表示设置对象为湿度上限
else if(Flag2==5) HumL=count;//Flag2为5,表示设置对象为湿度下限
while(Add==0);//等待按键松开
}
}
if(Sub==0)//按键减按下
{
delay(2);//延时消抖
if(Sub==0)//再次确认按下
{
count--;//数据减1
if(Flag2==1) ConTem=count;//Flag2为1,表示设置对象为恒温值
else if(Flag2==2) TemH=count;//Flag2为2,表示设置对象为温度上限
else if(Flag2==3) TemL=count;//Flag2为3,表示设置对象为温度下限
else if(Flag2==4) HumH=count;//Flag2为4,表示设置对象为湿度上限
else if(Flag2==5) HumL=count;//Flag2为5,表示设置对象为湿度下限
while(Sub==0);//等待按键松开
}
}
if(Back==0)//返回按键按下
{
delay(2);//延时消抖
if(Back==0)//再次确定按键按下
{
Flag1=0;//跳出屏幕切换
Flag2=1;//Flag2重新初始为1
time=0;//time 初始为0
while(Back==0);//等待按键松开
}
}
}
void SwitchDisplay()//切换屏幕显示
{
LCD_WriteCom(0x80);//第一行显示
LCD_WriteData('C');//显示字符C:,表示恒温
LCD_WriteData(':');
LCD_WriteData(ConTem/10+0x30);//显示恒温十位
LCD_WriteData(ConTem%10+0x30);//显示恒温个位
LCD_WriteData(' ');//显示空格
LCD_WriteData('T');//显示温度上限字符TH:
LCD_WriteData('H');
LCD_WriteData(':');
LCD_WriteData(TemH/10+0x30);//显示温度上限十位
LCD_WriteData(TemH%10+0x30);//显示温度上限个位
LCD_WriteData(' ');//显示空格
LCD_WriteData('T');//显示温度下限字符TL:
LCD_WriteData('L');
LCD_WriteData(':');
LCD_WriteData(TemL/10+0x30);//显示温度下限十位
LCD_WriteData(TemL%10+0x30);//显示温度下限个位
LCD_WriteCom(0x80+0x40);//第二行显示
LCD_WriteData('R');//显示湿度上限字符RH:
LCD_WriteData('H');
LCD_WriteData(':');
LCD_WriteData(HumH/10+0x30);//显示湿度上限十位
LCD_WriteData(HumH%10+0x30);//显示湿度上限个位
LCD_WriteData(' ');//显示空格
LCD_WriteData('R');//显示湿度下限字符RL:
LCD_WriteData('L');
LCD_WriteData(':');
LCD_WriteData(HumL/10+0x30);//显示湿度下限十位
LCD_WriteData(HumL%10+0x30);//显示湿度下限个位
LCD_WriteData(' ');//显示空格
LCD_WriteData('F');//显示字符Flag2:
LCD_WriteData('g');
LCD_WriteData(':');
LCD_WriteData(Flag2%10+0x30);//显示标志位Flag2
}
void Lcd_DisPlay()//正常显示
{
uchar i;
FirDHT11_receive();//1号DHT11接收数据
SecDHT11_receive();//2号DHT11接收数据
ThDHT11_receive();//3号DHT11接收数据
FinData[0]=(table[0]+table[2]+table[4])/3*10;//平均湿度
FinData[1]=(table[1]+table[3]+table[5])/3*10;//平均温度
LCD_WriteCom(0x80);//以下为显示程序
for(i=0;i<9;i++)
LCD_WriteData(table1[i]);
LCD_WriteData(FinData[1]/100+0x30);
LCD_WriteData(FinData[1]/10%10+0x30);
LCD_WriteData('.');
LCD_WriteData(FinData[1]%10+0x30);
LCD_WriteData('^');
LCD_WriteData('C');
LCD_WriteData(' ');
LCD_WriteCom(0x80+0x40);
for(i=0;i<9;i++)
LCD_WriteData(table2[i]);
LCD_WriteData(FinData[0]/100+0x30);
LCD_WriteData(FinData[0]/10%10+0x30);
LCD_WriteData('.');
LCD_WriteData(FinData[0]%10+0x30);
LCD_WriteData('%');
LCD_WriteData(' ');
LCD_WriteData(' ');
}
void AddDelHum_Alarm()//加湿除湿和报警函数
{
if(FinData[0]/10HumH)//湿度大于湿度上限值
{
DelHum=0;//开启除湿设备
//Buzzer=0;//蜂鸣器报警
}
else if(FinData[1]/10TemH)//温度低于温度下限或高于温度上限
Buzzer=0;//蜂鸣器报警
else
{
AddHum=DelHum=1;//关闭设备
//Buzzer=1;//关闭蜂鸣器
}
}
int main()
{
AddHeat=AddHum=DelHum=1;//初始化关闭设备
Buzzer=1;//关闭蜂鸣器
LCD_Init();//LCD1602初始化
PIDBEGIN();//开启PID
while(1)
{
KeyScan();//按键扫描
Lcd_DisPlay();//LCD1602显示温湿度
compare_temper();//温度比较
AddDelHum_Alarm();//湿度调节和报警
while(Flag1==1)//进入分屏模式
{
KeyScan();//按键扫描
SwitchDisplay();//分屏显示
}
}
}
void Timer0() interrupt 1
{
count1++;
if(count1<=(high_time))//加热设备开启时间
AddHeat=0;
else if(count1<=100)//加热设备关闭时间
{
AddHeat=1;
}
else
count1=0 ;
TH0=0x63;//定时器重新赋值
TL0=0xC0;
}
八,现象结果
(1)初始上电显示平均温湿度值
(2)分屏设置恒温值,温湿度上下限
C代表恒温值,TH代表温度上限,TL代表温度下限,RH代表湿度上限,RL代表湿度下限,Fg为1代表设置恒温值大小,Fg为2代表设置温度上限值大小,Fg为3代表设置温度下限值大小,Fg为4代表设置湿度上限值大小,Fg为5代表设置湿度下限值大小。
其他具体的一些调节现象大家可以自行演示一下哈哈。
九,总结心得
疫情在家状态自然没有在学校好了,但也写了一些程序了嘿嘿,程序方面有了一定提升,越努力越幸运,真的很感谢老师们,没有他们的教导,实践能力上不会有当前这么大的一个提升,很感激。
再有一个就是第一次用PID算法写了调节温度的程序,之前也只是了解,现在动手实践上了,心里还是挺兴奋的。
博观而约取,厚积而薄发。
祝大家都能够学有所成!