蓝桥杯第十二届省赛(第一次)代码错误总结
错误列表
1.
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;
}
}
满分代码的关键改进:
-
Led_Proc()调用Led_Disp():
-
每次
Led_Proc()执行完更新ucLed[]数组后 -
立即调用
Led_Disp(ucLed)刷新硬件 -
确保内存数据和硬件状态同步
-
-
Key_Proc()立即刷新LED:
-
按键处理修改状态变量后
-
立即调用
Led_Proc()刷新LED(case 4和case 5) -
不需要等待调度器下一次调用
-
-
Timer1中断不处理LED:
-
Timer1中断只负责数码管动态扫描
-
不再用PWM控制LED
-
LED和数码管刷新完全分离
-
优势对比:
| 特性 | PWM版本(错误) | 满分版本(正确) |
|---|---|---|
| Led_Proc()是否调用Led_Disp() | ✓ 每次都调用 | |
| LED刷新方式 | Timer1中断PWM控制 | Led_Proc()直接刷新 |
| 按键后LED刷新 | 等待调度器+PWM周期 | 立即刷新 |
| 最大延迟 | 最多4ms(1ms调度+3ms PWM) | 最多1ms(仅调度器周期) |
| 4T测评 | ✓ 通过 |
关键点:
-
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.
缺少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.
温度读取了两次导致数据不一致
错误代码:
void Get_Temperature()
{
Temperature = rd_temperature(); // 第1次读取
Temperature_100x = rd_temperature() * 100; // ❌ 第2次读取!
}
错误原因:
-
DS18B20每次读取需要750ms转换时间
-
连续读两次会导致:
-
第1次读取:可能得到上一次转换的旧值
-
第2次读取:可能触发新的转换,得到不同的值
-
-
结果:
Temperature和Temperature_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测评失败):
-
LED采用PWM控制导致状态延迟 - Timer1中断里用PWM控制LED,熄灭期最多延迟4ms,4T检测到"错误状态前沿"
-
缺少DS18B20初始化等待 - 可能读到85℃错误值,导致初始状态错误
中等错误(功能异常或数据不一致):
- 温度读取两次 - 导致数据不一致,可能引发逻辑错误
学到的经验
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
蓝桥杯第十二届省赛(第一次)代码错误总结