时钟程序(四)

以下是为您生成的 Markdown 格式分析报告,您可以直接复制保存为 .md 文件或在支持 Markdown 的编辑器(如 Typora、Obsidian、VS Code)中查看。


蓝桥杯代码分析:你的代码“体检报告”与通俗讲解

太棒了!你能写出这么长且逻辑完整的代码,说明你已经把单片机的“任督二脉”(定时器中断、数码管显示、按键逻辑)打通了。这是蓝桥杯省赛拿奖的基础。

这段代码实现了一个带闹钟功能的可调电子钟。但是,像所有的程序员一样,代码里藏着几个“调皮捣蛋的小鬼”(Bug)和一些可以写得更漂亮的地方。

我来把你的代码当成一篇小学生作文来批改,用最通俗的话告诉你哪儿做得好哪儿“生病”了


第一部分:你的代码“体检报告” (找 Bug)

1. 致命伤:看不见的“隐身符” (编译错误)

Timer0_Init 函数里,你的代码里混进了中文空格/全角空格

代码位置:

C

 ET0 = 1;  // 这里的空格有问题
 EA = 1;   // 这里前面的空格更有问题
  • 小学生能听懂的解释:

    编译器(翻译官)只认识英文。这些空格就像是你在英文作文里写了一个透明的汉字,虽然你肉眼看不见,但翻译官读到这里会卡住,然后报错说:“我不认识这个符号!”

  • 修改建议: 把这几行删掉,用纯英文输入法重新敲一遍。

2. 逻辑伤:复制粘贴的“后遗症” (显示错误)

Seg_Proc 函数里,当你设置分钟case 1)的时候,个位数的显示逻辑写错了。

代码位置:

C

 case 1: // 设置分钟
  Seg_Buf[2]=Seg_Flag?Clock_Set[1]/10%10:10;
  // 下面这句错了!你写成了 Clock_Set[2] (这是秒),应该是 Clock_Set[1] (这是分)
  Seg_Buf[3]=Seg_Flag?Clock_Set[2]%10:10; 
 break;
  • 小学生能听懂的解释:

    你本来想让“分针”闪烁,结果你手一滑,告诉单片机:“请把秒针的数字搬到分针的位置闪烁”。这就好比你让体育课代表去替数学课代表收作业,乱套啦!

3. 隐患:可能会“变哑巴”的闹钟

Led_Proc 里,你的判断逻辑是:

C

 if(Clock_Disp[0]==Alarm[0] && Clock_Disp[1]==Alarm[1] && Clock_Disp[2]==Alarm[2])
  • 小学生能听懂的解释:

    单片机跑得非常快。这行代码的意思是:“只有在时、分、秒完全相等的那一瞬间(1秒钟内)”,我才允许闹钟响。

    虽然你加了 Alarm_Enable_Flag 来锁住状态,这是对的!但是,如果程序跑得慢了一点点,或者逻辑稍微复杂点,容易错过这一秒。

    (不过在你目前的简单程序里,它是能工作的,做得不错!)


第二部分:用“小学班级”来解释你的代码逻辑

为了让你彻底明白自己写了什么,我们把单片机想象成一个小学班级

1. main 函数 —— 班主任

班主任(Main)只做一件事:宣布“上课”(初始化),然后就坐在讲台上看着大家自习(while(1) 死循环)。

2. Timer0Server 中断 —— 这里的“打铃员”

这是整个代码的心脏!

  • 他的特权: 每过 1毫秒(1ms),不管别人在干嘛,他都会冲进来大喊一声:“过了一毫秒啦!”

  • 他的工作:

    • 记时: 他手里拿着秒表,满1000次喊声(1000ms),他就把黑板上的“秒”字擦掉写个新的(时间+1)。

    • 刷新屏幕: 他还要负责快速切换数码管的每一位,因为速度太快,我们的眼睛以为它们是一直亮着的(视觉暂留)。

3. Key_Proc —— 负责“举手提问”的同学

  • 他负责盯着你的手指。

  • 你按一下键,他就举手告诉班主任:“老师,有人要改时间!”(切换 Mode)。

  • 你的优点: 你用了 switch-case,这就像把不同的问题分类处理,条理非常清晰!

4. Seg_Proc —— 黑板报专员

  • 他根据当前的模式(是正常看时间,还是调时间),决定在黑板(数码管)上写什么数字。

  • 你的闪烁功能 (Seg_Flag控制的三目运算符) 用得很棒!这就像是:“如果现在是修改模式,我就把这个数字擦掉、写上、擦掉、写上,让它闪起来吸引注意力。”


第三部分:大神进阶建议 (Next Steps)

你的代码已经有80分了,想考100分,建议修改这几个地方:

  1. 修复那个复制粘贴错误

    Seg_Proccase 1Clock_Set[2] 改回 Clock_Set[1]

  2. 让代码更“干净”

    Led_Proc 里,你的 uc_Led 变量在使用前没有给初始值。虽然单片机默认是0,但好习惯是手动写上 unsigned char uc_Led = 0;

  3. 按键反应优化

    你现在是按一下加1。如果我想把分钟从0调到30,要按30下,手都酸了!

    挑战任务: 试着写一个“长按连加”的功能。如果按键按住超过1秒不放,数字就自动哗哗哗地往上涨。

总结:

逻辑很通顺,模块化思维(把按键、显示、LED分开写)已经有了工程师的雏形。只要把那几个细微的Bug修好,这就是一个完美的蓝桥杯省赛模板!加油!

源代码

 /*头文件声明区域*/
 #include <REGX52.H>
 #include <Key.h>
 #include <Seg.h>
 ​
 ​
 ​
 /*变量声明区域*/
 unsigned char Key_Slow_Down;//按键减速专用变量 10ms
 unsigned char Seg_Slow_Down;//数码管减速专用变量 500ms
 unsigned char Key_Val,Key_Down,Key_Old;//按键扫描专用变量
 unsigned char Seg_Pos;//数码管扫描变量
 unsigned char Seg_Buf[6]={10,10,10,10,10,10};//数码管显示数据存放数组
 /*前五行为共有大模板变量*/
 ​
 unsigned char Seg_Disp_Mode;// 0-时钟显示界面 1-时钟设置界面 2-闹钟设置界面
 unsigned char Clock_Disp[3]={23,59,55};//时钟显示数组,上电时,显示如此
 unsigned int Timer_1000ms;//此处定义1000ms时为了让时钟倒计时以1s正常顺序运行
 unsigned char Seg_Point[6]={0,1,0,1,0,1};//显示界面为了右下角有点的存在
 unsigned char Clock_Set[3];//时钟设置数组,方便调整
 unsigned char Clock_Set_Index;//时钟设置索引,类似指针 0-小时 1-分钟 2-秒钟
 unsigned int Timer_500ms;//闪烁500ms
 ​
 bit Seg_Flag;//闪烁标志位
 unsigned char Alarm[3]={0,0,0};
 unsigned char Alarm_Set[3];
 bit Alarm_Flag;//闹钟标志
 unsigned char uc_Led=0;
 bit Alarm_Enable_Flag;//闹钟使能标志变量,防止闹钟到达相规定时间时,只有一瞬间满足
 ​
 ​
 ​
 ​
 /*按键处理函数*/
 void Key_Proc()
 {
     if(Key_Slow_Down) return;
     Key_Slow_Down=1;//按键减速程序
     
     Key_Val=Key_Read();//读取按下的键码值
     Key_Down=Key_Val&(Key_Val^Key_Old);//捕捉下降沿
     Key_Old=Key_Val;//辅助扫描
     
     if(Key_Down!=0)
         Alarm_Enable_Flag=0;
     switch(Key_Down)
     {
         case 1://切换到时钟设置界面
             Clock_Set_Index=0;//复位
             Clock_Set[0]=Clock_Disp[0];
             Clock_Set[1]=Clock_Disp[1];
             Clock_Set[2]=Clock_Disp[2];//这一步为将显示数值同步至时钟设置数值
             Seg_Disp_Mode=1;
             break;
         case 2://切换闹钟设置界面
             Clock_Set_Index=0;
             Alarm_Set[0]=Alarm[0];
             Alarm_Set[1]=Alarm[1];
             Alarm_Set[2]=Alarm[2];//这一步为将显示数值同步至闹钟设置数值
             Seg_Disp_Mode=2;
             break;
         case 3://时钟设置界面切换时分秒
             if(Seg_Disp_Mode==1)//确保在设置模式++有效果
             {
             Clock_Set_Index++;
                 if(Clock_Set_Index==3)
                     Clock_Set_Index=0;
             }             
             break;
         case 4:
             Alarm_Flag^=1;
             break;
         case 5://进入设置模式后,对时分秒各位调大
             if(Seg_Disp_Mode==1)
             {
                 Clock_Set[Clock_Set_Index]++;
                     if(Clock_Set[Clock_Set_Index]==(Clock_Set_Index==0?24:60))
                         Clock_Set[Clock_Set_Index]=0;
             }
                 else if(Seg_Disp_Mode==2)
                 {
                         Alarm_Set[Clock_Set_Index]++;
                             if(Alarm_Set[Clock_Set_Index]==(Clock_Set_Index==0?24:60))
                                 Alarm_Set[Clock_Set_Index]=0;
                 }
             break;
         case 6://进入设置模式后,对时分秒各位调小
             if(Seg_Disp_Mode==1)
             {
                 Clock_Set[Clock_Set_Index]--;
                     if(Clock_Set[Clock_Set_Index]==255)
                         Clock_Set[Clock_Set_Index]=(Clock_Set_Index==0?23:59);
             }
                 else if(Seg_Disp_Mode==2)
                 {
                         Alarm_Set[Clock_Set_Index]--;
                             if(Alarm_Set[Clock_Set_Index]==255)
                                 Alarm_Set[Clock_Set_Index]=(Clock_Set_Index==0?23:59);
                 }
             break;
         case 7://在修改后保存所修改值,直接退出至显示界面
             if(Seg_Disp_Mode==1)
             {
                 Clock_Disp[0]=Clock_Set[0];
                 Clock_Disp[1]=Clock_Set[1];
                 Clock_Disp[2]=Clock_Set[2];
             }
             else if(Seg_Disp_Mode==2)
             {
                 Alarm[0]=Alarm_Set[0];
                 Alarm[1]=Alarm_Set[1];
                 Alarm[2]=Alarm_Set[2];
             }
             Seg_Disp_Mode=0;
             break;
         case 8://在修改后不保存所修改值,直接退出至显示界面
             Seg_Disp_Mode=0;
             break;
     }
 }
 /*数码管处理函数*/
 void Seg_Proc()         
 {
 //  umsigned char i;
         
     if(Seg_Slow_Down) return;
     Seg_Slow_Down=1;//数码管减速程序 
     switch(Seg_Disp_Mode)
     {
         case 0://时钟显示界面
             Seg_Buf[0]=Clock_Disp[0]/10%10;
             Seg_Buf[1]=Clock_Disp[0]%10;
             Seg_Buf[2]=Clock_Disp[1]/10%10;
             Seg_Buf[3]=Clock_Disp[1]%10;
             Seg_Buf[4]=Clock_Disp[2]/10%10;
             Seg_Buf[5]=Clock_Disp[2]%10;//此处可以再简化,循环写法
             break;
         case 1://时钟设置界面
             Seg_Buf[0]=Clock_Set[0]/10%10;
             Seg_Buf[1]=Clock_Set[0]%10;
             Seg_Buf[2]=Clock_Set[1]/10%10;
             Seg_Buf[3]=Clock_Set[1]%10;
             Seg_Buf[4]=Clock_Set[2]/10%10;
             Seg_Buf[5]=Clock_Set[2]%10;
         
         switch(Clock_Set_Index)
         {
             case 0:
                             Seg_Buf[0]=Seg_Flag?Clock_Set[0]/10%10:10;
                             Seg_Buf[1]=Seg_Flag?Clock_Set[0]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
             case 1:
                             Seg_Buf[2]=Seg_Flag?Clock_Set[1]/10%10:10;
                             Seg_Buf[3]=Seg_Flag?Clock_Set[1]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
             case 2:
                             Seg_Buf[4]=Seg_Flag?Clock_Set[2]/10%10:10;
                             Seg_Buf[5]=Seg_Flag?Clock_Set[2]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
         }
             break;
         case 2://闹钟显示界面
             Seg_Buf[0]=Alarm_Set[0]/10%10;
             Seg_Buf[1]=Alarm_Set[0]%10;
             Seg_Buf[2]=Alarm_Set[1]/10%10;
             Seg_Buf[3]=Alarm_Set[1]%10;
             Seg_Buf[4]=Alarm_Set[2]/10%10;
             Seg_Buf[5]=Alarm_Set[2]%10;//此处可以再简化,循环写法
         switch(Clock_Set_Index)
         {
             case 0:
                             Seg_Buf[0]=Seg_Flag?Alarm_Set[0]/10%10:10;
                             Seg_Buf[1]=Seg_Flag?Alarm_Set[0]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
             case 1:
                             Seg_Buf[2]=Seg_Flag?Alarm_Set[1]/10%10:10;
                             Seg_Buf[3]=Seg_Flag?Alarm_Set[2]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
             case 2:
                             Seg_Buf[4]=Seg_Flag?Alarm_Set[2]/10%10:10;
                             Seg_Buf[5]=Seg_Flag?Alarm_Set[2]%10:10;//三目运算符,确保选中以500ms为周期闪烁
             break;
         }
             break;
     }
     
     
 }
 /*其他处理函数*/
 void Led_Proc()
 {
     if(Alarm_Flag==1)
             {
                 if(Clock_Disp[0]==Alarm[0]&&Clock_Disp[1]==Alarm[1]&&Clock_Disp[2]==Alarm[2])
                     Alarm_Enable_Flag=1;
                         if(Alarm_Enable_Flag==1)
                             {
                                 P2_3=0;
                                 P1=uc_Led;
                             }
                             else
                             {
                                 P2_3=1;
                                 P1=0xff;
                             }
                             
             }
         else
             {
                     P2_3=1;
                     P1=0xff;
             }
 }
 /*定时器0初始化函数*/
 void Timer0_Init(void)      //1毫秒@12.000MHz
 {
 ​
     TMOD &= 0xF0;           //设置定时器模式
     TMOD |= 0x01;           //设置定时器模式
     TL0 = 0x18;             //设置定时初始值
     TH0 = 0xFC;             //设置定时初始值
     TF0 = 0;                //清除TF0标志
     TR0 = 1;                //定时器0开始计时
     ET0 = 1;  // 允许闹钟0响铃时打断我
   EA = 1;   // 打开总开关:允许任何打断                      
     
 }
 ​
 /*中断服务函数*/
 void Timer0Server() interrupt 1//这是最高优先级的事情!
 {
     TL0 = 0x18;             //设置定时初始值
     TH0 = 0xFC;             //设置定时初始值
     if(++Key_Slow_Down==10) Key_Slow_Down=0;//中断10毫秒
     if(++Seg_Slow_Down==500) Seg_Slow_Down=0;//中断500毫秒
     if(++Seg_Pos==6) Seg_Pos=0;//中断6毫秒
     Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
     
 /*以下构成了时钟的根本流逝形式,进1相加*/
     if(++Timer_1000ms==1000)
     {
         Timer_1000ms=0;
         Clock_Disp[2]++;
         if(Clock_Disp[2]==60)
         {
             Clock_Disp[2]=0;
             Clock_Disp[1]++;
             if(Clock_Disp[1]==60)
             {
                 Clock_Disp[1]=0;
                 Clock_Disp[0]++;
                 if(Clock_Disp[0]==24)
                     Clock_Disp[0]=0;
             }
         }
     }
 /***********************************/
     if(++Timer_500ms==500)
     {
         Timer_500ms=0;
         Seg_Flag^=1;//异或算法 1-0嬗变,确保以500ms为周期闪烁
         if(Clock_Disp[0]>=12)
             uc_Led^=0xf0;
         else
             uc_Led^=0x0f;
         
     }
     
     
     
     
 }
 ​
 ​
 /*主函数程序*/
 void main()
 {
     Timer0_Init();
     
     while(1)
     {
         Key_Proc();
         Seg_Proc();
         Led_Proc();
     }
 }