蓝桥杯第十二届省赛(第一次)代码错误总结

蓝桥杯第十二届省赛(第一次)代码错误总结

错误列表

1. :cross_mark: Led_Proc()中没有调用Led_Disp()导致LED状态不刷新或延迟刷新

错误代码 (PWM调光版本 - 不是满分代码):

/**
 * @brief LED控制处理
 * 更新LED的状态,这里是固定的闪烁或者模式灯。
 */
void Led_Proc()
{
    idata unsigned char i;
    if(Led_Flag==0)
    {
        for(i=0;i<8;i++)
            ucLed[i]=0;
    }
    else
    {
        ucLed[0]=(AD_3_Data>AD_3_Data_Store)?1:0;
        ucLed[1]=(Freq>Freq_Store)?1:0;
        ucLed[2]=(Seg_Mode==0)?1:0;
        ucLed[3]=(Seg_Mode==1)?1:0;
        ucLed[4]=(Seg_Mode==2)?1:0;
        ucLed[5]=ucLed[6]=ucLed[7]=0;
    }
    // ❌ 没有调用Led_Disp()刷新硬件!
    // Led_Disp(ucLed); // 被注释掉了
}
​
/**
 * @brief 定时器1中断服务函数
 * 每1ms执行一次,处理需要定时执行的任务
 */
void Timer1_Isr(void) interrupt 3
{
    uwTick++; // 系统滴答时间加1
​
    // 数码管动态扫描
    Seg_Pos = (++Seg_Pos) % 8; // 切换到下一位数码管
    if (Seg_Buf[Seg_Pos] > 20)
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos] - ',', 1);
    else
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], 0);
​
    // ❌ LED控制 (PWM) - 在中断里用PWM控制LED
    pwm_period = (++pwm_period) % 10; // PWM周期计数器 (0-9)
    if (pwm_period < pwm_compare)
        Led_Disp(ucLed); // 在占空比的有效时间内点亮LED
    else
        Led_Off(); // 在占空比的无效时间内熄灭LED
​
    // 频率测量:每1000ms (1s) 更新一次
    if (++Time_1s == 1000)
    {
        Time_1s = 0; // 计时清零
        Freq = (TH0 << 8) | TL0;
        TH0 = TL0 = 0; // 清空T0计数器,开始下一个周期的测量
    }
    if(Key7_Flag==1)
    {
        Count_1000ms++;
        if(Count_1000ms>=1000)
            Count_1000ms=1000;
    }
}
​
/**
 * @brief 按键扫描处理
 * 检测按键的按下和抬起事件,并根据按键执行相应操作。
 */
void Key_Proc()
{
    Key_Val = Key_Read();
    Key_Down = Key_Val & (Key_Val ^ Key_Old);
    Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
    Key_Old = Key_Val;
​
    switch(Key_Down)
    {
        case 4:
            Seg_Mode++;
            if(Seg_Mode==3)
                Seg_Mode=0;
            if(Seg_Mode==1)
                Output_Mode=1;
            // ❌ 没有立即调用Led_Proc()刷新LED!
        break;
​
        case 5:
            if(Seg_Mode==2)
                Output_Mode^=1;
            // ❌ 没有立即调用Led_Proc()刷新LED!
        break;
​
        case 6:
            AD_3_Data_Store=AD_3_Data;
        break;
    }
​
    if(Key_Down==7)
        Key7_Flag=1;
    if(Key_Up==7)
    {
        if(Count_1000ms>=1000)
            Led_Flag^=1;
        else
            Freq_Store=Freq;
        Key7_Flag=Count_1000ms=0;
    }
}

错误原因:

问题1: Led_Proc()没有调用Led_Disp()刷新硬件

  • Led_Proc()只更新了ucLed[]数组(内存数据)

  • 没有调用Led_Disp(ucLed)刷新硬件端口

  • LED硬件状态不会立即改变

问题2: 依赖Timer1中断用PWM刷新LED

  • Timer1中断每1ms执行一次PWM控制逻辑

  • PWM周期为10ms,占空比60% (pwm_compare=6)

  • 0-5周期(6ms):调用Led_Disp(ucLed)点亮LED

  • 6-9周期(4ms):调用Led_Off()熄灭LED

问题3: 按键处理后没有立即刷新LED

  • Key_Proc()修改了Seg_Mode等状态变量

  • 没有立即调用Led_Proc()更新LED状态

  • 需要等待调度器下一次调用Led_Proc()(最多1ms延迟)

PWM控制导致的延迟分析:

假设按下S4时,pwm_period = 7(处于Led_Off阶段):
  ↓
Key_Proc()检测到按键 → 更新Seg_Mode(例如从0变成1)
  ↓ (此时L2应该灭,L3应该亮,但还没刷新)
等待调度器下一次调用Led_Proc()(最多1ms)
  ↓
Led_Proc()执行 → 更新ucLed数组(ucLed[2]=0,ucLed[3]=1)
  ↓ (内存数据已更新,但硬件还没刷新)
Timer1中断执行:
  pwm_period = 7 → Led_Off() ← LED全灭!新状态看不到!
  pwm_period = 8 → Led_Off() ← LED还是全灭!
  pwm_period = 9 → Led_Off() ← LED还是全灭!
  pwm_period = 0 → Led_Disp(ucLed) ← 3ms后才显示新状态!

总延迟计算:

调度器延迟(最多1ms) + PWM熄灭期延迟(最多3ms) = 最多4ms总延迟

4T测评失败原因:

  • 状态变化到LED硬件正确显示之间,存在最多4ms的延迟

  • 4T测评系统在微秒级检测,会捕捉到这个"错误状态前沿"

  • 导致测试点报错:“存在错误状态前沿”

正确代码 (满分版本):

/**
 * @brief LED控制处理
 * 更新LED的状态,这里是固定的闪烁或者模式灯。
 */
void Led_Proc()
{
    idata unsigned char i;
    if(Led_Flag==0)
    {
        for(i=0;i<8;i++)
            ucLed[i]=0;
    }
    else
    {
        ucLed[0]=(AD_3_Data>AD_3_Data_Store)?1:0;
        ucLed[1]=(Freq>Freq_Store)?1:0;
        ucLed[2]=(Seg_Mode==0)?1:0;
        ucLed[3]=(Seg_Mode==1)?1:0;
        ucLed[4]=(Seg_Mode==2)?1:0;
        ucLed[5]=ucLed[6]=ucLed[7]=0;
    }
    Led_Disp(ucLed);  // ✓ 立即刷新硬件!
}
​
/**
 * @brief 定时器1中断服务函数
 * 每1ms执行一次,处理需要定时执行的任务
 */
void Timer1_Isr(void) interrupt 3
{
    uwTick++; // 系统滴答时间加1
​
    // 数码管动态扫描
    Seg_Pos = (++Seg_Pos) % 8; // 切换到下一位数码管
    if (Seg_Buf[Seg_Pos] > 20)
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos] - ',', 1);
    else
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], 0);
​
    // ✓ Timer1中断只刷新数码管,不刷新LED!
​
    // 频率测量:每1000ms (1s) 更新一次
    if (++Time_1s == 1000)
    {
        Time_1s = 0; // 计时清零
        Freq = (TH0 << 8) | TL0;
        TH0 = TL0 = 0; // 清空T0计数器,开始下一个周期的测量
    }
    if(Key7_Flag==1)
    {
        Count_1000ms++;
        if(Count_1000ms>=1000)
            Count_1000ms=1000;
    }
}
​
/**
 * @brief 按键扫描处理
 * 检测按键的按下和抬起事件,并根据按键执行相应操作。
 */
void Key_Proc()
{
    Key_Val = Key_Read();
    Key_Down = Key_Val & (Key_Val ^ Key_Old);
    Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
    Key_Old = Key_Val;
​
    switch(Key_Down)
    {
        case 4:
            Seg_Mode++;
            if(Seg_Mode==3)
                Seg_Mode=0;
            if(Seg_Mode==1)
                Output_Mode=1;
            Led_Proc();  // ✓ 立即调用Led_Proc()刷新LED!
        break;
​
        case 5:
            if(Seg_Mode==2)
                Output_Mode^=1;
            Led_Proc();  // ✓ 立即调用Led_Proc()刷新LED!
        break;
​
        case 6:
            AD_3_Data_Store=AD_3_Data;
        break;
    }
​
    if(Key_Down==7)
        Key7_Flag=1;
    if(Key_Up==7)
    {
        if(Count_1000ms>=1000)
            Led_Flag^=1;
        else
            Freq_Store=Freq;
        Key7_Flag=Count_1000ms=0;
    }
}

满分代码的关键改进:

  1. Led_Proc()调用Led_Disp():

    • 每次Led_Proc()执行完更新ucLed[]数组后

    • 立即调用Led_Disp(ucLed)刷新硬件

    • 确保内存数据和硬件状态同步

  2. Key_Proc()立即刷新LED:

    • 按键处理修改状态变量后

    • 立即调用Led_Proc()刷新LED(case 4和case 5)

    • 不需要等待调度器下一次调用

  3. Timer1中断不处理LED:

    • Timer1中断只负责数码管动态扫描

    • 不再用PWM控制LED

    • LED和数码管刷新完全分离

优势对比:

特性 PWM版本(错误) 满分版本(正确)
Led_Proc()是否调用Led_Disp() :cross_mark: 没有调用 ✓ 每次都调用
LED刷新方式 Timer1中断PWM控制 Led_Proc()直接刷新
按键后LED刷新 等待调度器+PWM周期 立即刷新
最大延迟 最多4ms(1ms调度+3ms PWM) 最多1ms(仅调度器周期)
4T测评 :cross_mark: 失败(错误状态前沿) ✓ 通过

关键点:

  • Led_Proc()中必须调用Led_Disp(ucLed)直接刷新硬件

  • 按键处理后立即调用Led_Proc()实现零延迟刷新

  • Timer1中断只负责数码管动态扫描,不刷新LED

  • 不要在Timer1中断里用PWM控制LED - PWM的熄灭期会导致状态变化延迟显示

  • LED刷新和数码管刷新完全分离,职责清晰

  • LED状态变化零延迟(只有调度器1ms周期),避免瞬时错误状态

为什么不能用PWM:

  • PWM有熄灭周期(例如60%占空比有4ms熄灭期)

  • 状态变化可能发生在熄灭期,导致新状态无法立即显示

  • 即使新的ucLed数组已更新,也要等到下一个PWM亮周期才显示

  • 这个延迟会被4T测评系统检测为"错误状态前沿"

  • 结论:LED指示灯应该直接控制,不使用PWM


2. :cross_mark: 缺少DS18B20初始化等待

错误代码:

void main()
{
    System_Init();
    Scheduler_Init();
    Timer1_Init();  // ❌ 直接启动,没有等待DS18B20初始化
​
    while (1)
    {
        Scheduler_Run();
    }
}

错误原因:

  • DS18B20温度传感器上电后需要时间初始化

  • 初始读数是85℃(这是DS18B20的固定默认值)

  • 如果测评系统在DS18B20刚上电时就开始检测温度值,会读到85℃

  • 导致:

    • 温度显示错误(显示85℃而不是实际温度)

    • DAC输出错误(因为比较的是错误的温度值)

    • 测评系统检测到错误的初始状态

正确代码:

方法1: 等待85℃消失(推荐)

void main()
{
    system_init();
    while(temp_read() == 85);  // ✓ 等待DS18B20初始化完成!
    scheduler_init();
    Timer1_Init();
​
    while(1)
    {
        scheduler_run();
    }
}

方法2: 触发转换后延时

void main()
{
    system_init();
    read_temper();   // 触发第一次转换
    Delay750ms();    // 等待750ms(DS18B20转换时间)
    scheduler_init();
    Timer1_Init();
​
    while(1)
    {
        scheduler_run();
    }
}

关键点:

  • 在启动调度器和Timer1之前,必须等待DS18B20初始化完成

  • 方法1更可靠:while(temp_read() == 85); 循环等待真实温度

  • 方法2需要精确延时:至少750ms

  • 确保第一次读取的温度值是真实值,不是85℃


3. :cross_mark: 温度读取了两次导致数据不一致

错误代码:

void Get_Temperature()
{
    Temperature = rd_temperature();        // 第1次读取
    Temperature_100x = rd_temperature() * 100;  // ❌ 第2次读取!
}

错误原因:

  • DS18B20每次读取需要750ms转换时间

  • 连续读两次会导致:

    • 第1次读取:可能得到上一次转换的旧值

    • 第2次读取:可能触发新的转换,得到不同的值

  • 结果: TemperatureTemperature_100x的值可能不一致!

示例:

第1次rd_temperature()返回: 24.25
第2次rd_temperature()返回: 24.30 (传感器值变化了)
​
结果:
Temperature = 24.25
Temperature_100x = 2430 (应该是2425)
  • 导致温度显示和DAC判断使用的温度值不一致

  • 可能引发逻辑错误

正确代码:

void Get_Temperature()
{
    Temperature = rd_temperature();  // ✓ 只读一次!
    Temperature_100x = Temperature * 100;  // ✓ 基于同一个值计算
}

或者:

void temper_proc()
{
    temper_100x = read_temper() * 100;  // 读一次后直接放大100倍
}

关键点:

  • DS18B20只读取一次

  • 所有需要的数据都基于这一次读取的值进行计算

  • 避免多次调用rd_temperature()导致数据不一致

  • 保证温度显示和DAC判断使用相同的温度值


总结

最严重的错误(会导致4T测评失败):

  1. LED采用PWM控制导致状态延迟 - Timer1中断里用PWM控制LED,熄灭期最多延迟4ms,4T检测到"错误状态前沿"

  2. 缺少DS18B20初始化等待 - 可能读到85℃错误值,导致初始状态错误

中等错误(功能异常或数据不一致):

  1. 温度读取两次 - 导致数据不一致,可能引发逻辑错误

学到的经验

1. LED和数码管刷新策略要分离,且LED不要用PWM控制

核心设计思想:

调度器调用led_proc() → 直接刷新硬件led_disp()
Timer1中断 → 只刷新数码管

优势:

  • LED状态变化零延迟(只有调度器1ms周期)

  • 避免Timer1中断同时处理两个外设的时序冲突

  • 代码逻辑更清晰,职责分离

为什么不能在Timer1中断里用PWM控制LED:

  • PWM有熄灭周期,会导致状态变化无法立即显示

  • 例如60%占空比PWM:6ms亮,4ms灭

  • 如果状态在4ms熄灭期内变化,需要等待下一个亮周期才显示

  • 最多延迟3ms,4T测评系统会检测到"错误状态前沿"

  • 结论:LED指示灯应该直接控制,不使用PWM

2. 4T测评系统的检测精度极高

  • 微秒级的瞬时错误也会被捕捉

  • 状态变化到硬件刷新的延迟必须最小化

  • 不能依赖"人眼看不出来"的侥幸心理

3. 外设初始化要等待就绪

  • DS18B20上电后需要750ms转换时间

  • 初始读数是85℃(固定默认值)

  • 必须等待真实温度值出现后再启动系统

4. 数据一致性原则

  • 同一个数据不要多次读取

  • 所有计算都基于同一次读取的值

  • 避免传感器值变化导致的数据不一致

5. 时序设计至关重要

  • 实时系统中,时序比功能更重要

  • 状态变化必须立即反映到硬件

  • 不能有"等待下一次中断"的延迟


复盘检查清单

在蓝桥杯单片机比赛中,遇到类似题目时,务必检查:

  • Led_Proc()函数中调用Led_Disp()直接刷新硬件

  • Timer1中断只刷新数码管,不刷新LED

  • LED不使用PWM控制,直接点亮/熄灭(避免熄灭期延迟)

  • main函数中等待DS18B20初始化(while(temp_read()==85))

  • 温度传感器只读取一次

  • 所有温度相关变量基于同一次读取计算

  • LED和数码管的刷新策略独立


生成时间:2026-02-07
蓝桥杯第十二届省赛(第一次)代码错误总结