第七周作业旧模板解析

旧模板解析

头文件区域

 #include <STC15F2K60S2.H>//单片机寄存器专用头文件
 #include <Init.h>//初始化底层驱动专用头文件
 #include <Led.h>//Led底层驱动专用头文件
 #include <Key.h>//按键底层驱动专用头文件
 #include <Seg.h>//数码管底层驱动专用头文件
  • 理解:这是典型的模块化分层。Init.h 通常封装了板级初始化(如关闭蜂鸣器、关闭LED、设置IO口模式);Seg.hLed.hKey.h 则是底层硬件驱动接口。

  • 意义:将底层寄存器操作封装起来,主程序只关心业务逻辑,不关心具体引脚怎么翻转,便于代码移植和维护。

变量声明区域

 unsigned char Key_Val,Key_Down,Key_Old,Key_Up;//按键专用变量
 unsigned char Key_Slow_Down;//按键减速专用变量
 unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};//数码管显示数据存放数组
 unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点数据存放数组
 unsigned char Seg_Pos;//数码管扫描专用变量
 unsigned int Seg_Slow_Down;//数码管减速专用变量
 unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//Led显示数据存放数组
  • 核心理解

    • 显存(Buffer)机制Seg_BufSeg_PointucLed 是“显示缓冲区”。主程序修改这些数组的值,中断服务程序只负责把这些值刷到硬件上。显示与计算分离,避免了画面闪烁。

    • 减速标志Key_Slow_DownSeg_Slow_Down 不是简单的延时变量,而是时间片标志。它们由中断累加,在主循环中判断,实现了非阻塞的定时任务调度。

按键处理函数

 void Key_Proc()
 {
     if(Key_Slow_Down) return;
     Key_Slow_Down = 1;//键盘减速程序
 ​
     Key_Val = Key_Read();//实时读取键码值
     Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
     Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
     Key_Old = Key_Val;//辅助扫描变量
 }
  • 精髓:使用了二次采样边沿检测法

    • Key_Old ^ Key_Val:异或得出发生变化的位(由0变1或由1变0)。

    • Key_Val 做与运算,得到当前为1且刚刚发生变化的位,即下降沿(按下)。

    • ~Key_Val 做与运算,得到当前为0且刚刚发生变化的位,即上升沿(释放)。

  • 好处:既实现了消抖(10ms采样一次),又精准捕获了“按下瞬间”和“松开瞬间”,且不阻塞 CPU

信息处理函数

 void Seg_Proc()
 {
     if(Seg_Slow_Down)return;
     Seg_Slow_Down = 1;//数码管减速程序
 }

这段代码本质上是一个非阻塞的定时执行门控,它确保 Seg_Proc 内部的业务逻辑每隔 500ms 才被执行一次。

:warning: 注意事项(避坑指南)

  • 不要在 Seg_Proc 里写 Seg_Disp() 驱动函数:驱动扫描已经在定时器中断里全自动完成了,这里只负责往 Seg_Buf[] 里填数。如果在这里调用显示驱动,会与中断抢占资源,导致画面撕裂或鬼影。

  • 如果赛题要求数据实时性极高(如频率计、PWM实时占空比显示),500ms 的刷新会有滞后感。这时候你可以把 500 改成 50(即 50ms),或者干脆把数据更新放在 Key_Proc 里(按键按下立即刷新),甚至直接在主循环不加限制地刷新(但要保证中断里的扫描不受影响)。

其他显示函数(LED和蜂鸣器)

 void Led_Proc()
 {
 ​
 }

定时器0初始化函数

 void Timer0_Init(void)      //1毫秒@12.000MHz
 {
     AUXR &= 0x7F;           //定时器时钟12T模式
     TMOD &= 0xF0;           //设置定时器模式
     TL0 = 0x18;             //设置定时初始值
     TH0 = 0xFC;             //设置定时初始值
     TF0 = 0;                //清除TF0标志
     TR0 = 1;                //定时器0开始计时
     ET0 = 1;                //定时器中断0打开
     EA = 1;                 //总中断打开
 }

定时器0、12MHz、16位自动重载、12T

添加“ET0 = 1”定时器中断0打开“EA = 1”总中断打开

定时器0中断服务函数

 void Timer0Server() interrupt 1
 {
     if(++Key_Slow_Down == 10)Key_Slow_Down = 0;//按键减速专用
     if(++Seg_Slow_Down == 500)Seg_Slow_Down = 0;//数码管减速专用
     if(++Seg_Pos == 8)Seg_Pos = 0;//数码管,Led显示专用
     Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
     Led_Disp(Seg_Pos,ucLed[Seg_Pos]);
 }
  • 时间基准:1ms 中断一次。

  • 动态扫描:每 1ms 切换一位数码管和对应 LED,8ms 完整扫描一遍 8 位数码管。刷新频率为 125Hz(1 / 0.008s),远超人眼临界闪烁频率,保证了显示稳定无鬼影。

  • 任务调度:中断只负责“累加计数”和“物理刷新”,绝不处理复杂逻辑(如数值计算),保证了中断的快速响应。

main函数区

 void main()
 {
     System_Init();
     Timer0_Init();
     while(1)
     {
         Key_Proc();
         Seg_Proc();
         Led_Proc();
     }
 ​
 }
  • 理解:主循环是一个超循环(Super Loop)。由于中断已经搞定了数码管刷新,主循环不需要处理显示动作。

  • 分工

    • Key_Proc:独立识别按键事件。

    • Seg_Proc & Led_Proc:通常在这里编写业务代码,比如“根据按键改变 Seg_Buf 里的数值”或“控制 ucLed 的亮灭”。因为中断在背后刷新,你在主循环里改显存,画面会平滑更新。

总结:该模板的精妙与不足

维度 评价
优点 非阻塞架构:没有 delay,CPU 利用率极高; 显存映射:画面稳定,逻辑清晰; 按键算法:精准捕获边沿,支持组合键与长按扩展; 可读性强:分层明确,符合工程规范。
不足/风险 Seg_Proc 周期为 500ms,若用于实时性要求高的动态数据(如秒表),显存刷新会有 0.5s 的视觉滞后,通常需要改为立即刷新或更短周期; 主循环虽然快,但若 Seg_ProcLed_Proc 中存在耗时操作(如乘除运算),仍会拖慢 Key_Proc 的检测灵敏度。