从时序到代码:51单片机驱动DS18B20温度传感器的深度复盘

今天的学习目标非常明确,就是攻克那个经典又让人爱恨交加的"单总线"器件------DS18B20数字温度传感器。作为一个在嵌入式领域经久不衰的明星元件,它虽然只需要一根线就能通信,但背后的时序逻辑却极其考验对单片机GPIO口的操作功底。

整理这份笔记与代码,不仅是记录参数,更是对"线与逻辑"和"严格时序"的一次深度梳理。

一、 认识硬件:不仅是测温,更是精度的艺术

DS18B20之所以应用广泛,首先在于它出色的参数指标。在我们的设备温度和环境温度监测场景中,它的表现相当稳健。

量程与精度是首要关注的指标。它支持 -55℃ 到 +125℃ 的宽范围测量。在常温25℃的环境下,其精度可达 ±0.5℃。这意味着如果你测到24.5℃到25.5℃,都在合理误差范围内。

更有意思的是它的分辨率配置。DS18B20允许我们根据需求在9位到12位之间切换,这直接决定了温度变化的"细腻程度":

·9位时,分辨率是0.5℃(变化跨度大);

·10位是0.25℃;

·11位是0.125℃;

·而最高12位分辨率下,精度可达 0.0625℃。这对于需要捕捉微小温度波动的场景至关重要。

在工作电压方面,它支持 3V - 5.5V,完美兼容51单片机的5V逻辑电平。

二、 通信原理:独特的"单总线"与"线与特性"

DS18B20最特别的地方在于它采用单总线(One-Wire)协议。51单片机只需要一个IO口(通常接P3.7)即可完成双向通信。但这并不是简单的"你发我收",而是一种半双工、异步、串行的通信方式。

这里必须理解"线与特性"(Wired-AND)。在硬件电路上,DS18B20的DQ引脚是开漏输出的。这意味着:

·当单片机输出高电平(1),而DS18B20输出低电平(0)时,总线电平为0(低电平优先)。

·只有当双方都输出高电平时,总线才会被上拉电阻拉高到1。

这就解释了为什么我们在操作时,"释放总线"(即单片机输出1,由外部上拉电阻决定电平)是如此重要的动作。

在代码实现上,我们可以用宏定义来简化位操作,避免影响 P3 端口的其他引脚:

#define DQ_PIN_LOW (P3 &= ~(1 << 7)) // DQ引脚拉低

#define DQ_PIN_HIGH (P3 |= (1 << 7)) // DQ引脚拉高

#define DQ_CHECK ((P3 & (1 << 7)) != 0) // 判断DQ引脚为高电平

三、 核心时序:与微秒赛跑(代码实现篇)

这一部分的学习是最具挑战性的,因为DS18B20对时间极其敏感。任何一步的延时错误,都会导致通信失败。我们将时序逻辑直接落实在代码中。

  1. 复位时序(初始化)

通信的第一步永远是"唤醒"。单片机必须先将总线拉低 480us - 960us 发送复位脉冲,然后释放总线等待 15-60us。如果DS18B20存在且正常,它会在 60-240us 后拉低总线作为"应答脉冲"。

// ds18b20 复位

int ds18b20_reset(void)

{

int time = 0;

// 1. 主机向ds18b20发送复位脉冲

DQ_PIN_LOW;

Delay10us(70); // 拉低约700us (满足480us-960us)

DQ_PIN_HIGH;

Delay10us(6); // 释放总线,等待约60us

// 2. 等待低电平到来(检测DS18B20的应答脉冲)

while (DQ_CHECK && time < 30) // 超时时间约300us

{

Delay10us(1);

time++;

}

// 3. 超时处理

if (time >= 30)

{

uart_sendstr("wait ds18b20_high timeout!\n");

return -2;

}

return 1;

}

【代码笔记】:time < 30 是一个很好的超时退出机制,防止因为传感器损坏导致程序死循环。

  1. 写时序(Write)

写操作分为写"0"和写"1"。所有的写操作,都必须由主机从拉低总线开始。

·写0: 拉低总线 60us - 120us,然后释放。

·写1: 拉低总线 小于15us,然后迅速释放。

void write_ds18b20(unsigned char dat)

{

int i = 0;

for (i = 0; i < 8; i++)

{

if (dat & 1) // 写1

{

DQ_PIN_LOW;

nop(); nop(); // 拉低约2us (<15us)

DQ_PIN_HIGH;

Delay10us(6); // 等待60us,给DS18B20采样时间

}

else // 写0

{

DQ_PIN_LOW;

Delay10us(6); // 拉低约60us (满足60us-120us)

DQ_PIN_HIGH;

nop(); // 【关键细节】释放总线后加1us恢复时间

}

dat >>= 1;

}

}

【代码笔记】:写0结束时加一个 nop() 是非常重要的,这确保了总线有充分的恢复时间,防止DS18B20将连续的写0误判。

  1. 读时序(Read)

读操作同样由主机发起。主机拉低总线大于1us,然后释放,接着在15us内进行采样。

// 主机读取一个字节

unsigned char read_ds18b20(void)

{

unsigned char dat = 0;

int i = 0;

for (i = 0; i < 8; i++)

{

DQ_PIN_LOW;

nop(); nop();

DQ_PIN_HIGH; // 主机拉低>1us后释放总线

// 【关键细节】等待约10us,给DS18B20拉低总线的反应时间

Delay10us(1);

if (DQ_CHECK) // 此时读取总线电平

{

dat |= (1 << i); // 代表读到1

}

Delay10us(6); // 位周期等待,满足60us的读时隙

}

return dat;

}

【代码笔记】:很多初学者读出来全是0,就是因为 DQ_PIN_HIGH 之后没有延时,立刻去读,此时DS18B20还没反应过来把总线拉低。加上 Delay10us(1) 就能解决。

四、 采集流程:从指令到数据

掌握了时序,采集温度的逻辑就变得像流水线一样清晰。整个流程主要分为两个阶段:

  1. 启动转换:复位 -> 发送 0xCC (跳过ROM) -> 发送 0x44 (开启转换) -> 延时等待。

  2. 读取温度:复位 -> 发送 0xCC (跳过ROM) -> 发送 0xBE (读取暂存器) -> 读取两个字节。

// 读取ds18b20温度

float get_temp(void)

{

unsigned char temp_low = 0;

unsigned char temp_high = 0;

short temp = 0; // 【关键】使用有符号短整型,以处理负数温度

// 阶段一:启动温度转换

ds18b20_reset(); // 复位

write_ds18b20(0xCC); // skip ROM

write_ds18b20(0x44); // 开启温度转换

// 等待温度转换完毕。12位分辨率最大需要750ms,这里给足1s

Delay1ms(1000);

// 阶段二:读取温度

ds18b20_reset(); // 复位

write_ds18b20(0xCC); // skip ROM

write_ds18b20(0xBE); // 读取温度

temp_low = read_ds18b20(); // 读取温度低位

temp_high = read_ds18b20(); // 读取温度高位

// 2个字节合并温度值(符号位扩展)

temp = temp_high << 8;

temp |= temp_low;

// 12位分辨率下,最小刻度是 0.0625℃

return temp * 0.0625;

}

【代码笔记】:这里 short temp 的使用非常关键。如果 DS18B20 测到负温度(例如 -10℃),高字节的最高位会是1。如果使用 unsigned short,负数会被解析成一个巨大的正数。short 会自动完成补码转原码的过程,配合 * 0.0625 就能得到正确的负数温度。

五、 总结与反思

通过今天的学习与代码编写,我深刻体会到DS18B20虽然硬件连接简单(VCC、GND、DQ三根线),但软件模拟时序的复杂度不容小觑。它的"单总线"协议实际上是用时间换空间的典型设计。

对于51单片机来说,没有硬件外设支持,全靠GPIO口的拉高拉低和微秒级的延时函数来"硬"写时序。这让我明白,在嵌入式开发中,对时间的敬畏是第一位的。哪怕是一个几十微秒的延时偏差,都会导致读写数据的错位。

最后,加上 UART 串口的 sprintf 打印函数,形成完整的闭环,使得调试过程一目了然:

// main.c 主逻辑

int main(void)

{

float temp = 0;

char s32 = {0};

uart_init();

while (1)

{

temp = get_temp();

sprintf(s, "temp = %.2f\n", temp);

uart_sendstr(s);

// 注意:这里实际上建议加一个延时,比如 Delay1ms(1000),

// 否则串口打印太快,且DS18B20会一直处于转换状态导致发热影响测量

}

return 0;

}

搞定DS18B20,对于后续理解其他复杂的单总线或I2C器件,绝对是一次极好的练兵。

相关推荐
艾芯微科技2 小时前
ESDUNL24VC1 单向 ESD 静电保护二极管参数、电路设计与 PCB 防护布局
网络·单片机·嵌入式硬件·集成测试·51单片机
M78佐菲11 小时前
51单片机学习笔记:ds18b20要点整理
linux·笔记·嵌入式硬件·学习·51单片机
m0_7527531214 小时前
单片机基础知识
单片机·51单片机
xx~t20 小时前
嵌入式——单片机2
嵌入式硬件·学习·51单片机·串口·蜂鸣器
一直C20 小时前
DS18B20数字温度传感器超详细学习笔记|51单片机单总线通信
51单片机·ds18b20·one-wire
xx~t21 小时前
嵌入式——51单片机3
linux·c语言·嵌入式硬件·51单片机
sunoo-22921 小时前
【51单片机实战】温控下位机完整实现:DS18B20采集 + 自定义二进制串口协议 + PWM声光告
笔记·学习·51单片机·串口
m0_752753121 天前
51单片机中的定时器和中断等
单片机·51单片机
AAA代码批发商1 天前
DAYS41 UART 串口通信 + DS18B20 单总线温度传感器
嵌入式硬件·51单片机