如何在51单片机修改电子万年历时间
51单片机制作万年历过程中的修改时间部分,主要说明设计算法,软件特性可以在proteus上仿真。修改时间是人机交互的一部分,因此修改时间的实现与具体的人机交互方式息息相关,本系统采用4x4矩阵键盘作为人间交互的接口。下面直接上代码:
修改时间涉及万年历的年/月/日/时/分等信息,直接引用时间类型定义time_t。
对外接口
对外接口主要由修改时间初始化/去初始化,修改时间响应按键,修改时间显示等操作组成。
void change_time_init(void); // 修改时间初始化
void change_time_deinit(void); // 修改时间去初始化
void change_time_response_key(key_t key); // 按键响应
void change_time_display(void); // 修改时间显示
修改时间初始化
主要是预置修改时间显示格式、界面等,包含年/月/日/时/分等信息,界面如下图所示:
/*******************************************************************************
* 函 数 名 : change_time_init
* 函数功能 : 修改时间初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_init(void) // 修改时间初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set time:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(4, time.year);
lcd1602_write_data('-');
lcd1602_write_num(2, time.month);
lcd1602_write_data('-');
lcd1602_write_num(2, time.day);
lcd1602_set_pos(11, 0);
lcd1602_write_num(2, time.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, time.miunte);
lcd1602_set_pos(0, 0);
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
cur_pos = 1;
}
矩阵键盘
键盘如下图所示:
修改时间响应按键
用户通过4x4矩阵键盘修改时间,事实上是某函数响应按键的过程,从修改时间设计分析,修改时间主要是修改年/月/日/时/分等信息,如下表所示:
年 | 月 | 日 | 时 | 分 |
---|---|---|---|---|
0-9999 | 1-12 | 1-31 | 0-23 | 0-59 |
其中年份可进一步细分为千位、百位、十位和个位,月/日/时/分等信息可进一步细分为十位和个位2项修改项,总计有12项修改项,如下表所示:
年份千位 | 年份百位 | 年份十位 | 年份个位 | 月份十位 | 月份个位 | 日期十位 | 日期个位 | 小时十位 | 小时个位 | 分钟十位 | 分钟个位 |
---|---|---|---|---|---|---|---|---|---|---|---|
取值0-9 | 取值0-9 | 取值0-9 | 取值0-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 |
修改时间其实是在描述一个分段函数,根据光标当前所在位置,按键0-9直接修改时间的年/月/日/时/分等信息,add/sub将年/月/日/时/分等信息加一/减一,enter键用于循环移动光标位置。修改时间按键响应代码如下:
/*******************************************************************************
* 函 数 名 : change_time_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_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) {
time.year = (1000 * key) + (time.year % 1000);
} else if (cur_pos == 2) {
time.year = (time.year / 1000 * 1000) + (100 * key) + (time.year % 100);
} else if (cur_pos == 3) {
time.year = (time.year / 1000 * 1000) + ((time.year % 1000) / 100 * 100) + (10 * key) + (time.year % 10);
} else if (cur_pos == 4) {
time.year = (time.year / 10 * 10) + key;
} else if ((cur_pos == 5) && (key <= KEY_1)) { // 月十位不能超过1
time.month = 10 * key + time.month % 10;
} else if (cur_pos == 6) { // 月个位取值0-9,3-9有限制
if (key <= KEY_2) {
time.month = key + time.month / 10 * 10;
} else if ((key > KEY_2) && (time.month / 10 < 1)) {
time.month = key + time.month / 10 * 10;
}
} else if ((cur_pos == 7) && (key <= KEY_3)) { // 天十位不能超过3
time.day = 10 * key + time.day % 10;
} else if (cur_pos == 8) { // 天个位取值0-9,2-9有限制
if (key <= KEY_1) {
time.day = key + time.day / 10 * 10;
} else if ((key > KEY_1) && (time.day / 10 < 3)) {
time.day = key + time.day / 10 * 10;
}
} else if ((cur_pos == 9) && (key <= KEY_2)) { // 时十位取值0-2
time.hour = 10 * key + time.hour % 10;
} else if (cur_pos == 10) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
time.hour = key + time.hour / 10 * 10;
} else if ((key > KEY_3) && (time.hour / 10 < 2)) {
time.hour = key + time.hour / 10 * 10;
}
} else if ((cur_pos == 11) && (key <= KEY_5)) { // 分十位取值0-5
time.miunte = 10 * key + time.miunte % 10;
} else if (cur_pos == 22) { // 分个位取值0-9
time.miunte = key + time.miunte / 10 * 10;
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
if (time.month == 0) {
time.month = 1;
}
if (time.day == 0) {
time.day = 1;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2) || (cur_pos == 3) || (cur_pos == 4)) {
if (time.year < YEAR_MAX) {
time.year++;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (time.month < 12) {
time.month++;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (time.day < 31) {
time.day++;
}
} else if ((cur_pos == 9) || (cur_pos == 10)) {
if (time.hour < 23) {
time.hour++;
}
} else if ((cur_pos == 11) || (cur_pos == 12)) {
if (time.miunte < 59) {
time.miunte++;
}
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2) || (cur_pos == 3) || (cur_pos == 4)) {
if (time.year > 0) {
time.year--;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (time.month > 1) {
time.month--;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (time.day > 1) {
time.day--;
}
} else if ((cur_pos == 9) || (cur_pos == 10)) {
if (time.hour > 0) {
time.hour--;
}
} else if ((cur_pos == 11) || (cur_pos == 12)) {
if (time.miunte > 0) {
time.miunte--;
}
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > CHANGE_TIME_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: break;
case KEY_FUN:
case KEY_BACK: change_time_deinit(); break; // 退出修改时间设置
default: break;
}
}
显示修改时间
用于显示更新年/月/日/时/分等信息。
/*******************************************************************************
* 函 数 名 : change_time_display
* 函数功能 : 修改时间显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_display(void) // 修改时间显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(4, time.year);
lcd1602_set_pos(5, 0);
lcd1602_write_num(2, time.month);
lcd1602_set_pos(8, 0);
lcd1602_write_num(2, time.day);
lcd1602_set_pos(11, 0);
lcd1602_write_num(2, time.hour);
lcd1602_set_pos(14, 0);
lcd1602_write_num(2, time.miunte);
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(2, 0);
} else if (cur_pos == 4) {
lcd1602_set_pos(3, 0);
} else if (cur_pos == 5) {
lcd1602_set_pos(5, 0);
} else if (cur_pos == 6) {
lcd1602_set_pos(6, 0);
} else if (cur_pos == 7) {
lcd1602_set_pos(8, 0);
} else if (cur_pos == 8) {
lcd1602_set_pos(9, 0);
} else if (cur_pos == 9) {
lcd1602_set_pos(11, 0);
} else if (cur_pos == 10) {
lcd1602_set_pos(12, 0);
} else if (cur_pos == 11) {
lcd1602_set_pos(14, 0);
} else if (cur_pos == 12) {
lcd1602_set_pos(15, 0);
}
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
}
附录 - 修改时间(change_time.h)
#ifndef __CHANGE_TIME_H__
#define __CHANGE_TIME_H__
#include "include.h"
#include "clock.h"
#include "keyboard.h"
#define CHANGE_TIME_CUR_POS_MAX 12 // 游标最大位置
void change_time_init(void); // 修改时间初始化
void change_time_deinit(void); // 修改时间去初始化
void change_time_response_key(key_t key); // 按键响应
void change_time_display(void); // 修改时间显示
#endif
附录 - 修改时间(change_time.c)
#include "change_time.h"
#include "lcd1602.h"
static u8 cur_pos = 1; // 游标位置
/*******************************************************************************
* 函 数 名 : change_time_init
* 函数功能 : 修改时间初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_init(void) // 修改时间初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_clean();
lcd1602_set_pos(0, 1);
lcd1602_write_str("set time:");
lcd1602_set_pos(0, 0);
lcd1602_write_num(4, time.year);
lcd1602_write_data('-');
lcd1602_write_num(2, time.month);
lcd1602_write_data('-');
lcd1602_write_num(2, time.day);
lcd1602_set_pos(11, 0);
lcd1602_write_num(2, time.hour);
lcd1602_write_data(':');
lcd1602_write_num(2, time.miunte);
lcd1602_set_pos(0, 0);
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
cur_pos = 1;
}
/*******************************************************************************
* 函 数 名 : change_time_deinit
* 函数功能 : 修改时间去初始化
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_deinit(void) // 修改时间去初始化
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL);
lcd1602_clean();
}
/*******************************************************************************
* 函 数 名 : change_time_response_key
* 函数功能 : 按键响应
* 输 入 : key:按键值
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_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) {
time.year = (1000 * key) + (time.year % 1000);
} else if (cur_pos == 2) {
time.year = (time.year / 1000 * 1000) + (100 * key) + (time.year % 100);
} else if (cur_pos == 3) {
time.year = (time.year / 1000 * 1000) + ((time.year % 1000) / 100 * 100) + (10 * key) + (time.year % 10);
} else if (cur_pos == 4) {
time.year = (time.year / 10 * 10) + key;
} else if ((cur_pos == 5) && (key <= KEY_1)) { // 月十位不能超过1
time.month = 10 * key + time.month % 10;
} else if (cur_pos == 6) { // 月个位取值0-9,3-9有限制
if (key <= KEY_2) {
time.month = key + time.month / 10 * 10;
} else if ((key > KEY_2) && (time.month / 10 < 1)) {
time.month = key + time.month / 10 * 10;
}
} else if ((cur_pos == 7) && (key <= KEY_3)) { // 天十位不能超过3
time.day = 10 * key + time.day % 10;
} else if (cur_pos == 8) { // 天个位取值0-9,2-9有限制
if (key <= KEY_1) {
time.day = key + time.day / 10 * 10;
} else if ((key > KEY_1) && (time.day / 10 < 3)) {
time.day = key + time.day / 10 * 10;
}
} else if ((cur_pos == 9) && (key <= KEY_2)) { // 时十位取值0-2
time.hour = 10 * key + time.hour % 10;
} else if (cur_pos == 10) { // 时个位取值0-9,4-9有限制
if (key <= KEY_3) {
time.hour = key + time.hour / 10 * 10;
} else if ((key > KEY_3) && (time.hour / 10 < 2)) {
time.hour = key + time.hour / 10 * 10;
}
} else if ((cur_pos == 11) && (key <= KEY_5)) { // 分十位取值0-5
time.miunte = 10 * key + time.miunte % 10;
} else if (cur_pos == 22) { // 分个位取值0-9
time.miunte = key + time.miunte / 10 * 10;
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
if (time.month == 0) {
time.month = 1;
}
if (time.day == 0) {
time.day = 1;
}
break;
}
case KEY_ADD: { // 加1
if ((cur_pos == 1) || (cur_pos == 2) || (cur_pos == 3) || (cur_pos == 4)) {
if (time.year < YEAR_MAX) {
time.year++;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (time.month < 12) {
time.month++;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (time.day < 31) {
time.day++;
}
} else if ((cur_pos == 9) || (cur_pos == 10)) {
if (time.hour < 23) {
time.hour++;
}
} else if ((cur_pos == 11) || (cur_pos == 12)) {
if (time.miunte < 59) {
time.miunte++;
}
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
break;
}
case KEY_SUB: { // 减1
if ((cur_pos == 1) || (cur_pos == 2) || (cur_pos == 3) || (cur_pos == 4)) {
if (time.year > 0) {
time.year--;
}
} else if ((cur_pos == 5) || (cur_pos == 6)) {
if (time.month > 1) {
time.month--;
}
} else if ((cur_pos == 7) || (cur_pos == 8)) {
if (time.day > 1) {
time.day--;
}
} else if ((cur_pos == 9) || (cur_pos == 10)) {
if (time.hour > 0) {
time.hour--;
}
} else if ((cur_pos == 11) || (cur_pos == 12)) {
if (time.miunte > 0) {
time.miunte--;
}
}
if ((time.day > 30) && ((time.month == 4) || // 各个月份天数修正
(time.month == 6) ||
(time.month == 9) ||
(time.month == 11))) {
time.day = 30;
} else if ((time.day >= 29) && (time.month == 2)) {
time.day = 29;
if (!(!(time.year % 4) && ((time.year % 100) || !(time.year % 400)))) {
time.day = 28;
}
}
break;
}
case KEY_ENTER: { // 移动游标
cur_pos++;
if (cur_pos > CHANGE_TIME_CUR_POS_MAX) {
cur_pos = 1;
}
break;
}
case KEY_TRANS: break;
case KEY_FUN:
case KEY_BACK: change_time_deinit(); break; // 退出修改时间设置
default: break;
}
}
/*******************************************************************************
* 函 数 名 : change_time_display
* 函数功能 : 修改时间显示
* 输 入 : void
* 输 出 : void
* 说 名 : none
*******************************************************************************/
void change_time_display(void) // 修改时间显示
{
lcd1602_write_cmd(LCD1602_DISPLAY_CONTROL); // 关闭光标和闪烁
lcd1602_set_pos(0, 0);
lcd1602_write_num(4, time.year);
lcd1602_set_pos(5, 0);
lcd1602_write_num(2, time.month);
lcd1602_set_pos(8, 0);
lcd1602_write_num(2, time.day);
lcd1602_set_pos(11, 0);
lcd1602_write_num(2, time.hour);
lcd1602_set_pos(14, 0);
lcd1602_write_num(2, time.miunte);
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(2, 0);
} else if (cur_pos == 4) {
lcd1602_set_pos(3, 0);
} else if (cur_pos == 5) {
lcd1602_set_pos(5, 0);
} else if (cur_pos == 6) {
lcd1602_set_pos(6, 0);
} else if (cur_pos == 7) {
lcd1602_set_pos(8, 0);
} else if (cur_pos == 8) {
lcd1602_set_pos(9, 0);
} else if (cur_pos == 9) {
lcd1602_set_pos(11, 0);
} else if (cur_pos == 10) {
lcd1602_set_pos(12, 0);
} else if (cur_pos == 11) {
lcd1602_set_pos(14, 0);
} else if (cur_pos == 12) {
lcd1602_set_pos(15, 0);
}
lcd1602_write_cmd(0x08 | // 显示光标并闪烁
LCD1602_DISPLAY_ENABLE |
LCD1602_CURSOR_ENABLE |
LCD1602_CURSOR_TWINKLE);
}