基于 51 单片机串口通信的电子钟设计与实现
课程大作业-基于 51 单片机的串口电子钟
基于 51 单片机的串口电子钟
一、电子钟系统设计方案
1. 项目概述
本项目旨在设计一个基于51单片机的电子钟系统,最终实现以下功能:
2. 工程文件组成
工程文件主要由以下模块组成:

3.程序运行流程
电子钟程序的运行主要依赖于几个关键组件的协同工作:主循环、中断服务、定时器控制、用户交互和显示更新。以下是对电子钟程序运行流程的简要分析:
3.1. 系统初始化
在程序的开始,会进行一系列的初始化操作:
3.2. 主循环
程序进入一个无限循环,这是电子钟的核心运行逻辑:
3.3. 中断服务
中断服务是电子钟响应外部事件的关键:
3.4. 定时器控制
定时器是电子钟时间控制的核心:
3.5. 用户交互
用户通过按键和串口与电子钟进行交互:
3.6. 显示更新
电子钟的显示更新通常由以下触发:
3. 7. EEPROM存储
电子钟使用EEPROM存储时间设置,以保持在断电后时间不丢失:
3.8. 报警功能
当设定的闹钟时间到达时,电子钟会通过蜂鸣器发出声音提醒用户:
3.9. 辅助功能
4、注意事项

5、缺陷
本次项目最终仅实现了前6个功能,最后一个功能(自行设计一个串口助手)尚未实现。
6、51单片机型号
设计过程当中所用到的单片机为乐育STC89C52学习板
二、代码展示
1、主模块mian.c
/*********************************************************************************************************
设计任务: 基于51单片机设计的电子钟
模块名称: main.c
作者:liujiajing
日期:2024年5月24日
*********************************************************************************************************/
/*********************************************************************************************************
头文件和引脚定义
**********************************************************************************************************/
#include <reg52.h> // 包含51单片机的寄存器定义
#include <stdio.h> // 标准输入输出库
#include "globals.h" // 全局变量声明
#include "InitUART.h" //串口中断服务函数
#include "EEPROM.h" // EEPROM操作函数
#include "InitTimer.h" // 定时器初始化相关函数
#include "InitInterrupt.h" // 中断初始化相关函数
#include "Delay.h" // 延时函数
#include "SegmentFlash.h" // 数码管闪烁控制函数
#include "WebDisplay.h" // 网络显示相关函数
#include "Lightflash.h" // 流水灯控制函数
sbit LED1 = P2^4; //定义LED1
sbit LED2 = P2^5; //定义LED2
sbit LED3 = P2^6; //定义LED3
sbit LED4 = P2^7; //定义LED4
sbit BEEP = P1^0; //定义蜂鸣器BEEP
sbit KEY3 = P3^4; //定义按键KEY3
/*********************************************************************************************************
全局变量的定义
*********************************************************************************************************/
unsigned char seg_flash = 0; // 用于控制数码管用于控制显示模式,当seg_flash=0时显示小时分钟,当seg_flash=1时显示月日
unsigned char s_num = 0; // 串口数据接收计数器
unsigned int year, month, day, hour, min, sec; // 存储时间的变量
unsigned int what_d; // 控制星期显示的变量
unsigned int led = 0; // 控制流水灯的变量
unsigned int set_month, set_day, set_hour, set_min; // 存储闹钟时间的变量
unsigned char alarm; // 闹钟控制变量
unsigned char set; // 设置控制变量
unsigned char arrBUFF[22]; // 用于存储串口接收的中间数据
unsigned char arr_Days[12]={31,29,31,30,31,30,31,31,30,31,30,31}; // 定义了一个数组,存储每个月的天数
/*********************************************************************************************************
主函数
*********************************************************************************************************/
void main()
{
seg_flash = 0; //初始数码管控制变量为0
P2 = 0xEF; //定义P2寄存器,点亮LED灯
led = 0; //定义LED灯控制变量初始化为0
// 配置中断和定时器
InitInterrupt1(); // 初始化外部中断1
InitInterrupt0(); // 初始化外部中断0
InitTimer0(); // 初始化定时器0
InitUART(); // 初始化串口通信
if(0x0000 == IAPByteRead(0x2C00)) { //如果数据已被初始化
year = IAPByteRead(0x2000);
month = IAPByteRead(0x2200);
day = IAPByteRead(0x2400);
hour = IAPByteRead(0x2600);
min = IAPByteRead(0x2800);
sec = IAPByteRead(0x2A00);
} else {
IAPByteWrite(0x2C00,0x0000); //若无,初始化参数
year = 2024;
month = 5;
day = 20;
hour = 13;
min = 14;
sec = 0;
}
//主循环
while(1) {
if(1==set && arrBUFF[0]=='s' && arrBUFF[1]=='e' && arrBUFF[2]=='t' && '_'==arrBUFF[20]) {
set = 0;
year = ((arrBUFF[4] - 48) * 1000 )+ ((arrBUFF[5] - 48) * 100) + ((arrBUFF[6] - 48) * 10) + (arrBUFF[7] - 48);//计算设定时间年、月、日、时、分
month = ((arrBUFF[9] - 48) * 10) + (arrBUFF[10] - 48);
day = ((arrBUFF[12] - 48) * 10) + (arrBUFF[13] - 48);
hour = ((arrBUFF[15] - 48) * 10) + (arrBUFF[16] - 48);
min = ((arrBUFF[18] - 48) * 10) + (arrBUFF[19] - 48);
IAPSectorErase(0x2000); //将擦除起始地址为0x2000的扇区
IAPByteWrite(0x2000,year); //将year的值写入起始地址为0x2000的扇区
IAPSectorErase(0x2200); //擦除其实地址为0x2200的扇区
IAPByteWrite(0x2200,month); //将month写入起始地址为0x2200的扇区
IAPSectorErase(0x2400); //擦除起始地址为0x2400的扇区
IAPByteWrite(0x2400,day); //将day写入起始地址为0x2400的扇区
IAPSectorErase(0x2600); //擦除其实地址为0x2600的扇区
IAPByteWrite(0x2600,hour); //将hour值写入起始地址为0x2600的扇区
IAPSectorErase(0x2800); //擦除起始地址为0x2800的扇区
IAPByteWrite(0x2800,min); //将min值写入起始地址为0x2800的扇区
}
else if(1==set && 'a'==arrBUFF[0] && 'l'==arrBUFF[1] && 'a'==arrBUFF[2] && 'r'==arrBUFF[3] && 'm'==arrBUFF[4] && '_'==arrBUFF[17]) { //判断串口发送命令是否满足时间设定条件
alarm = 1;//设定闹钟控制参数为1
set_month = ((arrBUFF[6] - 48) * 10) + (arrBUFF[7] - 48);//计算设定闹钟时间月、日、时、分
set_day = ((arrBUFF[9] - 48) * 10) + (arrBUFF[10] - 48);
set_hour = ((arrBUFF[12] - 48) * 10) + (arrBUFF[13] - 48);
set_min = ((arrBUFF[15] - 48) * 10) + (arrBUFF[16] - 48);
set = 0;//设置时间设置控制参数为1
}
if(0 == seg_flash) {
SegmentFlash(hour, min);//按键未被按下时显示时间
} else if(1 == seg_flash) { //按键被按下显示日期
SegmentFlash(month, day);
}
//蜂鸣器鸣叫实现闹钟功能
if(month == set_month && day == set_day && hour == set_hour && min == set_min && 1 == alarm) {
BEEP = 0;
}
//按下按键3停止闹钟
if(0 == KEY3) {
DelayNms(50);
if(0 == KEY3) {
alarm = 0;//
BEEP = 1;
}
}
}
}
2、头文件及函数模块
2.1、全局变量定义头文件"globals.h"
/********************globals.h***********************/
#ifndef _GLOBALS_H_
#define _GLOBALS_H_
// 'seg_flash' 是一个全局变量,用于数码管控制显示模式。
extern unsigned char seg_flash;
// 's_num' 是一个全局计数器,用于跟踪串口数据接收的进度或其他计数功能。
extern unsigned char s_num;
// 'arr_Days' 是一个数组,用于存储每个月的天数。这有助于处理日期计算,特别是月份变换时。
extern unsigned char arr_Days[];
// 下面的变量用于存储电子钟的当前时间。
// 'year' 存储当前年份。
// 'month' 存储当前月份。
// 'day' 存储当前日期。
// 'hour' 存储当前小时(24小时制)。
// 'min' 存储当前分钟。
// 'sec' 存储当前秒。
extern unsigned int year, month, day, hour, min, sec;
// 'what_d' 是用于存储星期几的变量。
// 'led' 是用于控制LED或流水灯的变量。
extern unsigned int what_d, led;
// 下列变量用于设置闹钟的时间。
// 'set_month' 存储闹钟的月份。
// 'set_day' 存储闹钟的日期。
// 'set_hour' 存储闹钟的小时。
// 'set_min' 存储闹钟的分钟。
extern unsigned int set_month, set_day, set_hour, set_min;
// 'alarm' 是一个标志变量,用于指示闹钟是否激活。
extern unsigned char alarm;
// 'set' 是一个控制设置模式的变量,用于指示电子钟是否处于设置状态。
extern unsigned char set;
// 'arrBUFF' 是一个用于存储串口接收到的数据的缓冲区。
extern unsigned char arrBUFF[];
#endif // _GLOBALS_H_
2.2、延时函数头文件"Delay.h"及"Delay.c"
/************Delay.h************/
#ifndef __DELAY_H__
#define __DELAY_H__
void DelayNms(int nms);
#endif
/******************************Delay.c***********************************/
#include <reg52.h>
#include "Delay.h"
/*********************************************************************************************************
函数名称:DelyNms
函数功能:延时函数
输入参数:void
输出参数:void
返回值:void
*********************************************************************************************************/
void DelayNms(int nms)//延时函数实现
{
unsigned int i;
for(i=0; i<nms; i++)
{
}
}
2.3、“EEPROM.h"头文件及"EEPROM.c”
/******************************** *****EEPROM.h**********************************************************/
/*********************************************************************************************************
特殊功能寄存器定义
*********************************************************************************************************/
sfr ISP_DATA = 0XE2; //
sfr ISP_ADDRH = 0XE3; //
sfr ISP_ADDRL = 0XE4; //
sfr ISP_CMD = 0XE5; //
sfr ISP_TRIG = 0XE6; //
sfr ISP_CONTR = 0XE7; //
/*********************************************************************************************************
枚举结构体定义
*********************************************************************************************************/
/*********************************************************************************************************
API函数声明
*********************************************************************************************************/
void IAPSectorErase(unsigned int addr); //擦除指定扇区函数
void IAPByteWrite(unsigned int addr, unsigned char dat); //字节写入函数
unsigned char IAPByteRead(unsigned int addr); //字节读取函数
/***********************************EEPROM.c***************************************/
/*********************************************************************************************************
包含头文件
*********************************************************************************************************/
#include <reg52.h>
#include <EEPROM.h>
/*********************************************************************************************************
内部函数声明
/*********************************************************************************************************/
static void IAPTdigger();//触发IAP功能
static void IAPDisable();//禁用IAP功能
/*********************************************************************************************************
内部函数实现
/*********************************************************************************************************/
static void IAPTrigger()//触发寄存器
{
ISP_TRIG = 0x46;
ISP_TRIG = 0xB9;
}
static void IAPDisable()
{
ISP_CONTR = 0x00;
ISP_CMD = 0x00;
ISP_TRIG = 0x00;
}
/*********************************************************************************************************
API函数声明及实现
/*********************************************************************************************************/
unsigned char IAPByteRead(unsigned int addr)
{
unsigned char dat; //定义数据缓存变量
ISP_CONTR = 0x81; //打开IAP功能,允许编程改变Flash,设置Flash操作等待时间
ISP_CMD = 0x01; //允许对"Data Flash/EEPROM区"进行字节读取
ISP_ADDRL = addr; //IAP操作地址寄存器低位
ISP_ADDRH = addr >> 8; //IAP操作地址寄存器高位
IAPTrigger(); //触发IAP功能
dat = ISP_DATA; //将需要读出的数据放进缓存变量
IAPDisable(); //禁用IAP功能
return dat; //将读取到的数据作为返回值
}
void IAPSectorErase(unsigned int addr)
{
ISP_CONTR = 0x81; //打开IAP功能,允许编程改变Flash,设置Flash操作等待时间
ISP_CMD = 0x03; //允许对"Data Flash/EEPROM区"进行扇区擦除
ISP_ADDRL = addr; //写入IAP操作地址寄存器低位
ISP_ADDRH = addr >> 8; //写入IAP操作地址寄存器高位
IAPTrigger(); //触发IAP功能
IAPDisable(); //禁用IAP功能
}
void IAPByteWrite(unsigned int addr, unsigned char dat)
{
ISP_CONTR = 0x81; //打开IAP功能,允许编程改变Flash,设置Flash操作等待时间
ISP_CMD = 0x02; //允许对"Data Flash/EEPROM区"进行字节写入
ISP_ADDRL = addr; //IAP操作地址寄存器低位
ISP_ADDRH = addr >> 8; //IAP操作地址寄存器高位
ISP_DATA = dat; //将需要写入的数据放进ISP_DATA
IAPTrigger(); //触发IAP功能
IAPDisable(); //禁用IAP功能
}
2.4、数码管显示头文件"segmentFlash.h"及"segmentFlash.c"
/****************************segmentFlash.h****************************/
#ifndef __SEGMENTFLASH_H__
#define __SEGMENTFLASH_H__
void SegmentFlash(int num1, int num2);//数码管显示函数
#endif
/**************************************SegmentFlash.c*****************************************************
数码管显示函数
/*********************************************************************************************************/
#include <reg52.h>
#include "SegmentFlash.h"
#include "Delay.h"
sbit SegmentG1 = P2^3;//定义数码管1
sbit SegmentG2 = P2^2;//定义数码管2
sbit SegmentG3 = P2^1;//定义数码管3
sbit SegmentG4 = P2^0;//定义数码管4
static unsigned char s_arrNumber[] =
{0x03, 0x9f, 0x25, 0x0d, 0x99, 0x49, 0x41, 0x1f, 0x01, 0x09};//定义数码管显示数字0~9
void SegmentFlash(int num1, int num2)//输入变量num1为 时|月 和 分|日
{
P0 = s_arrNumber[num1 / 10];
SegmentG1 = 0;
DelayNms(5);
SegmentG1 = 1;
P0 = s_arrNumber[num1 % 10];
SegmentG2 = 0;
DelayNms(5);
SegmentG2 = 1;
P0 = s_arrNumber[num2 / 10];
SegmentG3 = 0;
DelayNms(5);
SegmentG3 = 1;
P0 = s_arrNumber[num2 % 10];
SegmentG4 = 0;
DelayNms(5);
SegmentG4 = 1;
}
2.5、星期显示头文件"WebDisplay.h"及"WebDisplay.c"
/********************************WebDisplay.h**************************************/
#ifndef __WEBDISPLAY_H__
#define __WEBDISPLAY_H__
void WebDisplay(); // 星期显示函数
#endif
/*************************************WebDisplay.c**********************************************************************************************************/
#include <reg52.h>
#include "WebDisplay.h"
#include "globals.h"
static unsigned int all_days;//定义总天数
/*************************************************************************
星期显示函数
**************************************************************************/
void WebDisplay()
{
unsigned char i; //定义累加循环变量i
all_days = 0;
for(i = month; i > 1; i--)
{
all_days = all_days + arr_Days[i - 2];//前几月已过天数
}
all_days = all_days + day; //加上本月已过天数即为已过总天数
what_d = all_days % 7; //计算星期几
}
2.6、外部中断服务函数头文件"InitInterrupt.h"及"InitInterrupt.c"
/*****************************InitInterrupt.h***********************************************/
#ifndef _INITINTERRUPT_H_
#define _INITINTERRUPT_H_
#include "globals.h"
void InitInterrupt1(); //外部中断1配置函数
void InitInterrupt0(); //外部中断0配置函数
void External0_Handler(); //外部中断0服务函数
void External1_Handler(); //外部中断1服务函数
#endif // _INITINTERRUPT_H_
/**************************************InitInterrupt.c*****************************************************
外部中断服务函数
/*********************************************************************************************************/
#include <reg52.h>
#include "InitInterrupt.h"
/*********************************************************************************************************
内部函数声明
/*********************************************************************************************************/
void InitInterrupt0();//外部中断0配置函数
void InitInterrupt1();//外部中断1配置函数
void External0_Handler() ;
void External1_Handler() ;
/*********************************************************************************************************
内部函数实现
/*********************************************************************************************************/
void InitInterrupt0()
{
IT0 = 1;//设置外部中断0的触发方式为沿下降沿触发
EX0 = 1;//打开外部中断0的中断允许
EA = 1;//打开总中断允许
}
void InitInterrupt1()
{
IT1 = 1;//设置外部中断1的触发方式为沿下降沿触发
EX1 = 1;//打开外部中断1的中断允许
EA = 1;//打开总中断允许
}
/*********************************************************************************************************
外部中断0服务函数
/*********************************************************************************************************/
void External0_Handler() interrupt 0 //编写外部中断0服务函数,当外部中断触发时d值取反,数码管显示日期
{
if(0 == seg_flash)
{
seg_flash = 1;
}
else if(1 == seg_flash)
{
seg_flash = 0;
}
}
/*********************************************************************************************************
外部中断1服务函数
/*********************************************************************************************************/
void External1_Handler() interrupt 2//通过外部中断1重置串口通信信息写入位置,实现命令更新
{
s_num = 0;
}
2.7、时器中断服务函数头文件"InitTimer.h"及"InitTimer.c"
/*************************InitTimer.h*********************************/
#ifndef __INITUART_H_
#define __INITUART_H_
void InitUART(); //串口配置函数
void UART_handler(); //串口中断服务函数
#endif
/************************************InitTimer.c*********************************************************************/
#include <reg52.h>
#include <stdio.h>
#include "EEPROM.h"
#include "InitTimer.h"
#include "InitInterrupt.h"
#include "globals.h"
#include "Lightflash.h"
#include "WebDisplay.h"
/*********************************************************************************************************
内部函数声明
/*********************************************************************************************************/
void InitTimer0();//定时器0配置函数
void Timer0_Handler();//定时器中断服务函数
/*********************************************************************************************************
InitTimer0定时器0配置函数
/*********************************************************************************************************/
void InitTimer0() {
TMOD |= 0x01;//设置定时器为工作模式1
TH0 = 0xFC;//设置定时器高8位
TL0 = 0x18;//设置定时器0计数值的低8位,1ms后溢出
TR0 = 1;//打开计时器
ET0 = 1;//打开定时器0
EA = 1;//打开总中断允许
}
/*********************************************************************************************************
定时器中断服务函数
/*********************************************************************************************************/
void Timer0_Handler() interrupt 1 { //编写定时器0的中断服务函数
static unsigned int s_iCounter;//定义变量控制秒值
InitInterrupt0();
TH0 = 0XFC;
TL0 = 0X18;
s_iCounter++;
if(s_iCounter>=900) { //当计时1秒时
s_iCounter = 0; //计时控制变量清零
sec++; //秒值加1
Lightflash(); //实现流水灯
led++;
IAPSectorErase(0x2A00); //擦除起始地址为0x2A00的扇区
IAPByteWrite(0x2A00,sec); //将sec的值写入该扇区
if(sec >= 60) {
sec = 0;
min++;
IAPSectorErase(0x2800); //擦除起始地址为0x2800的扇区;
IAPByteWrite(0x2800,min); //将min值写入起始地址为0x2800的扇区;
}
if(min >= 60) {
min = 0;
hour++;
IAPSectorErase(0x2600); //擦除其实地址为0x2600的扇区
IAPByteWrite(0x2600,hour); //将hour值写入起始地址为0x2600的扇区
}
if(hour >= 24) {
hour = 0;
day++;
IAPSectorErase(0x2400); //擦除起始地址为0x2400的扇区;
IAPByteWrite(0x2400,day); //将day写入起始地址为0x2400的扇区;
}
if(day > arr_Days[month-1]) {
day = 1;
month++;
IAPSectorErase(0x2200); //擦除其实地址为0x2200的扇区
IAPByteWrite(0x2200,month); //将month写入起始地址为0x2200的扇区
}
if(month > 12) {
month = 1;
year++;
IAPSectorErase(0x2000); //将擦除起始地址为0x2000的扇区
IAPByteWrite(0x2000,year); //将year的值写入起始地址为0x2000的扇区
}
printf("%d %d %d %d:%d:%d_", year, month, day, hour, min, sec);
WebDisplay();
switch(what_d) { //实现星期显示
case 1:
printf("monday\n");
break;
case 2:
printf("tuesday\n");
break;
case 3:
printf("wednesday\n");
break;
case 4:
printf("thursday\n");
break;
case 5:
printf("friday\n");
break;
case 6:
printf("saturday\n");
break;
case 0:
printf("sunday\n");
break;
}
printf("alarm:%d_%d_%d:%d\n",set_month, set_day, set_hour, set_min);//实现闹钟时间显示
}
}
2.8、串口中断服务函数头文件"InitTimer.h"及"InitTimer.c"
/*************************InitTimer.h*********************************/
#ifndef __INITUART_H_
#define __INITUART_H_
void InitUART(); //串口配置函数
void UART_handler(); //串口中断服务函数
#endif
/***************************************InitUART.c***********************************************************/
#include <reg52.h>
#include "InitUART.h"
#include "globals.h"
/*********************************************************************************************************
内部函数声明
/*********************************************************************************************************/
void InitUART(); //串口配置函数
void UART_handler(); //串口中断服务函数
/*********************************************************************************************************
InitUART串口配置函数
/*********************************************************************************************************/
void InitUART()
{
ES = 1; //打开串口接收中断允许
EA = 1; //打开总中断允许
PT0 = 1; //设置串口中断优先级为高优先级
SCON = 0x50; //设置串口为工作模式1,并打开接收允许
TMOD |= 0x20; //设置定时器1为工作模式2
PCON = 0x80; //设置比特率加倍
TL1 = 0xF3; //设置定时器1计数初值
TH1 = TL1; //设置定时器1重装载值,等于计数初值
TR1 = 1; //打开定时器
TI = 1; //设置串口中断请求标志
}
/*********************************************************************************************************
串口中断服务函数
/*********************************************************************************************************/
void UART_handler() interrupt 4//实现命令写入,初始化命令设置参数
{
if(RI == 1)
{
arrBUFF[s_num] = SBUF;
RI = 0;
set = 1;
if(arrBUFF[s_num]>='0' && arrBUFF[s_num]<='z')
{
s_num ++;
}
}
}
2.9、流水灯实现函数头文件"Lightflash.h"及"Lightflash.c"
/***************************Lightflash.h*************************/
#ifndef __LIGHTFLASH_H_
#define __LIGHTFLASH_H_
void Lightflash();//流水灯实现函数
#endif
/**************************************Lightflash.c***************************************************/
#include <reg52.h>
#include "Lightflash.h"
#include "globals.h"
/*********************************************************************************************************
内部函数声明
/*********************************************************************************************************/
void Lightflash();//流水灯实现函数
/*********************************************************************************************************
Lightflash流水灯实现函数
/*********************************************************************************************************/
void Lightflash()
{
if(led < 4)//实现流水灯样式
{
P2 = P2 << 1;
}
else
{
P2 = 0xEF;
led = 0;
}
}
三、致谢
本学期的课程任务和竞赛比较多,51单片机课程大作业汇报延迟了好久才得以汇报,在此感谢致行班创新实验室的所有老师们指导和辛勤付出,还有思民同学的帮助。
作者:突破ljj