单片机十六届省赛

蓝桥杯单片机第 16 届省赛(第一场)错误笔记

总体错误分类

编号 错误类型 影响测试点 严重程度
1 Led_Disp() 从未调用,LED 硬件永远不刷新 几乎所有 LED 相关测试 ★★★★★
2 数码管 Mode 0 显示 Distance(距离)而非 Temperature(温度) 所有环境状态界面显示测试 ★★★★★
3 缺少继电器控制代码 所有继电器/计数测试 ★★★★★
4 数码管 Mode 2 显示实测值而非参数值 所有参数设置界面显示测试 ★★★★
5 长按清零计时逻辑写在 Seg_Proc(每 20ms),实际需 40s 才触发 统计界面 S8+S9 长按清零 ★★★★
6 Get_Distance 初始 Last_Distance=0 导致第一次误判为"跑动" 初始状态及运动状态相关测试 ★★★
7 Running_Flag 无条件翻转,不跑动时也在累积相位 L8 闪烁间隔 error 相关测试 ★★★
8 LED 光强等级切换时旧指示灯未熄灭 光强等级变化后的 LED 测试 ★★★
9 Distance_Flag=0(未接近)时 L1-L4 未清零 距离未触发接近时的 LED 测试 ★★★
10 Led_Relay_Lock 未在 Led_Proc 中使用 进入参数界面后的 LED/继电器锁定测试 ★★★

错误详解

错误 1:Led_Disp() 从未调用

位置Led_Proc() 函数末尾

现象

  • ucLed[] 数组值被正确计算,但从未写入硬件

  • 所有 LED 停留在 System_Init() 的初始熄灭状态

  • 仅当所有 LED 应该熄灭时测试结果"正确"(测试 21、25),其余 LED 测试全部报"存在错误状态指示灯"

根本原因:参考满分代码在 Led_Proc() 末尾调用 Led_Disp(ucled),我的代码遗漏了这一行。

// 错误:计算了 ucLed 但没有输出到硬件
if(Motion_Mode==3)
    ucLed[7]=(Running_Flag);
// ← 缺少 Led_Disp(ucLed);
​
// 正确:
if(Motion_Mode==3)
    ucLed[7]=(Running_Flag);
Led_Disp(ucLed);  // 必须调用!

教训:每次修改 ucLed[] 数组后,必须手动调用 Led_Disp() 将数据推送到硬件,驱动层不会自动刷新。


错误 2:Mode 0 数码管显示距离而非温度

位置Seg_Proc()Seg_Mode==0 分支

现象:环境状态界面第 1-2 位本应显示温度(如 C23),却显示了距离(如 C16

根本原因:写代码时将变量名写错,使用了 Distance 而非 Temperature

// 错误:
Seg_Buf[1] = Distance / 10 % 10;
Seg_Buf[2] = Distance % 10;
​
// 正确:
Seg_Buf[1] = Temperature / 10 % 10;
Seg_Buf[2] = Temperature % 10;

教训:数码管各界面显示内容要与变量名严格对应,Mode 0 显示环境状态(温度 + 光强等级),不是距离。


错误 3:缺少继电器控制代码

位置Led_Proc() 函数

现象

  • 继电器始终保持 System_Init() 后的断开状态

  • Relay_Count(吸合次数)始终为 0

  • 统计数据界面显示 NC 0 而非正确计数

根本原因Led_Proc() 中完全没有编写继电器控制逻辑。

// 缺少以下代码块:
if(Distance_Flag && Hot_Flag)
{
    Relay(1);
    if(!Relay_Old) Relay_Count++;  // 仅在上升沿计数(断→合)
    Relay_Old = 1;
}
else
{
    Relay(0);
    Relay_Old = 0;
}

教训:继电器条件是同时满足"距离接近(Distance_Flag=1)“AND"高温(Hot_Flag=1)”,缺一不可。计数要用边沿检测(Relay_Old),防止持续满足条件时重复计数。


错误 4:Mode 2 数码管显示实测值而非参数值

位置Seg_Proc()Seg_Mode==2 分支

现象:参数设置界面本应显示"设定的阈值参数",却显示了"当前传感器实测值"

// 错误(显示实测值):
Seg_Buf[6] = Temperature / 10 % 10;  // 实际温度
Seg_Buf[7] = Temperature % 10;
​
Seg_Buf[6] = Distance / 10 % 10;     // 实际距离
Seg_Buf[7] = Distance % 10;
​
// 正确(显示参数值):
Seg_Buf[6] = Temperature_Ctr / 10 % 10;  // 温度阈值参数
Seg_Buf[7] = Temperature_Ctr % 10;
​
Seg_Buf[6] = Distance_Ctr / 10 % 10;     // 距离阈值参数
Seg_Buf[7] = Distance_Ctr % 10;

教训:区分"实测变量"(TemperatureDistance)与"参数变量"(Temperature_CtrDistance_Ctr),参数设置界面显示的是可调节的阈值,而非传感器读数。


错误 5:长按清零计时逻辑错误

位置Seg_Proc()Seg_Mode==3 分支 & Timer ISR

现象:S8+S9 同时长按实际需要约 40 秒才能清零,而非要求的 2 秒

根本原因:把计时递增写在了 Seg_Proc() 里(每 20ms 调用一次):

// 错误:Seg_Proc 每 20ms 调用一次
// 需要 2000 次 × 20ms = 40000ms = 40s 才触发!
if((Key_Old==89) && (++Relay_Time_2s >= 2000))
{
    Relay_Count = 0;
}

正确做法:标志位(Relay_Time_Flag)在 Key_Proc 中根据 Key_Old==89 设置,计时递增在 Timer ISR(每 1ms)中执行,2000ms = 2s。

// Key_Proc 中(每 10ms 执行):
if(Seg_Mode == 3)
{
    if(Relay_Time_2s >= 2000) Relay_Count = 0;  // 达到 2s 则清零
    if(Key_Old == 89)
        Relay_Time_Flag = 1;   // S8+S9 同时按住
    else
    {
        Relay_Time_Flag = 0;
        Relay_Time_2s = 0;     // 松开则重置计时
    }
}
​
// Timer ISR 中(每 1ms 执行):
if(Relay_Time_Flag)
{
    if(++Relay_Time_2s >= 2000) Relay_Time_2s = 2001;  // 封顶
}
else
    Relay_Time_2s = 0;

教训:精确计时(1ms/2ms 级别)必须放在定时器中断 ISR 中,不能放在有速率限制的业务函数里。


错误 6:首次超声波测距误判为"跑动"

位置Get_Distance() 函数

现象:系统上电后,第一次测距时 L8 立刻闪烁(误判为跑动),需等待数秒才恢复正确状态

根本原因Last_Distance 初始值为 0,第一次测量(如距离=16cm)时:

delta = |16 - 0| = 16 ≥ 10  →  误判为"跑动",锁定 3 轮

修复:用一个首次标志位跳过第一次的差值计算:

idata bit First_Dist_Done;  // 新增变量,默认 0
​
void Get_Distance()
{
    // ... 读取 Distance、设置 Distance_Flag ...
​
    if(!First_Dist_Done)          // 第一次:只记录基准值
    {
        First_Dist_Done = 1;
        Last_Distance = Distance;
        return;                   // 不计算差值,不更新状态
    }
    // 第二次起:正常计算 delta 和运动状态
    // ...
}

教训:差值类算法(位移差、速度差)在第一帧时没有"上一帧"数据,必须单独处理初始化,否则会产生虚假的大差值。


错误 7:Running_Flag 无条件翻转

位置Timer1_Isr() 中断服务函数

现象:L8 闪烁间隔测试报 error,进入跑动状态时闪烁相位随机(可能起始就是亮的)

根本原因:定时器 ISR 无条件每 100ms 翻转一次 Running_Flag,即使在静止/徘徊状态下也在翻转:

// 错误:不管当前运动状态,一直翻转
if(++Running_100ms >= 100)
{
    Running_Flag ^= 1;
    Running_100ms = 0;
}

修复:只在跑动状态(Motion_Mode==3)时翻转,其他状态重置为 0:

// 正确:
if(Motion_Mode == 3)
{
    if(++Running_100ms >= 100)
    {
        Running_Flag ^= 1;
        Running_100ms = 0;
    }
}
else
{
    Running_100ms = 0;
    Running_Flag = 0;  // 非跑动状态复位,保证进入跑动时从"灭"开始
}

教训:闪烁控制变量必须在非激活状态时复位,确保每次进入闪烁状态时都从确定的初始相位(灭)开始,保证闪烁间隔准确。


错误 8 & 9:LED 状态不清零

位置Led_Proc() 函数

错误 8:光强等级降低时旧 LED 未熄灭

// 错误:只点亮,不熄灭
if(Light_Level == 1) { ucLed[0] = 1; }         // L2-L4 旧状态保留!
if(Light_Level == 2) { ucLed[0]=1; ucLed[1]=1; }  // L3-L4 旧状态保留!
​
// 正确:每个等级都明确设置所有 4 个 LED
if(Light_Level == 1)
    { ucLed[0]=1; ucLed[1]=0; ucLed[2]=0; ucLed[3]=0; }
else if(Light_Level == 2)
    { ucLed[0]=1; ucLed[1]=1; ucLed[2]=0; ucLed[3]=0; }
// ...

错误 9:距离未接近时 L1-L4 未清零

// 错误:没有 else 分支
if(Distance_Flag) { /* 设置 L1-L4 */ }
// Distance_Flag=0 时,L1-L4 保留上次的值!
​
// 正确:必须有 else 清零
if(Distance_Flag) { /* 设置 L1-L4 */ }
else { ucLed[0]=0; ucLed[1]=0; ucLed[2]=0; ucLed[3]=0; }

教训:LED 控制要用完整的 if-else,或者在每次刷新前先全部清零,避免"旧状态残留"。


错误 10:未使用 Led_Relay_Lock

位置Led_Proc() 函数

现象:进入参数设置界面(Mode 2)后,L1-L4 和继电器仍然随传感器值实时变化,而非保持进入前的状态

根本原因Key_Proc 中正确设置了 Led_Relay_Lock,但 Led_Proc 没有检查这个标志:

// 错误:Led_Proc 中没有锁定检查
if(Distance_Flag) { /* 直接刷新 L1-L4 */ }
​
// 正确:只有未锁定时才刷新
if(!Led_Relay_Lock)
{
    if(Distance_Flag) { /* 刷新 L1-L4 */ }
    else { /* 清零 L1-L4 */ }
    /* 继电器控制 */
    /* L8 控制 */
}
Led_Disp(ucLed);  // Led_Disp 在锁定检查外面,始终刷新硬件

教训:进入参数界面时,LED 和继电器要冻结当前状态,让用户专注于调参而不受外部扰动。声明了锁定标志就要在所有相关位置检查它。