51单片机制作万年历过程中的闹钟部分,主要说明设计算法,软件特性可以在proteus上仿真。闹钟是人机交互的一部分,因此闹钟的实现与具体的人机交互方式息息相关,本系统采用4x4矩阵键盘作为人间交互的接口。下面直接上代码:
闹钟主要由时/分确立,以及闹钟的状态组成。闹钟可逻辑抽象为数据类型alarm_t:
typedef enum { // 闹钟状态
ALARM_OFF = 0,
ALARM_ON = 1,
ALARM_BUTT
} alarm_state_t;
typedef struct { // 闹钟
u8 hour;
u8 miunte;
alarm_state_t state;
} alarm_t;
static alarm_t alarm = { 0, 0, ALARM_OFF }; // 闹钟配置
static u8 cur_pos = 1; // 游标位置
对外接口主要由闹钟初始化,检测闹钟,设置闹钟,关闭闹钟等操作组成。
void alarm_init(void); // 闹钟初始化
void alarm_deinit(void); // 闹钟去初始化
u8 alarm_check(u8 now_h, u8 now_m); // 闹钟检测
void alarm_response_key(key_t key); // 按键响应
void alarm_display(void); // 闹钟显示
void alarm_close(void); // 关闭闹钟
/*******************************************************************************
* 函 数 名 : alarm_init
* 函数功能 : 闹钟初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_init(void) // 闹钟初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set alarm:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, alarm.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, alarm.miunte);
lcd1602_set_pos(13, 0);
if (alarm.state == ALARM_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 = ALARM_CUR_POS_MAX;
}
通过对比在一定时间范围内,检测到闹钟有效便激活蜂鸣器,否则关闭。激活蜂鸣器部分主要思想如下:
1、闹钟状态必须为ALARM_ON,即:打开闹钟;
2、在时/分检测到相等的情况下,往后推迟的ALARM_CONTINUE_TIME分钟时间激活蜂鸣器;
3、否则关闭蜂鸣器。
/*******************************************************************************
* 函 数 名 : alarm_check
* 函数功能 : 闹钟检测
* 输 入 : now_h/now_m:当前时/分
* 输 出 : u8:0没有激活,other已激活
* 说 名 : none
*******************************************************************************/
u8 alarm_check(u8 now_h, u8 now_m) // 闹钟检测
{
static u8 state = 0;
if (alarm.state != ALARM_ON) {
buzzer_disable_passive();
return 0;
}
if ((state == 0) && (alarm.hour == now_h) && (alarm.miunte == now_m)) {
state = 1;
}
if (state != 1) {
buzzer_disable_passive();
return 0;
}
if (((now_h * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) >= 0) &&
((now_h * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) <= ALARM_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
if ((alarm.hour == 23) && (now_h == 0)) {
if (((24 * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) >= 0) &&
((24 * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) <= ALARM_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
}
return 0;
}
创建闹钟简而言之就是响应按键,当不同的按键按下,作出不同的操作。对于一个闹钟可以设定3项修改部分,如下表所示:
时 | 分 | 闹钟状态 |
---|---|---|
0-23 | 0-59 | on/off |
其中时/分可进一步细分为十位和个位2项修改项,总计有5项修改项,如下表所示0:
小时十位 | 小时个位 | 分钟十位 | 分钟个位 | 闹钟状态 |
---|---|---|---|---|
0 | 0 | 0 | 0 | on/off |
取值0-2 | 取值0-9(小时十位<2),取值0-3(小时十位=2) | 取值0-5 | 取值0-9 | on/off |
设定闹钟其实是在描述一个分段函数,根据光标当前所在位置,按键0-9直接修改闹钟时/分值,add/sub将时/分值加一/减一,trans用于切换闹钟状态,enter键用于循环移动光标位置。闹钟按键响应代码如下:
/*******************************************************************************
* 函 数 名 : alarm_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_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_2)) { // 时十位取值0-2
alarm.hour = 10 * key + alarm.hour % 10;
} else if (cur_pos == 2) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
alarm.hour = key + alarm.hour / 10 * 10;
} else if ((key > KEY_3) && (alarm.hour / 10 < 2)) {
alarm.hour = key + alarm.hour / 10 * 10;
}
} else if ((cur_pos == 3) && (key <= KEY_5)) { // 分十位取值0-5
alarm.miunte = 10 * key + alarm.miunte % 10;
} else if (cur_pos == 4) { // 分个位取值0-9
alarm.miunte = key + alarm.miunte / 10 * 10;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (alarm.hour < 23) {
alarm.hour++;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (alarm.miunte < 59) {
alarm.miunte++;
}
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (alarm.hour > 0) {
alarm.hour--;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (alarm.miunte > 0) {
alarm.miunte--;
}
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > ALARM_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: { // 切换闹钟状态
if (cur_pos == 5) {
if (alarm.state == ALARM_OFF) {
alarm.state = ALARM_ON;
} else {
alarm.state = ALARM_OFF;
}
}
break;
}
case KEY_FUN:
case KEY_BACK: alarm_deinit(); break; // 退出闹钟设置
default: break;
}
}
用于更新闹钟时/分值和闹钟状态。
/*******************************************************************************
* 函 数 名 : alarm_display
* 函数功能 : 闹钟显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_display(void) // 闹钟显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, alarm.hour);
lcd1602_set_pos(3, 0);
lcd1602_write_num(2, alarm.miunte);
lcd1602_set_pos(13, 0);
if (alarm.state == ALARM_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) {
if (alarm.state == ALARM_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);
}
#ifndef __ALARM_H__
#define __ALARM_H__
#include "include.h"
#include "keyboard.h"
#define ALARM_CONTINUE_TIME 10 // 响铃时间,分钟计时,1-60
#define ALARM_CUR_POS_MAX 5 // 游标最大位置
typedef enum { // 闹钟状态
ALARM_OFF = 0,
ALARM_ON = 1,
ALARM_BUTT
} alarm_state_t;
typedef struct { // 闹钟
u8 hour;
u8 miunte;
alarm_state_t state;
} alarm_t;
void alarm_init(void); // 闹钟初始化
void alarm_deinit(void); // 闹钟去初始化
u8 alarm_check(u8 now_h, u8 now_m); // 闹钟检测
void alarm_response_key(key_t key); // 按键响应
void alarm_display(void); // 闹钟显示
void alarm_close(void); // 关闭闹钟
#endif
#include "alarm.h"
#include "lcd1602.h"
#include "buzzer.h"
static alarm_t alarm = { 0, 0, ALARM_OFF }; // 闹钟配置
static u8 cur_pos = 1; // 游标位置
/*******************************************************************************
* 函 数 名 : alarm_init
* 函数功能 : 闹钟初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_init(void) // 闹钟初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set alarm:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, alarm.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, alarm.miunte);
lcd1602_set_pos(13, 0);
if (alarm.state == ALARM_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 = ALARM_CUR_POS_MAX;
}
/*******************************************************************************
* 函 数 名 : alarm_deinit
* 函数功能 : 闹钟去初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_deinit(void) // 闹钟去初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL);
lcd1602_clean();
}
/*******************************************************************************
* 函 数 名 : alarm_check
* 函数功能 : 闹钟检测
* 输 入 : now_h/now_m:当前时/分
* 输 出 : u8:0没有激活,other已激活
* 说 名 : none
*******************************************************************************/
u8 alarm_check(u8 now_h, u8 now_m) // 闹钟检测
{
static u8 state = 0;
if (alarm.state != ALARM_ON) {
buzzer_disable_passive();
return 0;
}
if ((state == 0) && (alarm.hour == now_h) && (alarm.miunte == now_m)) {
state = 1;
}
if (state != 1) {
buzzer_disable_passive();
return 0;
}
if (((now_h * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) >= 0) &&
((now_h * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) <= ALARM_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
if ((alarm.hour == 23) && (now_h == 0)) {
if (((24 * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) >= 0) &&
((24 * 60 + now_m) - (alarm.hour * 60 + alarm.miunte) <= ALARM_CONTINUE_TIME)) {
buzzer_enable_passive();
return 1;
} else {
state = 0;
}
}
return 0;
}
/*******************************************************************************
* 函 数 名 : alarm_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_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_2)) { // 时十位取值0-2
alarm.hour = 10 * key + alarm.hour % 10;
} else if (cur_pos == 2) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
alarm.hour = key + alarm.hour / 10 * 10;
} else if ((key > KEY_3) && (alarm.hour / 10 < 2)) {
alarm.hour = key + alarm.hour / 10 * 10;
}
} else if ((cur_pos == 3) && (key <= KEY_5)) { // 分十位取值0-5
alarm.miunte = 10 * key + alarm.miunte % 10;
} else if (cur_pos == 4) { // 分个位取值0-9
alarm.miunte = key + alarm.miunte / 10 * 10;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (alarm.hour < 23) {
alarm.hour++;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (alarm.miunte < 59) {
alarm.miunte++;
}
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2)) {
if (alarm.hour > 0) {
alarm.hour--;
}
} else if ((cur_pos == 3) || (cur_pos == 4)) {
if (alarm.miunte > 0) {
alarm.miunte--;
}
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > ALARM_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: { // 切换闹钟状态
if (cur_pos == 5) {
if (alarm.state == ALARM_OFF) {
alarm.state = ALARM_ON;
} else {
alarm.state = ALARM_OFF;
}
}
break;
}
case KEY_FUN:
case KEY_BACK: alarm_deinit(); break; // 退出闹钟设置
default: break;
}
}
/*******************************************************************************
* 函 数 名 : alarm_display
* 函数功能 : 闹钟显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_display(void) // 闹钟显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(2, alarm.hour);
lcd1602_set_pos(3, 0);
lcd1602_write_num(2, alarm.miunte);
lcd1602_set_pos(13, 0);
if (alarm.state == ALARM_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) {
if (alarm.state == ALARM_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);
}
/*******************************************************************************
* 函 数 名 : alarm_close
* 函数功能 : 关闭闹钟
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void alarm_close(void) // 关闭闹钟
{
alarm.state = ALARM_OFF;
}