一、多定时器分工架构

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

定时器

负责功能

配置要点

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

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

总时隙 >

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,进入下一位

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() 完整代码

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 温度采集与串口上报

#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地址

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐