具体任务:
基础任务
基于51核心板,设计一个具有实用价值的电子钟。要求能够在串口助手上每间隔1秒打印一
次日期与时间,格式为:年-月-日 时:分:秒 星期
进阶任务
1.为电子钟添加流水灯功能
2.能在数码管上显示当前的小时、分钟值,并能够使用按键切换显示为月、日
3.能将断电时的日期与时间保存至 EEPROM,并且能够在重新上电后恢复断电前的日期与时间,继续计时
代码:
EEPROM.h部分
//定义ISP_IAP相关的特殊寄存器
sfr ISP_DATA = 0xE2; //定义ISP_IAP操作时的数据寄存器
sfr ISP_ADDRH = 0xE3;//定义ISP_IAP操作地址寄存器高位
sfr ISP_ADDRL = 0xE4;//定义ISP_IAP操作地址寄存器低位
sfr ISP_CMD = 0xE5; //定义ISP_IAP命令寄存器
sfr ISP_TRIG = 0xE6; //定义ISP_IAP命令触发寄存器
sfr ISP_CONTR = 0xE7;//定义ISP_IAP控制寄存器
//声明API函数
void IAPSectorErase(unsigned int addr);//擦除指定扇区函数
void IAPByteWrite(unsigned int addr,unsigned char dat);//字节写入函数
unsigned char IAPByteRead(unsigned int addr);//字节读取函数
EEPROM.c部分
//头文件
#include <EEPROM.h>
#include <reg52.h>
//定义内部函数
static void IAPTrigger(void)
{
ISP_TRIG = 0x46;//对ISP_IAP命令触发寄存器写入触发命令0x46
ISP_TRIG = 0xB9;//对ISP_IAP命令触发寄存器写入触发命令0xB9
}
static void IAPDisable(void)
{
ISP_CONTR = 0x00;//禁用IAP读写EEPROM
ISP_CMD = 0x00; //无IAP操作
ISP_TRIG = 0x00; //关闭IAP功能
}
//定义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功能
}
main.c部分
#include <reg52.h>
#include <stdio.h>
#include <EEPROM.h>
//位定义
sbit LED1 = P2 ^ 4;
sbit LED2 = P2 ^ 5;
sbit LED3 = P2 ^ 6;
sbit LED4 = P2 ^ 7;
sbit SegmentG1 =P2^3;//定义数码管1
sbit SegmentG2 =P2^2;//定义数码管2
sbit SegmentG3 =P2^1;//定义数码管3
sbit SegmentG4 =P2^0;//定义数码管4
sbit KEY1 =P3^2;//定义按键KEY1
//定义数码管显示数字0-9
static unsigned char s_arrNumber[]={0x03,0x9f,0x25,0x0d,0x99,0x49,0x41,0x1f,0x01,0x09};
//延时函数
static void delay(int nms)
{
unsigned int i, j;
for (i = 0; i < nms; i++)
{
for (j = 0; j < 123; j++)
{
}
}
}
//定时器0配置函数(1ms溢出)
static void InitTimer0()
{
TMOD|=0x01;//TMOD|= 一定要这样!
TH0 = 0xFC;
TL0 = 0x18;
TR0 = 1;
}
//串口配置函数
static void InitUART()
{
SCON = 0X50; //设置 串口 工作模式1,并打开接收允许
TMOD|= 0X20; //设置 定时器1 工作模式2(8位自动重装定时器)
PCON = 0X80; //设置 波特率加倍
TL1 = 0XF3; //设置 定时器1 计数初值,波特率为4800
TH1 = TL1; //设置 定时器1重装载值,等于计数初值
TR1 = 1; //打开 定时器1
TI = 1; //发送中断标志位值1,实现用printf在串口显示信息
}
//中断配置函数
static void InitInterrupt()
{
ET0 = 1; //定时器0中断允许
IT0=1;//设置外部中断0触发方式为下降触发
EX0=1;//打开外部中断0中断允许
EA = 1; //总中断允许
ES=1;//打开串口接收中断允许
PT0=1;
}
//定义初始时间
int year = 2022;
static unsigned int month = 1;
static unsigned int day = 1;
static unsigned int hour = 0;
static unsigned int min = 0;
static unsigned int sec = 0;
static unsigned int week;
static unsigned int M1,M2,M3,M4;
//定时器0中断服务函数
void Timer0_Handler()interrupt 1
{
static unsigned int s_iCounter;//定义计数变量
TH0 = 0xFC; //重新设置计数初值高8位
TL0 = 0x18; //重新设置计数初值低8位,1ms后溢出
s_iCounter++; //每进行一次中断,计数变量+1
//一条IAPSectorErase()就耗时数十上百秒,因此让每次续电过后1秒就写入擦除再写入新的数值进EEPROM
if(s_iCounter>=1000)//计数变量到1000即1000ms=1s
{
s_iCounter = 0;//计数变量置零
sec++; //秒值
IAPSectorErase(0x2000);//擦除起始地址为0x2000的扇区
IAPByteWrite(0x2000,min);//对起始地址为0x2000的扇区写入P2寄存器的值
IAPSectorErase(0x2200);//擦除起始地址为0x2200的扇区
IAPByteWrite(0x2200,hour);//对起始地址为0x2200的扇区写入P2寄存器的值
IAPSectorErase(0x2400);//擦除起始地址为0x2400的扇区
IAPByteWrite(0x2400,day);//对起始]地址为0x2400的扇区写入P2寄存器的值
IAPSectorErase(0x2600);//擦除起始地址为0x2600的扇区
IAPByteWrite(0x2600,month);//对起始地址为0x2600的扇区写入P2寄存器的值
IAPSectorErase(0x2800);//擦除起始地址为0x2800的扇区
IAPByteWrite(0x2800,sec);//对起始地址为0x2800的扇区写入P2寄存器的值
IAPSectorErase(0x2A00);//擦除起始地址为0x2A00的扇区
IAPByteWrite(0x2A00,week);//对起始地址为0x2A00的扇区写入P2寄存器的值
printf("%d-%d-%d %d:%d:%d",year,month,day,hour,min,sec);
printf(" 星期%d\n",week);
}
if(sec >= 60)//分钟值
{
sec = 0;
min++;
}
if(min >= 60)//小时值
{
min = 0;
hour++;
}
if(hour >= 24)//日期值
{
hour = 0;
day++;
}
if(month == 1 && day >=31)//月份值
{
month++;
day = 1;
}
if(month == 2 && day >=28)
{
month++;
day = 1;
}
if(month == 3 && day >=31)
{
month++;
day = 1;
}
if(month == 4 && day >=30)
{
month++;
day = 1;
}
if(month == 5 && day >=31)
{
month++;
day = 1;
}
if(month == 6 && day >=30)
{
month++;
day = 1;
}
if(month == 7 && day >=31)
{
month++;
day = 1;
}
if(month == 8 && day >=31)
{
month++;
day = 1;
}
if(month == 9 && day >=30)
{
month++;
day = 1;
}
if(month == 10 && day >=31)
{
month++;
day = 1;
}
if(month == 11 && day >=30)
{
month++;
day = 1;
}
if(month == 12 && day >=31)
{
month++;
day = 1;
}
if(month > 12)//年份值
{
month = 1;
year++;
}
week = (12+day) % 7;//星期值:2022.1.1是周六,推理:13除以7余数为6,13-1=12,所以12+day
if(week == 0)
{
week = 7;
}
}
//主函数
void main()
{
InitUART();//初始化串口
InitInterrupt();
InitTimer0();
while(1)
{
//先进行读取,否则下面执行很久后再读取影响重启值
if(0x00==IAPByteRead(0x2C00))
{
min=IAPByteRead(0x2000);//读取起始地址为0x2000的扇区中的值 恢复min值
hour=IAPByteRead(0x2200);//读取起始地址为0x2200的扇区中的值 恢复hour值
day=IAPByteRead(0x2400);//读取起始地址为0x2400的扇区中的值 恢复day值
month=IAPByteRead(0x2600);//读取起始地址为0x2600的扇区中的值 恢复month值
sec=IAPByteRead(0x2800);//读取起始地址为0x2800的扇区中的值 恢复sec值
week=IAPByteRead(0x2A00);//读取起始地址为0x2800的扇区中的值 恢复week值
}
/*
在程序第一次烧录之前由于用串口助手擦除了EEPROM所有位即全部置1,烧录后程序直接开始执行所以读取为0x11111111即255,又在
第一秒到之前经过定时器中断服务函数内的判断语句各自进位,产生了不能设置初始值的问题,所以另开辟了一个扇区,让单片机识别
出自己是不是第一次烧录,赋予初始值即可
*/
else
{
IAPByteWrite(0x2C00,0x00);
min=0;
hour=0;
day=1;
month=1;
sec=0;
}
//对通过按键切换时间日期的实现
if(1==KEY1)
{
//数码管显示时间
M4=min%10;
M3=(min/10)%10;//将对应的分位进行赋值
M2=hour%10;
M1=(hour/10)%10;//将对应的时位进行赋值
P0=s_arrNumber[M1];//数码管1显示数字
SegmentG1=0;//打开数码管1
delay(50);
SegmentG1=1;//关闭数码管1
P0=s_arrNumber[M2];//数码管2显示数字
SegmentG2=0;//打开数码管2
delay(50);
SegmentG2=1;//关闭数码管2
P0=s_arrNumber[M3];//数码管3显示数字
SegmentG3=0;//打开数码管3
delay(50);//延时1ms
SegmentG3=1;
P0=s_arrNumber[M4];//数码管4显示数字
SegmentG4=0;//打开数码管4
delay(50);
SegmentG4=1;//关闭数码管4
}
//数码管显示月,日
else if(0==KEY1) //第一次检测到KEY1按键被按下
{
delay(50);//等待约50ms后再次检测按键是否被按下,清除按键抖动带来的影响
if(0 == KEY1)
{
M4=day%10;
M3=(day/10)%10;
M2=month%10;
M1=(month/10)%10;
P0=s_arrNumber[M1];//数码管1显示数字
SegmentG1=0;//打开数码管1
delay(50);
SegmentG1=1;//关闭数码管1
P0=s_arrNumber[M2];//数码管2显示数字
SegmentG2=0;//打开数码管2
delay(50);
SegmentG2=1;//关闭数码管2
P0=s_arrNumber[M3];//数码管3显示数字
SegmentG3=0;//打开数码管3
delay(50);//延时1ms
SegmentG3=1;
P0=s_arrNumber[M4];//数码管4显示数字
SegmentG4=0;//打开数码管4
delay(50);
SegmentG4=1;//关闭数码管4
//数码管开了延时后要立马关了再开下一个,否则会都显示最后设置的数!!!
//不能延时太久中间,否则输出会造成误差!!!
while(0==KEY1) ;
}
}
LED1 = 0;
delay(100);
LED2 = 0;
delay(100);
LED3 = 0;
delay(100);
LED4 = 0;
delay(100);
LED1 = 1;
delay(100);
LED2 = 1;
delay(100);
LED3 = 1;
delay(100);
LED4 = 1;
delay(100);
}
}