一、DS18B20是什么
DS18B20是DALLAS公司生产的数字温度传感器,最大的特点是采用单总线(1-Wire)协议------只用一根数据线就能完成双向通信。与传统的热敏电阻或模拟温度传感器相比,它把温度采集、模数转换和数字输出全部集成在芯片内部,直接输出数字温度值,省去了外部ADC。
它的主要特性包括:
- 测温范围:−55℃ ~ +125℃
- 精度:−10℃ ~ +85℃范围内误差不超过±0.5℃
- 分辨率:可编程为9~12位,对应0.5℃、0.25℃、0.125℃、0.0625℃
- 转换时间:9位分辨率约93.75ms,12位约750ms
- 供电:3.0~5.5V,支持寄生电源供电(从数据线取电)
- 唯一序列号:每片DS18B20有64位激光ROM,可在同一总线上挂载多个传感器实现多点测温
TO-92封装的三个引脚分别是:GND(1脚) 、DQ(2脚,数据输入/输出) 、VDD(3脚,电源)。外部供电时VDD接电源,寄生供电时VDD必须接地。
二、单总线协议:为什么51单片机需要软件模拟
单总线协议的特点是"一根线走天下"------数据双向传输、供电(寄生模式)都靠这一根线。但标准51单片机(如AT89C51/STC89C52)的硬件上并不支持单总线协议 ,必须用软件精确模拟时序。
这意味着:每一个微秒级的延时都会直接影响通信成败。DS18B20的时序间隙以微秒计算,网上直接复制的驱动代码经常因为晶振频率或单片机型号不同而失效。下面逐一拆解三种核心时序。
2.1 复位时序(初始化)
主机拉低总线480960μs**,然后释放总线(拉高)。DS18B20检测到上升沿后,等待1560μs,再拉低总线60~240μs**发出"存在脉冲"。主机在释放总线后读取总线电平,如果读到低电平,说明器件在线。
主机拉低 ≥480μs → 释放 → 等待15~60μs → DS18B20拉低60~240μs → 主机采样
2.2 写时序
所有写时序由主机拉低总线发起,每个写时隙至少60μs,两个写时隙之间至少1μs恢复时间。
- 写"0" :主机拉低总线,保持至少60μs,然后释放。DS18B20会在主机拉低后的15~45μs窗口内采样总线电平,此时总线仍为低,识别为0。
- 写"1" :主机拉低总线,**115μs**内迅速释放。DS18B20在1545μs窗口采样时,总线已被上拉电阻拉高,识别为1。
关键点:写1时拉低的时间极短,写0时拉低的时间较长。这个"窗口采样"机制是单总线协议的核心。
2.3 读时序
主机拉低总线1~15μs 后释放,然后在15μs内采样总线电平。如果读到高电平则为1,低电平则为0。每个读时隙至少60μs。
主机在读时隙期间必须释放总线(置1),否则会与DS18B20的输出冲突。
三、操作命令与读取流程
3.1 主要命令
DS18B20的命令分为ROM命令和功能命令两类:
ROM命令:
| 命令 | 代码 | 用途 |
|---|---|---|
| 跳过ROM | 0xCC | 不区分器件,对所有器件操作。单器件时必用 |
| 匹配ROM | 0x55 | 后跟64位序列号,选中特定器件 |
| 搜索ROM | 0xF0 | 遍历总线上的所有器件序列号 |
| 读ROM | 0x33 | 读取器件的64位ROM(单器件时) |
功能命令:
| 命令 | 代码 | 用途 |
|---|---|---|
| 温度转换 | 0x44 | 启动温度测量,12位分辨率需等待约750ms |
| 读暂存器 | 0xBE | 读取9字节暂存器,前两字节为温度值 |
| 写暂存器 | 0x4E | 写入TH、TL报警阈值 |
| 复制暂存器 | 0x48 | 将阈值写入EEPROM |
3.2 温度读取的标准流程
一次完整的温度读取分四步:
第一步:复位 → 检测存在脉冲
第二步:发送 0xCC(跳过ROM)
第三步:发送 0x44(启动转换)→ 延时等待(12位约750ms)
第四步:复位 → 0xCC → 0xBE → 连续读取两个字节
注意第四步必须重新复位,因为温度转换命令执行后DS18B20的状态已经改变。
3.3 温度数据的换算
DS18B20以16位二进制补码 输出温度值,低字节在前,高字节在后。12位分辨率下,每个LSB对应0.0625℃。
温度值(℃)= 原始16位值 × 0.0625
例如读到 0x0191(低字节0x91,高字节0x01),拼成16位为0x0191 = 401,401 × 0.0625 ≈ 25.06℃。
负温度的处理:高5位为符号位,全1表示负温。补码转原码后取负即可。
四、C51代码实现
4.1 硬件连接
以外部供电方式为例:
DS18B20 VDD → VCC (5V)
DS18B20 GND → GND
DS18B20 DQ → P1.4(接4.7K上拉电阻到VCC)
上拉电阻是必须的,它保证总线在空闲时维持高电平,同时也为寄生供电提供能量通路。
4.2 底层驱动函数
c
#include <reg51.h>
#include <intrins.h>
sbit DQ = P1^4; // 数据引脚
// 微秒级延时,t为倍数,具体时间取决于晶振
void Delay_OneWire(unsigned int t)
{
while(t--);
}
// 复位并检测存在脉冲
// 返回1:器件存在;返回0:器件不存在
bit DS18B20_Reset(void)
{
bit presence;
DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80); // 拉低约480~960us
DQ = 1;
Delay_OneWire(10); // 等待15~60us
presence = DQ; // 读取存在脉冲
Delay_OneWire(5);
return presence; // 0表示存在(DS18B20拉低总线)
}
// 写一个字节,低位在前
void DS18B20_WriteByte(unsigned char dat)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
DQ = 0;
DQ = dat & 0x01; // 写1:拉低后立刻置高;写0:保持低
Delay_OneWire(5); // 约60us时隙
DQ = 1;
dat >>= 1;
}
Delay_OneWire(5); // 时隙间恢复
}
// 读一个字节,低位在前
unsigned char DS18B20_ReadByte(void)
{
unsigned char i, dat = 0;
for(i = 0; i < 8; i++)
{
DQ = 0;
dat >>= 1;
DQ = 1; // 释放总线,让DS18B20输出
if(DQ) dat |= 0x80; // 15us内采样
Delay_OneWire(5);
}
return dat;
}
注意延时函数的校准 :Delay_OneWire(1) 的实际时间与晶振频率和编译器优化级别有关。上述代码在12MHz晶振、Keil默认优化下的粗略对应关系是1个t约6μs左右,具体需要用示波器或逻辑分析仪实测校准。如果读不到数据,首先检查延时是否匹配。
4.3 温度读取函数
c
// 读取温度,返回浮点摄氏度值
float DS18B20_ReadTemp(void)
{
unsigned char low, high;
int raw;
float temp;
DS18B20_Reset();
DS18B20_WriteByte(0xCC); // 跳过ROM
DS18B20_WriteByte(0x44); // 启动温度转换
// 等待转换完成,12位分辨率约750ms
// 实际项目中建议用定时器计时,此处用延时简化
Delay_OneWire(60000); // 根据实际晶振校准
Delay_OneWire(60000);
DS18B20_Reset();
DS18B20_WriteByte(0xCC);
DS18B20_WriteByte(0xBE); // 读暂存器
low = DS18B20_ReadByte(); // 温度低8位
high = DS18B20_ReadByte(); // 温度高8位
raw = (high << 8) | low; // 拼成16位
temp = raw * 0.0625; // 12位分辨率换算
return temp;
}
4.4 完整示例:读取并显示温度(串口输出)
把上面的函数组合起来,通过串口把温度发送出去:
c
#include <reg51.h>
#include <intrins.h>
sbit DQ = P1^4;
// ... 此处省略 Delay_OneWire、Reset、WriteByte、ReadByte、ReadTemp ...
// 串口初始化(9600bps @ 11.0592MHz)
void UART_Init(void)
{
TMOD = 0x20; // T1方式2
TH1 = 0xFD;
TL1 = 0xFD;
TR1 = 1;
SM0 = 0; SM1 = 1; // 方式1
REN = 1;
// 此示例用查询发送,不开中断
}
void UART_SendByte(unsigned char dat)
{
SBUF = dat;
while(!TI);
TI = 0;
}
void UART_SendString(char *str)
{
while(*str) UART_SendByte(*str++);
}
void main(void)
{
float temp;
char buf[20];
int tempInt, tempFrac;
UART_Init();
UART_SendString("DS18B20 Ready\r\n");
while(1)
{
temp = DS18B20_ReadTemp();
// 整数部分和小数部分分开处理,避免sprintf占用资源
tempInt = (int)temp;
tempFrac = (int)((temp - tempInt) * 100);
UART_SendString("Temp: ");
// 简单的整数转字符串(省略完整实现)
// ...
UART_SendString("\r\n");
Delay_OneWire(50000); // 间隔约1秒再读
}
}
4.5 蓝桥杯CT107D的特殊注意事项
蓝桥杯官方CT107D开发板上,DS18B20连接在P1.4,但官方提供的 onewire.c 驱动中的延时函数与实际硬件不匹配,直接使用往往读不到正确温度。常见的修正方式是增大延时倍数:
c
void Delay_OneWire(unsigned int t)
{
t = t * 12; // 官方驱动需要扩大12倍
while(t--);
}
如果参加蓝桥杯,建议先用官方驱动测试,读不到温度再逐步调整延时倍数,直到结果稳定。
五、供电方式与电路
5.1 外部供电(推荐)
DS18B20的VDD接3.0~5.5V电源,GND接地,DQ接单片机I/O并接4.7K上拉电阻到VCC。这是最稳定的方式,多点测温时尤其推荐使用。
5.2 寄生供电
VDD和GND都接地,DS18B20从DQ数据线取电:总线高电平时给内部电容充电,低电平时由电容供电。这种方式省一根电源线,但缺点明显:
- 仅适合单器件场景,多点测温时4.7K上拉电阻无法提供足够能量
- 温度转换期间总线需要保持高电平,否则供电中断
- 不适用于电池供电系统
实际项目建议优先使用外部供电,除非布线空间极其受限。
六、多点测温
DS18B20的64位ROM序列号使多点测温成为可能。总线上挂多个器件时,不能用0xCC(跳过ROM),必须用0x55(匹配ROM) 后跟目标器件的64位序列号来选中特定器件。
获取总线上的所有序列号需要用搜索ROM命令(0xF0) 配合二叉树搜索算法,这个过程比较复杂,涉及位碰撞检测。对于大多数应用,如果已知器件的序列号(可以通过单器件连接时先读一次ROM获得),直接存起来用0x55匹配即可。
七、常见问题与排查
1. 读温度恒为0xFFFF或0x0550:几乎一定是时序不准。重点检查复位脉冲宽度、写/读时隙是否在规格范围内。用示波器测量DQ引脚的实际波形是最直接的调试手段。
2. 检测不到存在脉冲:先检查接线(VDD、GND、DQ是否接对)、上拉电阻是否4.7K、供电是否正常。如果使用寄生供电,确认VDD是否已接地。
3. 温度跳变或偶尔出错 :读数据时如果被中断打断,时序会被破坏。在DS18B20读写期间关闭中断 (EA = 0)可以解决,读完再恢复。但关闭中断会影响其他实时任务,折中方案是把读温度操作放在主循环中,确保中断不会在关键时隙内触发。
4. 转换时间不够:12位分辨率需要750ms,如果延时不足就读暂存器,读到的是上一次的转换结果或全1。可以用定时器精确计时,或者读暂存器后检查温度值是否合理。
5. 网上代码直接复制不工作 :这是最常见的问题。延时函数与晶振和编译器优化强相关,必须根据实际硬件校准。建议用示波器测量一个写时隙的实际宽度,与规格对比后调整 Delay_OneWire 的参数。
八、总结
DS18B20驱动的核心可以归纳为三点:
第一,单总线靠软件模拟。51单片机没有硬件单总线外设,复位、读、写三种时序全部要用I/O口配合微秒级延时实现。
第二,时序是成败关键。复位拉低480~960μs,写0保持低60μs以上,写1在15μs内释放,读时在15μs内采样。延时函数必须根据实际晶振校准,不能直接套用网上代码。
第三,读取流程是固定的四步 :复位 → 0xCC → 0x44(等转换)→ 复位 → 0xCC → 0xBE → 读两字节 → ×0.0625。把这个流程封装成一个 ReadTemp() 函数,上层调用就非常简单了。
掌握这三点,DS18B20从"读不出数据"到"稳定测温"的跨越就完成了。