蓝桥杯第十二届省赛单片机真题(第一场) — 刷题总结

蓝桥杯第十二届省赛单片机真题(第一场) — 刷题总结

一、题目概述

基于 STC15F2K60S2 单片机(12MHz),实现一个 DS18B20温度采集与DAC输出系统,包含:

  • DS18B20 温度采集与显示

  • 温度参数设置(整数,默认25°C)

  • 两种 DAC 输出模式(S5切换)

  • PCF8591 AD回读DAC输出电压

  • LED 指示当前模式/界面


二、功能模块实现

2.1 数码管显示(3个界面,S4 切换)

界面 首位标识 显示内容 格式
0 C (11) 实时温度 C XX.XX
1 P (12) 温度参数设置 P XX
2 A (13) DAC输出电压回读 A X.XX

2.2 按键功能

按键 功能 有效界面
S4 切换界面(0→1→2→0) 全局
S5 切换DAC模式(模式1↔模式2) 全局
S8 温度参数-1 界面1(参数设置)
S9 温度参数+1 界面1(参数设置)

关键逻辑:退出参数设置界面时温度参数才生效。

2.3 DAC输出模式

模式 规则
模式1 T < 参数 → 0V;T >= 参数 → 5V
模式2 T ≤ 20°C → 1.0V;20~40°C线性1.0~4.0V;T ≥ 40°C → 4.0V

2.4 LED 指示

LED 条件
L1 模式1
L2 温度显示界面
L3 参数设置界面
L4 DAC输出界面

三、踩坑记录(重点)

踩坑1:模式2线性插值 — 整数除法导致精度丢失

错误代码:

Da_1000x = 1000 + 3*(Temperature_100x - 2000) / 2;  // 电压×1000
Da_Write(Da_1000x / 1000 % 10 * 51);                 // 转DAC值

问题: Da_1000x / 1000 是整数除法,直接截断小数部分。

例如 T=30°C:Da_1000x = 25002500 / 1000 = 2(丢掉了0.5),2 * 51 = 102(≈2.0V),实际应输出2.5V(DAC=127)。

修复: 不要先算电压再转DAC,而是直接计算DAC值,把乘法放在除法前面:

DAC_data = 51 + (unsigned long)(Temperature_100x - 2000) * 153 / 2000;
Da_Write(DAC_data);

公式推导:

V = 1.0 + (T - 20) × 3.0 / 20.0
DAC = V × 51 = 51 + (T - 20) × 153 / 20

用 Temperature_100x 替换 T(放大100倍):
DAC = 51 + (Temperature_100x - 2000) × 153 / 2000

教训:整数运算中,乘法放前面、除法放后面,避免中间过程被截断丢失精度。


踩坑2:模式2线性插值 — 中间乘法溢出

问题代码:

DAC_data = 51 + (Temperature_100x - 2000) * 153 / 2000;

问题: (Temperature_100x - 2000) * 153 最大值为 1999 × 153 = 305,847,而 unsigned int 最大值为 65,535,溢出

修复: 在乘法之前将其中一个操作数强转为 unsigned long(32位):

DAC_data = 51 + (unsigned long)(Temperature_100x - 2000) * 153 / 2000;

原理: C语言规则 — 两个操作数类型不同时,窄类型自动提升为宽类型。unsigned long × int → 整个乘法在32位下进行。

注意转换位置:

// 正确:先转再乘,乘法在32位下进行
(unsigned long)(Temperature_100x - 2000) * 153

// 错误:先乘后转,括号内乘法仍在16位下溢出,转了也救不回来
(unsigned long)((Temperature_100x - 2000) * 153)

教训:中间产物的类型由参与运算的操作数中最宽的类型决定。类型转换必须放在溢出发生之前(乘法之前),而不是之后。


踩坑3:模式2分支不完整 — 缺少 T ≥ 40°C 和边界条件

错误代码:

if(Temperature_100x < 2000) Da_Write(1 * 51);
if((Temperature_100x > 2000) && (Temperature_100x < 4000))
{
    // 线性区间...
}
// 缺少 T >= 40°C 的分支!

问题:

  1. T ≥ 40°C 时没有任何输出,DAC保持上次的值
  2. T = 20°C(恰好2000)时,< 2000> 2000 都不匹配,落入空白区

修复:if-else if-else 三段互斥结构,边界用 <=>=

if(Temperature_100x <= 2000)
{
    Da_Write(51);        // T ≤ 20°C:1.0V
}
else if(Temperature_100x >= 4000)
{
    Da_Write(204);       // T ≥ 40°C:4.0V
}
else
{
    // 20°C < T < 40°C:线性插值
    DAC_data = 51 + (unsigned long)(Temperature_100x - 2000) * 153 / 2000;
    Da_Write((unsigned char)DAC_data);
}

教训:分段函数必须用 if-else if-else 保证互斥且无遗漏,边界值要明确归属。


四、核心知识点:线性插值在单片机中的实现

4.1 数学公式

已知两端点 (T_A, V_A)(T_B, V_B),求中间任意点:

V = V_A + (T - T_A) × (V_B - V_A) / (T_B - T_A)

本题:(20°C, 1.0V)(40°C, 4.0V)

V = 1.0 + (T - 20) × 3.0 / 20.0

4.2 单片机整数化三步法

第一步:目标量直接化
不要先算电压再转DAC,直接算最终需要的DAC值:

DAC = V × 51 = 51 + (T - 20) × 153 / 20

第二步:变量放大对齐
代码中温度放大了100倍,分母同步放大:

DAC = 51 + (T_100x - 2000) × 153 / 2000

第三步:防溢出类型提升
中间产物最大 305,847 > 65,535,在乘法前强转32位:

DAC = 51 + (unsigned long)(T_100x - 2000) * 153 / 2000;

4.3 验证表

温度 T_100x DAC计算 DAC值 电压
20°C 2000 ≤2000 → 51 51 1.0V
25°C 2500 51+500×153/2000 89 1.75V
30°C 3000 51+1000×153/2000 127 2.49V
35°C 3500 51+1500×153/2000 165 3.24V
40°C 4000 ≥4000 → 204 204 4.0V

五、通用经验总结

编号 经验 适用场景
1 整数运算中先乘后除,避免中间截断丢精度 所有需要保留精度的整数计算
2 中间产物可能溢出时,在乘法之前强转宽类型 unsigned int 乘积 > 65535 的场景
3 类型转换位置决定一切:转在括号外 vs 括号内效果完全不同 所有涉及类型提升的表达式
4 分段函数用 if-else if-else,边界用 <=/>= 明确归属 DAC分段输出、阈值判断
5 线性插值单片机实现:直接算目标量 → 变量放大对齐 → 防溢出提升 PCF8591 DAC、PWM占空比等线性映射
void AD_DA()
{
	unsigned char dac_val = 0;

	if(DACMODE == 0)
	{
		if(Temperature_100x < Temperature_parameter*100) dac_val = 0;
		else dac_val = 255;
	}else
	{
		if(Temperature_100x <= 2000) dac_val = 51;
		else if(Temperature_100x >= 4000) dac_val = 204;
		else dac_val = 51 + (unsigned long)(Temperature_100x - 2000) * 153 / 2000;
	}

	Da_Write(dac_val);
	AD_0_Data_100x = (unsigned int)dac_val * 100 / 51;  // 直接从DAC值计算电压×100
}```