蓝桥杯单片机第 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;
教训:区分"实测变量"(Temperature、Distance)与"参数变量"(Temperature_Ctr、Distance_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 和继电器要冻结当前状态,让用户专注于调参而不受外部扰动。声明了锁定标志就要在所有相关位置检查它。