蓝桥杯第九届省赛代码错误总结

蓝桥杯第九届省赛代码错误总结

错误列表

1. :cross_mark: 系统流转计时器未累加

错误代码:

void Led_Proc()
{
    unsigned char i;
    ucLed[0]=1;  // 多余的语句
    // ❌ 没有 Sys_Tick++ 语句
    if(Sys_Tick == Led_Time_Data[Led_Mode])  // Sys_Tick永远是0
    {
        Sys_Tick = 0;
        switch(Led_Mode)
        {
            // ...
        }
    }
}

错误原因:

  • 计时器变量Sys_Tick必须累加才能计时

  • 忘记累加导致条件永远不满足,LED不会流转

  • 多余的ucLed[0]=1会导致L1一直亮着

正确代码:

void Led_Proc()
{
    unsigned char i;
    if(Led_flag==0)
    {
        for(i=0;i<8;i++)
            ucLed[i]=0;
    }
    else
    {
        Sys_Tick++;  // ✓ 每次调用累加计时器
        if(Sys_Tick == Led_Time_Data[Led_Mode])
        {
            Sys_Tick = 0;
            switch(Led_Mode)
            {
                // ...
            }
        }
    }
}

2. :cross_mark: 亮度控制变量名混淆

错误代码:

void AD_DA()
{
    Voltage = Ad_Read(0x83) / 51.0;
    if((Voltage >= 0) && (Voltage < 1))
        pwm_period = 0;  // ❌ 修改了PWM周期计数器
    else if((Voltage >= 1) && (Voltage < 2))
        pwm_period = 1;  // ❌ 错误
    // ...
}
​
// 定时器中断中:
void Timer1_Isr(void) interrupt 3
{
    // ...
    pwm_period = (++pwm_period) % 5;  // pwm_period在中断中自动累加
    if(pwm_period < pwm_compare)      // 用pwm_compare控制占空比
        Led_Disp(ucLed);
    else
        Led_Off();
}

错误原因:

  • pwm_period:PWM周期计数器,在中断中自动累加(0→1→2→3→4→0循环)

  • pwm_compare:PWM比较值,决定占空比/亮度等级

  • AD_DA()中修改pwm_period会破坏PWM循环,导致亮度控制失效

正确代码:

void AD_DA()
{
    Voltage = Ad_Read(0x83) / 51.0;
    if((Voltage >= 0) && (Voltage < 1.25))
        pwm_compare = 1;  // ✓ 修改PWM比较值(亮度等级1)
    else if((Voltage >= 1.25) && (Voltage < 2.5))
        pwm_compare = 2;  // ✓ 亮度等级2
    else if((Voltage >= 2.5) && (Voltage < 3.75))
        pwm_compare = 3;  // ✓ 亮度等级3
    else
        pwm_compare = 4;  // ✓ 亮度等级4
}

同样的错误也出现在:

  • 数码管显示亮度等级(第177-178行)

3. :cross_mark: 流转间隔调整使用错误的索引

错误代码:

case 5:  // S5加键
    if(Seg_Mode == 0)
    {
        if(Seg_Set_Index == 0)
        {
            Led_Mode++;
            if(Led_Mode == 4)
                Led_Mode = 0;
        }
        else
        {
            Led_Time_Data[ucLed_index] += 100;  // ❌ 使用了LED指针索引
            if(Led_Time_Data[ucLed_index] >= 1200)
                Led_Time_Data[ucLed_index] = 1200;
        }
    }
break;

错误原因:

  • ucLed_index:LED流转指针(0-7),指示当前点亮哪个LED

  • Led_Mode:当前运行模式(0-3),对应模式1-4

  • 应该调整当前运行模式的流转间隔,而不是LED指针位置的间隔

  • 导致调整的参数不是正在显示的模式

正确代码:

else
{
    Led_Time_Data[Led_Mode] += 100;  // ✓ 使用当前模式索引
    if(Led_Time_Data[Led_Mode] >= 1200)
        Led_Time_Data[Led_Mode] = 1200;
}

同样的错误也出现在:

  • S4减键调整(第115行)

  • 数码管显示流转间隔(第151-153行、第163-165行)


4. :cross_mark: 模式切换条件过于复杂(可优化)

原始代码:

case 0:
    // 模式1 L1-L8
    for(i=0; i<8; i++)
        ucLed[i] = 0;
    ucLed[ucLed_index] = 1;
    ucLed_index++;
    if(ucLed_index >= 8)
        ucLed_index = 0;
    // ❌ 判断是否是L8点亮(过于复杂)
    if((ucLed[7]==1) && (ucLed[0]==0) && (ucLed[1]==0) && ...)
        Led_Mode = 1;
break;

优化原因:

  • 题目要求固定顺序循环运行,不需要判断具体LED状态

  • 索引归零时就是一轮结束,可以直接切换模式

  • 简化逻辑,减少出错可能

优化后的代码:

case 0:
    // 模式1 L1-L8
    for(i=0; i<8; i++)
        ucLed[i] = 0;
    ucLed[ucLed_index] = 1;
    ucLed_index++;
    if(ucLed_index >= 8)
    {
        ucLed_index = 0;
        Led_Mode = 1;  // ✓ 一轮结束直接切换
    }
break;

注意: 虽然不影响功能,但简化逻辑可以避免出错。同样的优化也适用于case 1。


5. :cross_mark: 数码管显示高位为0时未熄灭

错误代码:

// 长亮时:
Seg_Buf[4] = Led_Time_Data[Led_Mode]/1000%10;  // ❌ 千位为0时显示0,不美观
​
// 闪烁时:
Seg_Buf[4] = Seg_Star_Flag ? Led_Time_Data[Led_Mode]/1000%10 : 10;
// ❌ 虽然闪烁时熄灭,但当千位为0时,亮起时会显示0

错误原因:

  • 数码管显示多位数字时,高位为0应该熄灭(显示为空),而不是显示"0"

  • 例如:400ms应该显示为"_400"(千位熄灭),而不是"0400"

  • 这是数码管显示的常规做法,提升显示美观度

正确代码:

// 长亮时:千位为0则熄灭
Seg_Buf[4] = ((Led_Time_Data[Led_Mode]/1000%10) == 0) ? 10 : (Led_Time_Data[Led_Mode]/1000%10);
​
// 闪烁时:先判断千位是否为0,再判断是否闪烁
Seg_Buf[4] = ((Led_Time_Data[Led_Mode]/1000%10) == 0) ? 10 :
             (Seg_Star_Flag ? Led_Time_Data[Led_Mode]/1000%10 : 10);

代码解析:

长亮写法:

// 单层三元运算符
(条件) ? 真值 : 假值
​
// 展开为:
if((Led_Time_Data[Led_Mode]/1000%10) == 0)
    Seg_Buf[4] = 10;  // 千位为0,显示熄灭
else
    Seg_Buf[4] = Led_Time_Data[Led_Mode]/1000%10;  // 千位不为0,显示数字

闪烁写法(嵌套三元运算符):

// 外层:判断千位是否为0
// 内层:根据闪烁标志决定显示还是熄灭
​
(千位==0) ? 熄灭 : (闪烁标志 ? 显示数字 : 熄灭)
​
// 展开为:
if((Led_Time_Data[Led_Mode]/1000%10) == 0)
{
    Seg_Buf[4] = 10;  // 千位为0,一直熄灭
}
else
{
    if(Seg_Star_Flag)
        Seg_Buf[4] = Led_Time_Data[Led_Mode]/1000%10;  // 闪烁亮起时显示数字
    else
        Seg_Buf[4] = 10;  // 闪烁熄灭时显示熄灭
}

6. :cross_mark: S4按键短长按判断存在的问题

当前代码实现:

void Key_Proc()
{
    // ... 按键扫描代码 ...
​
    if(Key_Down == 4)  // S4按下
        Time_Flag = 1;  // 启动计时
​
    if(Count_2000Ms < 2000)  // ⚠️ 短按判断(外层)
    {
        if(Key_Up == 4)  // S4抬起
        {
            Time_Flag = Count_2000Ms = 0;  // 状态复位
            if(Seg_Mode==0)
            {
                // 执行减操作...
            }
        }
    }
    else  // ⚠️ 长按判断(外层)
    {
        if(Key_Old == 4)  // S4长按中
        {
            if(Seg_Mode==0)
                Seg_Mode=Seg_flag=1;  // ⚠️ 会重复执行
        }
        if(Key_Up == 4)  // S4抬起
            Time_Flag = Count_2000Ms = Seg_Mode=Seg_flag=0;
    }
}
​
// 定时器中断中累加计时器
void Timer1_Isr(void) interrupt 3
{
    // ...
    if(Time_Flag == 1)
    {
        if(++Count_2000Ms >= 2000)
            Count_2000Ms = 2000;  // 防止溢出
    }
}

存在的问题:

  1. 长按触发重复执行 :warning:

    • Count_2000Ms >= 2000后,每次Key_Proc()(每10ms)都会进入else分支

    • 如果按键一直按着,Seg_Mode=Seg_flag=1会重复赋值很多次

    • 虽然多次赋值1不影响功能,但逻辑不优雅

  2. 短按判断时机不够精确 :warning:

    • 外层if持续检查Count_2000Ms < 2000,即使没按键也在判断

    • 更好的做法是在按键松开时判断计时器值

优化建议(参考标准写法):

bit Long_Press_Triggered = 0;  // 添加长按触发标志
​
void Key_Proc()
{
    // ... 按键扫描代码 ...
​
    // S4按下:启动计时
    if(Key_Down == 4)
    {
        Time_Flag = 1;
        Long_Press_Triggered = 0;  // ✓ 重置长按标志
    }
​
    // 长按判断:达到2000ms且未触发过
    if(Count_2000Ms >= 2000 && !Long_Press_Triggered && Key_Old == 4)
    {
        Long_Press_Triggered = 1;  // ✓ 标记已触发,只执行一次
        if(Seg_Mode == 0)
            Seg_Mode = Seg_flag = 1;
    }
​
    // S4松开:根据计时器值判断短按/长按
    if(Key_Up == 4)
    {
        if(Count_2000Ms < 2000)  // 短按
        {
            if(Seg_Mode == 0)
            {
                // 执行减操作...
            }
        }
        else  // 长按松开
        {
            Seg_Mode = Seg_flag = 0;  // 关闭显示
        }
​
        // 复位计时器
        Time_Flag = Count_2000Ms = 0;
    }
}

优化要点:

  • :white_check_mark: 使用Long_Press_Triggered标志位防止重复触发

  • :white_check_mark: 在按键松开时统一判断短按/长按,逻辑更清晰

  • :white_check_mark: 计时器复位统一处理,不会遗漏

注意: 当前代码虽然可以工作,但优化后逻辑更清晰,更符合编程规范。


总结

最严重的错误(会导致功能完全无法工作):

  1. 系统计时器未累加 - LED不会流转,核心功能失效

  2. PWM变量混淆 - 亮度控制失效

中等错误(功能异常或不完整):

  1. 流转间隔索引错误 - 调整的不是当前模式的参数

  2. 高位0未熄灭 - 显示不美观,不符合数码管显示规范

轻微问题(可优化但不影响基本功能):

  1. 模式切换条件复杂 - 可以简化

  2. 长短按逻辑不完善 - 可能重复执行赋值


学到的经验

  1. 计时器逻辑三部曲 - 累加 → 判断 → 复位,不能忘记累加

  2. 变量命名要区分 - period是计数器,compare是阈值;index是位置,mode是模式

  3. 数码管高位处理 - 高位为0时应该熄灭,使用三元运算符优雅处理

  4. 嵌套三元运算符技巧 - (外层条件) ? 值1 : (内层条件 ? 值2 : 值3) 可以处理多层逻辑

  5. 长按判断防重复 - 使用标志位确保长按只触发一次

  6. 按键状态机要清晰 - 按下启动计时 → 中断累加 → 松开时判断,逻辑要分明

  7. 逻辑要追求简洁 - 能用简单判断就不用复杂条件(KISS原则)

  8. 优化不影响功能也要做 - 代码可读性和可维护性同样重要


生成时间:2026-02-03
蓝桥杯第九届省赛代码调试总结