专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

单片机开源项目之按键可调时电子钟(矩阵按键+红外遥控按键进行调时)

作者:寒竹子   来源:本站原创   点击数:  更新时间:2014年05月02日   【字体:

 此程序是基于51hei单片机开发板上面写的,如需要移植到自己的电路上,修改相应的端口即可,开发板完整的电路图下载:  点这里 (注意:只需要看相关部分即可如矩阵键盘 数码管 红外等,其他部分可以忽略)

/**
  ************************************************************************
 * @file    :   main.c
 * @author  :   xr
 * @date    :   2014年4月21日 22:23:12 - 2014年4月26日21:22:29
 * @version :   V1.2.3
 * @brief   :   按键可调时电子钟(矩阵按键+红外遥控按键进行调时)   单片机STC89C52RC MCU 晶振 11.0592MHZ
 ************************************************************************
 */

#include <reg52.h>
#include "main.h"
/*定义结构体来封装从DS1302中读取的时间和日期和设置到DS1302中的时间和日期*/
struct Time {
 unsigned char year;  //DS1302中只存放的是年的低两位字节
  unsigned char month;
 unsigned char day;
    unsigned char hour;
 unsigned char min;
 unsigned char sec;
 unsigned char week;
};
/*定义结构体时间变量来保存时间和日期*/
struct Time timeBuf; //此处必须用结构体变量,不能用结构体指针否则写入失败!
unsigned char setTimeIndex = 0; //设置时间状态及设置光标位置及设置时间位置的索引值(0时正常运行,1-12为设置时间状态,1-12是设置的位置)
bit flag200ms = 0;
unsigned char thr0, tlr0;
//红外通信解码的键码和标准PC机编码映射表
unsigned char code IrdCodeMap[] = {0x45, 0x46, 0x47, //开关,Mode, 静音
                                   0x44, 0x40, 0x43, //播放/暂停 快退, 快进
                                   0x07, 0x15, 0x09, //EQ, 减, 加
                                   0x16, 0x19, 0x0D, //0, 返回, U/SD
                                   0x0C, 0x18, 0x5E, //1, 2, 3
                                   0x08, 0x1C, 0x5A, //4, 5, 6
                                   0x42, 0x52, 0x4A};//7, 8, 9
//外部变量声明
extern bit flagIrd; //红外数据码值接收完毕标志位
extern unsigned char irdCode[4]; //保存NEC协议解码的四个字节的数据码(用户码+用户反码,键码+键码反码)
extern void InitLCD1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char * str);
extern void LcdSetCoursor(unsigned char x, unsigned char y);
extern void LcdOpenCoursor();
extern void InitDS1302();
extern void GetTimeFromDS1302(struct Time * time);
extern void SetTimeToDS1302(struct Time * time);
extern void KeyDriver();
extern void KeyScan();
extern void LcdCoursorRight();
extern void LcdCoursorLeft();
void ConfgiTimer0(unsigned int xms);
void RefreshLcdShowTime();
extern void ConfigIrdByTimer1();
void IrdDrive();
void IrdKeyAction();
extern void SetTimeBcdByte(unsigned char keyNum);
/*主程序main()*/
void main()
{
    unsigned char psec = 0xFF;//用于检测sec秒是否变化,若变化则刷新时间显示
    ConfgiTimer0(1); //定时1ms
    ConfigIrdByTimer1();//配置红外通信
 InitLCD1602();
    InitDS1302();
   
    /*液晶初始化显示*/
    LcdShowStr(1, 0, "*");
 LcdShowStr(2, 0, "20  -  -  ");
    LcdShowStr(12, 0, "*");
    LcdShowStr(14, 0, "--");
 LcdShowStr(0, 1, "time: --:--:--");
 
 while (1)
    {
       
        KeyDriver();//检测按键动作
       
        if (flagIrd)
        {
            flagIrd = 0;
            IrdKeyAction();//检测红外按键动作
        }
       
        if (flag200ms == 1 && (setTimeIndex == 0))  //每200ms且setTimeIndex==0处于非设定时间状态时刷新一次时间显示
        {
            flag200ms = 0;
            GetTimeFromDS1302(&timeBuf); //从DS1302中获取时间到timeBuf结构体指针变量的成员中
            if (timeBuf.sec != psec) //当前秒值和上一次的秒值不相等
            {
                RefreshLcdShowTime();//刷新时间显示
                psec = timeBuf.sec;//备份当前的秒值(秒寄存器值)
            }
        }
    }   
   
}
/*定时器T0配置*/
void ConfgiTimer0(unsigned int xms)
{
    unsigned long tmp;
    tmp = 11059200/12;//周期频率
    tmp = (tmp * xms) / 1000;//定时xms需要的计数值
    tmp = 65536-tmp;//定时装入的初值
    thr0 = (unsigned char)(tmp >> 8);
    tlr0 = (unsigned char)tmp;
    TMOD &= 0xF0;//清零T0控制位
    TMOD |= 0x01;//T0方式1,16位可设定时模式
    TH0 = thr0;
    TL0 = tlr0;
    TR0 = 1;
    ET0 = 1;
    EA  = 1;
}
/*将一个BCD码字节数据分解显示到LCD1602的(x, y)坐标上*/
void LcdShowBCDByte(unsigned char x, unsigned char y, unsigned char bcdbyte)
{
    unsigned char str[4];
   
    str[0] = (bcdbyte >> 4) + '0';//取BCD码的高四位字节
    str[1] = (bcdbyte & 0x0F) + '0';//取BCD码的第四位字节
    str[2] = '\0';
   
    LcdShowStr(x, y, str);
}
/*刷新时间显示到LCD1602液晶上*/
void RefreshLcdShowTime()
{
    LcdShowBCDByte(4, 0, timeBuf.year); //显示年
    LcdShowBCDByte(7, 0, timeBuf.month);
    LcdShowBCDByte(10, 0, timeBuf.day);
    LcdShowBCDByte(6, 1, timeBuf.hour);
    LcdShowBCDByte(9, 1, timeBuf.min);
    LcdShowBCDByte(12, 1, timeBuf.sec);
    LcdShowBCDByte(14, 0, timeBuf.week); //显示星期
}
/************以下函数功能是BCD码字节的高位和低位数字+1和-1*******************/
/*递增BCD码的高位数字*/
unsigned char IncrementBCDByteHigh(unsigned char bcdbyte)
{
    if ((bcdbyte & 0xF0) < 0x90) //取bcdbyte的高四位字节是否小于9
    {
        bcdbyte += 0x10;//高四位字节+1
    }
    else
    {
        //高四位字节数值到9归零
        bcdbyte &= 0x0F;//0000 1111
    }
   
    return (bcdbyte); //返回修改后的BCD码值
}
/*递增BCD码的低位字节数字*/
unsigned char IncrementBCDByteLow(unsigned char bcdbyte)
{
    if ((bcdbyte & 0x0F) < 0x09) //取bcdbyte的低四位字节数字
    {
        bcdbyte += 0x01; //低位字节+1
    }
    else
    {
        //到9归零
        bcdbyte &= 0xF0;//低四位清零
    }
   
    return (bcdbyte);
}
/*递减BCD码数据字节的高字节数字*/
unsigned char DescBCDByteHigh(unsigned char bcdbyte)
{
    if ((bcdbyte & 0xF0) > 0) //取BCD码字节的高四位字节
    {
        bcdbyte -= 0x10; //高四位字节数字-1
    }
    else
    {
        //到0归9
        bcdbyte |= 0x90; //或bcdbyte &= 0x9F
    }
   
    return (bcdbyte);
}
/*递减BCD码数据字节的低字节数字*/
unsigned char DescBCDByteLow(unsigned char bcdbyte)
{
    if ((bcdbyte & 0x0F) > 0)
    {
        bcdbyte -= 0x01;//低位数字-1
    }
    else
    {
        //到0归9
        bcdbyte |= 0x09;
    }
   
    return (bcdbyte);
}

/*根据setTimeIndex的值来设置光标闪烁的位置*/
void LcdRefreshSetCoursor()
{
    switch (setTimeIndex)
    {
        case 1:  LcdSetCoursor(4, 0);  break;//设置年的十位数字
        case 2:  LcdSetCoursor(5, 0);  break;
        case 3:  LcdSetCoursor(7, 0);  break;
        case 4:  LcdSetCoursor(8, 0);  break;
        case 5:  LcdSetCoursor(10, 0); break;
        case 6:  LcdSetCoursor(11, 0); break;
        case 7:  LcdSetCoursor(6,  1); break;
        case 8:  LcdSetCoursor(7, 1);  break;
        case 9:  LcdSetCoursor(9, 1);  break;
        case 10: LcdSetCoursor(10, 1); break;
        case 11: LcdSetCoursor(12, 1); break;
        case 12: LcdSetCoursor(13, 1); break;
        default: break;
    }
    LcdOpenCoursor();
}
/*递增光标闪烁位置的时间值*/
void IncTimeBysetTimeIndex()
{
    switch (setTimeIndex)
    {
        case 1:  timeBuf.year  = IncrementBCDByteHigh(timeBuf.year);  break;//year10++年的十位数字++
        case 2:  timeBuf.year  = IncrementBCDByteLow(timeBuf.year);   break;//年的个位数字++
        case 3:  timeBuf.month = IncrementBCDByteHigh(timeBuf.month); break;//月十位++
        case 4:  timeBuf.month = IncrementBCDByteLow(timeBuf.month);  break;//月个位++
        case 5:  timeBuf.day   = IncrementBCDByteHigh(timeBuf.day);   break;
        case 6:  timeBuf.day   = IncrementBCDByteLow(timeBuf.day);    break;
        case 7:  timeBuf.hour  = IncrementBCDByteHigh(timeBuf.hour);  break;
        case 8:  timeBuf.hour  = IncrementBCDByteLow(timeBuf.hour);   break;
        case 9:  timeBuf.min   = IncrementBCDByteHigh(timeBuf.min);   break;
        case 10: timeBuf.min   = IncrementBCDByteLow(timeBuf.min);    break;
        case 11: timeBuf.sec   = IncrementBCDByteHigh(timeBuf.sec);   break;
        case 12: timeBuf.sec   = IncrementBCDByteLow(timeBuf.sec);    break;
        default: break;
    }
    RefreshLcdShowTime();//刷新时间显示
    LcdRefreshSetCoursor();//刷新光标闪烁显示
}
/*递减光标位置的时间值*/
void DecTimeBySetTimeIndex()
{
    switch (setTimeIndex)
    {
        case 1:  timeBuf.year  = DescBCDByteHigh(timeBuf.year);  break;//年十位数字递减
        case 2:  timeBuf.year  = DescBCDByteLow(timeBuf.year);   break;//年个位数字递减
        case 3:  timeBuf.month = DescBCDByteHigh(timeBuf.month); break;
        case 4:  timeBuf.month = DescBCDByteLow(timeBuf.month);  break;
        case 5:  timeBuf.day   = DescBCDByteHigh(timeBuf.day);   break;
        case 6:  timeBuf.day   = DescBCDByteLow(timeBuf.day);    break;
        case 7:  timeBuf.hour  = DescBCDByteHigh(timeBuf.hour);  break;
        case 8:  timeBuf.hour  = DescBCDByteLow(timeBuf.hour);   break;
        case 9:  timeBuf.min   = DescBCDByteHigh(timeBuf.min);   break;
        case 10: timeBuf.min   = DescBCDByteLow(timeBuf.min);    break;
        case 11: timeBuf.sec   = DescBCDByteHigh(timeBuf.sec);   break;
        case 12: timeBuf.sec   = DescBCDByteLow(timeBuf.sec);    break;
        default: break;
    }
    /*先刷新时间显示,再刷新光标闪烁显示*/
    RefreshLcdShowTime();
    LcdRefreshSetCoursor();
}
/*红外按键动作函数*/
void IrdKeyAction()
{
    if (irdCode[2] == 0x44) // >>||
    {
        if (setTimeIndex == 0) //正常运行状态
        {
            setTimeIndex = 1;
            LcdRefreshSetCoursor();//刷新光标设置显示    
        }
        else
        {
            SetTimeToDS1302(&timeBuf); //将时间写入到DS1302中
            setTimeIndex = 0;
            RefreshLcdShowTime();
        }
    }
    else if (irdCode[2] == 0x40) //|<<
    {
        if (setTimeIndex != 0)
        {
            LcdCoursorLeft();
        }
    }
    else if (irdCode[2] == 0x43) // >>|
    {
        if (setTimeIndex != 0)
        {
            LcdCoursorRight();
        }
    }
    else if (irdCode[2] == 0x15) //-
    {
        DecTimeBySetTimeIndex();
    }
    else if (irdCode[2] == 0x09) //+
    {
        IncTimeBysetTimeIndex();
    }
    else if (irdCode[2] == 0x19) //EQ
    {
        setTimeIndex = 0;
        RefreshLcdShowTime();
    }
    else if (irdCode[2] == 0x16) //数字键0
    {
        SetTimeBcdByte(0);
    }
    else if (irdCode[2] == 0x0C) //数字键1
    {
        SetTimeBcdByte(1);
    }
    else if (irdCode[2] == 0x18) //数字键2
    {
        SetTimeBcdByte(2);
    }
    else if (irdCode[2] == 0x5E)
    {
        SetTimeBcdByte(3);
    }
    else if (irdCode[2] == 0x08)
    {
        SetTimeBcdByte(4);
    }
    else if (irdCode[2] == 0x1C)
    {
        SetTimeBcdByte(5);
    }
    else if (irdCode[2] == 0x5A)
    {
        SetTimeBcdByte(6);
    }
    else if (irdCode[2] == 0x42)
    {
        SetTimeBcdByte(7);
    }
    else if (irdCode[2] == 0x52)
    {
        SetTimeBcdByte(8);
    }
    else if (irdCode[2] == 0x4A)
    {
        SetTimeBcdByte(9);
    }
    else
    {
    }
}
/*T0中断服务*/
void Timer0_ISP() interrupt 1
{
    static unsigned char counter = 0;
   
    TH0 = thr0;
    TL0 = tlr0; //1ms
    counter++;
    KeyScan();//按键扫描
   
    if (counter >= 200)
    {
        counter = 0;
        flag200ms = 1;//200ms
    }    
}

 #ifndef _MAIN_H_
#define _MAIN_H_
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
#endif //_MAIN_H_

 /**
  ************************************************************************
 * @file    :   Lcd1602.c
 * @author  :   xr
 * @date    :   2014年4月21日 22:23:12
 * @version :   V1.2.3
 * @brief   :   LCD1602底层驱动   单片机STC89C52RC MCU 晶振 11.0592MHZ
 ************************************************************************
 */
#include <reg52.h>
//LCD1602
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_EN = P1^5;
#define LCD1602_DB P0
/*LCD1602忙碌等待*/
void LCD1602Wait()
{
 unsigned char sta;//读取LCD1602状态字
 
    /*读取液晶状态字之前必须将P0口全部拉高*/
 LCD1602_DB = 0xFF;
 
 LCD1602_RS = 0;
 LCD1602_RW = 1;
 LCD1602_EN = 0;
 
 do
 {
  LCD1602_EN = 1;
  sta = LCD1602_DB;//读状态字
  LCD1602_EN = 0;
 } while (sta & 0x80); //检测最高位是否为1,1忙碌,0空闲
}
/*LCD1602写命令*/
void LCD1602WriteCmd(unsigned char cmd)
{
 //读写前要进行液晶的忙碌等待
 LCD1602Wait();
 
 LCD1602_RS = 0;
 LCD1602_RW = 0;
 LCD1602_EN = 0;
 LCD1602_DB = cmd;
 LCD1602_EN = 1;//高脉冲
 LCD1602_EN = 0;//关闭液晶输出
}
/*LCD1602写数据*/
void LCD1602WriteData(unsigned char dat)
{
 LCD1602Wait();
 
 LCD1602_RS = 1;
 LCD1602_RW = 0;
 LCD1602_EN = 0;
 LCD1602_DB = dat;//送入数据
 LCD1602_EN = 1;//高脉冲
 LCD1602_EN = 0;//关闭液晶输出
}
/*液晶初始化*/
void InitLCD1602()
{
 LCD1602WriteCmd(0x38); //写指令38H
 LCD1602WriteCmd(0x0C); //开显示不显示光标
 LCD1602WriteCmd(0x06); //写入字符时字符指针++且光标++
 LCD1602WriteCmd(0x01); //显示清屏
}
/*在LCD1602的坐标(x, y)位置显示str*/
void LcdShowStr(unsigned char x, unsigned char y, unsigned char * str)
{
 unsigned char addr;
 
 if (y == 0)
 {
  addr = 0x00 + x; //第一行的x位置显示
 }
 else
 {
  addr = 0x40 + x; //第二行x的位置显示
 }
 LCD1602WriteCmd(addr + 0x80);
 while (*str != '\0')
 {
  LCD1602WriteData(*str++);
 }
}
/*设置光标的位置为(x, y)*/
void LcdSetCoursor(unsigned char x, unsigned char y)
{
 unsigned char addr;
 
 if (y == 0)
 {
  addr = 0x00 + x;
 }
 else
 {
  addr = 0x40 + x;
 }
    LCD1602WriteCmd(addr | 0x80); //写入光标闪烁地址
}
/*打开光标显示*/
void LcdOpenCoursor()
{
 LCD1602WriteCmd(0x0F); //显示光标病使光标闪烁
}
/*关闭光标显示*/
void LcdCloseCoursor()
{
 LCD1602WriteCmd(0x0C); //开显示但不显示光标
}

 /**
  ************************************************************************
 * @file    :   Ds1302.c
 * @author  :   xr
 * @date    :   2014年4月21日 22:23:12
 * @version :   V1.2.3
 * @brief   :   DS1302底层驱动   单片机STC89C52RC MCU 晶振 11.0592MHZ
 ************************************************************************
 */
#include <reg52.h>
//DS1302
sbit DS1302_SIO = P3^4;//数据IO
sbit DS1302_SCK = P3^5;//时钟线
sbit DS1302_CE  = P1^7;//片选使能
/*使用外部数据类型时必须在这个文件中再一次声明*/
struct Time {
 unsigned char year;//年在DS1302中只存放的是低两位,这里用unsigned char型
 unsigned char month;
 unsigned char day;
 unsigned char hour;
 unsigned char min;
 unsigned char sec;
 unsigned char week;
};
/*向SIO发送一个字节数据*/
void DS1302WriteByte(unsigned char byte)
{
 unsigned char mask = 0x01;//低位在前,逐位发送
 /*初始化DS1302时,将SIO和SCK引脚拉低,空闲*/
 for (mask = 0x01; mask != 0; mask <<= 1)
 {
  if ((byte & mask) != 0)  //上升沿DS1302进行数据的采样锁存
  {
   DS1302_SIO = 1;
  }
  else
  {
   DS1302_SIO = 0;
  }
  DS1302_SCK = 1;
  DS1302_SCK = 0;//下降沿主机进行数据输出
 }
 //主机发送数据完成后要进行释放SIO数据线
 DS1302_SIO = 1;
}
/*读取SIO数据线上的数据*/
unsigned char DS1302ReadByte()
{
 unsigned char mask = 0x01;//低位在先,逐位接收
 unsigned char byte = 0;
 
 for (mask = 0x01; mask != 0; mask <<= 1)
 {
  if (DS1302_SIO != 0) //上升沿主机进行数据采样锁存
  {
   byte |= mask; //相应位置1
  }
  else
  {
   byte &= (~mask);//相应位清零
  }
  DS1302_SCK = 1;
  DS1302_SCK = 0;//DS1302在下降沿进行数据的输出
 }
 
 return (byte);
}
/*向DS1302的reg地址寄存器中写入单个字节数据*/
void DS1302WriteSingleByte(unsigned char reg, unsigned char dat)
{
 /*首先打开DS1302片选端,然后写寄存器地址reg,然后再写入数据dat*/
 DS1302_CE = 1;
 DS1302WriteByte((reg << 1) | 0x80);//写入寄存器地址,reg<<1留出读写方向位,reg的高位是1,次高位为0
 DS1302WriteByte(dat);              //写入数据
 DS1302_CE = 0;
 DS1302_SIO = 0;//因为板子上没有SIO口没有加上拉电阻,开漏输出不定态,将SIO=0,单片机IO口会输出稳定的0态
}
/*从DS1302的reg地址寄存器中读取一个字节的数据*/
unsigned char DS1302ReadSingleByte(unsigned char reg)
{
 unsigned char byte = 0;
 
 DS1302_CE = 1;
 DS1302WriteByte((reg << 1) | 0x81); //写入寄存器地址reg并在读写方向上选择读
 byte = DS1302ReadByte();
 DS1302_CE = 0;
 DS1302_SIO = 0; //SIO输出稳定的0状态
 
 return (byte);
}
/*Burst模式写八个字节到DS1302八个寄存器中*/
void DS1302BurstWrite(unsigned char * date)
{
 unsigned char i = 0;
 
 DS1302_CE = 1;
 DS1302WriteByte(0xBE); //写入突发模式指令(Burst模式)
 for (i = 0; i < 8; i++)
 {
  DS1302WriteByte(date[i]);
 }
 DS1302_CE = 0;
 DS1302_SIO = 0;
}
/*Burst模式从DS1302寄存器中连续读取八个字节的数据*/
void DS1302BurstRead(unsigned char * date)
{
 unsigned char i = 0;
 
 DS1302_CE = 1; //片选使能
 DS1302WriteByte(0xBF); //突发读指令
 for (i = 0; i < 8; i++)
 {
  date[i] = DS1302ReadByte();
 }
 DS1302_CE = 0;
 DS1302_SIO = 0;
}
/*从DS1302中获取当前时间*/
void GetTimeFromDS1302(struct Time * time)
{
 unsigned char buff[8];//保存八个寄存器中的数据
 
 DS1302BurstRead(buff); //将DS1302寄存器中的数据读入buff数组中
 
 time->year  = buff[6];//将年寄存器中的bcd码取出来存入结构体time->year中
 time->month = buff[4];
 time->week  = buff[5];
 time->day   = buff[3];
 time->hour  = buff[2];
 time->min   = buff[1];
 time->sec   = buff[0];
}
/*设置时间,将当前修改后的时间值设置到DS1302中*/
void SetTimeToDS1302(struct Time * time)
{
 unsigned char buff[8]; //保存当前修改后的time结构体成员的值
 
 buff[0] = time->sec; //将秒寄存器中的值存入buff[0
 buff[1] = time->min;
 buff[2] = time->hour;
 buff[3] = time->day;
 buff[4] = time->month;
    buff[5] = time->week;
    buff[6] = time->year;
    buff[7] = 0x00;    //WP写保护寄存器
   
    DS1302BurstWrite(buff); //将buff中的值以Burst模式写入DS1302中
}
/*初始化DS1302*/
void InitDS1302()
{
 struct Time code InitTime[] = {0x14, 0x04, 0x22, 0x23, 0x59, 0x59, 0x02}; //2014年4月22日23:59:59
    unsigned char psec; //检测DS1302的停止位CH
    /*初始化DS1302的通信引脚*/
    DS1302_SCK = 0;
    DS1302_CE  = 0;
   
    psec = DS1302ReadSingleByte(0x00); //0x00<<1 | 0x81=0x81, 读取秒寄存器
    if ((psec & 0x80) != 0) //最高位CH=1,时钟停止
    {
        DS1302WriteSingleByte(7, 0x00); //去除写保护
        SetTimeToDS1302(InitTime); //将初始时间设置到DS1302中
    }   
}

 /**
  **************************************************************************
  * @file    :   Infrared.c
  * @author  :   xr
  * @date    :   2014年4月23日22:01:12
  * @version :   V1.2.3
  * @brief   :   红外通信解码-NEC协议解码
  **************************************************************************
  */
 
#include <reg52.h>
//红外通信引脚
sbit IRD = P3^3;//红外通信引脚,外部中断1引脚
bit flagIrd = 0; //红外数据码值接收完毕标志位
unsigned char irdCode[4]; //保存NEC协议解码的四个字节的数据码(用户码+用户反码,键码+键码反码)
/*红外通信-定时器T1计数进行红外接收数据判定的时间获取*/
void ConfigIrdByTimer1()
{
    TMOD &= 0x0F;//清零T1控制位
    TMOD |= 0x10;//使用T1方式1,16位可设定模式
    TH1 = 0;
    TL1 = 0;//开始时没有红外信号,清零T1计数
    TR1 = 0;//开始时关闭T1计数
    ET1 = 0;//关闭T1中断,只用T1计数功能
    /*外部中断1配置*/
    IE1 = 0;//外部中断1标志位清零
    IT1 = 1;//设置外部中断1为下降沿触发
    EX1 = 1;//开启外部中断
    EA  = 1;//开总中断
}
/*获取IRD引脚即红外引脚高电平T1计数值*/
unsigned int GetHighTimers()
{
    IRD = 1;//在检测红外引脚之前,要先将IRD引脚拉高释放
    TH1 = 0;
    TL1 = 0;//先清零上一次的T1计数值
    TR1 = 1;//开启T1计数
    while (IRD)
    {  //高电平T1进行计数
        if (TH1 > 0x40) //0x40 * 256 * (12/11059200) * 1000 = 17.7ms
        {
            break; //当时间>17.7ms时我们认为是误码,强制退出
        }
    }
    TR1 = 0; //关闭T1计数
   
    return (TH1 * 256 + TL1); //返回IRD引脚高电平时T1的计数值
}
/*获得低电平载波时间*/
unsigned int GetLowTimers()
{
    IRD = 1;//在检测红外引脚之前,要先将IRD引脚释放
    TH1 = 0;
    TL1 = 0; //清零T1计数值
    TR1 = 1; //开启T1计数
   
    while (!IRD)
    {
        //超时判断
        if (TH1 > 0x40) //超出17.7ms就退出
        {
            break;
        }
    }
    TR1 = 0;//关闭T1计数
   
    return (TH1 * 256 + TL1);//返回T1计数值
}
/*外部中断1服务,判定红外并接收红外NEC解码*/
void EXINT1_ISP() interrupt 2 //EXINT1中断标号为2
{
    unsigned char byte = 0;//接收解码数据
    unsigned int time = 0;//接收引导码和空闲与载波的时间
    unsigned char i, j;
   
    time = GetLowTimers();//载波 引导码(9ms的载波+4.5ms的空闲):范围(8.5ms-9.5ms)
    if ((time < 7833) ||(time > 8755))  //计数周期=12/11059200 计数时间=(TH1*256+TL1)*12/11059200
    {
        //误码,清零中断标志并退出中断
        IE1 = 0;
        return;
    }
   
    time = GetHighTimers();//空闲 4.5ms的空闲:范围:4ms-5ms
    if ((time < 3686) || (time > 4608)) //判断时必须加上()
    {
        //不符合
        IE1 = 0;//外部中断标志位
        return; //退出中断函数
    }       
    //引导码正确,开始循环接收用户码和键码
    for (i = 0; i < 4; i++) //接收4个字节数据
    {
        for (j = 0; j < 8; j++) //接收8位
        {
            time = GetLowTimers();//载波:560us载波+560us/1.68ms空闲:340us 760us
            if ((time < 313) || (time > 700))
            {
                //误码
                IE1 = 0;//清零外部中断标志
                return;
            }
            time = GetHighTimers();//空闲
            if ((time > 313) && (time < 700)) //560us的空闲,比特值'0'time大于340us并且time<780us
            {
                byte >>= 1;//低位在先,逐位接收,将数据位右移到低位
            }
            else if ((time > 1271) && (time < 1658))  //1.68ms空闲:范围:1.38ms-1.8ms,比特值是'1'
            {
                byte >>= 1;//先右移一位,将这一位移到byte的最高位
                byte |= 0x80;//再将这一位置1
            }
            else
            {
                //接收的是误码
                IE1 = 0;//清零外部中断1标志位
                return;//退出中断函数
            }
        }   
            irdCode[i] = byte;//接收码值
    }
    flagIrd = 1;//接收完成标志置1
    IE1 = 0;//清零外部中断标志
}

 /**
  ************************************************************************
 * @file    :   Keyboard.c
 * @author  :   xr
 * @date    :   2014年4月21日 22:23:12
 * @version :   V1.2.3
 * @brief   :   按键底层驱动   单片机STC89C52RC MCU 晶振 11.0592MHZ
 ************************************************************************
 */
#include <reg52.h>
//KEY
sbit KEY_IN_1  = P2^4;
sbit KEY_IN_2  = P2^5;
sbit KEY_IN_3  = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
static unsigned char volatile keySta[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1},
                                    {1, 1, 1, 1}, {1, 1, 1, 1}}; //16个按键当前状态
unsigned char pdata keyCodeMap[4][4] = {  //按键编码根据ASCII码进行编码
                                    {'1', '2', '3', 0x26}, //数字键1,2,3和向上键
                                    {'4', '5', '6', 0x25}, //数字键4,5,6和向左键
                                    {'7', '8', '9', 0x28}, //数字键7,8,9和向下键
                                    {'0', 0x1B, 0x0D, 0x27} //数字键0和ESC键,回车键,向右键
                                };
                                   
/*需要使用main.c文件中的struct Time类型的结构体变量timeBuf,需要在重新进行声明*/
struct Time {
    unsigned char year;
    unsigned char month;
    unsigned char day;
    unsigned char hour;
    unsigned char min;
    unsigned char sec;
    unsigned char week;
};                    
extern struct Time timeBuf;
extern unsigned char setTimeIndex;//外部变量声明
void LcdCoursorRight();
void LcdCoursorLeft();
void SetTimeBcdByte(unsigned char keyNum);
void KeyAction(unsigned char keycode);
extern void LcdRefreshSetCoursor();
extern void LcdCloseCoursor();
extern void IncTimeBysetTimeIndex();
extern void DecTimeBySetTimeIndex();
extern GetTimeFromDS1302(struct Time * time);
extern void SetTimeToDS1302(struct Time * time);
extern void RefreshLcdShowTime();
/*按键驱动,进行按键状态的判断,传递相应按键的键码到按键动作函数中*/
void KeyDriver()
{
    static unsigned char pdata keyBackup[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1},
                                                  {1, 1, 1, 1}, {1, 1, 1, 1}}; //按键的备份即上一次按键的状态值
    unsigned char i, j;
    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 4; j++)
        {
            if (keySta[i][j] != keyBackup[i][j])
            {
                //按键有动作
                if (keyBackup[i][j] != 0) //根据上一次的备份值来确定本次的按键值,即按键的动作
                {
                    //按键按下,执行动作
                    KeyAction(keyCodeMap[i][j]); //传递相应的键码
                }
                //备份本次按键的值
                keyBackup[i][j] = keySta[i][j];
            }
        }
    }
}
/*按键的动态扫描,进行按键去抖*/
void KeyScan()
{
    static unsigned char keyout = 0;//按键行索引,每次都要用到上一次变化的keyout值所以定义为静态变量
    static unsigned char keybuff[4][4] = {{0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF},
                                          {0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF}}; //按键扫描缓冲区
    unsigned char i = 0;
   
    /*扫描按键的值,并将按键的状态值存入keybuff缓冲区中*/
    keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1; //将第keyout行的第一个按键的扫描值存入keybuff[keyout][0]缓冲区中
    keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;
    keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;
    keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4;
   
    /*更新扫描消抖后按键的状态*/
    for (i = 0; i < 4; i++) //每行有四个按键需要进行判断
    {
        if ((keybuff[keyout][i] & 0x1F) == 0x00) //5次按键扫描值都是0,表示按键是稳定按下
        {
            keySta[keyout][i] = 0;
        }
        else if ((keybuff[keyout][i] & 0x1F) == 0x1F) //5次按键的扫描值都是1,表示按键是稳定弹起
        {
            keySta[keyout][i] = 1;
        }
    }
    //行++
    keyout++;
    keyout &= 0x03;//实现到4归零
   
    /*根据keyout的值来确定选择哪一行按键进行扫描*/
    switch (keyout)
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;//扫描第1行
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;//扫描第2行
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;//扫描第3行
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;//扫描第4行
        default: break;
    }
}
/*按键动作,根据按键的键码来执行相应的操作*/
void KeyAction(unsigned char keycode)
{
    if (keycode >= '0' && keycode <= '9')
    {
        if (setTimeIndex != 0) //处于设置状态才相应
        SetTimeBcdByte(keycode - '0'); //数字键设置时间
    }
    else if (keycode == 0x26)  //向上键进行光标闪烁位置上的数值的++
    {
        IncTimeBysetTimeIndex();//递增BCD码的高位或低位数字
    }
    else if (keycode == 0x27) //向右键进行进行光标位置的右移
    {
       if (setTimeIndex != 0) //处于设置状态才相应
       LcdCoursorRight();//递减BCD码的高位或低位数字
    }
    else if (keycode == 0x25) //向左键进行光标闪烁位置的左移
    {
        if (setTimeIndex != 0)
        LcdCoursorLeft();
    }
    else if (keycode == 0x28) //向下键光标闪烁位置上的数值的递减
    {
        DecTimeBySetTimeIndex();
    }
    else if (keycode == 0x1B) //ESC键,取消当前的设置
    {
        setTimeIndex = 0;//时钟正常运行索引=0
        LcdCloseCoursor();//关闭光标闪烁
    }
    else if (keycode == 0x0D) //回车键,进行设置时间和恢复时钟运行的状态的切换
    {
        if (setTimeIndex == 0) //正常运行状态,开始切换到设置状态
        {
            setTimeIndex = 1;
            LcdRefreshSetCoursor();//刷新光标闪烁位置并让光标闪烁
        }
        else
        {
            //写入设置的时间值,并切换到正常运行状态
            SetTimeToDS1302(&timeBuf);
            GetTimeFromDS1302(&timeBuf);
            setTimeIndex = 0;
            LcdCloseCoursor();//关闭光标闪烁
        }
    }
}
/*setTimeIndex++,实现设置光标闪烁的右移*/
void LcdCoursorRight()
{
    if (setTimeIndex < 12)
        setTimeIndex++;//光标设置索引++
    else
        setTimeIndex = 1;       
    LcdRefreshSetCoursor();
}
/*setTimeIndex--,实现设置光标闪烁的左移*/
void LcdCoursorLeft()
{
    if (setTimeIndex > 1)
        setTimeIndex--;//光标设置索引--
    else
        setTimeIndex = 12;
    LcdRefreshSetCoursor();
}
/*设置BCD码的高位数字*/
unsigned char SetBCDHighByte(unsigned char bcd, unsigned char keyNum)
{
    bcd &= 0x0F;//清零高位
    bcd |= (keyNum << 4); //keyNum移至高位
   
    return (bcd);
}
/*设置BCD码的低位数字*/
unsigned char SetBCDLowByte(unsigned char bcd, unsigned char keyNum)
{
    bcd &= 0xF0;//低位清零
    bcd |= keyNum; //将keyNum数字放到低位
   
    return (bcd); //返回改变后的bcd字节数据
}
/*通过按键值设置时间值(设置时间值的高位和低位数字)*/
void SetTimeBcdByte(unsigned char keyNum) //传递按键的数值
{
    switch (setTimeIndex)
    {
        case 1: timeBuf.year  = SetBCDHighByte(timeBuf.year, keyNum); break;//设置年的高位BCD字节
        case 2: timeBuf.year  = SetBCDLowByte(timeBuf.year, keyNum); break; //设置年的低位BCD字节
        case 3: timeBuf.month = SetBCDHighByte(timeBuf.month, keyNum); break;
        case 4: timeBuf.month = SetBCDLowByte(timeBuf.month, keyNum); break;
        case 5: timeBuf.day   = SetBCDHighByte(timeBuf.day, keyNum); break;
        case 6: timeBuf.day   = SetBCDLowByte(timeBuf.day, keyNum); break;
        case 7: timeBuf.hour  = SetBCDHighByte(timeBuf.hour, keyNum); break;
        case 8: timeBuf.hour  = SetBCDLowByte(timeBuf.hour, keyNum); break;
        case 9: timeBuf.min   = SetBCDHighByte(timeBuf.min, keyNum); break;
        case 10: timeBuf.min  = SetBCDLowByte(timeBuf.min, keyNum); break;
        case 11: timeBuf.sec  = SetBCDHighByte(timeBuf.sec, keyNum); break;
        case 12: timeBuf.sec  = SetBCDLowByte(timeBuf.sec, keyNum); break;
    }
    RefreshLcdShowTime();   //刷新时间值显示
    LcdCoursorRight();      //光标闪烁右移
    LcdRefreshSetCoursor(); //刷新光标闪烁
}

 
关闭窗口

相关文章