蓝桥杯第十二届省赛单片机真题(第二场) — 刷题总结
一、题目概述
基于 STC15F2K60S2 单片机(12MHz),实现一个 频率/周期/电压综合测量系统,包含:
- Timer0 计数器模式测量频率,并计算周期
- PCF8591 双通道 AD 采集(通道1光敏 / 通道3变阻器)
- 3个显示界面(S4切换):频率 / 周期 / 电压
- S7 短按保存频率参数,长按切换 LED 使能
- LED 指示灯联动显示
二、踩坑记录(重点)
踩坑1:按键长按在 ISR 中检测 Key_Up — 条件互斥,永远无法触发
错误代码:
// Timer1 ISR 中
if(Key_Old == 7)
{
if(++ Key_Long >= 1000)
{
if(Key_Up == 7) // BUG:与外层条件互斥
{
Led_Enable ^= 1;
Key_Long = 0;
}
}
}
问题分析(3个致命错误):
| 编号 | 错误 | 原因 |
|---|---|---|
| 1 | Key_Up 在 ISR 中无效 |
Key_Up 是 Key_Proc()(主循环10ms)中的瞬态边沿值,ISR 每1ms执行,几乎不可能在 Key_Long >= 1000 时恰好捕获到 |
| 2 | 外层与内层条件互斥 | 松手时 Key_Old 变为0 → 外层 Key_Old == 7 为假;而 Key_Up == 7 恰好在松手时才为真。两个条件永远无法同时成立 |
| 3 | Key_Long 未在松手时清零 |
短按后残留累加值,下次按下从残值继续计数,可能误触发 |
修复:按住计时、达阈值立即触发、松手清零
// Timer1 ISR 中
if(Key_Val == 7)
{
Key_Long++;
if(Key_Long == 1000) // 用 == 防止持续触发
{
Led_Enable ^= 1;
}
}
else
{
Key_Long = 0; // 松手或按其他键时清零
}
教训:长按检测的核心三要素 — ①检测当前按键状态(不是边沿);②计数达阈值立即触发(不要等松手);③松手时必须清零计数器。
踩坑2:长按短按未做互斥 — 长按同时触发短按动作
错误代码:
// Key_Proc() 中
switch(Key_Down)
{
case 7:
Freq_Para = Freq; // S7 按下瞬间就触发
break;
}
问题: S7 按下的瞬间 Key_Down == 7 立即成立,短按动作已经执行。之后即使触发了长按,短按动作无法撤回。
修复:S7 短按改为松手时判断,长按阈值内才执行
// Key_Proc() 中 — S7 从 Key_Down 移到 Key_Up 判断
if(Key_Up == 7 && Key_Long < 1000)
{
Freq_Para = Freq; // 松手时计数<1000,说明是短按
}
互斥逻辑流程:
按下S7 → ISR开始计时 Key_Long++
├─ <1秒松手 → Key_Up==7 且 Key_Long<1000 → 执行短按(保存频率)
└─ ≥1秒按住 → Key_Long==1000 → 执行长按(切换LED使能)
→ 之后松手 → Key_Long≥1000 → 短按条件不成立,不误触
教训:长按短按互斥的标准做法 — 短按动作延迟到松手(
Key_Up)时执行,并判断Key_Long < 阈值才生效。
踩坑3:LED 使能关闭后未清零 — LED 维持最后状态不灭
错误代码:
void Led_Proc()
{
if(Led_Enable == 1)
{
// 正常设置 ucLed[0]~ucLed[4]...
}
// Led_Enable == 0 时,什么都不做!
}
问题: Led_Enable 切换为0后,Led_Proc 跳过整个 if 块。ucLed[] 数组保留上次的值,ISR 中 Led_Disp(ucLed) 持续输出 → LED 不灭。
修复: 增加 else 分支清零:
if(Led_Enable == 1)
{
// 正常逻辑...
}
else
{
memset(ucLed, 0, 8); // 关闭所有LED
}
教训:使能标志控制的外设,禁用分支必须显式清除输出状态,不能依赖"不更新就会灭"的假设。
三、按键长按模板(通用)
/* === 全局变量 === */
unsigned int Key_Long = 0; // 长按计时器
/* === Timer ISR(1ms)中 === */
if(Key_Val == TARGET_KEY)
{
Key_Long++;
if(Key_Long == THRESHOLD) // == 只触发一次
{
// 长按动作
}
}
else
{
Key_Long = 0; // 松手清零
}
/* === Key_Proc() 中 === */
// 需要互斥的短按:用 Key_Up 而非 Key_Down
if(Key_Up == TARGET_KEY && Key_Long < THRESHOLD)
{
// 短按动作
}
四、通用经验总结
| 编号 | 经验 | 适用场景 |
|---|---|---|
| 1 | 长按检测三要素:检测当前状态、达阈值触发、松手清零 | 所有长按需求 |
| 2 | 长按用 == 不用 >=,防止持续重复触发 |
ISR 中的计数触发 |
| 3 | 长短按互斥:短按延迟到 Key_Up 判断,条件加 Key_Long < 阈值 |
S7/S4 等需要区分长短按的按键 |
| 4 | ISR 中不要依赖主循环的瞬态边沿变量(Key_Up/Key_Down) |
任何 ISR 与主循环交互的场景 |
| 5 | 使能标志的禁用分支必须显式清除输出 | LED、DAC、蜂鸣器等外设控制 |