跳转至

项目说明

该项目实现一个简易的温室大棚

软件下载链接

硬件设计

软件设计

驱动编写

系统基础配置

我一般会定义一个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”。代码如下:

#include "keyScan.h"

void key_init(void)
{
    KEY1 = 1;   KEY2 = 1;   KEY3 = 1;   KEY4 = 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。

void bsp_timer_set_ms_IT(Timer_t Timer, uint16_t xms) 
{
    bsp_timer_set_us_IT(Timer, xms*1000);
}

然后,使能定时器中断后,每次进入定时器是需要重新设置初值的,如下。也是分了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输出了

char putchar(char c)
{
    SBUF = c;
    while(!TI);
    TI = 0;
    return c;
}

最后放上串口初始化部分的.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;
        }
    }
}