51单片机.之多功能闹钟编程

1、单片机例子,多功能电子闹钟

在这里插入图片描述

在这里插入图片描述

例子来自 金沙滩工作室,单片机开发版出售商,《手把手教你学51单片机(C语言版)》
配套 KST-51 单片机开发板 示例源代码
获取更多资料请访问:http://www.kingst.org
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1、 文件名:config.h

* 描  述:第19章 多功能电子钟 工程配置头文件

#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;  //矩阵按键的扫描输入引脚1
sbit KEY_IN_2  = P2^5;  //矩阵按键的扫描输入引脚2
sbit KEY_IN_3  = P2^6;  //矩阵按键的扫描输入引脚3
sbit KEY_IN_4  = P2^7;  //矩阵按键的扫描输入引脚4
sbit KEY_OUT_1 = P2^3;  //矩阵按键的扫描输出引脚1
sbit KEY_OUT_2 = P2^2;  //矩阵按键的扫描输出引脚2
sbit KEY_OUT_3 = P2^1;  //矩阵按键的扫描输出引脚3
sbit KEY_OUT_4 = P2^0;  //矩阵按键的扫描输出引脚4

sbit ADDR0 = P1^0;  //LED位选译码地址引脚0
sbit ADDR1 = P1^1;  //LED位选译码地址引脚1
sbit ADDR2 = P1^2;  //LED位选译码地址引脚2
sbit ADDR3 = P1^3;  //LED位选译码地址引脚3
sbit 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

1、文件名:Lcd1602.h

* 描  述:1602字符液晶驱动模块的头文件

#ifndef _LCD1602_H
#define _LCD1602_H

#ifndef _LCD1602_C

#endif

void 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, uint8 chr);

#endif

2、文件名:Lcd1602.c

* 备  注:因液晶的控制与LED显示共用P0口和P1口的部分资源,所以本例中在访问液晶时
*         都采取措施避免对P0和P1口线的访问冲突。

#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, uint8 chr)
{
    LcdSetCursor(x, y);  //设置起始地址
    LcdWriteDat(chr);    //写入ASCII字符
}
/* 初始化1602液晶 */
void InitLcd1602()
{
    LcdWriteCmd(0x38);  //16*2显示,5*7点阵,8位数据接口
    LcdWriteCmd(0x0C);  //显示器开,光标关闭
    LcdWriteCmd(0x06);  //文字不动,地址自动+1
    LcdWriteCmd(0x01);  //清屏
}

3、文件名:keyboard.h
描 述:4*4矩阵按键驱动模块的头文件

#ifndef _KEY_BOARD_H
#define _KEY_BOARD_H

#ifndef _KEY_BOARD_C

#endif

void KeyScan();
void KeyDriver();

#endif

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;
    }
}

5、* 文件名:DS1302.h

* 描  述:实时时钟芯片DS1302驱动模块的头文件

#ifndef _DS1302_H
#define _DS1302_H

struct sTime {  //日期时间结构
    uint16 year; //年
    uint8 mon;   //月
    uint8 day;   //日
    uint8 hour;  //时
    uint8 min;   //分
    uint8 sec;   //秒
    uint8 week;  //星期
};

#ifndef _DS1302_C

#endif

void InitDS1302();
void GetRealTime(struct sTime *time);
void SetRealTime(struct sTime *time);

#endif

6、* 文件名:DS1302.c

* 描  述:实时时钟芯片DS1302驱动模块
#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, uint8 dat)
{
    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-01 12: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为默认的初始时间
    }
}

7、* 文件名:DS18B20.h

* 描  述:温度传感器DS18B20驱动模块的头文件

#ifndef _DS18B20_H
#define _DS18B20_H
#ifndef _DS18B20_C
#endif

bit Start18B20();
bit Get18B20Temp(int16 *temp);

#endif

8、* 文件名:DS1302.c

* 描  述:实时时钟芯片DS1302驱动模块
#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, uint8 dat)
{
    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-01 12: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为默认的初始时间
    }
}

9、* 文件名:Infrared.h

* 描  述:采用NEC编码协议的红外遥控驱动模块的头文件

#ifndef _INFRARED_H
#define _INFRARED_H


#ifndef _INFRARED_C

#endif

void InitInfrared();
void InfraredDriver();

#endif

10、* 文件名:Infrared.c

* 描  述:采用NEC编码协议的红外遥控驱动模块
* 备  注:不同遥控器的键码值可能不同,如需确认每个键的键码值可使用lesson16_1的
*         示例程序(数码管右端显示的数值即是键码值,十六进制格式显示)。

#define  _INFRARED_C
#include "config.h"
#include "Infrared.h"
#include "Time.h"

const uint8 code IrCodeMap[][2] = {  //红外键码到标准PC键码的映射表
    {0x45,0x00}, {0x46,0x00}, {0x47,0x1B}, //开关->无  Mode->无   静音->ESC
    {0x44,0x00}, {0x40,0x25}, {0x43,0x27}, //播放->无  后退->向左 前进->向右
    {0x07,0x00}, {0x15,0x28}, {0x09,0x26}, // EQ->无   减号->向下 加号->向上
    {0x16, '0'}, {0x19,0x1B}, {0x0D,0x0D}, //'0'->'0'  箭头->ESC  U/SD->回车
    {0x0C, '1'}, {0x18, '2'}, {0x5E, '3'}, //'1'->'1'  '2'->'2'   '3'->'3'
    {0x08, '4'}, {0x1C, '5'}, {0x5A, '6'}, //'4'->'4'  '5'->'5'   '6'->'6'
    {0x42, '7'}, {0x52, '8'}, {0x4A, '9'}, //'7'->'7'  '6'->'8'   '9'->'9'
};

bit irflag = 0;   //红外接收标志,收到一帧正确数据后置1
uint8 ircode[4];  //红外代码接收缓冲区

/* 红外接收驱动,检测接收到的键码,调度相应动作函数 */
void InfraredDriver()
{
    uint8 i;
    
    if (irflag)
    {
        irflag = 0;
        for (i=0; i<sizeof(IrCodeMap)/sizeof(IrCodeMap[0]); i++) //遍历映射表
        {
            if (ircode[2] == IrCodeMap[i][0])  //在表中找到当前接收的键码后,
            {                                  //用对应的映射码执行函数调度,
                KeyAction(IrCodeMap[i][1]);    //直接调用按键动作函数即可。
                break;
            }
        }
    }
}
/* 初始化红外接收功能 */
void InitInfrared()
{
    IR_INPUT = 1;  //确保红外接收引脚被释放
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x10;  //配置T1为模式1
    TR1 = 0;       //停止T1计数
    ET1 = 0;       //禁止T1中断
    IT1 = 1;       //设置INT1为负边沿触发
    EX1 = 1;       //使能INT1中断
}
/* 获取当前高电平的持续时间 */
uint16 GetHighTime()
{
    TH1 = 0;  //清零T1计数初值
    TL1 = 0;
    TR1 = 1;  //启动T1计数
    while (IR_INPUT)  //红外输入引脚为1时循环检测等待,变为0时则结束本循环
    {
        if (TH1 >= 0x40)
        {            //当T1计数值大于0x4000,即高电平持续时间超过约18ms时,
            break;   //强制退出循环,是为了避免信号异常时,程序假死在这里。
        }
    }
    TR1 = 0;  //停止T1计数

    return (TH1*256 + TL1);  //T1计数值合成为16bit整型数,并返回该数
}
/* 获取当前低电平的持续时间 */
uint16 GetLowTime()
{
    TH1 = 0;  //清零T1计数初值
    TL1 = 0;
    TR1 = 1;  //启动T1计数
    while (!IR_INPUT)  //红外输入引脚为0时循环检测等待,变为1时则结束本循环
    {
        if (TH1 >= 0x40)
        {            //当T1计数值大于0x4000,即低电平持续时间超过约18ms时,
            break;   //强制退出循环,是为了避免信号异常时,程序假死在这里。
        }
    }
    TR1 = 0;  //停止T1计数

    return (TH1*256 + TL1);  //T1计数值合成为16bit整型数,并返回该数
}
/* INT1中断服务函数,执行红外接收及解码 */
void EXINT1_ISR() interrupt 2
{
    uint8 i, j;
    uint8 byt;
    uint16 time;
    
    //接收并判定引导码的9ms低电平
    time = GetLowTime();
    if ((time<7833) || (time>8755))  //时间判定范围为8.5~9.5ms,
    {                                //超过此范围则说明为误码,直接退出
        IE1 = 0;   //退出前清零INT1中断标志
        return;
    }
    //接收并判定引导码的4.5ms高电平
    time = GetHighTime();
    if ((time<3686) || (time>4608))  //时间判定范围为4.0~5.0ms,
    {                                //超过此范围则说明为误码,直接退出
        IE1 = 0;
        return;
    }
    //接收并判定后续的4字节数据
    for (i=0; i<4; i++)  //循环接收4个字节
    {
        for (j=0; j<8; j++)  //循环接收判定每字节的8个bit
        {
            //接收判定每bit的560us低电平
            time = GetLowTime();
            if ((time<313) || (time>718)) //时间判定范围为340~780us,
            {                             //超过此范围则说明为误码,直接退出
                IE1 = 0;
                return;
            }
            //接收每bit高电平时间,判定该bit的值
            time = GetHighTime();
            if ((time>313) && (time<718)) //时间判定范围为340~780us,
            {                             //在此范围内说明该bit值为0
                byt >>= 1;   //因低位在先,所以数据右移,高位为0
            }
            else if ((time>1345) && (time<1751)) //时间判定范围为1460~1900us,
            {                                    //在此范围内说明该bit值为1
                byt >>= 1;   //因低位在先,所以数据右移,
                byt |= 0x80; //高位置1
            }
            else  //不在上述范围内则说明为误码,直接退出
            {
                IE1 = 0;
                return;
            }
        }
        ircode[i] = byt;  //接收完一个字节后保存到缓冲区
    }
    irflag = 1;  //接收完毕后设置标志
    IE1 = 0;     //退出前清零INT1中断标志
}

11、* 文件名:Time.h

* 描  述:第19章 多功能电子钟 主要功能文件的头文件
#ifndef _TIME_H
#define _TIME_H


#ifndef _TIME_C

#endif

void RefreshTime();
void RefreshDate(uint8 ops);
void RefreshAlarm();
void AlarmMonitor();
void KeyAction(uint8 keycode);

#endif

12、* 文件名:Time.c

* 描  述:第19章 多功能电子钟 主要功能文件
#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
    0xFF, 0x99, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7,  //星期日
};

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();
        }
    }
}

13、* 文件名:LedBuzzer.h

* 描  述:点阵LED、数码管、独立LED和无源蜂鸣器的驱动模块头文件
#ifndef _LED_BUZZER_H
#define _LED_BUZZER_H

struct sLedBuff {  //LED显示缓冲区结构
    uint8 array[8];   //点阵缓冲区
    uint8 number[6];  //数码管缓冲区
    uint8 alone;      //独立LED缓冲区
};

#ifndef _LED_BUZZER_C
extern bit staBuzzer;
extern struct sLedBuff ledBuff;
#endif

void InitLed();
void FlowingLight();
void ShowLedNumber(uint8 index, uint8 num, uint8 point);
void ShowLedArray(uint8 *ptr);

#endif

14、* 文件名:LedBuzzer.c

* 描  述:点阵LED、数码管、独立LED和无源蜂鸣器的驱动模块
#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, uint8 num, 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;        //蜂鸣器静音
}

15、* 文件名:main.h

* 描  述:第19章 多功能电子钟 工程主文件的头文件
#ifndef _MAIN_H
#define _MAIN_H

enum eStaSystem {  //系统运行状态枚举
    E_NORMAL, E_SET_TIME, E_SET_ALARM
};

#ifndef _MAIN_C
extern enum eStaSystem staSystem;
#endif

void RefreshTemp(uint8 ops);
void ConfigTimer0(uint16 ms);

#endif

16、* 文件名:main.c

* 描  述:第19章 多功能电子钟 工程主文件
#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();     //初始化实时时钟模块
    InitInfrared();   //初始化红外接收模块
    InitLcd1602();    //初始化液晶模块
    Start18B20();     //启动首次温度转换
    
    while (!flag2s);  //上电后延时2秒
    flag2s = 0;
    RefreshTime();    //刷新当前时间
    RefreshDate(1);   //立即刷新日期显示
    RefreshTemp(1);   //立即刷新温度显示
    RefreshAlarm();   //闹钟设定值显示
    
    while (1)  //进入主循环
    {
        KeyDriver();      //执行按键驱动
        InfraredDriver(); //执行红外接收驱动
        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();   //执行按键扫描
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值