赞
踩
单片机——多功能电子钟
宗旨:技术的学习是有限的,分享的精神是无限的。
实现的功能有:走 时、校时、闹钟、温度、遥控这几个功能。要想实现这几个功能,其中走时所需要的就是时 钟芯片,即 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; //矩阵按键的扫描输入引脚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 /* _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
-
- #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, uint8chr);
-
- #endif /* _LCD1602_H */
-
- // 实时时钟芯片DS1302驱动模块的头文件DS1302.h:
- #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 /* _DS1302_H */
- // 温度传感器DS18B20驱动模块的头文件
- #ifndef _DS18B20_H
- #define _DS18B20_H
-
- #ifndef _DS18B20_C
-
- #endif
-
- bit Start18B20();
- bit Get18B20Temp(int16 *temp);
-
- #endif /* _DS18B20_H */
- // 多功能电子钟主要功能文件的头文件Time.h:
- #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 /* _TIME_H */
- // 4*4矩阵按键驱动模块的头文件keyboard.h:
- #ifndef _KEY_BOARD_H
- #define _KEY_BOARD_H
-
- #ifndef _KEY_BOARD_C
-
- #endif
-
- void 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_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, 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_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 /* _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(); //执行按键扫描
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。