项目说明¶
该项目实现一个简易的温室大棚
软件下载链接¶
硬件设计¶
软件设计¶
驱动编写¶
系统基础配置¶
我一般会定义一个main.h的文件,里面包含着全部常用的头文件和一些宏定义,比如这里面我定义了一些数据类型的重定义和系统时钟等。
#ifndef _MAIN_H
#define _MAIN_H
#include "reg52.h"
#include "stdio.h"
#ifndef uint8_t
#define uint8_t unsigned char
#endif
#ifndef uint16_t
#define uint16_t unsigned int
#endif
#define SOC_FREQ 11.0592
#endif
按键扫描驱动¶
按键部分,首先是使用了4个按键,直接是1端接地,一端接的单片机,断开情况下,按键IO口检测到的就是1,当按键按下的时候,IO口被拉低,就会检测到0。
首先处理按键,我们需要将他初始化,即全部接口拉高,但是我看仿真好像不拉高也可以,但是理论上应该是要先拉高的,手册上写的是准双向口在读取外部状态前,需要先锁存为“1”。代码如下:
然后就是按键的循环检测了。我写的按键扫描,需要将其放入周期调用的函数中,因为我使用的是计数来判断延时,这样的话和普通的Delay延时相比,好处就是不用在那里死等10ms或者20ms。
最好放入定时器中断,xms 周期调用的时间 ,单位/ms 假如5ms调用一次,这个值就设置成5,然后,因为你放置在5ms中断中,所以每隔5ms会调用一次,在调用过程中,如果检测到某个按键按下,计数器就会加加,但是一开始的计数并不会处理,会在佳佳后,发现不满足计算后等于总的延时时长后跳出,然后隔了5ms再次调用,key_delay_count的值又会加加,大概调用了5次后,按键还是处于按下状态的话,就满足了计算后大于总的延时时长,就会进入返回按键值部分的代码。
当按键返回按键值后,同时按键的状态也被设置成了按键按下状态,那么就不会再次进入返回按键值这部分的代码了,只有当所有按键抬起,进入到else if中,将标志位清零后,才能够进行下次的按键检测。
uint8_t key_scan(uint8_t xms)
{
static uint8_t key_state = 0; //按键当前状态
static uint8_t key_delay_count = 0; //按键延时计数
// 当按键状态为0 同时检测到某个按键按下的情况
if(key_state == 0 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0))
{
// 按键延时计数计一个数
key_delay_count ++;
// 按键延时计数 × 周期调用值 = 总的延时时长
if(key_delay_count * xms > 20)
{
// 设置按键状态为按下
key_state = 1;
// 延时计数值清零
key_delay_count = 0;
// 检测到对应的按键按下,就返回对应的按键
if(KEY1 == 0) return KEY1_VALUE;
else if(KEY2 == 0) return KEY2_VALUE;
else if(KEY3 == 0) return KEY3_VALUE;
else if(KEY4 == 0) return KEY4_VALUE;
}
}
// 假如全部按键处于抬起状态
else if(key_state == 1 && KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1)
{
// 清零所有状态位
key_state = 0;
key_delay_count = 0;
}
return 0;
}
下面放出.h文件中的全部配置,主要就是包含按键引脚的定义,以及一些宏定义的按键值
#ifndef _KEY_SCAN_H
#define _KEY_SCAN_H
#include "main.h"
sbit KEY1 = P1^0;
sbit KEY2 = P1^1;
sbit KEY3 = P1^2;
sbit KEY4 = P1^3;
#define KEY1_VALUE 1
#define KEY2_VALUE 2
#define KEY3_VALUE 3
#define KEY4_VALUE 4
// 按键初始化
void key_init(void);
// 按键扫描
uint8_t key_scan(uint8_t xms);
#endif
LCD1602驱动¶
关于LCD1602,有很多其他的博主介绍了如何使用LCD1602,以及他的基本逻辑。可以去看下面链接中的这位博主介绍的原理我觉得很详细。
https://blog.csdn.net/qq_44526422/article/details/106545562
但是我这里主要讲我的LCD显示使用的方法。写指令写数据的方式,我不叙述,可以看看上面其他的博客,主要放出我的写指令写数据的方式。其实就是使用的普中他们写的代码。
#include "LCD1602.h"
void Lcd1602_Delay1ms(uint16_t c)
{
uint8_t a,b;
for (; c>0; c--)
{
for (b=199;b>0;b--)
{
for(a=1;a>0;a--);
}
}
}
void LcdWriteCom(uint8_t com) //写入命令
{
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
Lcd1602_Delay1ms(1); //等待数据稳定
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
void LcdWriteData(uint8_t dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
还有一个问题就是,如果按照上面这样延时,在实际使用的时候LCD刷新会比较慢,使用实物的时候可以用上面的,仿真的时候可以把延时注释了,使用下面的函数,开头要加个1ms的延时,因为没有忙检测。
void LcdWriteCom(uint8_t com) //写入命令
{
Lcd1602_Delay1ms(1);
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
// Lcd1602_Delay1ms(1); //等待数据稳定
LCD1602_E = 1; //写入时序
// Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
void LcdWriteData(uint8_t dat) //写入数据
{
Lcd1602_Delay1ms(1);
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
// Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
// Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
然后是LCD的初始化,至于为什么这样初始化。主要就是打开显示什么的,具体的指令内容可以去看推荐博客的详细讲解。
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
关于显示,我个人常用的方式是将一串数据转换成字符串后整体的显示刷新,所以我只有一个显示字符串的函数。他的子函数中,有一个设置当前起始地址的函数。x代表列数,y代表行数。
void LcdSetCursor(uint8_t x, uint8_t y)
{
uint8_t addr;
if (y == 0) //由输入的屏幕坐标计算显示 RAM 的地址
addr = 0x00 + x; //第一行字符地址从 0x00 起始
else
addr = 0x40 + x; //第二行字符地址从 0x40 起始
LcdWriteCom(addr | 0x80); //设置 RAM 地址
}
void LcdShowStr(uint8_t x, uint8_t y, uint8_t *str)
{
LcdSetCursor(x, y); //设置起始地址
while (*str != '\0') //连续写入字符串数据,直到检测到结束符
{
LcdWriteData(*str++); //先取 str 指向的数据,然后 str 自加 1
}
}
其实这部分,我主要想将的是怎么使用我的这个函数,一般在初始化完成后,我会初始化一个缓冲区,然后使用sprintf将我想要显示的数据放置到缓冲区中,然后再使用显示字符串的方式显示这个数据。具体使用方式如下。
可以看到首先你得包含stdio函数库才能够使用sprintf,sprintf和printf看着很像,但是sprintf是将你写的支付串输出到你前面设置的这个数组中去。这样就能比较方便的显示一些数字,小数呀什么的。
#include "stdio.h"
unsigned char lcdShowBuff[16] = {0};
void main()
{
LcdInit(); // LCD显示初始化
while(1)
{
sprintf((char*)lcdShowBuff,"Freq:%.2fHz ",0.0);
LcdShowStr(0, 0, lcdShowBuff);
sprintf((char*)lcdShowBuff,"Count:%ld ",0);
LcdShowStr(0, 1, lcdShowBuff);
}
}
最后放出LCD1602.h中的具体实验配置
#ifndef _LCD1602_H
#define _LCD1602_H
#include "main.h"
#define LCD1602_DATAPINS P0
sbit LCD1602_RS = P2^6;
sbit LCD1602_RW = P2^5;
sbit LCD1602_E = P2^7;
void Lcd1602_Delay1ms(uint16_t c);
void LcdShowStr(uint8_t x,uint8_t y,uint8_t *str); //显示字符串
void LcdInit(); //1602初始化
#endif
定时器中断驱动¶
我们常会有一些需要定时完成的任务,比如上面的按键扫描,我们需要定义MS级的定时器中断,因为如果放在While中的话,while会有屏幕显示的话,很不好确定这个按键扫描掉用的周期时间。
我们来配置定时器中断。首先说明,主要使用的是定时器的模式1,关于定时器初始化值得计算方法,可以去看下面这个博主写的博客。
https://blog.csdn.net/qq_51272949/article/details/118945288
我将他初始化值,改成了使用单片机自己计算。代码如下,有一个Timer_t的数据结构类型,是一个枚举类型,在.h中后面会给出,其实就两个值Timer0和Timer1。SOC_FREQ这个值在main.h中给出,可以看前面main.h,关于reload_value的值可以看上面博客是怎么计算的,我这里直接在代码中给出了计算公式。然后就是正常的配置了,先设置TMOD的定时器模式,高4位配置定时器1,低4位配置定时器0。然后设置定时器初值、清除TF标志、启动定时器、打开定时器中断。
但是这个初始化,是不会打开总的中断寄存器的,只会打开控制定时器的中断开关ET,关于总开关也就是EA这个寄存器,这个寄存器我会在主函数进行打开和关闭。
void bsp_timer_set_us_IT(Timer_t Timer, uint16_t xus)
{
uint16_t reload_value; // 用于存储定时器初值
reload_value = 65536 - (uint16_t)(SOC_FREQ * xus / 12.0f);
// 设置定时器模式
if(Timer == Timer0){
TMOD &= 0xF0; // 清除低四位,保留高四位的定时器1模式
TMOD |= 0x01; // 设置定时器0为模式1(16位定时器模式)
// 将定时器初值分别写入低8位和高8位
TL0 = (uint8_t)(reload_value & 0xFF); // 低8位
TH0 = (uint8_t)(reload_value >> 8); // 高8位
TF0 = 0;// 清除TF0标志
TR0 = 1;// 启动定时器0
ET0 = 1;// 打开定时器0中断
}else if(Timer == Timer1){
TMOD &= 0x0F; // 清除高四位,保留低四位的定时器0模式
TMOD |= 0x10; // 设置定时器1为模式1(16位定时器模式)
TL1 = (uint8_t)(reload_value & 0xFF); // 低8位
TH1 = (uint8_t)(reload_value >> 8); // 高8位
TF1 = 0;// 清除TF0标志
TR1 = 1;// 启动定时器0
ET1 = 1;// 打开定时器0中断
}
}
刚刚这个是定时的us,然后我写了个定时ms的,其实就是初始化的时候把us乘了1000。如下。定时器中断初始化函数,设置定时时间(单位:毫秒),还未测试其鲁棒性,建议11.0592MHZ下不超过50ms。理论该频率能到71ms。
然后,使能定时器中断后,每次进入定时器是需要重新设置初值的,如下。也是分了us和ms的。但是这种设置方式的**缺点**就是,计算步骤变多了,如果你对时间要求比较高的话,你还是使用直接配置TH和TL的方式,两行代码带来的效率会更高一点。但是如果你的周期要求并不是很高,就可以使用这种方式。
void timer_reload_us(Timer_t Timer, uint16_t xus)
{
uint16_t reload_value; // 用于存储定时器初值
reload_value = 65536 - (uint16_t)(SOC_FREQ * xus / 12.0f);
// 设置定时器模式
if(Timer == Timer0){
// 将定时器初值分别写入低8位和高8位
TL0 = (uint8_t)(reload_value & 0xFF); // 低8位
TH0 = (uint8_t)(reload_value >> 8); // 高8位
}else if(Timer == Timer1){
TL1 = (uint8_t)(reload_value & 0xFF); // 低8位
TH1 = (uint8_t)(reload_value >> 8); // 高8位
}
}
void timer_reload_ms(Timer_t Timer, uint16_t xms)
{
timer_reload_us(Timer, xms * 1000);
}
在使用过程中,首先是初始化,然后在中断服务函数中重加载初值。注意需要关闭和打开总中断。经过测试,timer_reload_ms(Timer0,5);的偏差还是挺大的,建议还是直接使用TL0和TH0;后面想想怎么优化这个函数。初始化的时候还是可以使用函数。
void Timer0Routine(void) interrupt 1
{
//timer_reload_ms(Timer0,5);
TL0 = 0x00; //设置定时初始值
TH0 = 0xEE; //设置定时初始值
}
void main()
{
EA = 0; //关闭所有中断
bsp_timer_set_ms_IT(Timer0,5);
EA = 1; //打开所有中断
while(1)
{
}
}
最后放上该驱动的.h部分
#ifndef _BSP_TIMER_H
#define _BSP_TIMER_H
#include "main.h"
typedef enum{
Timer0 = 0,
Timer1
}Timer_t;
void bsp_timer_set_us_IT(Timer_t Timer, uint16_t xus);
void bsp_timer_set_ms_IT(Timer_t Timer, uint16_t xms);
void timer_reload_us(Timer_t Timer, uint16_t xus);
void timer_reload_ms(Timer_t Timer, uint16_t xms);
#endif
串口中断¶
关于串口,STC89C51单片机的串口,感觉在很多情况下的计算都不是很准确。在11.0592Mhz的时候,建议使用9600的波特率。
void Uart1_Init(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFD; //设置定时初始值
TH1 = 0xFD; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
ES = 1; //打开串口中断
}
如果有其他的设置,可以使用STC的工具来进行计算。
首先使用的是Proteus的COMPIM元器件模拟串口收发,使用的虚拟串口进行连接。关于虚拟串口的使用。请翻看我写的另外一篇博客的详细描述。这里不做叙述
https://blog.csdn.net/wan1234512/article/details/148975092?spm=1011.2415.3001.5331
使用的时候,首先是初始化,然后在中断服务函数中重加载初值。注意需要关闭和打开总中断。做了一个发送的测试,接收中断中会将接收到的值放进res。
void main()
{
EA = 0; //关闭所有中断
Uart1_Init();
EA = 1; //打开所有中断
UartSendBuffLen("hello\r\n",sizeof("hello\r\n"));
while(1)
{
}
}
void UartRoutine(void) interrupt 4
{
uint8_t res;
if(RI)
{
// 将接收到的值放进res
res = SBUF;
RI = 0;
}
}
为了能够使用printf来调用串口,我们还可以将串口输出重定向,这样我们就能使用串口printf输出了
最后放上串口初始化部分的.h代码
#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "main.h"
void Uart1_Init(void);
void UartSendBuffLen(uint8_t *str,uint8_t len);
#endif
DHT11温湿度传感器驱动¶
这是本项目最为重要的驱动,关于这个传感器的基本实现,可以看下面这位博主的博客,他将怎么读取时序介绍的很清楚。但是我使用他代码的时候遇到点小问题,在找了其他人的资料结合后才解决,但是他的原理介绍没有问题。
https://blog.csdn.net/m0_55849362/article/details/126426768
直接放代码。
#include "dht11.h"
void DHT11_delay_ms(uint16_t z)
{
uint16_t i,j;
for(i=z;i>0;i--)
for(j=110;j>0;j--);
}
static void Delay30us(void) //@11.0592MHz
{
unsigned char data i;
i = 11;
while (--i);
}
void Delay40us(void) //@11.0592MHz
{
unsigned char data i;
_nop_();
i = 15;
while (--i);
}
static void Delay80us(void) //@11.0592MHz
{
unsigned char data i;
i = 34;
while (--i);
}
void DHT11_start()
{
DHT11_PIN=1;
DHT11_delay_ms(1);
DHT11_PIN=0;
DHT11_delay_ms(30); // 延时18ms以上
DHT11_PIN=1;
Delay40us(); //延时20-40us
}
uint8_t DHT11_rec_byte()
{
uint8_t i,read_data=0;
for(i=0;i<8;i++) //从高到低依次接收8位数据
{
while(!DHT11_PIN); //等待50us低电平过去
Delay40us(); //0-高电平持续26~28us;1-高电平持续70us,
read_data<<=1; //移位使正确接收8位数据,数据为0时直接移位
if(DHT11_PIN==1) //数据为1时,使read_data加1来把最低位数据写1
read_data += 1;
while(DHT11_PIN); //等待数据线拉低
}
return read_data;
}
uint8_t DHT11_Get_Data(uint8_t *hum, uint8_t *temp) //接收40位的数据
{
uint8_t R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise;
DHT11_start();
if(DHT11_PIN==0)
{
while(DHT11_PIN==0); //等待拉高
Delay80us(); //拉高后延时80us
R_H=DHT11_rec_byte(); //接收湿度高八位
R_L=DHT11_rec_byte(); //接收湿度低八位
T_H=DHT11_rec_byte(); //接收温度高八位
T_L=DHT11_rec_byte(); //接收温度低八位
revise=DHT11_rec_byte(); //接收校正位
if((R_H+R_L+T_H+T_L)==revise) //校正
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
}
*hum = RH;
*temp = TH;
return 0;
}
return 1;
}
还有.h部分的基础配置内容。
#ifndef _DHT11_H
#define _DHT11_H
#include "main.h"
sbit DHT11_PIN = P1^6;
uint8_t DHT11_Get_Data(uint8_t *hum,uint8_t *temp);
#endif
主要的问题是,DHT11的时序要求很严格,我因为有使用中断,定时器和串口,就导致在读取时序的时候可能会被打断,这就导致无法完整的读取DHT11的数据,所以,在使用的时候,读取过程中,需要先把全部中断关了,然后再读取,读完后,再开启中断。
uint8_t humidity=0, temperature=0;//实际的湿度值和温度值
void main()
{
uint8_t ret;
EA = 0; //关闭所有中断
LcdInit(); // LCD显示初始化
bsp_timer_set_ms_IT(Timer0,5);
Uart1_Init();
EA = 1; //打开所有中断
while(1)
{
EA = 0; //关闭全部中断
ret = DHT11_Get_Data(&humidity, &temperature) ; //读取数据
EA = 1; //打开中断
sprintf((char*)lcdShowBuff," real data ");
LcdShowStr(0, 0, lcdShowBuff);
sprintf((char*)lcdShowBuff,"hum:%d temp:%d",(int)humidity,(int)temperature);
LcdShowStr(0, 1, lcdShowBuff);
delay_ms(100);
}
}
功能代码编写¶
初始化变量¶
首先,任务是分为了定时器任务和主函数循环任务,在任务执行前,先定义了许多的初始化变量。
#include "main.h"
#include "LCD1602.h"
#include "keyScan.h"
#include "bsp_timer.h"
#include "bsp_uart.h"
#include "dht11.h"
// 变量定义
sbit reduce_temp = P2^3; //控制降温机
sbit add_temp = P2^2; //控制升温机
sbit reduce_hum = P2^1; //控制减湿器
sbit add_hum = P2^0; //控制加湿器
sbit LED = P1^7; //心跳灯
uint8_t g_key_value = 0; // 按键按下的值
uint8_t hum_high = 30, hum_low = 25;//设置的湿度最大值和最小值
uint8_t temp_high = 35, temp_low = 25;//设置的温度最大值和最小值
uint8_t humidity = 0 , temperature = 0;//实际的湿度值和温度值
uint8_t ui_page = 0 ; //显示页面
uint8_t set_pos = 0 ; //设置序号 0:设置hum_high 1:设置hum_low 2:设置temp_high 3:设置temp_low
uint8_t read_dht11_flag = 0; //读取DHT11的标志位。
uint8_t ui_refresh_flag = 0; //界面刷新的标志位。
定时器任务¶
定时器任务是5ms执行一次,每次执行都会会做以下内容,代码如下。首先每次进入中断都需要重新定义定时器计算初值,这样每次计算的时间才是你想要的,我这里的时间是5MS的时间,如果是定义的其他时间记得自己修改。
然后就是按键扫描,得到按键值,根据按键值,会改变一些变量值或者状态值,具体改变值在KeyControlFunction中,等下讲解。按键处理完成后,调用的是控制继电器的代码,四个引脚控制着4个继电器的开关,将会根据实际情况开关继电器。
然后就是获得读取DHT11的标志位,因为读取DHT11比较耗时,我们不能放在定时器任务中执行,将其放置在了while循环中,等会讲解,这里的话,就是,计算量取余200等于0后,将读取标志置1,取余等于0,证明当前值是200的倍数,没计200个数才会产生倍数,每次进入1次中断加1,那证明要进200次中断才能触发这个条件,也就是200×5 == 1000ms。
由于界面显示,我想100ms刷新一次,所以就取余的20。
void Timer0Routine(void) interrupt 1
{
static uint16_t timerCount = 0;
// timer_reload_ms(Timer0,5); //这方式,对周期计时影响较大
TL0 = 0x00; //设置定时初始值 5ms
TH0 = 0xEE; //设置定时初始值
timerCount++;
g_key_value = key_scan(5);
KeyControlFunction(g_key_value);
ControlRelay();
// 计算到200取余 ,这样就相当于 200×5 == 1000ms 就是1s
if( timerCount % 200 == 0){
LED = !LED;
read_dht11_flag = 1; //读取DHT11的标志置1,表示可以开始读DHT11
}
// 计算到20取余 ,这样就相当于 20×5 == 100ms
if( timerCount % 20 == 0){
ui_refresh_flag = 1; //界面可刷新
}
}
定时器任务中的按键处理函数,KeyControlFunction具体内容如下。按键值按下,首先会根据按键值进入不同的处理函数,在不同的处理函数中,又会根据当前的页面实现不同的功能。
比如按键3按下的时候,会切换当前页面显示的表示变量。按键4按下的时候,会判断当前是不是页面1,如果是页面1,代表是设置页面,这样就可以改变当前的设置位。按键1、2按下,会根据当前不同的设置位,改变不同的变量值,也就是在设置温湿度的上下限值。
void KeyControlFunction(uint8_t key_value)
{
if(key_value == KEY1_VALUE)
{
if(ui_page == 1)
{
if(set_pos == 0) hum_high++;
else if(set_pos == 1) hum_low++;
else if(set_pos == 2) temp_high++;
else if(set_pos == 3) temp_low++;
}
}
else if(key_value == KEY2_VALUE)
{
if(ui_page == 1)
{
if(set_pos == 0) hum_high--;
else if(set_pos == 1) hum_low--;
else if(set_pos == 2) temp_high--;
else if(set_pos == 3) temp_low--;
}
else if(key_value == KEY3_VALUE)
{
if(ui_page == 0)
ui_page = 1;
else if(ui_page == 1)
ui_page = 0;
}
else if(key_value == KEY4_VALUE)
{
if(ui_page == 1)
{
set_pos ++;
if(set_pos > 3)
set_pos = 0;
}
}
}
定时器任务中的继电器控制函数,内容如下。主要是根据当前的情况,决定打开和关闭哪些继电器。检查当前温湿度的状态。如果温度大于设定值,控制减温器继电器打开,如果温度小于设定值,控制升温器继电器打开。如果湿度大于设定值,控制减湿器继电器打开,如果湿度小于设定值,控制加湿器继电器打开。
void ControlRelay()
{
if(temperature > temp_high)
{
reduce_temp = 1;add_temp = 0;
}else if(temperature < temp_low)
{
reduce_temp = 0;add_temp = 1;
}else
{
reduce_temp = 0;add_temp = 0;
}
if(humidity > hum_high)
{
reduce_hum = 1;add_hum = 0;
}else if(humidity < hum_low)
{
reduce_hum = 0;add_hum = 1;
}else
{
reduce_hum = 0;add_hum = 0;
}
}
while主循环任务¶
while主循环中,主要包含界面的显示,和DHT11的读取。代码如下。首先是一些外设的初始化,定时器,串口,按键啥的。
然后while中的第一个任务就是读取温湿度,在读取温湿度的时候,需要关闭全部的中断,不然会有问题,可能会读取失败。读完后,又把中断打开,每次读完数据,使用串口printf将数据打印出去。
界面显示中,会根据当前界面变量ui_page显示不同的界面内容,当该值为1的时候,就显示实际的温湿度值,当界面为2的时候,代表着修改最大最小值,所以显示的是当前的最大最小值,而且,我想要在显示目标变量的时候,能够闪烁,所以我就在加了一个blink_count的值,这个值在偶数的时候,把当前设置位的那两个数覆盖掉,看着就像是在闪烁的样子。
void main()
{
uint8_t ret;
uint8_t blink_count = 0;
uint8_t lcdShowBuff[16] = {0};
EA = 0; //关闭所有中断
LcdInit(); // LCD显示初始化
key_init();
LED = 0;
bsp_timer_set_ms_IT(Timer0,5); //初始化5ms定时器
Uart1_Init();
EA = 1; //打开所有中断
while(1)
{
if(read_dht11_flag == 1)
{
EA = 0;
ret = DHT11_Get_Data(&humidity, &temperature) ;
EA = 1;
printf("hum :%2d temp:%2d\r\n",(int)humidity,(int)temperature); //发送一次温湿度
read_dht11_flag = 0;
}
if( ui_refresh_flag == 1)
{
if(ui_page == 0)
{
sprintf((char*)lcdShowBuff," real data ");
LcdShowStr(0, 0, lcdShowBuff);
sprintf((char*)lcdShowBuff,"hum :%2d temp:%2d",(int)humidity,(int)temperature);
LcdShowStr(0, 1, lcdShowBuff);
}else if(ui_page == 1)
{
blink_count ++;
sprintf((char*)lcdShowBuff,"HumH:%2d HumL:%2d",(int)hum_high, (int)hum_low);
LcdShowStr(0, 0, lcdShowBuff);
sprintf((char*)lcdShowBuff,"TmpH:%2d TmpL:%2d",(int)temp_high,(int)temp_low);
LcdShowStr(0, 1, lcdShowBuff);
// 计数值为偶数的时候闪烁
if(blink_count % 2 == 0)
{
switch(set_pos)
{
case 0:LcdShowStr(5,0," ");break;
case 1:LcdShowStr(14,0," ");break;
case 2:LcdShowStr(5,1," ");break;
case 3:LcdShowStr(14,1," ");break;
}
}
}
ui_refresh_flag = 0;
}
}
}