蓝桥杯第十二届省赛(第二次)代码错误总结
错误列表
1.
PCF8591 AD采样没有读取两次导致数据不准确
错误代码:
void AD_DA()
{
// ❌ 每个通道只读一次!
AD_1_Data= (float)Ad_Read(0x41) / 51.0f ;
AD_1_Data_100x=AD_1_Data*100;
AD_3_Data= (float)Ad_Read(0x43) / 51.0f ;
AD_3_Data_100x=AD_3_Data*100;
Voltage_100x=(Output_Mode==1)?AD_1_Data_100x:AD_3_Data_100x;
}
错误原因:
PCF8591的工作特性:
-
第一次读取返回的是上一次AD转换的结果(可能是旧数据或初始值)
-
第二次读取才是本次转换的真实值
-
如果只读一次,可能读到的是上一次的旧值
导致的问题:
-
数据准确性下降,特别是低电压值时误差更明显
-
第一次采样可能读到默认值0或上次的残留值
-
导致显示不准确或显示0.00
4T测评失败点:
序号9:通道1,电压值1.3V,切换电压显示界面
- 正确结果:U-1 1.30
- 你的结果:数码管显示:U-1 0.00 ❌
序号10:通道1,电压值4.5V
- 正确结果:U-1 4.50
- 你的结果:数码管显示:U-1 0.00 ❌
序号11:通道1,电压值0.4V
- 正确结果:U-1 0.40
- 你的结果:数码管显示:U-1 0.00 ❌
序号12:通道3,电压值3.5V,切换通道3
- 正确结果:U-3 3.50
- 你的结果:数码管显示:U-3 0.39 ❌
序号13:通道3,电压值1.6V
- 正确结果:U-3 1.60
- 你的结果:数码管显示:U-3 0.39 ❌
序号14:通道3,电压值0.7V
- 正确结果:U-3 0.70
- 你的结果:数码管显示:U-3 0.39 ❌
正确代码(读两次 - 满分代码标准做法):
void AD_DA()
{
// 通道1(光敏):读两次
Ad_Read(0x41); // ✓ 第一次:清空上次结果
AD_1_Data= (float)Ad_Read(0x41) / 51.0f ; // ✓ 第二次:获取真实值
AD_1_Data_100x=AD_1_Data*100;
// 通道3(RB2):读两次
Ad_Read(0x43); // ✓ 第一次:清空上次结果
AD_3_Data= (float)Ad_Read(0x43) / 51.0f ; // ✓ 第二次:获取真实值
AD_3_Data_100x=AD_3_Data*100;
Voltage_100x=(Output_Mode==1)?AD_1_Data_100x:AD_3_Data_100x;
}
满分代码都采用读两次的方式:
// 学员Ꮶ的代码
ad_read(0x43); // 先读一次(清空上次结果)
voltage3 = ad_read(0x43) * 100 / 51; // 再读一次(获取本次结果)
ad_read(0x41);
voltage1 = ad_read(0x41) * 100 / 51;
// 学员cindyyy的代码(使用0x03直接读通道3)
AD_Channel3_RB2_100x = Ad_Read(0x03) * 100 / 51;
AD_Channel1_Light_100x = Ad_Read(0x01) * 100/51;
// 注:有些IIC库直接读通道号,但原理相同
关键点:
-
每个通道必须读两次:第一次清空旧值,第二次获取真实值
-
PCF8591是单通道依次转换的,第一次读取可能是上次通道的残留值
-
特别是低电压值(<1V)时,读两次的必要性更明显
-
这是所有满分代码的共同特征
为什么只读一次会出错:
假设依次读取通道1和通道3:
第1次调用Ad_Read(0x41):
→ 启动通道1转换
→ 但返回的是上一次(可能是通道3)的值
第1次调用Ad_Read(0x43):
→ 启动通道3转换
→ 但返回的是上一次(通道1)的值
结果:
AD_1_Data = 上次通道3的值(错误!)
AD_3_Data = 上次通道1的值(错误!)
读两次的正确流程:
第1次调用Ad_Read(0x41):
→ 启动通道1转换,返回旧值(丢弃)
第2次调用Ad_Read(0x41):
→ 再次读取,返回通道1的真实值 ✓
第1次调用Ad_Read(0x43):
→ 启动通道3转换,返回旧值(丢弃)
第2次调用Ad_Read(0x43):
→ 再次读取,返回通道3的真实值 ✓
2.
长按按键逻辑使用Key_Old容易出错
错误代码:
// 变量定义
idata unsigned int Led_Flag=1; // LED使能标志
idata unsigned int Led_Old_Flag=0; // ❌ 额外的标志变量
idata unsigned int Key7_Flag=0;
idata unsigned int Count_1000ms=0;
// 按键处理
if(Count_1000ms<1000)
{
if(Key_Up==7)
{
Key7_Flag=Count_1000ms=0;
Freq_Store=Freq;
}
}
else
{
if((Key_Old==7)&&(Led_Old_Flag!=Led_Flag)) // ❌ 使用Key_Old判断!
Led_Flag^=1;
if(Key_Up==7)
Key7_Flag=Count_1000ms=0;
}
错误原因:
使用Key_Old判断按键保持状态的问题:
-
Key_Old表示按键保持按下的状态 -
在按键保持期间,
Key_Old==7一直为真 -
Key_Proc()每10ms执行一次 -
容易导致重复执行或需要额外的标志变量(如
Led_Old_Flag)来防止重复 -
逻辑复杂,容易出错
额外标志变量的问题:
-
Led_Old_Flag需要手动更新和维护 -
如果忘记更新,逻辑就会出错
-
增加了代码复杂度
4T测评可能失败点:
虽然最终修改了这个逻辑,但使用Key_Old的方式容易导致:
- 长按功能重复触发
- 需要额外的防重复标志
- 逻辑混乱,不易维护
正确代码(使用Key_Down和Key_Up):
// 变量定义
idata unsigned int Led_Flag=1;
idata unsigned int Key7_Flag=0;
idata unsigned int Count_1000ms=0;
// ✓ 不需要Led_Old_Flag这个多余的变量!
// 按键处理
if(Key_Down==7)
Key7_Flag=1; // ✓ 按下时开始监测
if(Key_Up==7) // ⭐ 关键:在松开时判断!
{
if(Count_1000ms>=1000) // 如果超过1秒
Led_Flag^=1; // 长按:切换LED使能
else
Freq_Store=Freq; // 短按:缓存频率
Key7_Flag=Count_1000ms=0; // 清零标志和计数器
}
// 中断中计时
if(Key7_Flag==1)
{
Count_1000ms++;
if(Count_1000ms>=1000)
Count_1000ms=1000;
}
使用Key_Down和Key_Up的优势:
| 特性 | 使用Key_Old | 使用Key_Down和Key_Up |
|---|---|---|
| 触发时机 | 按键保持期间一直为真 | 按下/松开瞬间触发一次 |
| 重复执行 | 容易重复执行 | 不会重复执行 ✓ |
| 额外标志 | 需要防重复标志 | 不需要额外标志 ✓ |
| 逻辑清晰度 | 较复杂 | 简洁清晰 ✓ |
| 符合习惯 | 在保持时判断 | 在松开时判断 ✓ |
满分代码的统一模式:
┌─────────────┐
│ 按键按下(S7) │
└──────┬──────┘
│
▼
┌─────────────────┐
│ Key_Down==7触发 │ ← 只触发一次,开始计时
└──────┬──────────┘
│
▼
┌─────────────────┐
│ 中断中持续计时 │ ← Timer1_Isr()中每1ms累加
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Key_Up==7触发 │ ← 只触发一次,判断时长
└──────┬──────────┘
│
▼
┌────┴────┐
│ 判断时长 │
└────┬────┘
│
┌───┴───┐
│ │
▼ ▼
<1秒 ≥1秒
短按功能 长按功能
所有满分代码都使用Key_Down和Key_Up:
// 学员Ꮶ的代码
if(key_down == 7)
key_long_flag = 1;
if(key_up == 7)
{
if(Time_count<1000)
freq_save = freq; // 短按
else
led_flag ^= 1; // 长按
Time_count = 0;
key_long_flag = 0;
}
// 学员cindyyy的代码
case 7:
Long_Press_Detection = 1; // Key_Down时设置标志
break;
if(Key_Up == 7)
{
Long_Press_Detection = 0;
if(Time_Tick >= 1000)
Led_Show_Flag ^= 1; // 长按
else
Freq_Cache = Freq; // 短按
}
// 学员emo哥的代码
if(key_down==7)
ms_key_7=0; // 按下时重置计时
if((key_up==7)&&(ms_key_7>1000))
led_on ^=1; // 长按
else if((key_up==7)&&(ms_key_7<1000))
freq_cun=freq_sure; // 短按
关键点:
-
永远使用Key_Down检测按下,Key_Up检测松开
-
不要使用Key_Old做长按判断
-
Key_Down和Key_Up只在状态变化时触发一次,不会重复
-
逻辑清晰:按下→计时→松开判断
-
不需要额外的防重复标志变量
-
符合用户使用习惯(松开时生效)
为什么不用Key_Old:
Key_Old == 7 的问题:
按下S7后,Key_Old一直等于7
Key_Proc()每10ms执行一次
如果在超过1秒后判断:
第1次:Key_Old==7为真 → 执行功能
第2次:Key_Old==7还是真 → 又执行一次!
第3次:Key_Old==7还是真 → 又执行一次!
...
必须用额外的标志防止重复,增加复杂度
Key_Up == 7 的优势:
只在松开瞬间触发一次
不会重复执行
不需要防重复标志
逻辑简洁清晰
总结
最严重的错误(会导致4T测评失败):
- PCF8591 AD采样没有读取两次 - 数据不准确,特别是低电压值,序号9-14所有电压测试失败
中等错误(逻辑混乱容易出错):
- 长按按键逻辑使用Key_Old - 容易重复触发,需要额外标志,逻辑复杂
学到的经验
1. PCF8591必须读两次才能获取准确数据
核心原理:
PCF8591工作机制:
第1次读取 → 返回上一次转换的结果(旧值)
第2次读取 → 返回本次转换的真实值
标准写法:
// 每个通道读两次
Ad_Read(0x41); // 第一次:清空旧值
AD_1_Data = Ad_Read(0x41) / 51.0f; // 第二次:获取真实值
Ad_Read(0x43);
AD_3_Data = Ad_Read(0x43) / 51.0f;
优势:
-
确保数据准确性,特别是低电压值
-
避免读到上一次转换的残留值
-
所有满分代码都采用这种方式
-
符合PCF8591的工作特性
记忆技巧:
-
第一次是"热身",第二次才是"真实数据"
-
第一次"清场",第二次"采样"
2. 长按按键永远使用Key_Down和Key_Up
标准模式(所有满分代码一致):
// 1. 按下时:设置标志开始计时
if(Key_Down==7)
Key7_Flag=1;
// 2. 松开时:判断时长并执行功能
if(Key_Up==7)
{
if(Count_1000ms>=1000)
// 长按功能
else
// 短按功能
Key7_Flag=Count_1000ms=0; // 清零
}
// 3. 中断中:持续计时
if(Key7_Flag==1)
{
Count_1000ms++;
if(Count_1000ms>=1000)
Count_1000ms=1000; // 限制最大值
}
为什么不用Key_Old:
-
Key_Old==7在按键保持期间一直为真 -
会导致重复执行,需要额外的防重复标志
-
逻辑复杂,容易出错
使用Key_Down和Key_Up的好处:
-
只在按下/松开瞬间触发一次 -
不会重复执行 -
不需要额外标志变量 -
逻辑清晰简洁 -
符合用户使用习惯(松开时生效)
记忆规则:
-
按键检测只用Key_Down和Key_Up
-
Key_Old只用于计算Key_Down和Key_Up,不要在业务逻辑中使用
复盘检查清单
在蓝桥杯单片机比赛中,遇到类似题目时,务必检查:
AD采样相关:
-
PCF8591每个通道读两次(第一次清空,第二次使用)
-
通道号正确(0x41→通道1,0x43→通道3)
-
变量名和通道号一一对应(AD_1_Data对应0x41)
-
低电压值时特别注意读两次的必要性
按键长按/短按相关:
-
永远使用Key_Down检测按下
-
永远使用Key_Up检测松开
-
不要用Key_Old做长按判断
-
在Key_Up(松开)时判断时长
-
逻辑清晰:按下→计时→松开判断
-
不需要额外的防重复标志变量
代码质量相关:
-
删除未使用的变量
-
整数运算优于浮点运算
-
变量命名清晰明确
-
职责分离,逻辑清晰
对比满分代码的差距
满分代码的共同特征:
- AD采样读两次
// 学员Ꮶ
ad_read(0x43);
voltage3 = ad_read(0x43) * 100 / 51;
ad_read(0x41);
voltage1 = ad_read(0x41) * 100 / 51;
- 长按按键使用Key_Down和Key_Up
// 学员Ꮶ
if(key_down == 7)
key_long_flag = 1;
if(key_up == 7)
{
if(Time_count>=1000)
led_flag ^= 1; // 长按
else
freq_save = freq; // 短按
Time_count = 0;
key_long_flag = 0;
}
voltage1 // 对应通道1
voltage3 // 对应通道3
生成时间: 2026-02-08
蓝桥杯第十二届省赛(第二次)代码错误总结