蓝桥杯第十五届省赛(第一次)代码错误总结
错误列表
1.
DAC计算公式使用整数除法导致精度丢失
错误代码:
/**
* @brief ADC/DAC控制处理
* 读取电位器和温度的AD值,并输出DAC电压。
*/
void AD_DA()
{
if(Freq_Error==0)
{
if(Freq_Cor<=500)
Da_Write(51);
else if(Freq_Cor>=Data_1)
Da_Write(255);
else
{
// ❌ 错误的公式!整数除法导致精度丢失
Temp=(1+((float)(4/(Data_1-500)))*(Freq_Cor-500))*51;
// ^^^^^^^^^^^^^^^
// 这里先计算!结果永远是0!
Da_Write(Temp);
}
}
else
Da_Write(0);
}
错误原因:
问题核心: 整数除法在强制类型转换之前就已经计算完成
C语言类型转换规则:
-
(float)(表达式)是在表达式计算完成后才转换类型 -
整数÷整数=整数(直接截断,不保留小数)
-
转换成float为时已晚,精度已经丢失
错误流程分析:
假设 Data_1 = 2000
步骤1: 计算括号内的除法
4/(Data_1-500)
= 4/1500
= 0 ← 整数除法!直接截断!
步骤2: 强制转换为float
(float)0
= 0.0 ← 已经是0了,转换也没用!
步骤3: 继续计算
Temp = (1 + 0.0 * (Freq_Cor-500)) * 51
= (1 + 0) * 51
= 51
结果: 无论Freq_Cor是多少,DAC永远输出51 → 1.0V
完全失去了线性调节功能!
实际测试:
测试条件: Freq_Cor=2000, Data_1=5000
理论值:
V = 1 + 4*(2000-500)/(5000-500)
= 1 + 4*1500/4500
= 1 + 1.333...
= 2.333V → DAC = 119
错误代码计算:
4/(5000-500) = 4/4500 = 0(整数除法)
(float)0 = 0.0
Temp = (1 + 0.0*1500) * 51 = 51 → 1.0V ❌
误差: 应该2.33V,实际1.0V,完全错误!
为什么会犯这个错误:
-
误以为
(float)(4/(Data_1-500))会先转换类型再除法 -
实际上是先执行除法(整数÷整数=整数),再转换类型
-
括号的优先级:
(float)(表达式)是对整个表达式的结果转换 -
不是对表达式中某个操作数转换
正确代码:
/**
* @brief ADC/DAC控制处理
* 读取电位器和温度的AD值,并输出DAC电压。
*/
void AD_DA()
{
if(Freq_Error==0)
{
if(Freq_Cor<=500)
Da_Write(51);
else if(Freq_Cor>=Data_1)
Da_Write(255);
else
{
// ✓ 正确的公式!保证浮点运算
Temp = (1.0 + 4.0*(Freq_Cor-500)/(Data_1-500)) * 51;
// ^^^ ^^^
// 浮点数常量!保证整个运算都是浮点数
Da_Write(Temp);
}
}
else
Da_Write(0);
}
关键改进:
-
使用浮点数常量:
-
错误: 1→ 整数常量 -
✓ 正确:
1.0→ 浮点数常量 -
错误: 4→ 整数常量 -
✓ 正确:
4.0→ 浮点数常量
-
-
运算顺序:
错误版本: 4/(Data_1-500) → 整数除法 → 0 → (float)0 → 0.0 ❌ 正确版本: 4.0*(Freq_Cor-500) → 浮点乘法 → 6000.0 → 6000.0/4500 → 1.333... ✓ -
类型转换时机:
// 方法1: 使用浮点数常量(推荐) Temp = (1.0 + 4.0*(Freq_Cor-500)/(Data_1-500)) * 51; // 方法2: 强制转换操作数(也可以) Temp = (1 + (float)4/(Data_1-500)*(Freq_Cor-500)) * 51; // 方法3: 提前转换变量(也可以) Temp = (1 + 4.0*((float)Freq_Cor-500)/(Data_1-500)) * 51; // ❌ 错误: 转换结果(为时已晚) Temp = (1 + (float)(4/(Data_1-500))*(Freq_Cor-500)) * 51;
C语言类型转换记忆口诀:
“要想精度好,浮点要趁早!”
-
在进行除法运算之前,就让其中一个操作数变成浮点数
-
常用技巧:
-
✓
4.0 / x(浮点常量) -
✓
(float)4 / x(转换操作数) -
✓
4 * 1.0 / x(乘以浮点数) -
(float)(4 / x)(转换结果,太晚了!)
-
测试验证:
测试场景1: 频率2000Hz, 超限参数5000Hz
理论值: V = 1 + 4*(2000-500)/(5000-500) = 2.33V → DAC=119
错误代码:
4/(5000-500) = 0
Temp = (1 + 0*1500)*51 = 51 → 1.0V ❌
正确代码:
4.0*1500 = 6000.0
6000.0/4500 = 1.333...
Temp = (1.0+1.333)*51 = 119 → 2.33V ✓
测试场景2: 频率1000Hz, 超限参数3000Hz
理论值: V = 1 + 4*(1000-500)/(3000-500) = 1.8V → DAC=92
错误代码:
4/(3000-500) = 0
Temp = (1 + 0*500)*51 = 51 → 1.0V ❌
正确代码:
4.0*500 = 2000.0
2000.0/2500 = 0.8
Temp = (1.0+0.8)*51 = 92 → 1.8V ✓
测试场景3: 频率<=500Hz
理论值: V = 1.0V → DAC=51
错误代码: Temp = 51 → 1.0V ✓(这种情况碰巧对了)
正确代码: Temp = 51 → 1.0V ✓
测试场景4: 频率>=Data_1
理论值: V = 5.0V → DAC=255
错误代码: Temp = 255 → 5.0V ✓(这种情况碰巧对了)
正确代码: Temp = 255 → 5.0V ✓
为什么测试时没发现:
-
如果只测试边界值(<=500 或 >=Data_1),错误代码也能得到正确结果
-
只有测试中间值时,才会暴露整数除法问题
-
4T测评系统会测试各种中间值,所以能检测出这个错误
重要注意事项
2.
必须使用校准后的频率进行显示和DAC计算
问题描述:
在本题中,存在两个频率值:
-
原始测量频率
Freq- 从Timer0直接读取的NE555频率 -
校准后频率
Freq_Cor- 经过校准值调整后的频率 (Freq_Cor = Freq + Data_2)
正确的使用方式:
// 在Seg_Proc()中计算校准后频率
void Seg_Proc()
{
// ✓ 频率校准计算(关键!)
Freq_Cor = Freq + Data_2; // Data_2是校准值(-900~900)
// ✓ 使用Freq_Cor进行显示和判断
// 数码管显示
// DAC计算
// LED判断
// 最大频率记录
}
错误示例(常见错误):
// ❌ 错误:数码管显示用Freq
Seg_Buf[3] = Freq/10000%10; // 应该用Freq_Cor!
// ❌ 错误:DAC计算用Freq
Temp = (1.0 + 4.0*(Freq-500)/(Data_1-500)) * 51; // 应该用Freq_Cor!
// ❌ 错误:超限判断用Freq
if(Freq > Data_1) // 应该用Freq_Cor!
ucLed[1] = Led_200ms;
// ❌ 错误:最大频率记录用Freq
if(Freq > Freq_Max) // 应该用Freq_Cor!
{
Freq_Max = Freq; // 应该记录Freq_Cor!
}
正确示例:
void Seg_Proc()
{
// 1. 首先计算校准后频率
Freq_Cor = Freq + Data_2;
// 2. 数码管显示使用Freq_Cor
switch(Seg_Mode)
{
case 0://频率界面
Seg_Buf[3] = Freq_Cor/10000%10; // ✓ 使用Freq_Cor
Seg_Buf[4] = Freq_Cor/1000%10;
Seg_Buf[5] = Freq_Cor/100%10;
Seg_Buf[6] = Freq_Cor/10%10;
Seg_Buf[7] = Freq_Cor%10;
break;
}
// 3. 最大频率记录使用Freq_Cor
if(Freq_Cor > Freq_Max)
{
Freq_Max = Freq_Cor; // ✓ 记录校准后的频率
Read_Rtc(Show_Time);
}
}
void AD_DA()
{
// 4. DAC计算使用Freq_Cor
if(Freq_Cor<=500) // ✓ 使用Freq_Cor
Da_Write(51);
else if(Freq_Cor>=Data_1) // ✓ 使用Freq_Cor
Da_Write(255);
else
{
Temp = (1.0 + 4.0*(Freq_Cor-500)/(Data_1-500)) * 51; // ✓ 使用Freq_Cor
Da_Write(Temp);
}
}
void Led_Proc()
{
// 5. LED超限判断使用Freq_Cor
if(Freq_Cor > Data_1) // ✓ 使用Freq_Cor
ucLed[1] = Led_200ms;
else
ucLed[1] = 0;
}
为什么必须用Freq_Cor:
题目要求的逻辑流程:
Timer0测量NE555频率 → Freq(原始频率)
↓
加上校准值Data_2
↓
Freq_Cor = Freq + Data_2(校准后频率)
↓
┌──────────────────────┐
│ 这是真正的频率值! │
└──────────────────────┘
↓
所有功能都基于Freq_Cor:
- 数码管显示Freq_Cor
- DAC根据Freq_Cor输出电压
- LED根据Freq_Cor判断超限
- 记录的最大频率是Freq_Max(基于Freq_Cor)
实际影响示例:
场景1: 无校准值
Freq = 2000Hz, Data_2 = 0
Freq_Cor = 2000Hz
→ 显示2000Hz,DAC输出对应2000Hz的电压 ✓
场景2: 有正校准值
Freq = 2000Hz, Data_2 = 300
Freq_Cor = 2300Hz
→ 应该显示2300Hz,DAC输出对应2300Hz的电压
→ 如果用Freq,会显示2000Hz ❌ 校准功能失效!
场景3: 有负校准值
Freq = 2000Hz, Data_2 = -500
Freq_Cor = 1500Hz
→ 应该显示1500Hz,DAC输出对应1500Hz的电压
→ 如果用Freq,会显示2000Hz ❌ 校准功能失效!
检查要点:
在代码中搜索所有使用频率值的地方,确保使用的是Freq_Cor而不是Freq:
-
数码管显示 - 拆分显示的是
Freq_Cor -
DAC计算 - 公式中使用的是
Freq_Cor -
LED超限判断 - 比较的是
Freq_Cor > Data_1 -
最大频率记录 - 比较和赋值都用
Freq_Cor -
错误判断(负数检测) - 判断的是
Freq_Cor < 0
记忆要点:
Freq仅用于测量,Freq_Cor用于一切显示和判断!
总结
第15届省赛第一题的关键问题:
- DAC公式精度丢失 - 整数除法导致线性调节功能完全失效
错误严重程度:
| 问题 | 严重程度 | 影响 |
|---|---|---|
| DAC公式错误 | DAC输出固定1V,失去线性调节功能,4T测评直接扣分 | |
| 未使用校准后频率 | 校准功能失效,显示错误,DAC输出错误,逻辑混乱 |
学到的经验
1. C语言浮点运算的陷阱
核心原则:
整数÷整数=整数,要想精度好,浮点要趁早!
类型转换时机:
// ❌ 错误:转换结果(为时已晚)
(float)(4/1500) // 先算4/1500=0,再转float → 0.0
// ✓ 正确:转换操作数(趁早转换)
4.0/1500 // 浮点数除法 → 0.00266...
(float)4/1500 // 同上
4/(float)1500 // 同上
实用技巧:
-
直接用浮点常量:
1.0,4.0(最简单,推荐) -
强制转换操作数:
(float)变量 -
乘以1.0:
变量*1.0(巧妙利用类型提升)
调试方法:
-
涉及除法的公式,先用实际数值手算验证
-
检查中间结果是否是浮点数
-
使用
printf打印中间值(调试时)
2. 理解数据处理流程
在蓝桥杯单片机题目中,经常有"原始数据→处理→最终数据"的流程:
传感器采集 → 原始数据 → 校准/滤波/转换 → 处理后数据 → 显示/控制
关键原则:
-
原始数据仅用于采集
-
处理后数据用于一切业务逻辑
-
不要混用原始数据和处理后数据
本题示例:
NE555 → Freq(原始) → +Data_2校准 → Freq_Cor(处理后) → 显示/DAC/LED
类比其他题目:
-
温度传感器:
temp_raw → 滤波 → temp_final → 显示 -
ADC采集:
adc_raw → 电压转换 → voltage → 判断 -
距离测量:
time_raw → 速度计算 → distance → 显示
复盘检查清单
在蓝桥杯单片机比赛中,写DAC控制和数据处理时,务必检查:
DAC公式部分:
-
涉及除法的公式使用浮点数常量(1.0, 4.0)
-
不要先算整数除法再转float
-
用实际数值手算验证公式正确性
-
测试多个中间值,不只测试边界值
数据处理流程:
-
明确区分原始数据和处理后数据
-
所有显示和判断使用处理后数据(如Freq_Cor)
-
数码管显示使用Freq_Cor
-
DAC计算使用Freq_Cor
-
LED判断使用Freq_Cor
-
最大频率记录使用Freq_Cor
代码质量:
-
变量命名语义清晰,不产生歧义
-
数据流程清晰,职责分离
-
逻辑结构清晰,易于理解
生成时间: 2026-02-14
蓝桥杯第十五届省赛(第一次)代码错误总结
第一次4T测评成绩: 85分(满分)
备注: 修复DAC公式错误,第一次测试即满分