蓝桥杯单片机通关秘籍:DS18B20 深度解析与工程规范
前言:在蓝桥杯单片机比赛中,DS18B20 温度传感器是绝对的“常客”。它看起来只是一个黑色的小豆子,但想要驯服它,不仅需要读懂它的“心”(时序),还要懂得如何优雅地在代码中安顿它(工程规范)。本篇笔记将带你从底层驱动到上层代码架构,彻底攻克这个模块。
第一章:避坑指南——大模板中的“隐形杀手”
在开始攻克 DS18B20 之前,先回顾一下数码管显示(Seg.c)中一个极易被忽视的错误。很多同学代码逻辑是对的,但数码管就是显示乱码,原因往往出在函数参数的顺序上。
1.1 参数顺序的重要性
在编写数码管显示函数时,我们通常会定义三个参数:
- wela (位选):决定哪一位数码管亮。
- dula (段选):决定显示什么数字。
- point (小数点):决定是否显示小数点。
⚠️ 警告:在 Seg.c 的底层驱动函数里,入口参数 wela、dula、point 的定义顺序与调用顺序必须严格一致!
比喻时刻:
这就好比你去银行柜台办事,柜员要求你依次递交“身份证、银行卡、密码”。
- 如果你按照“银行卡、身份证、密码”的顺序递进去,柜员(编译器/单片机)就会处理错误,导致业务办理失败(数码管乱码)。
- 切记:怎么定义的,就怎么传参,顺序绝对不能乱!
第二章:DS18B20 —— 单总线上的“独行侠”
2.1 硬件初相识
DS18B20 是一个非常有个性的传感器,它只需要 1根数据线 就能完成双向通信,我们称之为“单总线”(One-Wire)通信。
- 接线法则:
- GND:接地(地基)。
- VCC:接电源(能量源)。
- DQ (Data):数据线(唯一的传声筒)。
2.2 它的“内心世界”(寄存器)
当你给它上电复位时,它会处于一种“刚睡醒”的状态,此时温度寄存器里的默认值是 +85°C。如果你上电瞬间读取到了85°C,别慌,那是因为它还没来得及转换第一次温度。
温度数据的存储格式(16位二进制):
想象一个由16个小格子组成的抽屉:
- Bit 0-3 (小数位):负责存储精细的温度值(分辨率高达0.0625°C)。
- Bit 4-10 (整数位):负责存储温度的整数部分。
- Bit 11-15 (符号位):负责告诉你现在是零上还是零下。
- 全为 0:正温度(暖和)。
- 全为 1:负温度(冷)。
- 注:DS18B20 只会出现这两种符号位情况,不会有0和1混杂。
2.3 关键功能指令(核心考点)
想要让 DS18B20 干活,单片机(主机)必须发送特定的“暗号”。我们将这些指令按操作流程进行了编号和拆解:
指令 1:跳过 ROM 指令 [0xCC]
解释:
这是“点名”环节的简化版。
正常情况下总线上有多个设备,主机需要叫名字(序列号)才能指定谁回答。但蓝桥杯板子上只有一个 DS18B20,它是“独生子”。发送 0xCC 就是对它说:“别管名字了,我知道只有你,听我指挥!”
指令 2:温度转换指令 [0x44]
解释:
这是启动测量的“开关”。
发送此指令后,DS18B20 开始采集环境温度并进行计算。
- 注意细节:转换需要时间(毫秒级)。如果是“寄生电源”(偷电模式),主机必须在转换期间拉高总线供电。但在蓝桥杯板子上是外部供电,发送完这个指令后,我们可以通过读取总线状态来判断进度:读到 0 表示正在忙(转换中),读到 1 表示忙完了(转换完成)。转换好的数据会暂存在内部寄存器中。
指令 3:读取暂存寄存器指令 [0xBE]
解释:
这是“交作业”的命令。
该指令告诉 DS18B20:“把你刚刚测好、放在暂存器里的数据吐出来。”
- 读取流程:它会像流水线一样,从第0字节(温度低8位)一直吐到第8字节(CRC校验)。
- 实战技巧:在比赛中,我们通常只关心温度数据(前两个字节)。所以,读完前两个字节(LSB和MSB)后,主机可以直接发复位信号打断它,不需要把后面没用的数据都读完。
指令 4:写入暂存寄存器指令 [0x4E]
解释:
这是“设定规则”的命令。
用于向 DS18B20 写入配置信息,比如设置报警温度的上限(TH)和下限(TL),或者设置分辨率(9-12位)。
- 写入顺序:必须严格遵循 3 个字节的顺序:TH -> TL -> 配置寄存器。且数据传输遵循 LSB First(低位先出)原则。写入前通常建议先复位。
第三章:实战代码 —— 编写驱动与转换公式
在比赛资源包中,官方会提供 onewire.c(底层驱动),我们需要把它添加到工程中,并自己编写 onewire.h 以及上层读取函数。
3.1 读取温度的核心代码
// 记得包含必要的头文件
#include "onewire.h"
// 读取温度函数
float rd_Temperature()
{
unsigned char low, high; // 用于暂存读回来的高低八位数据
// — 第一阶段:命令它干活(转换温度) —
init_ds18b20(); // 1. 握手:复位初始化
Write_DS18B20(0xcc); // 2. 指令1:跳过ROM
Write_DS18B20(0x44); // 3. 指令2:开始温度转换!
// — 第二阶段:把数据读回来 —
// 注意:转换需要时间,为防止程序卡死,通常连续调用时
// 读取到的可能是上一次的转换结果,这在比赛中通常是允许的。
init_ds18b20(); // 1. 再次握手:准备读取
Write_DS18B20(0xcc); // 2. 指令1:再次跳过ROM
Write_DS18B20(0xbe); // 3. 指令3:读取暂存器数据
low = Read_DS18B20(); // 4. 接收低8位 (LSB)
high = Read_DS18B20(); // 5. 接收高8位 (MSB)
// — 第三阶段:数据合成与换算 —
// high << 8 : 把高8位移到它该在的位置(左移8位)
// | low : 把低8位拼上去
// / 16.0 : 核心算法!
return ((high << 8) | low) / 16.0;
}
3.2 为什么要除以 16.0?
这涉及到二进制小数的原理。DS18B20 的低 4 位是小数位。
- Bit 0 代表
2
−
4
=
0.0625
2^{-4} = 0.0625
2−4=0.0625 - Bit 1 代表
2
−
3
=
0.125
2^{-3} = 0.125
2−3=0.125 - Bit 2 代表
2
−
2
=
0.25
2^{-2} = 0.25
2−2=0.25 - Bit 3 代表
2
−
1
=
0.5
2^{-1} = 0.5
2−1=0.5
将一个 16 位的整数右移 4 位(相当于除以
2
4
=
16
2^4 = 16
24=16),就能把小数点对齐到正确的位置。例如,若读出的原始 16 位二进制数代表整数 2000,那么实际温度就是
2000
/
16
=
125.0
∘
C
2000 / 16 = 125.0^\\circ C
2000/16=125.0∘C。
第四章:工程规范 —— C语言中的“防重复包含”机制
准备工作完毕后,我们需要在 main.c 中引用 onewire.h。这里涉及到两个至关重要的 C 语言工程概念:Include Guard(头文件卫士) 和 引用路径。
4.1 什么是“防重复包含机制”?
简单来说,它的作用是:保证头文件里的内容,在一个 .c 文件(编译单元)里只被“抄写”一次。
核心原理:#include 的本质是“复制粘贴”
首先你要知道,编译器是“傻瓜式”的。当它看到 #include "onewire.h" 时,它不会思考,只会把 onewire.h 里的所有文字,原封不动地复制过来。
如果没有卫士(悲剧发生的场景):
假设你有一个 config.h,里面定义了 sbit LED = P1^0;。
编译器在处理 main.c 时,会先把 config.h 的内容复制一次,然后处理 onewire.h 时,又把 config.h 的内容复制了一次。
结果:编译器看到两行 sbit LED = P1^0;,它直接崩溃报错:“Redefinition(重定义)!你到底要定义几个 LED?”
解决方案:游乐园的“隐形印章”
为了解决这个问题,我们利用 C 语言预处理指令制作一个“卫士”。请看下面的代码模板:
#ifndef __ONEWIRE_H_ // 1. 门卫检查:我们要进门了吗?
#define __ONEWIRE_H_ // 2. 盖章确认:进来了,先盖个章!
// … 头文件内容(函数声明、变量定义) …
#endif // 3. 后门:结束
通俗比喻:游乐园检票逻辑
我们将编译器比作一个只认死理的保安,将头文件比作游乐园。
- 场景:你走到门口。
- 保安问:“你手上盖章了吗?(系统里记录过 __ONEWIRE_H_ 这个名字了吗?)”
- 如果没盖章:保安放你进去。
- 如果盖了章:保安直接把你拦在门外,指着旁边的快速通道说:“你已经进过去一次了,直接跳到出口(#endif 后面)去吧,别再进去占位置了。”
- 场景:进门后的第一件事。
- 动作:保安在你手上盖个章(在编译器内存里定义这个标记)。
- 作用:标记“这个文件已经被读取过了”,防止下次再被读取。
- 场景:游乐园的出口。
总结:当你写任何一个 .h 文件时,请形成肌肉记忆,永远加上这三行代码。这就叫“防重复包含机制”。
第五章:尖括号 < > 与双引号 " " 的爱恨情仇
在引用头文件时,很多小白分不清 #include <…> 和 #include "…"。其实,它们的区别在于编译器“找书”(搜索文件)的路线不同。
5.1 尖括号 < >:去“公立图书馆”找
- 含义:引用 标准库 或 编译器自带的库。
- 搜索路线:编译器会 直接 去它的安装目录下的 include 文件夹里找。
- 它不看哪里:它通常不会去你的当前工程文件夹里找。
- 适用场景:
- C 标准库:<stdio.h>, <math.h>
- 单片机寄存器定义:<STC15F2K60S2.H>, <reg52.h>
- 例子:#include <STC15F2K60S2.H>
- 这告诉编译器:“别在我桌子上(工程目录)乱翻,直接去系统库(Keil 安装目录)把这个芯片定义拿来。”
5.2 双引号 " ":先在“家里”找,找不到再去“图书馆”
- 含义:引用 你自己编写的 或 项目私有的 头文件。
- 搜索路线:
- 第一步:编译器先在 当前源文件(.c)所在的目录 查找。
- 第二步:如果家里没找到,它才会勉为其难地去 系统标准库目录 查找。
- 适用场景:
- 你自己写的驱动:"onewire.h", "key.h"
- 项目配置文件:"config.h"
- 例子:#include "onewire.h"
- 这告诉编译器:“这个文件就在我现在的工程文件夹里,先在这里找。如果实在找不到,你再去系统目录看看。”
5.3 避坑总结表
| < > | 仅系统目录 | 官方库、IDE自带库 | #include <STC15F2K60S2.H> |
| " " | 当前目录
→ \\to → 系统目录 |
自写 .h 文件、项目模块 | #include "onewire.h" |
第六章:最终调用
准备工作都做完了,我们该如何在主程序中调用这个芯片呢?
在 main.c 文件里:
void main()
{
float Temperature; // 定义变量存储温度
// 初始化系统…
while(1)
{
// 实时获取温度
Temperature = rd_Temperature();
// 此处可以添加显示代码,将 Temperature 显示在数码管上
// …
}
}
这条语句会让温度值实时更新在 Temperature 这个变量里,接下来你想怎么显示它,就是数码管的事情啦!




