51单片机 - 电子万年历 - 闹钟

51单片机 - 电子万年历 - 闹钟

本文用于阐述使用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);
}

附录 - 闹钟(alarm.h)

#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

附录 - 闹钟(alarm.c)

#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;
}
发布了15 篇原创文章 · 获赞 17 · 访问量 1649

猜你喜欢

转载自blog.csdn.net/weixin_44413515/article/details/104682602