C51--BigTask 电子钟设计

具体任务:

基础任务

基于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);
		
  }
}

成果展示:

  • 13
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值