旧模板解析
头文件区域
#include <STC15F2K60S2.H>//单片机寄存器专用头文件
#include <Init.h>//初始化底层驱动专用头文件
#include <Led.h>//Led底层驱动专用头文件
#include <Key.h>//按键底层驱动专用头文件
#include <Seg.h>//数码管底层驱动专用头文件
-
理解:这是典型的模块化分层。
Init.h通常封装了板级初始化(如关闭蜂鸣器、关闭LED、设置IO口模式);Seg.h、Led.h、Key.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_Buf、Seg_Point、ucLed是“显示缓冲区”。主程序修改这些数组的值,中断服务程序只负责把这些值刷到硬件上。显示与计算分离,避免了画面闪烁。 -
减速标志:
Key_Slow_Down和Seg_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 才被执行一次。
注意事项(避坑指南)
-
不要在
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_Proc 或 Led_Proc 中存在耗时操作(如乘除运算),仍会拖慢 Key_Proc 的检测灵敏度。 |