51单片机——多功能电子钟

来源:互联网 发布:软件测试员培训班 编辑:程序博客网 时间:2024/06/11 15:09

单片机——多功能电子钟


宗旨:技术的学习是有限的,分享的精神是无限的。


实现的功能有:走 时、校时、闹钟、温度、遥控这几个功能。要想实现这几个功能,其中走时所需要的就是时 钟芯片,即 DS1302;时间需要显示给人看,就需要显示器件,我们用到了点阵、数码管、 独立 LED、液晶;再来看校时,校时需要输入器件。

 

注重模块化思想:

// 工程配置头文件config.h:#ifndef _CONFIG_H#define _CONFIG_H/* 通用头文件 */#include <reg52.h>#include <intrins.h>/* 数据类型定义 */typedef signed  char         int8;   // 8位有符号整型数typedef signed  int            int16;  //16位有符号整型数typedef signed  long         int32;  //32位有符号整型数typedef unsigned  char     uint8;  // 8位无符号整型数typedef unsigned  int        uint16; //16位无符号整型数typedef unsigned  long     uint32; //32位无符号整型数/* 全局运行参数定义 */#define SYS_MCLK   (11059200/12)  //系统主时钟频率,即振荡器频率÷12/* IO引脚分配定义 */sbit KEY_IN_1  = P2 ^ 4; //矩阵按键的扫描输入引脚1sbit KEY_IN_2  = P2 ^ 5; //矩阵按键的扫描输入引脚2sbit KEY_IN_3  = P2 ^ 6; //矩阵按键的扫描输入引脚3sbit KEY_IN_4  = P2 ^ 7; //矩阵按键的扫描输入引脚4sbit KEY_OUT_1 = P2 ^ 3; //矩阵按键的扫描输出引脚1sbit KEY_OUT_2 = P2 ^ 2; //矩阵按键的扫描输出引脚2sbit KEY_OUT_3 = P2 ^ 1; //矩阵按键的扫描输出引脚3sbit KEY_OUT_4 = P2 ^ 0; //矩阵按键的扫描输出引脚4sbit ADDR0 = P1 ^ 0; //LED位选译码地址引脚0sbit ADDR1 = P1 ^ 1; //LED位选译码地址引脚1sbit ADDR2 = P1 ^ 2; //LED位选译码地址引脚2sbit ADDR3 = P1 ^ 3; //LED位选译码地址引脚3sbit ENLED = P1 ^ 4; //LED显示部件的总使能引脚#define LCD1602_DB  P0  //1602液晶数据端口sbit LCD1602_RS = P1 ^ 0; //1602液晶指令/数据选择引脚sbit LCD1602_RW = P1 ^ 1; //1602液晶读写引脚sbit LCD1602_E  = P1 ^ 5; //1602液晶使能引脚sbit DS1302_CE = P1 ^ 7; //DS1302片选引脚sbit DS1302_CK = P3 ^ 5; //DS1302通信时钟引脚sbit DS1302_IO = P3 ^ 4; //DS1302通信数据引脚sbit I2C_SCL = P3 ^ 7; //I2C总线时钟引脚sbit I2C_SDA = P3 ^ 6; //I2C总线数据引脚sbit BUZZER = P1 ^ 6; //蜂鸣器控制引脚sbit IO_18B20 = P3 ^ 2; //DS18B20通信引脚sbit IR_INPUT = P3 ^ 3; //红外接收引脚#endif /* _CONFIG_H  */<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
// 头文件Lcd1602.h:#ifndef  _LCD1602_H#define _LCD1602_H#ifndef  _LCD1602_C#endifvoid InitLcd1602();void LcdClearScreen();void LcdOpenCursor();void LcdCloseCursor();void LcdSetCursor(uint8 x, uint8 y);void LcdShowStr(uint8 x, uint8 y, uint8*str);void LcdShowChar(uint8 x, uint8 y, uint8chr);#endif /* _LCD1602_H  */// 实时时钟芯片DS1302驱动模块的头文件DS1302.h:#ifndef  _DS1302_H#define _DS1302_Hstruct sTime    //日期时间结构{    uint16 year; //年    uint8 mon;   //月    uint8 day;   //日    uint8 hour;  //时    uint8 min;   //分    uint8 sec;   //秒    uint8 week;  //星期};#ifndef  _DS1302_C#endifvoid InitDS1302();void GetRealTime(struct sTime *time);void SetRealTime(struct sTime *time);#endif /* _DS1302_H  */
// 温度传感器DS18B20驱动模块的头文件#ifndef  _DS18B20_H#define _DS18B20_H#ifndef  _DS18B20_C#endifbit Start18B20();bit Get18B20Temp(int16 *temp);#endif /* _DS18B20_H */

// 多功能电子钟主要功能文件的头文件Time.h:#ifndef  _TIME_H#define _TIME_H#ifndef   _TIME_C#endifvoid RefreshTime();void RefreshDate(uint8 ops);void RefreshAlarm();void AlarmMonitor();void KeyAction(uint8 keycode);#endif /* _TIME_H */

// 4*4矩阵按键驱动模块的头文件keyboard.h:#ifndef  _KEY_BOARD_H#define _KEY_BOARD_H#ifndef  _KEY_BOARD_C#endifvoid KeyScan();void KeyDriver();#endif /* _KEY_BOARD_H */<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

// 点阵LED、数码管、独立LED和无源蜂鸣器的驱动模块头文件:LedBuzzer.h:#ifndef  _LED_BUZZER_H#define _LED_BUZZER_Hstruct sLedBuff    //LED显示缓冲区结构{    uint8 array[8];   //点阵缓冲区    uint8 number[6];  //数码管缓冲区    uint8 alone;      //独立LED缓冲区};#ifndef  _LED_BUZZER_Cextern bit staBuzzer;extern struct sLedBuff ledBuff;#endifvoid InitLed();void FlowingLight();void ShowLedNumber(uint8 index, uint8num, uint8 point);void ShowLedArray(uint8 *ptr);#endif /* _LED_BUZZER_H */<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

// 主文件的头文件main.h:#ifndef  _MAIN_H#define _MAIN_Henum eStaSystem    //系统运行状态枚举{    E_NORMAL, E_SET_TIME, E_SET_ALARM};#ifndef  _MAIN_Cextern enum eStaSystem staSystem;#endifvoid RefreshTemp(uint8 ops);void ConfigTimer0(uint16 ms);#endif /* _MAIN_H */

//=============================================================================

// Lcd1602.c:#define  _LCD1602_C#include "config.h"#include "Lcd1602.h"uint8 tmpP0;   //暂存P0口的值bit tmpADDR0;  //暂存LED位选译码地址0的值bit tmpADDR1;  //暂存LED位选译码地址1的值/* 暂停LED动态扫描,暂存相关引脚的值 */void LedScanPause(){    ENLED = 1;    tmpP0 = P0;    tmpADDR0 = ADDR0;    tmpADDR1 = ADDR1;}/* 恢复LED动态扫描,恢复相关引脚的值 */void  LedScanContinue(){    ADDR0 = tmpADDR0;    ADDR1 = tmpADDR1;    P0 = tmpP0;    ENLED = 0;}/* 等待液晶准备好 */void  LcdWaitReady(){    uint8 sta;    LCD1602_DB = 0xFF;    LCD1602_RS = 0;    LCD1602_RW = 1;    do    {        LCD1602_E = 1;        sta = LCD1602_DB; //读取状态字        LCD1602_E = 0;    }    while (sta & 0x80);   //bit7等于1表示液晶正忙,重复检测直到其等于0为止}/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值 */void  LcdWriteCmd(uint8 cmd){    LedScanPause();    LcdWaitReady();    LCD1602_RS = 0;    LCD1602_RW = 0;    LCD1602_DB = cmd;    LCD1602_E  = 1;    LCD1602_E  = 0;    LedScanContinue();}/* 向LCD1602液晶写入一字节数据,dat-待写入数据值 */void  LcdWriteDat(uint8 dat){    LedScanPause();    LcdWaitReady();    LCD1602_RS = 1;    LCD1602_RW = 0;    LCD1602_DB = dat;    LCD1602_E  = 1;    LCD1602_E  = 0;    LedScanContinue();}/* 清屏 */void LcdClearScreen(){    LcdWriteCmd(0x01);}/* 打开光标的闪烁效果 */void  LcdOpenCursor(){    LcdWriteCmd(0x0F);}/* 关闭光标显示 */void  LcdCloseCursor(){    LcdWriteCmd(0x0C);}/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */void  LcdSetCursor(uint8 x, uint8 y){    uint8 addr;    if (y == 0)  //由输入的屏幕坐标计算显示RAM的地址    {        addr = 0x00 + x;    //第一行字符地址从0x00起始    }    else    {        addr = 0x40 + x;    //第二行字符地址从0x40起始    }    LcdWriteCmd(addr | 0x80);  //设置RAM地址}/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */void  LcdShowStr(uint8 x, uint8 y, uint8*str){    LcdSetCursor(x, y);   //设置起始地址    while (*str != '\0')  //连续写入字符串数据,直到检测到结束符    {        LcdWriteDat(*str++);    }}/* 在液晶上显示一个字符,(x,y)-对应屏幕上的起始坐标,chr-字符ASCII码 */void  LcdShowChar(uint8 x, uint8 y, uint8chr){    LcdSetCursor(x, y);  //设置起始地址    LcdWriteDat(chr);    //写入ASCII字符}/* 初始化1602液晶 */void  InitLcd1602(){    LcdWriteCmd(0x38);  //16*2显示,5*7点阵,8位数据接口    LcdWriteCmd(0x0C);  //显示器开,光标关闭    LcdWriteCmd(0x06);  //文字不动,地址自动+1    LcdWriteCmd(0x01);  //清屏}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

// 实时时钟芯片DS1302驱动模块DS1302.c:#define  _DS1302_C#include "config.h"#include "DS1302.h"/* 发送一个字节到DS1302通信总线上 */void DS1302ByteWrite(uint8 dat){    uint8 mask;    for (mask = 0x01; mask != 0; mask <<= 1) //低位在前,逐位移出    {        if ((mask & dat) != 0) //首先输出该位数据        {            DS1302_IO = 1;        }        else        {            DS1302_IO = 0;        }        DS1302_CK = 1;       //然后拉高时钟        DS1302_CK = 0;       //再拉低时钟,完成一个位的操作    }    DS1302_IO = 1;           //最后确保释放IO引脚}/* 由DS1302通信总线上读取一个字节 */uint8 DS1302ByteRead(){    uint8 mask;    uint8 dat = 0;    for (mask = 0x01; mask != 0; mask <<= 1) //低位在前,逐位读取    {        if (DS1302_IO != 0)  //首先读取此时的IO引脚,并设置dat中的对应位        {            dat |= mask;        }        DS1302_CK = 1;       //然后拉高时钟        DS1302_CK = 0;       //再拉低时钟,完成一个位的操作    }    return dat;              //最后返回读到的字节数据}/* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */void DS1302SingleWrite(uint8 reg, uint8dat){    DS1302_CE = 1;                  //使能片选信号    DS1302ByteWrite((reg << 1) | 0x80); //发送写寄存器指令    DS1302ByteWrite(dat);           //写入字节数据    DS1302_CE = 0;                  //除能片选信号}/* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */uint8 DS1302SingleRead(uint8 reg){    uint8 dat;    DS1302_CE = 1;                  //使能片选信号    DS1302ByteWrite((reg << 1) | 0x81); //发送读寄存器指令    dat = DS1302ByteRead();         //读取字节数据    DS1302_CE = 0;                  //除能片选信号    return dat;}/* 用突发模式连续写入8个寄存器数据,dat-待写入数据指针 */void DS1302BurstWrite(uint8 *dat){    uint8 i;    DS1302_CE = 1;    DS1302ByteWrite(0xBE);  //发送突发写寄存器指令    for (i = 0; i < 8; i++) //连续写入8字节数据    {        DS1302ByteWrite(dat[i]);    }    DS1302_CE = 0;}/* 用突发模式连续读取8个寄存器的数据,dat-读取数据的接收指针 */void DS1302BurstRead(uint8 *dat){    uint8 i;    DS1302_CE = 1;    DS1302ByteWrite(0xBF);  //发送突发读寄存器指令    for (i = 0; i < 8; i++) //连续读取8个字节    {        dat[i] = DS1302ByteRead();    }    DS1302_CE = 0;}/* 获取实时时间,即读取DS1302当前时间并转换为时间结构体格式 */void GetRealTime(struct sTime *time){    uint8 buf[8];    DS1302BurstRead(buf);    time->year = buf[6] + 0x2000;    time->mon  = buf[4];    time->day  = buf[3];    time->hour = buf[2];    time->min  = buf[1];    time->sec  = buf[0];    time->week = buf[5];}/* 设定实时时间,时间结构体格式的设定时间转换为数组并写入DS1302 */void SetRealTime(struct sTime *time){    uint8 buf[8];    buf[7] = 0;    buf[6] = time->year;    buf[5] = time->week;    buf[4] = time->mon;    buf[3] = time->day;    buf[2] = time->hour;    buf[1] = time->min;    buf[0] = time->sec;    DS1302BurstWrite(buf);}/* DS1302初始化,如发生掉电则重新设置初始时间 */void InitDS1302(){    uint8 dat;    struct sTime code InitTime[] =   //默认初始值:2014-01-0112:30:00 星期3    {        0x2014, 0x01, 0x01, 0x12, 0x30, 0x00, 0x03    };    DS1302_CE = 0;  //初始化DS1302通信引脚    DS1302_CK = 0;    dat = DS1302SingleRead(0);  //读取秒寄存器    if ((dat & 0x80) != 0)      //由秒寄存器最高位CH的值判断DS1302是否已停止    {        DS1302SingleWrite(7, 0x00);  //撤销写保护以允许写入数据        SetRealTime(&InitTime);     //设置DS1302为默认的初始时间    }}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

// 温度传感器DS18B20驱动模块DS18B20.c:DS18B20.cs#define  _DS18B20_C#include "config.h"#include "DS18B20.h"/* 软件延时函数,延时时间(t*10)us */void DelayX10us(uint8 t){    do    {        _nop_();        _nop_();        _nop_();        _nop_();        _nop_();        _nop_();        _nop_();        _nop_();    }    while (--t);}/* 复位总线,获取存在脉冲,以启动一次读写操作 */bit Get18B20Ack(){    bit ack;    EA = 0;   //禁止总中断    IO_18B20 = 0;     //产生500us复位脉冲    DelayX10us(50);    IO_18B20 = 1;    DelayX10us(6);    //延时60us    ack = IO_18B20;   //读取存在脉冲    while(!IO_18B20); //等待存在脉冲结束    EA = 1;   //重新使能总中断    return ack;}/* 向DS18B20写入一个字节,dat-待写入字节 */void Write18B20(uint8 dat){    uint8 mask;    EA = 0;   //禁止总中断    for (mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次移出8个bit    {        IO_18B20 = 0;         //产生2us低电平脉冲        _nop_();        _nop_();        if ((mask & dat) == 0) //输出该bit值        {            IO_18B20 = 0;        }        else        {            IO_18B20 = 1;        }        DelayX10us(6);        //延时60us        IO_18B20 = 1;         //拉高通信引脚    }    EA = 1;   //重新使能总中断}/* 从DS18B20读取一个字节,返回值-读到的字节 */uint8 Read18B20(){    uint8 dat;    uint8 mask;    EA = 0;   //禁止总中断    for (mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次采集8个bit    {        IO_18B20 = 0;         //产生2us低电平脉冲        _nop_();        _nop_();        IO_18B20 = 1;         //结束低电平脉冲,等待18B20输出数据        _nop_();              //延时2us        _nop_();        if (!IO_18B20)        //读取通信引脚上的值        {            dat &= ~mask;        }        else        {            dat |= mask;        }        DelayX10us(6);        //再延时60us    }    EA = 1;   //重新使能总中断    return dat;}/* 启动一次18B20温度转换,返回值-表示是否启动成功 */bit Start18B20(){    bit ack;    ack = Get18B20Ack();   //执行总线复位,并获取18B20应答    if (ack == 0)          //如18B20正确应答,则启动一次转换    {        Write18B20(0xCC);  //跳过ROM操作        Write18B20(0x44);  //启动一次温度转换    }    return ~ack;   //ack==0表示操作成功,所以返回值对其取反}/* 读取DS18B20转换的温度值,返回值-表示是否读取成功 */bit Get18B20Temp(int16 *temp){    bit ack;    uint8 LSB, MSB; //16bit温度值的低字节和高字节    ack = Get18B20Ack();    //执行总线复位,并获取18B20应答    if (ack == 0)           //如18B20正确应答,则读取温度值    {        Write18B20(0xCC);   //跳过ROM操作        Write18B20(0xBE);   //发送读命令        LSB = Read18B20();  //读温度值的低字节        MSB = Read18B20();  //读温度值的高字节        *temp = ((int16)MSB << 8) + LSB; //合成为16bit整型数    }    return~ack;  //ack==0表示操作应答,所以返回值为其取反值}

// 多功能电子钟主要功能文件Time.c:#define  _TIME_C#include "config.h"#include "DS1302.h"#include "LedBuzzer.h"#include "Lcd1602.h"#include "Time.h"#include "main.h"uint8 code WeekMod[] =    //星期X字符图片表{    0xFF, 0x99, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7,  //星期日(红心)    0xEF, 0xE7, 0xE3, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3,  //星期1    0xC3, 0x81, 0x9D, 0x87, 0xC3, 0xF9, 0xC1, 0x81,  //星期2    0xC3, 0x81, 0x9D, 0xC7, 0xC7, 0x9D, 0x81, 0xC3,  //星期3    0xCF, 0xC7, 0xC3, 0xC9, 0xC9, 0x81, 0xCF, 0xCF,  //星期4    0x81, 0xC1, 0xF9, 0xC3, 0x87, 0x9D, 0x81, 0xC3,  //星期5    0xC3, 0x81, 0xF9, 0xC3, 0x81, 0x99, 0x81, 0xC3,  //星期6};bit staMute = 0;  //静音标志位uint8 AlarmHour = 0x07;  //闹钟时间的小时数uint8 AlarmMin  = 0x30; //闹钟时间的分钟数struct sTime CurTime;    //当前日期时间uint8 SetIndex = 0;  //设置位索引uint8 pdata SetAlarmHour;    //闹钟小时数设置缓冲uint8 pdata SetAlarmMin;     //闹钟分钟数设置缓冲struct sTime pdata SetTime;  //日期时间设置缓冲区/* 获取当前日期时间,并刷新时间和星期的显示 */void RefreshTime(){    GetRealTime(&CurTime);                  //获取当前日期时间    ShowLedNumber(5, CurTime.hour >> 4, 0); //时    ShowLedNumber(4, CurTime.hour & 0xF, 1);    ShowLedNumber(3, CurTime.min >> 4, 0); //分    ShowLedNumber(2, CurTime.min & 0xF, 1);    ShowLedNumber(1, CurTime.sec >> 4, 0); //秒    ShowLedNumber(0, CurTime.sec & 0xF, 0);    ShowLedArray(WeekMod + CurTime.week * 8); //星期}/* 日期刷新函数,ops-刷新选项:为0时只当日期变化才刷新,非0则立即刷新 */void RefreshDate(uint8 ops){    uint8 pdata str[12];    static uint8 backup = 0;    if ((backup != CurTime.day) || (ops != 0))    {        str[0] = ((CurTime.year >> 12) & 0xF) + '0'; //4位数年份        str[1] = ((CurTime.year >> 8) & 0xF) + '0';        str[2] = ((CurTime.year >> 4) & 0xF) + '0';        str[3] = (CurTime.year & 0xF) + '0';        str[4] = '-';                       //分隔符        str[5] = (CurTime.mon >> 4) + '0';   //月份        str[6] = (CurTime.mon & 0xF) + '0';        str[7] = '-';                       //分隔符        str[8] = (CurTime.day >> 4) + '0';   //日期        str[9] = (CurTime.day & 0xF) + '0';        str[10] = '\0';         //字符串结束符        LcdShowStr(0, 0, str);  //显示到液晶上        backup = CurTime.day;   //刷新上次日期值    }}/* 刷新闹钟时间的显示 */void RefreshAlarm(){    uint8 pdata str[8];    LcdShowStr(0, 1, "Alarm at ");     //显示提示标题    str[0] = (AlarmHour >> 4) + '0';  //闹钟小时数    str[1] = (AlarmHour & 0xF) + '0';    str[2] = ':';                      //分隔符    str[3] = (AlarmMin >> 4) + '0';   //闹钟分钟数    str[4] = (AlarmMin & 0xF) + '0';    str[5] = '\0';                    //字符串结束符    LcdShowStr(9, 1, str);            //显示到液晶上}/* 闹钟监控函数,抵达设定的闹钟时间时执行闹铃 */void AlarmMonitor(){    if ((CurTime.hour == AlarmHour) && (CurTime.min == AlarmMin)) //检查时间匹配    {        if (!staMute)  //检查是否静音        {            staBuzzer = ~staBuzzer;    //实现蜂鸣器断续鸣叫        }        else        {            staBuzzer = 0;        }    }    else    {        staMute = 0;        staBuzzer = 0;    }}/* 将设置时间及标题提示显示到液晶上 */void ShowSetTime(){    uint8 pdata str[18];    str[0]  = ((SetTime.year >> 4) & 0xF) + '0'; //2位数年份    str[1]  = (SetTime.year & 0xF) + '0';    str[2]  = '-';    str[3]  = (SetTime.mon >> 4) + '0';  //月份    str[4]  = (SetTime.mon & 0xF) + '0';    str[5]  = '-';    str[6]  = (SetTime.day >> 4) + '0';  //日期    str[7]  = (SetTime.day & 0xF) + '0';    str[8]  = '-';    str[9]  = (SetTime.week & 0xF) + '0'; //星期    str[10] = ' ';    str[11] = (SetTime.hour >> 4) + '0';  //小时    str[12] = (SetTime.hour & 0xF) + '0';    str[13] = ':';    str[14] = (SetTime.min >> 4) + '0';   //分钟    str[15] = (SetTime.min & 0xF) + '0';    str[16] = '\0';    LcdShowStr(0, 0, "Set Date Time");  //显示提示标题    LcdShowStr(0, 1, str);             //显示设置时间值}/* 将设置闹钟及标题提示显示到液晶上 */void ShowSetAlarm(){    uint8 pdata str[8];    str[0] = (SetAlarmHour >> 4) + '0';   //小时    str[1] = (SetAlarmHour & 0xF) + '0';    str[2] = ':';    str[3] = (SetAlarmMin >> 4) + '0';    //分钟    str[4] = (SetAlarmMin & 0xF) + '0';    str[5] = '\0';    LcdShowStr(0, 0, "Set Alarm"); //显示提示标题    LcdShowStr(0, 1, str);          //显示设定闹钟值}/* 取消当前设置,返回正常运行状态 */void CancelCurSet(){    staSystem = E_NORMAL;    LcdCloseCursor();  //关闭光标    LcdClearScreen();  //液晶清屏    RefreshTime();   //刷新当前时间    RefreshDate(1);  //立即刷新日期显示    RefreshTemp(1);  //立即刷新温度显示    RefreshAlarm();  //闹钟设定值显示}/* 时间或闹钟设置时,设置位右移一位,到头后折回 */void SetRightShift(){    if (staSystem == E_SET_TIME)    {        switch (SetIndex)        {        case 0:            SetIndex = 1;            LcdSetCursor(1, 1);            break;        case 1:            SetIndex = 2;            LcdSetCursor(3, 1);            break;        case 2:            SetIndex = 3;            LcdSetCursor(4, 1);            break;        case 3:            SetIndex = 4;            LcdSetCursor(6, 1);            break;        case 4:            SetIndex = 5;            LcdSetCursor(7, 1);            break;        case 5:            SetIndex = 6;            LcdSetCursor(9, 1);            break;        case 6:            SetIndex = 7;            LcdSetCursor(11, 1);            break;        case 7:            SetIndex = 8;            LcdSetCursor(12, 1);            break;        case 8:            SetIndex = 9;            LcdSetCursor(14, 1);            break;        case 9:            SetIndex = 10;            LcdSetCursor(15, 1);            break;        default:            SetIndex = 0;            LcdSetCursor(0, 1);            break;        }    }    else if (staSystem == E_SET_ALARM)    {        switch (SetIndex)        {        case 0:            SetIndex = 1;            LcdSetCursor(1, 1);            break;        case 1:            SetIndex = 2;            LcdSetCursor(3, 1);            break;        case 2:            SetIndex = 3;            LcdSetCursor(4, 1);            break;        default:            SetIndex = 0;            LcdSetCursor(0, 1);            break;        }    }}/* 时间或闹钟设置时,设置位左移一位,到头后折回 */void SetLeftShift(){    if (staSystem == E_SET_TIME)    {        switch (SetIndex)        {        case 0:            SetIndex = 10;            LcdSetCursor(15, 1);            break;        case 1:            SetIndex = 0;            LcdSetCursor(0, 1);            break;        case 2:            SetIndex = 1;            LcdSetCursor(1, 1);            break;        case 3:            SetIndex = 2;            LcdSetCursor(3, 1);            break;        case 4:            SetIndex = 3;            LcdSetCursor(4, 1);            break;        case 5:            SetIndex = 4;            LcdSetCursor(6, 1);            break;        case 6:            SetIndex = 5;            LcdSetCursor(7, 1);            break;        case 7:            SetIndex = 6;            LcdSetCursor(9, 1);            break;        case 8:            SetIndex = 7;            LcdSetCursor(11, 1);            break;        case 9:            SetIndex = 8;            LcdSetCursor(12, 1);            break;        default:            SetIndex = 9;            LcdSetCursor(14, 1);            break;        }    }    else if (staSystem == E_SET_ALARM)    {        switch (SetIndex)        {        case 0:            SetIndex = 3;            LcdSetCursor(4, 1);            break;        case 1:            SetIndex = 0;            LcdSetCursor(0, 1);            break;        case 2:            SetIndex = 1;            LcdSetCursor(1, 1);            break;        default:            SetIndex = 2;            LcdSetCursor(3, 1);            break;        }    }}/* 输入设置数字,修改对应的设置位,并显示该数字,ascii-输入数字的ASCII码 */void InputSetNumber(uint8 ascii){    uint8 num;    num = ascii - '0';    if (num <= 9)  //只响应0~9的数字    {        if (staSystem == E_SET_TIME)        {            switch (SetIndex)            {            case 0:                SetTime.year = (SetTime.year & 0xFF0F) | (num << 4);                LcdShowChar(0, 1, ascii);                break;     //年份高位数字            case 1:                SetTime.year = (SetTime.year & 0xFFF0) | (num);                LcdShowChar(1, 1, ascii);                break;      //年份低位数字            case 2:                SetTime.mon = (SetTime.mon & 0x0F) | (num << 4);                LcdShowChar(3, 1, ascii);                break;      //月份高位数字            case 3:                SetTime.mon = (SetTime.mon & 0xF0) | (num);                LcdShowChar(4, 1, ascii);                break;      //月份低位数字            case 4:                SetTime.day = (SetTime.day & 0x0F) | (num << 4);                LcdShowChar(6, 1, ascii);                break;      //日期高位数字            case 5:                SetTime.day = (SetTime.day & 0xF0) | (num);                LcdShowChar(7, 1, ascii);                break;      //日期低位数字            case 6:                SetTime.week = (SetTime.week & 0xF0) | (num);                LcdShowChar(9, 1, ascii);                break;      //星期数字            case 7:                SetTime.hour = (SetTime.hour & 0x0F) | (num << 4);                LcdShowChar(11, 1, ascii);                break;      //小时高位数字            case 8:                SetTime.hour = (SetTime.hour & 0xF0) | (num);                LcdShowChar(12, 1, ascii);                break;      //小时低位数字            case 9:                SetTime.min = (SetTime.min & 0x0F) | (num << 4);                LcdShowChar(14, 1, ascii);                break;      //分钟高位数字            default:                SetTime.min = (SetTime.min & 0xF0) | (num);                LcdShowChar(15, 1, ascii);                break;      //分钟低位数字            }            SetRightShift();  //完成该位设置后自动右移        }        else if (staSystem == E_SET_ALARM)        {            switch (SetIndex)            {            case 0:                SetAlarmHour = (SetAlarmHour & 0x0F) | (num << 4);                LcdShowChar(0, 1, ascii);                break;      //小时高位数字            case 1:                SetAlarmHour = (SetAlarmHour & 0xF0) | (num);                LcdShowChar(1, 1, ascii);                break;      //小时低位数字            case 2:                SetAlarmMin = (SetAlarmMin & 0x0F) | (num << 4);                LcdShowChar(3, 1, ascii);                break;      //分钟高位数字            default:                SetAlarmMin = (SetAlarmMin & 0xF0) | (num);                LcdShowChar(4, 1, ascii);                break;      //分钟低位数字            }            SetRightShift();  //完成该位设置后自动右移        }    }}/* 切换系统运行状态 */void SwitchSystemSta(){    if (staSystem == E_NORMAL)  //正常运行切换到时间设置    {        staSystem = E_SET_TIME;        SetTime.year = CurTime.year;  //当前时间拷贝到时间设置缓冲区中        SetTime.mon  = CurTime.mon;        SetTime.day  = CurTime.day;        SetTime.hour = CurTime.hour;        SetTime.min  = CurTime.min;        SetTime.sec  = CurTime.sec;        SetTime.week = CurTime.week;        LcdClearScreen();  //液晶清屏        ShowSetTime();     //显示设置时间        SetIndex = 255;    //与接下来的右移一起将光标设在最左边的位置上        SetRightShift();        LcdOpenCursor();   //开启光标    }    else if (staSystem == E_SET_TIME)  //时间设置切换到闹钟设置    {        staSystem = E_SET_ALARM;        SetTime.sec = 0;          //秒清零,即当设置时间后从0秒开始走时        SetRealTime(&SetTime);    //设定时间写入实时时钟        SetAlarmHour = AlarmHour; //当前闹钟值拷贝到设置缓冲区        SetAlarmMin  = AlarmMin;        LcdClearScreen();  //液晶清屏        ShowSetAlarm();    //显示设置闹钟        SetIndex = 255;    //与接下来的右移一起将光标设在最左边的位置上        SetRightShift();    }    else  //闹钟设置切换会正常运行    {        staSystem = E_NORMAL;        AlarmHour = SetAlarmHour;  //设定的闹钟值写入闹钟时间        AlarmMin  = SetAlarmMin;        LcdCloseCursor();  //关闭光标        LcdClearScreen();  //液晶清屏        RefreshTime();   //刷新当前时间        RefreshDate(1);  //立即刷新日期显示        RefreshTemp(1);  //立即刷新温度显示        RefreshAlarm();  //闹钟设定值显示    }}/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */void KeyAction(uint8 keycode){    if  ((keycode >= '0') && (keycode <= '9')) //数字键输入当前位设定值    {        InputSetNumber(keycode);    }    else if (keycode == 0x25)  //向左键,向左切换设置位    {        SetLeftShift();    }    else if (keycode == 0x27)  //向右键,向右切换设置位    {        SetRightShift();    }    else if (keycode == 0x0D)  //回车键,切换运行状态/保存设置    {        SwitchSystemSta();    }    else if (keycode == 0x1B)  //Esc键,静音/取消当前设置    {        if (staSystem == E_NORMAL) //处于正常运行状态时闹铃静音        {            staMute = 1;        }        else                       //处于设置状态时退出设置        {            CancelCurSet();        }    }}
// 4*4矩阵按键驱动模块keyboard.c:#define  _KEY_BOARD_C#include "config.h"#include "keyboard.h"#include "Time.h"const uint8 code KeyCodeMap[4][4] =   //矩阵按键到标准键码的映射表{    { '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键、  回车键、 向右键};uint8 pdata KeySta[4][4] =    //全部矩阵按键的当前状态{    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}, {1, 1, 1, 1}};/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */void KeyDriver(){    uint8 i, j;    static uint8 pdata backup[4][4] =   //按键值备份,保存前一次的值    {        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}, {1, 1, 1, 1}    };    for (i = 0; i < 4; i++) //循环检测4*4的矩阵按键    {        for (j = 0; j < 4; j++)        {            if (backup[i][j] != KeySta[i][j])   //检测按键动作            {                if (backup[i][j] != 0)           //按键按下时执行动作                {                    KeyAction(KeyCodeMap[i][j]); //调用按键动作函数                }                backup[i][j] = KeySta[i][j];    //刷新前一次的备份值            }        }    }}/* 按键扫描函数,需在定时中断中调用,推荐调用间隔1ms */void KeyScan(){    uint8 i;    static uint8 keyout = 0;   //矩阵按键扫描输出索引    static uint8 keybuf[4][4] =    //矩阵按键扫描缓冲区    {        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}    };    //将一行的4个按键值移入缓冲区    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;    //消抖后更新按键状态    for (i = 0; i < 4; i++) //每行4个按键,所以循环4次    {        if ((keybuf[keyout][i] & 0x0F) == 0x00)        {            //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下            KeySta[keyout][i] = 0;        }        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)        {            //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起            KeySta[keyout][i] = 1;        }    }    //执行下一次的扫描输出    keyout++;        //输出索引递增    keyout &= 0x03;  //索引值加到4即归零    switch (keyout)  //根据索引值,释放当前输出引脚,拉低下次的输出引脚    {    case 0:        KEY_OUT_4 = 1;        KEY_OUT_1 = 0;        break;    case 1:        KEY_OUT_1 = 1;        KEY_OUT_2 = 0;        break;    case 2:        KEY_OUT_2 = 1;        KEY_OUT_3 = 0;        break;    case 3:        KEY_OUT_3 = 1;        KEY_OUT_4 = 0;        break;    default:        break;    }}
// 点阵LED、数码管、独立LED和无源蜂鸣器的驱动模块LedBuzzer.c:#define  _LED_BUZZER_C#include "config.h"#include "LedBuzzer.h"uint8 code LedChar[] =    //数码管显示字符转换表{    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};bit staBuzzer = 0; //蜂鸣器状态控制位,1-鸣叫、0-关闭struct sLedBuff ledBuff; //LED显示缓冲区,默认初值全0,正好达到上电全亮的效果/* LED初始化函数,初始化IO、配置定时器 */void InitLed(){    //初始化IO口    P0 = 0xFF;    ENLED = 0;    //配置T2作为动态扫描定时    T2CON = 0x00;  //配置T2工作在16位自动重载定时器模式    RCAP2H = ((65536 - SYS_MCLK / 1500) >> 8); //配置重载值,每秒产生1500次中断,    RCAP2L = (65536 - SYS_MCLK / 1500);  //以使刷新率达到100Hz无闪烁的效果    TH2 = RCAP2H;  //设置初值等于重载值    TL2 = RCAP2L;    ET2 = 1;       //使能T2中断    PT2 = 1;       //设置T2中断为高优先级    TR2 = 1;       //启动T2}/* 流水灯实现函数,间隔调用实现流动效果 */void FlowingLight(){    static uint8 i = 0;    const uint8 code tab[] =    //流动表    {        0x7F, 0x3F, 0x1F, 0x0F, 0x87, 0xC3, 0xE1, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF    };    ledBuff.alone = tab[i];   //表中对应值送到独立LED的显示缓冲区    if (i < (sizeof(tab) - 1)) //索引递增循环,遍历整个流动表    {        i++;    }    else    {        i = 0;    }}/* 数码管上显示一位数字,index-数码管位索引(从右到左对应0~5),**    num-待显示的数字,point-代表是否显示此位上的小数点 */void ShowLedNumber(uint8 index, uint8num, uint8 point){    ledBuff.number[index] = LedChar[num]; //输入数字转换为数码管字符0~F    if (point != 0)    {        ledBuff.number[index] &= 0x7F; //point不为0时点亮当前位的小数点    }}/* 点阵上显示一帧图片,ptr-待显示图片指针 */void ShowLedArray(uint8 *ptr){    uint8 i;    for (i = 0; i < sizeof(ledBuff.array); i++)    {        ledBuff.array[i] = *ptr++;    }}/* T2中断服务函数,LED动态扫描、蜂鸣器控制 */void InterruptTimer2() interrupt 5{    static uint8 i = 0;  //LED位选索引    TF2 = 0;  //清零T2中断标志    //全部LED动态扫描显示    if (ENLED == 0)  //LED使能时才进行动态扫描    {        P0 = 0xFF;                      //关闭所有段选位,显示消隐        P1 = (P1 & 0xF0) | i;           //位选索引值赋值到P1口低4位        P0 = *((uint8 data*)&ledBuff + i); //缓冲区中索引位置的数据送到P0口        if (i < (sizeof(ledBuff) - 1))  //索引递增循环,遍历整个缓冲区        {            i++;        }        else        {            i = 0;        }    }    //由蜂鸣器状态位控制蜂鸣器    if (staBuzzer == 1)    {        BUZZER = ~BUZZER;    //蜂鸣器鸣叫    }    else    { BUZZER = 1; }        //蜂鸣器静音}

// 多功能电子钟工程主文件main.c:#define  _MAIN_C#include "config.h"#include "Lcd1602.h"#include "LedBuzzer.h"#include "keyboard.h"#include "DS1302.h"#include "DS18B20.h"#include "Infrared.h"#include "Time.h"#include "main.h"bit flag2s = 0;    //2s定时标志位bit flag200ms = 0; //200ms定时标志uint8 T0RH = 0;    //T0重载值的高字节uint8 T0RL = 0;    //T0重载值的低字节enum eStaSystem staSystem = E_NORMAL;  //系统运行状态void main(){    EA = 1;           //开总中断    ConfigTimer0(1);  //配置T0定时1ms    InitLed();        //初始化LED模块    InitDS1302();     //初始化实时时钟模块    InitLcd1602();    //初始化液晶模块    Start18B20();     //启动首次温度转换    while (!flag2s);  //上电后延时2秒    flag2s = 0;    RefreshTime();    //刷新当前时间    RefreshDate(1);   //立即刷新日期显示    RefreshTemp(1);   //立即刷新温度显示    RefreshAlarm();   //闹钟设定值显示    while (1)  //进入主循环    {        KeyDriver();      //执行按键驱动        if (flag200ms)    //每隔200ms执行以下分支        {            flag200ms = 0;            FlowingLight();  //流水灯效果实现            RefreshTime();   //刷新当前时间            AlarmMonitor();  //监控闹钟            if (staSystem == E_NORMAL)  //正常运行时刷新日期显示            {                RefreshDate(0);            }        }        if (flag2s)  //每隔2s执行以下分支        {            flag2s = 0;            if (staSystem == E_NORMAL)  //正常运行时刷新温度显示            {                RefreshTemp(0);            }        }    }}/* 温度刷新函数,读取当前温度并根据需要刷新液晶显示,** ops-刷新选项:为0时只当温度变化才刷新,非0则立即刷新 */void RefreshTemp(uint8 ops){    int16 temp;    uint8 pdata str[8];    static int16 backup = 0;    Get18B20Temp(&temp); //获取当前温度值    Start18B20();        //启动下一次转换    temp >>= 4;          //舍弃4bit小数位    if ((backup != temp) || (ops != 0)) //按需要刷新液晶显示    {        str[0] = (temp / 10) + '0'; //十位转为ASCII码        str[1] = (temp % 10) + '0'; //个位转为ASCII码        str[2] = '\'';             //用'C代替℃        str[3] = 'C';        str[4] = '\0';             //字符串结束符        LcdShowStr(12, 0, str);    //显示到液晶上        backup = temp;             //刷新上次温度值    }}/* 配置并启动T0,ms-T0定时时间 */void ConfigTimer0(uint16 ms){    uint32 tmp;    tmp = (SYS_MCLK * ms) / 1000; //计算所需的计数值    tmp = 65536 - tmp;        //计算定时器重载值    tmp = tmp + 33;           //补偿中断响应延时造成的误差    T0RH = (uint8)(tmp >> 8); //定时器重载值拆分为高低字节    T0RL = (uint8)tmp;    TMOD &= 0xF0;   //清零T0的控制位    TMOD |= 0x01;   //配置T0为模式1    TH0 = T0RH;     //加载T0重载值    TL0 = T0RL;    ET0 = 1;        //使能T0中断    TR0 = 1;        //启动T0}/* T0中断服务函数,实现系统定时和按键扫描 */void InterruptTimer0() interrupt 1{    static uint8 tmr2s = 0;    static uint8 tmr200ms = 0;    TH0 = T0RH;  //重新加载重载值    TL0 = T0RL;    tmr200ms++;  //定时200ms    if (tmr200ms >= 200)    {        tmr200ms = 0;        flag200ms = 1;        tmr2s++;  //定时2s        if (tmr2s >= 10)        {            tmr2s = 0;            flag2s = 1;        }    }    KeyScan();   //执行按键扫描}


0 0
原创粉丝点击