51单片机-04-DS18B20 数字温度传感器

一、多定时器分工架构

系统同时需要驱动蜂鸣器、数码管、串口,三件事各需独立计时,用三个定时器分别负责:

|------------|--------------|-----------------------------------------------|
| 定时器 | 负责功能 | 配置要点 |
| Timer0 | 驱动蜂鸣器(PWM方波) | 模式1,16位;中断中翻转 P2.5;初值 g_i 可变控制频率 |
| Timer1 | UART 波特率发生器 | 模式2,8位自动重装;TH1=TL1=232(2400bps);不触发中断 |
| Timer2 | 数码管动态刷新(1ms) | 初值 64613;中断中调用 num_to_buff() + digiter_show() |

|---------------|------------------------------------------------------------------------------|
| 注意 Timer2 | STC89C52 才有 Timer2;标准 8051 只有 Timer0/Timer1。Timer1 被 UART 占用后,蜂鸣器必须用 Timer0。 |

二、DS18B20 传感器规格

|--------------|-----------------------------------------------------------|
| 参数 | 规格 |
| 量程 | −55°C ~ +125°C |
| 精度(误差) | ±0.5°C |
| 分辨率(可配置) | 9位=0.5°C / 10位=0.25°C / 11位=0.125°C / 12位=0.0625°C(默认12位) |
| 接口类型 | GPIO 单总线(1-Wire),DQ 一根线完成双向通信 |
| 工作电压 | 3V ~ 5.5V |

三、单总线(1-Wire)协议原理

3.1 硬件连接

  • DQ 线上接 4.7kΩ 上拉电阻到 VCC,空闲时总线保持高电平
  • 51 单片机是主机,DS18B20 是从机,主机发起所有通信
  • 同一条 DQ 线可挂多个 DS18B20(靠 ROM 地址区分)

3.2 线与特性(Open-Drain 开漏)

|--------------|--------------|----------|---------------|
| A 操作 | B 操作 | 总线结果 | 说明 |
| 拉低 | 释放 | 低电平 | A 把总线拉低,B 不阻止 |
| 释放 | 拉低 | 低电平 | B 把总线拉低 |
| 拉低 | 拉低 | 低电平 | 两个都拉低 |
| 释放 | 释放 | 高电平 | 都释放,上拉电阻把总线拉高 |

|-------------|------------------------------------------------------------------------|
| 关键 释放总线 | 作为数据接收方时,必须先释放总线(DQ_HIGH),让上拉电阻把总线拉高,再去检测从机发来的电平变化。否则主机一直拉低,永远读不到从机数据。 |

四、DS18B20 通信时序

4.1 宏定义(P3.7 接 DQ)

#define DQ_HIGH (P3 |= (1 << 7)) // 释放总线(高电平/高阻态)

#define DQ_DOWN (P3 &= ~(1 << 7)) // 拉低总线

#define DQ_CHECK ((P3 & (1 << 7)) != 0) // 读总线:非0=高,0=低

4.2 复位时序

|--------------------------------------------------|
| 复位流程 ds18b20_reset() |
| 1. 主机将 DQ 拉低 480~960us → 发出「复位脉冲」 |
| 2. 主机释放 DQ,上拉电阻将总线拉高 |
| 3. DS18B20 检测到上升沿后,等待 15~60us |
| 4. DS18B20 将 DQ 拉低 60~240us → 发出「存在脉冲」(我在!) |
| 5. DS18B20 释放 DQ,总线恢复高电平 |
| 6. 主机在释放后 60~240us 内检测到低电平 → 复位成功,返回 1 |

cs 复制代码
int ds18b20_reset(void) 
{

    int time = 0;

    // 发复位脉冲:拉低 700us(超过最小480us)

    DQ_DOWN;  Delay10us(70);

    DQ_HIGH;  Delay10us(6);   // 释放,等60us让总线稳定

    // 等待 DS18B20 把总线拉低(存在脉冲)

    while (DQ_CHECK && time < 30) { Delay10us(1); time++; }

    if (time >= 30) return -1;  // 超时:无设备

    // 等待 DS18B20 释放总线(恢复高)

    time = 0;

    while (!DQ_CHECK && time < 30) { Delay10us(1); time++; }

    if (time >= 30) return -2;  // 超时:通信异常

    return 1;  // 复位成功

}

4.3 写时序(51 向 DS18B20 发送1字节)

|-----------|------------------|--------------------|------------|
| 写的bit | 主机动作 | DS18B20 动作 | 时间要求 |
| 写 1 | 拉低 >1us 后立即释放 | 45us内采样,检测到高 = 收到1 | 总时隙 >60us |
| 写 0 | 拉低 60~120us 后释放 | 60us内采样,检测到低 = 收到0 | 总时隙 > |

cs 复制代码
void write_ds18b20(unsigned char dat)
 {

    int i;

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

        if (dat & 1) 
       {          // 当前bit=1(低位先行)

            DQ_DOWN; _nop_(); _nop_();  // 拉低约2us

            DQ_HIGH; Delay10us(5);       // 释放,等50us

        }
       else 
        {                // 当前bit=0

            DQ_DOWN; Delay10us(6);       // 拉低60us

            DQ_HIGH; Delay10us(6);       // 释放,等60us

        }

        dat >>= 1;             // 移到下一位

    }

}

4.4 读时序(DS18B20 向 51 发送1字节)

|------------------------------------|
| 读时隙(每个bit执行一次) |
| 1. 主机将 DQ 拉低 >1us(触发读时隙) |
| 2. 主机立即释放 DQ |
| 3. DS18B20 控制总线:发送0则拉低,发送1则保持高 |
| 4. 主机在释放后 15us 内采样 DQ:高=1,低=0 |
| 5. 等待约 60us,完成此bit,进入下一位 |

cs 复制代码
unsigned char read_ds18b20(void)

{

    int i;

    unsigned char dat = 0;

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

        DQ_DOWN; _nop_(); _nop_();  // 拉低约2us,触发读时隙

        DQ_HIGH; _nop_(); _nop_(); _nop_();  // 释放,等约3us

        if (DQ_CHECK)           // 采样:高=1

            dat |= (1 << i);   // 低位先收,逐位存入

        Delay10us(6);           // 等60us,完成此bit

    }

    return dat;

}

五、DS18B20 温度采集完整流程

|------------------------------------------------------------------------|
| get_temp() 完整步骤 |
| 1. ds18b20_reset() 复位,确认设备在线 |
| 2. write_ds18b20(0xCC) Skip ROM,跳过64位ROM匹配(总线只有一个设备时使用) |
| 3. write_ds18b20(0x44) Convert T,命令DS18B20开始ADC温度转换 |
| 4. Delay1ms(1000) 等待1s,12位精度转换最长需要750ms |
| 5. ds18b20_reset() 再次复位,开始读数据 |
| 6. write_ds18b20(0xCC) 再次 Skip ROM |
| 7. write_ds18b20(0xBE) Read Scratchpad,读取暂存器 |
| 8. temp_low = read_ds18b20() 读取温度低字节 |
| 9. temp_high = read_ds18b20() 读取温度高字节 |
| 10. temp = (temp_high<<8)|temp_low,return temp * 0.0625 换算为摄氏度 |

5.1 温度数据16位格式

|---------------------------|----------|-------------------|
| | 权重 | 说明 |
| bit15~bit11 (5位) | 符号位 S | 全0=正温度;全1=负温度(补码) |
| bit10~bit4 (7位) | 整数部分 | 温度的整数位 |
| bit3 (1位) | 0.5°C | 小数第1位 |
| bit2 (1位) | 0.25°C | 小数第2位 |
| bit1 (1位) | 0.125°C | 小数第3位 |
| bit0 (1位) | 0.0625°C | 小数第4位(12位精度最小单位) |

换算公式:摄氏度 = (short)原始16位值 × 0.0625

例:原始值 = 0x01AC = 428(十进制),428 × 0.0625 = 26.75°C

|------------------|------------------------------------------------------------------------------------------|
| 负温度 short 类型 | 使用有符号 short(而非 unsigned short),是因为 DS18B20 负温度时高字节符号位为1,short 能正确处理补码,乘以 0.0625 后自动得到负值。 |

5.2 get_temp() 完整代码

cs 复制代码
float get_temp(void) 
{

    unsigned char temp_low = 0, temp_high = 0;

    short temp = 0;

    // 第一阶段:触发温度转换

    ds18b20_reset();

    write_ds18b20(0xCC);  // Skip ROM

    write_ds18b20(0x44);  // 开始转换

    Delay1ms(1000);       // 等待转换完成

    // 第二阶段:读取温度数据

    ds18b20_reset();

    write_ds18b20(0xCC);  // Skip ROM

    write_ds18b20(0xBE);  // Read Scratchpad

    temp_low  = read_ds18b20();  // 先读低字节

    temp_high = read_ds18b20();  // 再读高字节

    // 第三阶段:拼合 & 换算

    temp  = temp_high << 8;  // 高字节移到高8位

    temp |= temp_low;        // 低字节填入低8位

    return temp * 0.0625;    // 乘以分辨率 = 摄氏度

}

六、延时函数 delay.c

|------------------|-------------|-------------------------------|
| 函数 | 用途 | 说明 |
| delay(n) | 粗略软件延时 | 空循环n次,不精确,用于非时序场合 |
| Delay10us(n) | 精确延时 10us×n | 用 nop() 填充,1-Wire时序必用 |
| Delay1ms(n) | 精确延时 1ms×n | 调用 Delay10us(100) 共n次,等待温度转换用 |

|-----------------|---------------------------------------------------------------------------------------------------|
| 知识点 nop() | 来自 intrins.h,执行一个空操作(NOP指令),占一个机器周期(约1.085us @11.0592MHz)。多个 nop() 叠加可精确控制微秒级延时,是1-Wire时序的关键手段。 |

七、main.c 温度采集与串口上报

cs 复制代码
#include <stdio.h>   // 提供 sprintf

int main(void)
 {

    float t = 0;

    char ds_temp[32];    // 存放格式化后的字符串

    uart_init();         // 初始化串口(2400bps)

    while (1) {

        t = get_temp();                        // 读温度(约需1秒)

        sprintf(ds_temp, "temp:%.2f\r\n", t); // 格式化为字符串

        uart_sendstr(ds_temp);                 // 串口发送给上位机

    }

}

|------------------------|-----------------------------------------------------------------------------------------------------------------|
| sprintf 格式化字符串 | sprintf(buf, "temp:%.2f\r\n", t) 把浮点数 t 格式化为 "temp:26.75\r\n" 存入 buf。%.2f 表示保留2位小数。\r\n 是回车换行,串口助手正确换行必须。 |

八、DS18B20 命令速查

|----------|------------------|-------------------------------|
| 命令码 | 名称 | 用途 |
| 0xCC | Skip ROM | 总线只有一个设备时使用,跳过64位ROM匹配,直接操作 |
| 0x44 | Convert T | 触发ADC开始采样,12位精度最长需等待750ms |
| 0xBE | Read Scratchpad | 读取暂存器,先低字节后高字节(共9字节,一般只读前2字节) |
| 0x4E | Write Scratchpad | 配置分辨率(写入 TH/TL报警值 + 配置寄存器) |
| 0x55 | Match ROM | 总线多设备时,指定某个设备的64位ROM地址通信 |
| 0xF0 | Search ROM | 枚举总线上所有DS18B20的ROM地址 |

相关推荐
国科安芯12 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao14 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉17 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者18 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间19 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious19 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
KaifuZeng2 天前
通信与接口协议面试一、基本概念
嵌入式硬件·通信与接口协议
wuyk5552 天前
24. C 语言模块化:不是拆几个.c 文件那么简单
c语言·开发语言·stm32·单片机