🔧

蓝桥杯第十一届省赛单片机真题 — 刷题总结

蓝桥杯第十一届省赛单片机真题刷题总结:电压监测与统计系统,涵盖AD采集、EEPROM存储、下降沿检测等知识点

一、题目概述

基于 STC15F2K60S2 单片机(12MHz),实现一个 电压监测与统计系统,包含:

  • AD 电压采集与实时显示
  • 电压阈值设置(EEPROM 掉电保存)
  • 电压下降沿检测与计数
  • LED 状态指示
  • 非法按键错误计数

二、功能模块实现

2.1 数码管显示(3个界面,S12 切换)

界面 首位标识 显示内容 格式
0 U (11) 实时采集电压 U X.XX
1 P (12) 电压阈值设置 P X.XX
2 n (13) 下降沿计数 n XX
1
2
3
4
5
// 界面0示例:实时电压
Seg_Buf[0] = 11;  // 'U'
Seg_Buf[5] = AD_3_Data_100x / 100 % 10 + ',';  // 整数位+小数点
Seg_Buf[6] = AD_3_Data_100x / 10 % 10;
Seg_Buf[7] = AD_3_Data_100x / 1 % 10;

2.2 按键功能

按键 功能 有效界面 非法操作
S12 切换界面(0→1→2→0) 全局 不计错误
S13 清零下降沿计数 界面2 Error++
S16 阈值+0.5V(循环) 界面1 Error++
S17 阈值-0.5V(循环) 界面1 Error++

关键逻辑:切换到界面2时自动保存阈值到 EEPROM。

1
2
3
4
5
6
7
8
9
case 12:
    if(++Seg_Show_Mod == 2)  // 进入界面2时保存
    {
        Voltage_E2PROM = Voltage_Set / 10;
        EEPROM_Write(&Voltage_E2PROM, 0, 1);
    }
    if(Seg_Show_Mod == 3) Seg_Show_Mod = 0;
    Error = 0;
break;

2.3 电压下降沿检测

采用 Flag 标志位 实现边沿检测:

1
2
电压 >= 阈值 → Flag = 1(标记已越过阈值)
Flag == 1 且 电压 < 阈值 → 检测到下降沿,Flag = 0,计数+1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if(Voltage_Get >= Voltage_Set)
{
    Time5000 = 0;
    Flag = 1;
}
if((Flag) && (Voltage_Get < Voltage_Set))
{
    Flag = 0;
    Voltage_Count++;
}

2.4 LED 指示

LED 条件 含义
L1 Time5000 >= 5000 电压持续低于阈值超过5秒
L2 Voltage_Count % 2 == 1 下降沿计数为奇数
L3 Error >= 3 非法按键操作累计≥3次

三、踩坑记录(重点)

踩坑1:Time5000 == 5000 只命中 1ms

原始代码:

1
2
if(Time5000 == 5000) ucLed[0] = 1;
    else ucLed[0] = 0;

问题: Time5000 由 1ms 中断持续递增,等于 5000 仅持续 1ms,之后变为 5001、5002…,条件几乎不可能被主循环捕获。

修复: 使用 >= 替代 ==

1
2
if(Time5000 >= 5000) ucLed[0] = 1;
    else ucLed[0] = 0;

教训:对中断中持续递增的计数器,判断时必须用 >=,不能用 ==

踩坑2:Time5000 没有清零逻辑

原始代码: Time5000 从上电开始只增不减,无法正确表达"持续低于阈值5秒"的含义。

修复: 在电压 ≥ 阈值时清零,重新开始计时:

1
2
3
4
5
if(Voltage_Get >= Voltage_Set)
{
    Time5000 = 0;  // 电压达到阈值,重置5秒计时
    Flag = 1;
}

教训:定时计数器必须有明确的"重置时机",否则只是一个单调递增的计数器,无法表达时间区间。

踩坑3:LED 判断被困在下降沿代码块内

原始代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if((Flag) && (Voltage_Get < Voltage_Set))
{
    Flag = 0;
    Voltage_Count++;
    if(Time5000 >= 5000) ucLed[0] = 1;  // ← 在下降沿块内
        else ucLed[0] = 0;
    if(Voltage_Count % 2) ucLed[1] = 1;
        else ucLed[1] = 0;
    if(Error >= 3) ucLed[2] = 1;
        else ucLed[2] = 0;
}

问题: LED 状态只在下降沿发生的瞬间才更新。如果没有新的下降沿,即使条件满足(如 Error ≥ 3),LED 也不会改变。

修复: 将 LED 判断移到下降沿块外部,使其每次 Led_Proc() 调用都执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 下降沿检测
if((Flag) && (Voltage_Get < Voltage_Set))
{
    Flag = 0;
    Voltage_Count++;
}
// LED 判断 — 独立于下降沿,持续刷新
if(Time5000 >= 5000) ucLed[0] = 1;
    else ucLed[0] = 0;
if(Voltage_Count % 2) ucLed[1] = 1;
    else ucLed[1] = 0;
if(Error >= 3) ucLed[2] = 1;
    else ucLed[2] = 0;

教训:LED/显示等"状态输出"逻辑应独立于"事件检测"逻辑,确保状态能持续刷新,不要和事件触发耦合在一起。

踩坑4:计数界面高位未熄灭(前导零问题)

问题: 计数界面(界面2)固定显示两位数,当 Voltage_Count 为个位数(如 3)时,数码管显示 03 而非 3,不符合题目要求。

原始代码:

1
2
3
4
case 2:
    Seg_Buf[6] = Voltage_Count / 10 % 10;  // 十位始终显示,count=3时显示0
    Seg_Buf[7] = Voltage_Count / 1 % 10;
break;

修复: 高位为0时赋值10(熄灭码),实现前导零消隐:

1
2
3
4
5
case 2:
    Seg_Buf[6] = Voltage_Count / 10 % 10;
    if(Seg_Buf[6] == 0) Seg_Buf[6] = 10;  // 高位为0则熄灭
    Seg_Buf[7] = Voltage_Count / 1 % 10;
break;

教训:数码管显示数值时,高位前导零应消隐(赋值为熄灭码10),这是蓝桥杯的常见扣分点。


四、通用经验总结

编号 经验 适用场景
1 中断计数器判断用 >=,不用 == 所有基于中断递增的定时判断
2 定时计数器必须有清零时机 超时检测、周期计时
3 状态输出与事件检测分离 LED、数码管等需持续刷新的外设
4 大括号作用域要仔细确认 缩进容易误导,需对照 {} 配对
5 Flag 边沿检测模式:>=阈值→置1<阈值且Flag→清0并计数 电压/信号的上升沿/下降沿检测
6 计数显示高位为0时应熄灭(显示10),不要显示前导零 数码管计数/数值显示
comments powered by Disqus