目录

一、主要功能

二、使用步骤

三、硬件资源

四、软件设计

五、实验现象

一、主要功能

1、LCD1602液晶显示时间、模式、光照值
2、按键设置模式、阈值、时间​
3、手动模式按键开关晾衣架
4、光敏模式按照光照阈值开关晾衣架
5、时间模式按设置时间开关晾衣架

二、使用步骤

本设计以51单片机为核心,结合光敏传感器、数模转换芯片、LCD1602显示屏及步进电机,实现智能窗帘的自动与手动控制。系统支持光照强度阈值调节、温度感知及定时功能,适用于智能家居场景。‌

三、硬件资源

1、51单片机核心模块
2、按键模块
3、数模转换传感器、MQ烟雾传感器模块
4、时钟模块
5、步进电机模块
6、LCD1602显示模块

四、软件设计

#include <reg52.h>
#include <intrins.h>

#define uchar unsigned char        // 以后unsigned char就可以用uchar代替
#define uint  unsigned int        // 以后unsigned int 就可以用uint 代替


sbit ADC_CS     = P1^6;             // ADC0832的CS引脚
sbit ADC_CLK    = P1^7;             // ADC0832的CLK引脚
sbit ADC_DAT    = P3^2;             // ADC0832的DI/DO引脚
sbit SCK_P      = P1^0;                // 时钟芯片DS1302的SCK管脚
sbit SDA_P      = P1^1;                // 时钟芯片DS1302的SDA管脚
sbit RST_P      = P1^2;                // 时钟芯片DS1302的RST管脚
sbit LcdRs_P    = P1^3;       // 1602液晶的RS管脚       
sbit LcdRw_P    = P1^4;       // 1602液晶的RW管脚 
sbit LcdEn_P    = P1^5;       // 1602液晶的EN管脚
sbit KeyMode_P  = P3^3;                // 模式切换
sbit KeySet_P   = P3^4;                // 设置时间按键
sbit KeySet2_P  = P3^5;                // 设置时间模式的开关时间和光照控制强度
sbit KeyDown_P  = P3^6;                // 减按键
sbit KeyUp_P    = P3^7;                // 加按键
sbit Led_P      = P2^0;                // 指示灯
sbit YYOPEN            =    P3^0;

uchar gMode=1;                                // 1是手动模式,2是时间自动模式,3是亮度自动模式
uchar OpenHour    = 18;                // 开启窗帘的小时
uchar OpenMinute  = 20;                // 开启窗帘的分钟
uchar CloseHour   = 10;                // 关闭窗帘的小时
uchar CloseMinute = 30;                // 关闭窗帘的分钟
uchar gLight      = 80;                // 窗帘开关的阈值
uchar gLightmax=99;
uchar code Clock[]={0x80,0x40,0x20,0x10};             // 步进电机顺时针旋转数组
uchar code AntiClock[]={0x10,0x20,0x40,0x80};        // 步进电机逆时针旋转数组

uchar TimeBuff[7]={25,9,1,6,18,30,40};                    // 时间数组,默认2025年9月1日,星期五,18:30:40
// TimeBuff[0] 代表年份,范围00-99
// TimeBuff[1] 代表月份,范围1-12
// TimeBuff[2] 代表日期,范围1-31
// TimeBuff[3] 代表星期,范围1-7
// TimeBuff[4] 代表小时,范围00-23
// TimeBuff[5] 代表分钟,范围00-59
// TimeBuff[6] 代表秒钟,范围00-59

/*********************************************************/
// 毫秒级的延时函数,time是要延时的毫秒数
/*********************************************************/
void DelayMs(uint time)
{
    uint i,j;
    for(i=0;i<time;i++)
        for(j=0;j<112;j++);
}


/*********************************************************/
// 1602液晶写命令函数,cmd就是要写入的命令
/*********************************************************/
void LcdWriteCmd(uchar cmd)

    LcdRs_P = 0;
    LcdRw_P = 0;
    LcdEn_P = 0;
    P0=cmd;
    DelayMs(2);
    LcdEn_P = 1;    
    DelayMs(2);
    LcdEn_P = 0;    
}


/*********************************************************/
// 1602液晶写数据函数,dat就是要写入的数据
/*********************************************************/
void LcdWriteData(uchar dat)
{
    LcdRs_P = 1; 
    LcdRw_P = 0;
    LcdEn_P = 0;
    P0=dat;
    DelayMs(2);
    LcdEn_P = 1;    
    DelayMs(2);
    LcdEn_P = 0;
}


/*********************************************************/
// 1602液晶初始化函数
/*********************************************************/
void LcdInit()
{
    LcdWriteCmd(0x38);        // 16*2显示,5*7点阵,8位数据口
    LcdWriteCmd(0x0C);        // 开显示,不显示光标
    LcdWriteCmd(0x06);        // 地址加1,当写入数据后光标右移
    LcdWriteCmd(0x01);        // 清屏
}


/*********************************************************/
// 液晶光标定位函数
/*********************************************************/
void LcdGotoXY(uchar line,uchar column)
{
    // 第一行
    if(line==0)        
        LcdWriteCmd(0x80+column); 
    // 第二行
    if(line==1)        
        LcdWriteCmd(0x80+0x40+column); 
}


/*********************************************************/
// 液晶显示内容的初始化
/*********************************************************/
void LcdShowInit()
{
    LcdGotoXY(0,0);
    LcdPrintStr("20  -  -     :  ");
    LcdGotoXY(1,0);
    LcdPrintStr("           gz:  ");
    LcdGotoXY(1,0);
    LcdPrintMode(gMode);
}


/*********************************************************/
// 初始化DS1302
/*********************************************************/
void DS1302_Init(void)
{
    RST_P=0;            // RST脚置低
    SCK_P=0;            // SCK脚置低
    SDA_P=0;            // SDA脚置低                
}


/*********************************************************/
// 从DS1302读出一字节数据
/*********************************************************/
uchar DS1302_Read_Byte(uchar addr) 
{
    uchar i;
    uchar temp;
    
    RST_P=1;                                
    

    for(i=0;i<8;i++) 
    {     
        if(addr&0x01) 
            SDA_P=1;
        else 
            SDA_P=0;
        
        SCK_P=1;
        _nop_();
        SCK_P=0;
        _nop_();
        
        addr=addr>> 1;
    }
    
    /* 读出该地址的数据 */
    for(i=0;i<8;i++) 
    {
        temp=temp>>1;
        
        if(SDA_P) 
            temp|= 0x80;
        else 
            temp&=0x7F;
        
        SCK_P=1;
        _nop_();
        SCK_P=0;
        _nop_();
    }
    
    RST_P=0;
    
    return temp;
}


/*********************************************************/
// 向DS1302写入一字节数据
/*********************************************************/
void DS1302_Write_Byte(uchar addr, uchar dat)
{
    uchar i;
    
    RST_P = 1;
    
    for(i=0;i<8;i++) 
    { 
        if(addr&0x01) 
            SDA_P=1;
        else 
            SDA_P=0;

        SCK_P=1;
        _nop_();
        SCK_P=0;
        _nop_();
        
        addr=addr>>1;
    }
    
    /* 写入数据:dat*/
    for(i=0;i<8;i++) 
    {
        if(dat&0x01) 
            SDA_P=1;
        else 
            SDA_P=0;
    
        SCK_P=1;
        _nop_();
        SCK_P=0;
        _nop_();
        
        dat=dat>>1;
    }
    
    RST_P=0;                    
}


/*********************************************************/
// 向DS1302写入时间数据
/*********************************************************/
void DS1302_Write_Time() 
{
  uchar i;
    uchar temp1;
    uchar temp2;
    
    for(i=0;i<7;i++)            // 十进制转BCD码
    {
        temp1=(TimeBuff[i]/10)<<4;
        temp2=TimeBuff[i]%10;
        TimeBuff[i]=temp1+temp2;
    }
    
    DS1302_Write_Byte(0x8E,0x00);                                // 关闭写保护 
    DS1302_Write_Byte(0x80,0x80);                                // 暂停时钟 
    DS1302_Write_Byte(0x8C,TimeBuff[0]);                // 年 
    DS1302_Write_Byte(0x88,TimeBuff[1]);                // 月 
    DS1302_Write_Byte(0x86,TimeBuff[2]);                // 日 
    DS1302_Write_Byte(0x8A,TimeBuff[3]);                // 星期
    DS1302_Write_Byte(0x84,TimeBuff[4]);                // 时 
    DS1302_Write_Byte(0x82,TimeBuff[5]);                // 分
    DS1302_Write_Byte(0x80,TimeBuff[6]);                // 秒
    DS1302_Write_Byte(0x80,TimeBuff[6]&0x7F);        // 运行时钟
    DS1302_Write_Byte(0x8E,0x80);                                // 打开写保护  
}

/*********************************************************/
// 从DS1302读出时间数据
/*********************************************************/
void DS1302_Read_Time()  

    uchar i;

    TimeBuff[0]=DS1302_Read_Byte(0x8D);                        // 年 
    TimeBuff[1]=DS1302_Read_Byte(0x89);                        // 月 
    TimeBuff[2]=DS1302_Read_Byte(0x87);                        // 日 
    TimeBuff[3]=DS1302_Read_Byte(0x8B);                        // 星期
    TimeBuff[4]=DS1302_Read_Byte(0x85);                        // 时 
    TimeBuff[5]=DS1302_Read_Byte(0x83);                        // 分 
    TimeBuff[6]=(DS1302_Read_Byte(0x81))&0x7F;        // 秒 

    for(i=0;i<7;i++)        // BCD转十进制
    {           
        TimeBuff[i]=(TimeBuff[i]/16)*10+TimeBuff[i]%16;
    }
}


/*********************************************************/
// 按键扫描(设置当前时间)
/*********************************************************/
void KeyScanf1()
{
    if(KeySet_P==0)
    {
        LcdGotoXY(0,13);                // 显示秒钟的冒号
        LcdWriteData(':');
        LcdWriteCmd(0x0f);            // 启动光标闪烁
        LcdGotoXY(0,3);                    // 定位光标到年份闪烁
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
        
        /* 调整年份 */
        while(1)
        {
            if(KeyDown_P==0)                            // 如果减按键被下去
            {
                if(TimeBuff[0]>0)                        // 判断年份是否大于0        
                    TimeBuff[0]--;                        // 是的话就减去1
                LcdGotoXY(0,2);                            // 光标定位到年份的位置
                LcdPrintNum(TimeBuff[0]);        // 刷新显示改变后的年份
                LcdGotoXY(0,3);                            // 定位光标到年份闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeyUp_P==0)                                // 如果加按键被下去
            {
                if(TimeBuff[0]<99)                    // 判断年份是否小于99
                    TimeBuff[0]++;                        // 是的话就加上1
                LcdGotoXY(0,2);                            // 光标定位到年份的位置
                LcdPrintNum(TimeBuff[0]);        // 刷新显示改变后的年份
                LcdGotoXY(0,3);                            // 定位光标到年份闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeySet_P==0)
            {
                break;
            }
        }
        
        LcdGotoXY(0,6);                    // 定位光标到月份闪烁
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
            
        /* 调整月份 */
        while(1)
        {
            if(KeyDown_P==0)                            // 如果减按键被下去
            {
                if(TimeBuff[1]>1)                        // 判断月份是否大于1        
                    TimeBuff[1]--;                        // 是的话就减去1
                LcdGotoXY(0,5);                            // 光标定位到月份的位置
                LcdPrintNum(TimeBuff[1]);        // 刷新显示改变后的月份
                LcdGotoXY(0,6);                            // 定位光标到月份闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeyUp_P==0)                                // 如果加按键被下去
            {
                if(TimeBuff[1]<12)                    // 判断月份是否小于12
                    TimeBuff[1]++;                        // 是的话就加上1
                LcdGotoXY(0,5);                            // 光标定位到月份的位置
                LcdPrintNum(TimeBuff[1]);        // 刷新显示改变后的月份
                LcdGotoXY(0,6);                            // 定位光标到月份闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeySet_P==0)
            {
                break;
            }
        }
        
        LcdGotoXY(0,9);                    // 定位光标到日期闪烁
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
        
        /* 调整日期 */
        while(1)
        {
            if(KeyDown_P==0)                            // 如果减按键被下去
            {
                if(TimeBuff[2]>1)                        // 判断日期是否大于1        
                    TimeBuff[2]--;                        // 是的话就减去1
                LcdGotoXY(0,8);                            // 光标定位到日期的位置
                LcdPrintNum(TimeBuff[2]);        // 刷新显示改变后的日期
                LcdGotoXY(0,9);                            // 定位光标到日期闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeyUp_P==0)                                // 如果加按键被下去
            {
                if(TimeBuff[2]<31)                    // 判断日期是否小于31
                    TimeBuff[2]++;                        // 是的话就加上1
                LcdGotoXY(0,8);                            // 光标定位到日期的位置
                LcdPrintNum(TimeBuff[2]);        // 刷新显示改变后的日期
                LcdGotoXY(0,9);                            // 定位光标到日期闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeySet_P==0)
            {
                break;
            }
        }
        
        LcdGotoXY(0,12);                // 定位光标到小时闪烁
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
        
        
        /* 调整小时 */
        while(1)
        {
            if(KeyDown_P==0)                            // 如果减按键被下去
            {
                if(TimeBuff[4]>0)                        // 判断小时是否大于0
                    TimeBuff[4]--;                        // 是的话就减去1
                LcdGotoXY(0,11);                        // 光标定位到小时的位置
                LcdPrintNum(TimeBuff[4]);        // 刷新显示改变后的小时
                LcdGotoXY(0,12);                        // 定位光标到小时闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeyUp_P==0)                                // 如果加按键被下去
            {
                if(TimeBuff[4]<23)                    // 判断小时是否小于23
                    TimeBuff[4]++;                        // 是的话就加上1
                LcdGotoXY(0,11);                        // 光标定位到小时的位置
                LcdPrintNum(TimeBuff[4]);        // 刷新显示改变后的小时
                LcdGotoXY(0,12);                        // 定位光标到小时闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeySet_P==0)
            {
                break;
            }
        }
        
        LcdGotoXY(0,15);                // 定位光标到分钟闪烁
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
        
        /* 调整分钟 */
        while(1)
        {
            if(KeyDown_P==0)                            // 如果减按键被下去
            {
                if(TimeBuff[5]>0)                        // 判断分钟是否大于0
                    TimeBuff[5]--;                        // 是的话就减去1
                LcdGotoXY(0,14);                        // 光标定位到分钟的位置
                LcdPrintNum(TimeBuff[5]);        // 刷新显示改变后的分钟
                LcdGotoXY(0,15);                        // 定位光标到分钟闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeyUp_P==0)                                // 如果加按键被下去
            {
                if(TimeBuff[5]<59)                    // 判断分钟是否小于59
                    TimeBuff[5]++;                        // 是的话就加上1
                LcdGotoXY(0,14);                        // 光标定位到分钟的位置
                LcdPrintNum(TimeBuff[5]);        // 刷新显示改变后的分钟
                LcdGotoXY(0,15);                        // 定位光标到分钟闪烁
                DelayMs(300);                                // 延时0.3秒左右
            }
            
            if(KeySet_P==0)
            {
                break;
            }
        }
        
        /* 退出前的设置 */
        LcdWriteCmd(0x0C);            // 关闭光标闪烁
        DS1302_Write_Time();        // 把新设置的时间值存入DS1302芯片
        DelayMs(10);                        // 延时等待,消除按键按下的抖动
        while(!KeySet_P);                // 等待按键释放
        DelayMs(10);                        // 延时等待,消除按键松开的抖动
    }
}


/*********************************************************/
// 按键扫描(模式切换)
/*********************************************************/
void KeyScanf3()
{
    if(KeyMode_P==0)
    {
        gMode++;                            // 切换到下一模式
        if(gMode==4)                    // 如果到尽头了
            gMode=1;                        // 回到第一种模式
        LcdGotoXY(1,0);                // 光标定位
        LcdPrintMode(gMode);    // 显示模式
        DelayMs(10);                    // 去除按键按下的抖动
        while(!KeyMode_P);        // 等待按键是否
        DelayMs(10);                    // 去除按键松开的抖动
    }
}



void main()
{
    uchar light;
    
    LcdInit();            // 执行液晶初始化    
    DS1302_Init();    // 时钟芯片的初始化
    LcdShowInit();    // 液晶显示内容的初始化
    while(1)
    {
        DS1302_Read_Time();                // 获取当前时钟芯片的时间,存在数组time_buf中
        FlashTime();                            // 刷新时间显示

        light=Get_ADC0832();            // 读取光照强度
        light=light/2.5;                    // 缩小光照检测结果(在0-99)
        if(light>99)                            // 如果大于99
            light=99;                                // 则依然保持99
        LcdGotoXY(1,14);                    // 光标定位
        LcdPrintNum(light);                // 显示光照强度
        
        KeyScanf1();                            // 按键扫描(时间的设置)
        KeyScanf2();                            // 按键扫描(阈值的设置)
        KeyScanf3();                            // 按键扫描(模式切换)
        DelayMs(100);                            // 延时0.1秒
    }
}

五、实验现象

演示视频:

【51单片机-B063】【protues仿真】基于51单片机智能窗帘仿真

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐