如何在51单片机实现日程提醒(生日闹钟)
51单片机制作万年历过程中的日程提醒部分,主要说明设计算法,软件特性可以在proteus上仿真。日程提醒是人机交互的一部分,因此日程提醒的实现与具体的人机交互方式息息相关,本系统采用4x4矩阵键盘作为人间交互的接口。下面直接上代码:
日程定义
日程主要由月/日与时/分确立,以及日程的状态组成。日程可逻辑抽象为数据类型schedule_t:
typedef enum { // 日程状态
SCHEDULE_OFF = 0,
SCHEDULE_ON = 1,
SCHEDULE_BUTT
} schedule_state_t;
typedef struct { // 日程
u8 month;
u8 day;
u8 hour;
u8 miunte;
schedule_state_t state;
} schedule_t;
static schedule_t schedule = { 0, 0, 0, 0, SCHEDULE_OFF }; // 日程配置
static u8 cur_pos = 1; // 游标位置
对外接口
对外接口主要由日程初始化,检测日程提醒,设置日程,关闭日程提醒等操作组成。
void schedule_init(void); // 日程初始化
void schedule_deinit(void); // 日程去初始化
u8 schedule_check(u8 now_month, u8 now_day, u8 now_hour, u8 now_miunte); // 日程检测
void schedule_response_key(key_t key); // 按键响应
void schedule_display(void); // 日程显示
void schedule_close(void); // 关闭日程
日程提醒初始化
主要是预置日程显示格式、界面等,前两位为月/日参数显示,使用“-”连接,后两位为时/分参数显示,使用“:”连接,界面如下图所示:
/*******************************************************************************
* 函 数 名 : schedule_init
* 函数功能 : 日程初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_init(void) // 日程初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set schedule:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, schedule.month);
lcd1602_write_data('-');
lcd1602_write_num(2, schedule.day);
lcd1602_set_pos(6, 0);
lcd1602_write_num(2, schedule.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, schedule.miunte);
lcd1602_set_pos(13, 0);
if (schedule.state == SCHEDULE_ON) {
lcd1602_write_str(" on");
} else {
lcd1602_write_str("off");
}
lcd1602_set_pos(0, 0);
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
cur_pos = SCHEDULE_CUR_POS_MAX;
}
日程提醒检测
通过对比在一定时间范围内,检测到日程有效便激活蜂鸣器,否则关闭。激活蜂鸣器部分主要思想如下:
1、日程状态必须为SCHEDULE_ON,即:打开日程提醒;
2、在月/日与时/分检测到相等的情况下,往后推迟的SCHEDULE_CONTINUE_TIME分钟时间激活蜂鸣器;
3、否则关闭蜂鸣器。
/*******************************************************************************
* 函 数 名 : schedule_check
* 函数功能 : 日程检测
* 输 入 : now_month/now_day/now_hour/now_miunte:当前月/日/时/分
* 输 出 : u8:0没有激活,other已激活
* 说 名 : none
*******************************************************************************/
u8 schedule_check(u8 now_month, u8 now_day, u8 now_hour, u8 now_miunte) // 日程检测
{
static u8 state = 0;
if (schedule.state != SCHEDULE_ON) {
buzzer_disable_passive();
return 0;
}
if ((state == 0) && (schedule.month == now_month) && (schedule.day == now_day) &&
(schedule.hour == now_hour) && (schedule.miunte == now_miunte)) {
state = 1;
}
if (state != 1) {
buzzer_disable_passive();
return 0;
}
if (((now_hour * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) >= 0) &&
((now_hour * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) <= SCHEDULE_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
if ((schedule.hour == 23) && (now_hour == 0)) {
if (((24 * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) >= 0) &&
((24 * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) <= SCHEDULE_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
}
return 0;
}
矩阵键盘
键盘如下图所示:
设置日程
用户通过4x4矩阵键盘设定日程,事实上是日程某函数响应按键的过程,从日程抽象类型schedule_t分析,日程设定主要是修改月份、日期、小时和分钟,以及日程状态,如下表所示:
月 | 日 | 时 | 分 | 日程状态 |
---|---|---|---|---|
1-12 | 1-31 | 0-23 | 0-59 | on/off |
其中月份、日期、小时和分钟可进一步细分为十位和个位2项修改项,总计有9项修改项,如下表所示:
月份十位 | 月份个位 | 日期十位 | 日期个位 | 小时十位 | 小时个位 | 分钟十位 | 分钟个位 | 日程状态 |
---|---|---|---|---|---|---|---|---|
取值0-1 | 月份十位<1,取值1-9;月份十位=1,取值0-2 | 取值0-3 | 日期十位<3,取值1-9;日期十位=3,取值0-1 | 取值0-2 | 小时十位<2,取值0-9;小时十位=2,取值0-3 | 取值0-5 | 取值0-9 | on/off |
设定日程其实是在描述一个分段函数,根据光标当前所在位置,按键0-9直接修改日程的月份/日期/小时/分钟值,add/sub将月份/日期/小时/分钟值加一/减一,trans用于切换日程状态,enter键用于循环移动光标位置。日程按键响应代码如下:
/*******************************************************************************
* 函 数 名 : schedule_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_response_key(key_t key) // 按键响应
{
switch (key) {
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9: {
if ((cur_pos == 1) && (key <= KEY_1)) { // 月十位不能超过1
schedule.month = 10 * key + schedule.month % 10;
} else if (cur_pos == 2) { // 月个位取值0-9,3-9有限制
if (key <= KEY_2) {
schedule.month = key + schedule.month / 10 * 10;
} else if ((key > KEY_2) && (schedule.month / 10 < 1)) {
schedule.month = key + schedule.month / 10 * 10;
}
} else if ((cur_pos == 3) && (key <= KEY_3)) { // 天十位不能超过3
schedule.day = 10 * key + schedule.day % 10;
} else if (cur_pos == 4) { // 天个位取值0-9,2-9有限制
if (key <= KEY_1) {
schedule.day = key + schedule.day / 10 * 10;
} else if ((key > KEY_1) && (schedule.day / 10 < 3)) {
schedule.day = key + schedule.day / 10 * 10;
}
} else if ((cur_pos == 5) && (key <= KEY_2)) { // 时十位取值0-2
schedule.hour = 10 * key + schedule.hour % 10;
} else if (cur_pos == 6) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
schedule.hour = key + schedule.hour / 10 * 10;
} else if ((key > KEY_3) && (schedule.hour / 10 < 2)) {
schedule.hour = key + schedule.hour / 10 * 10;
}
} else if ((cur_pos == 7) && (key <= KEY_5)) { // 分十位取值0-5
schedule.miunte = 10 * key + schedule.miunte % 10;
} else if (cur_pos == 8) { // 分个位取值0-9
schedule.miunte = key + schedule.miunte / 10 * 10;
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (schedule.month < 12) {
schedule.month++;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (schedule.day < 31) {
schedule.day++;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (schedule.hour < 23) {
schedule.hour++;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (schedule.miunte < 59) {
schedule.miunte++;
}
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (schedule.month > 0) {
schedule.month--;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (schedule.day > 0) {
schedule.day--;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (schedule.hour > 0) {
schedule.hour--;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (schedule.miunte > 0) {
schedule.miunte--;
}
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > SCHEDULE_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: { // 切换日程状态
if (cur_pos == 9) {
if (schedule.state == SCHEDULE_OFF) {
schedule.state = SCHEDULE_ON;
} else {
schedule.state = SCHEDULE_OFF;
}
}
break;
}
case KEY_FUN:
case KEY_BACK: schedule_deinit(); break; // 退出日程设置
default: break;
}
}
日程显示
用于更新月份、日期、小时和分钟,以及日程状态。
/*******************************************************************************
* 函 数 名 : schedule_display
* 函数功能 : 日程显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_display(void) // 日程显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, schedule.month);
lcd1602_set_pos(3, 0);
lcd1602_write_num(2, schedule.day);
lcd1602_set_pos(6, 0);
lcd1602_write_num(2, schedule.hour);
lcd1602_set_pos(9, 0);
lcd1602_write_num(2, schedule.miunte);
lcd1602_set_pos(13, 0);
if (schedule.state == SCHEDULE_ON) {
lcd1602_write_str(" on");
} else {
lcd1602_write_str("off");
}
if (cur_pos == 1) { // 指定光标位置
lcd1602_set_pos(0, 0);
} else if (cur_pos == 2) {
lcd1602_set_pos(1, 0);
} else if (cur_pos == 3) {
lcd1602_set_pos(3, 0);
} else if (cur_pos == 4) {
lcd1602_set_pos(4, 0);
} else if (cur_pos == 5) {
lcd1602_set_pos(6, 0);
} else if (cur_pos == 6) {
lcd1602_set_pos(7, 0);
} else if (cur_pos == 7) {
lcd1602_set_pos(9, 0);
} else if (cur_pos == 8) {
lcd1602_set_pos(10, 0);
} else if (cur_pos == 9) {
if (schedule.state == SCHEDULE_ON) {
lcd1602_set_pos(14, 0);
} else {
lcd1602_set_pos(13, 0);
}
}
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
}
附录 - 日程提醒(schedule.h)
#ifndef __SCHEDULE_H__
#define __SCHEDULE_H__
#include "include.h"
#include "keyboard.h"
#define SCHEDULE_CONTINUE_TIME 30 // 响铃时间,分钟计时,1-60
#define SCHEDULE_CUR_POS_MAX 9 // 游标最大位置
typedef enum { // 日程状态
SCHEDULE_OFF = 0,
SCHEDULE_ON = 1,
SCHEDULE_BUTT
} schedule_state_t;
typedef struct { // 日程
u8 month;
u8 day;
u8 hour;
u8 miunte;
schedule_state_t state;
} schedule_t;
void schedule_init(void); // 日程初始化
void schedule_deinit(void); // 日程去初始化
u8 schedule_check(u8 now_month, u8 now_day, u8 now_hour, u8 now_miunte); // 日程检测
void schedule_response_key(key_t key); // 按键响应
void schedule_display(void); // 日程显示
void schedule_close(void); // 关闭日程
#endif
附录 - 日程提醒(schedule.c)
#include "schedule.h"
#include "lcd1602.h"
#include "buzzer.h"
static schedule_t schedule = { 0, 0, 0, 0, SCHEDULE_OFF }; // 日程配置
static u8 cur_pos = 1; // 游标位置
/*******************************************************************************
* 函 数 名 : schedule_init
* 函数功能 : 日程初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_init(void) // 日程初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set schedule:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, schedule.month);
lcd1602_write_data('-');
lcd1602_write_num(2, schedule.day);
lcd1602_set_pos(6, 0);
lcd1602_write_num(2, schedule.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, schedule.miunte);
lcd1602_set_pos(13, 0);
if (schedule.state == SCHEDULE_ON) {
lcd1602_write_str(" on");
} else {
lcd1602_write_str("off");
}
lcd1602_set_pos(0, 0);
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
cur_pos = SCHEDULE_CUR_POS_MAX;
}
/*******************************************************************************
* 函 数 名 : schedule_deinit
* 函数功能 : 日程去初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_deinit(void) // 日程去初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL);
lcd1602_clean();
}
/*******************************************************************************
* 函 数 名 : schedule_check
* 函数功能 : 日程检测
* 输 入 : now_month/now_day/now_hour/now_miunte:当前月/日/时/分
* 输 出 : u8:0没有激活,other已激活
* 说 名 : none
*******************************************************************************/
u8 schedule_check(u8 now_month, u8 now_day, u8 now_hour, u8 now_miunte) // 日程检测
{
static u8 state = 0;
if (schedule.state != SCHEDULE_ON) {
buzzer_disable_passive();
return 0;
}
if ((state == 0) && (schedule.month == now_month) && (schedule.day == now_day) &&
(schedule.hour == now_hour) && (schedule.miunte == now_miunte)) {
state = 1;
}
if (state != 1) {
buzzer_disable_passive();
return 0;
}
if (((now_hour * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) >= 0) &&
((now_hour * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) <= SCHEDULE_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
if ((schedule.hour == 23) && (now_hour == 0)) {
if (((24 * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) >= 0) &&
((24 * 60 + now_miunte) - (schedule.hour * 60 + schedule.miunte) <= SCHEDULE_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
}
return 0;
}
/*******************************************************************************
* 函 数 名 : schedule_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_response_key(key_t key) // 按键响应
{
switch (key) {
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9: {
if ((cur_pos == 1) && (key <= KEY_1)) { // 月十位不能超过1
schedule.month = 10 * key + schedule.month % 10;
} else if (cur_pos == 2) { // 月个位取值0-9,3-9有限制
if (key <= KEY_2) {
schedule.month = key + schedule.month / 10 * 10;
} else if ((key > KEY_2) && (schedule.month / 10 < 1)) {
schedule.month = key + schedule.month / 10 * 10;
}
} else if ((cur_pos == 3) && (key <= KEY_3)) { // 天十位不能超过3
schedule.day = 10 * key + schedule.day % 10;
} else if (cur_pos == 4) { // 天个位取值0-9,2-9有限制
if (key <= KEY_1) {
schedule.day = key + schedule.day / 10 * 10;
} else if ((key > KEY_1) && (schedule.day / 10 < 3)) {
schedule.day = key + schedule.day / 10 * 10;
}
} else if ((cur_pos == 5) && (key <= KEY_2)) { // 时十位取值0-2
schedule.hour = 10 * key + schedule.hour % 10;
} else if (cur_pos == 6) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
schedule.hour = key + schedule.hour / 10 * 10;
} else if ((key > KEY_3) && (schedule.hour / 10 < 2)) {
schedule.hour = key + schedule.hour / 10 * 10;
}
} else if ((cur_pos == 7) && (key <= KEY_5)) { // 分十位取值0-5
schedule.miunte = 10 * key + schedule.miunte % 10;
} else if (cur_pos == 8) { // 分个位取值0-9
schedule.miunte = key + schedule.miunte / 10 * 10;
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (schedule.month < 12) {
schedule.month++;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (schedule.day < 31) {
schedule.day++;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (schedule.hour < 23) {
schedule.hour++;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (schedule.miunte < 59) {
schedule.miunte++;
}
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (schedule.month > 0) {
schedule.month--;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (schedule.day > 0) {
schedule.day--;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (schedule.hour > 0) {
schedule.hour--;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (schedule.miunte > 0) {
schedule.miunte--;
}
}
if ((schedule.day > 30) && ((schedule.month == 4) || // 各个月份天数修正
(schedule.month == 6) ||
(schedule.month == 9) ||
(schedule.month == 11))) {
schedule.day = 30;
} else if ((schedule.day > 29) && (schedule.month == 2)) {
schedule.day = 29;
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > SCHEDULE_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: { // 切换日程状态
if (cur_pos == 9) {
if (schedule.state == SCHEDULE_OFF) {
schedule.state = SCHEDULE_ON;
} else {
schedule.state = SCHEDULE_OFF;
}
}
break;
}
case KEY_FUN:
case KEY_BACK: schedule_deinit(); break; // 退出日程设置
default: break;
}
}
/*******************************************************************************
* 函 数 名 : schedule_display
* 函数功能 : 日程显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_display(void) // 日程显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, schedule.month);
lcd1602_set_pos(3, 0);
lcd1602_write_num(2, schedule.day);
lcd1602_set_pos(6, 0);
lcd1602_write_num(2, schedule.hour);
lcd1602_set_pos(9, 0);
lcd1602_write_num(2, schedule.miunte);
lcd1602_set_pos(13, 0);
if (schedule.state == SCHEDULE_ON) {
lcd1602_write_str(" on");
} else {
lcd1602_write_str("off");
}
if (cur_pos == 1) { // 指定光标位置
lcd1602_set_pos(0, 0);
} else if (cur_pos == 2) {
lcd1602_set_pos(1, 0);
} else if (cur_pos == 3) {
lcd1602_set_pos(3, 0);
} else if (cur_pos == 4) {
lcd1602_set_pos(4, 0);
} else if (cur_pos == 5) {
lcd1602_set_pos(6, 0);
} else if (cur_pos == 6) {
lcd1602_set_pos(7, 0);
} else if (cur_pos == 7) {
lcd1602_set_pos(9, 0);
} else if (cur_pos == 8) {
lcd1602_set_pos(10, 0);
} else if (cur_pos == 9) {
if (schedule.state == SCHEDULE_ON) {
lcd1602_set_pos(14, 0);
} else {
lcd1602_set_pos(13, 0);
}
}
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
}
/*******************************************************************************
* 函 数 名 : schedule_close
* 函数功能 : 关闭日程
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void schedule_close(void) // 关闭日程
{
schedule.state = SCHEDULE_OFF;
}