一、题目概述
基于 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 |
2.4 LED 指示
| LED | 条件 |
|---|---|
| L1 | 模式1 |
| L2 | 温度显示界面 |
| L3 | 参数设置界面 |
| L4 | DAC输出界面 |
三、踩坑记录(重点)
踩坑1:模式2线性插值 — 整数除法导致精度丢失
错误代码:
|
|
问题: Da_1000x / 1000 是整数除法,直接截断小数部分。
例如 T=30°C:Da_1000x = 2500,2500 / 1000 = 2(丢掉了0.5),2 * 51 = 102(≈2.0V),实际应输出2.5V(DAC=127)。
修复: 不要先算电压再转DAC,而是直接计算DAC值,把乘法放在除法前面:
|
|
公式推导:
|
|
教训:整数运算中,乘法放前面、除法放后面,避免中间过程被截断丢失精度。
踩坑2:模式2线性插值 — 中间乘法溢出
问题代码:
|
|
问题: (Temperature_100x - 2000) * 153 最大值为 1999 × 153 = 305,847,而 unsigned int 最大值为 65,535,溢出。
修复: 在乘法之前将其中一个操作数强转为 unsigned long(32位):
|
|
原理: C语言规则 — 两个操作数类型不同时,窄类型自动提升为宽类型。unsigned long × int → 整个乘法在32位下进行。
注意转换位置:
|
|
教训:中间产物的类型由参与运算的操作数中最宽的类型决定。类型转换必须放在溢出发生之前(乘法之前),而不是之后。
踩坑3:模式2分支不完整 — 缺少 T ≥ 40°C 和边界条件
错误代码:
|
|
问题:
- T ≥ 40°C 时没有任何输出,DAC保持上次的值
- T = 20°C(恰好2000)时,
< 2000和> 2000都不匹配,落入空白区
修复: 用 if-else if-else 三段互斥结构,边界用 <= 和 >=:
|
|
教训:分段函数必须用
if-else if-else保证互斥且无遗漏,边界值要明确归属。
四、核心知识点:线性插值在单片机中的实现
4.1 数学公式
已知两端点 (T_A, V_A) 和 (T_B, V_B),求中间任意点:
|
|
本题:(20°C, 1.0V) → (40°C, 4.0V)
|
|
4.2 单片机整数化三步法
第一步:目标量直接化 不要先算电压再转DAC,直接算最终需要的DAC值:
|
|
第二步:变量放大对齐 代码中温度放大了100倍,分母同步放大:
|
|
第三步:防溢出类型提升 中间产物最大 305,847 > 65,535,在乘法前强转32位:
|
|
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占空比等线性映射 |