本章将学习ESP32-S3 如何读取外部温度传感器的温度,来得到较为准确的环境温度。将学习单总线技术,通过它来实现 ESP32-S3 和外部温度传感器 DS18B20 的通信,并把从温度传感器得到的温度显示在 LCD 上。
        本章分为如下几个小节:
29.1 DS18B20 简介
29.2 硬件设计
29.3 程序设计
29.4 下载验证

29.1 DS18B20 介绍

29.1.1 DS18B20 简介
        DS18B20 是由 DALLAS 半导体公司推出的一种“单总线”接口的温度传感器,它有不同封装形式,下面我用一个表格汇总其主要封装类型的区别。

特性维度​

​TO-92 (直插)​

​SO/SOIC (贴片)​

​μSOP (超薄贴片)​

​不锈钢防水封装​

​​外观与引脚形态​​

类似普通三极管,​​引脚直插​

体形扁平,​​引脚从两侧引出​​,适合贴片焊接

体形​​非常小巧扁平​​,引脚间距更细

在基础芯片封装外,​​增加了金属护套和防水材料​

​​防护等级与耐用性​​

​较低​​,芯片直接暴露

​较低​​,芯片有塑料包裹但依然脆弱

​较低​​,芯片有塑料包裹但依然脆弱

​非常高​​,不锈钢外壳抗腐蚀、防水、防潮,适合恶劣环境

​​适用场景​​

实验板、面包板、​​干燥温和的室内环境​​(如家用电器、学习板)

​需要小型化的PCB产品​​(如智能家居设备内部)

​空间极度受限的紧凑型电子产品​​(如可穿戴设备)

​潮湿、多尘、易磕碰或需要浸入式测量的工业环境​​(如电缆沟、锅炉、农业大棚)

​​安装方式​​

穿孔焊接(Through-hole)

表面贴装(SMD)

表面贴装(SMD)

​螺纹固定(如M10X1)、法兰安装或导管连接​​,提供机械保护

​​热响应速度​​

中等

中等

较快

​通常较慢​​,因金属外壳和内部灌胶增加了热惯性

​​价格​​

​最低​

中等

中等

​最高​

​​额外要求​​

​需要小心布线​​,笨重的探头和较长的引线可能影响安装和热传递

        由于手头只有一个不锈钢防水封装​的DS18B20,因此本实验使用它做实验,具体实物图如下:

图 29.1.1.1 DS18B20 实物图

        DS18B20 有三个引脚(红、黑、黄三色线),管脚含义如下:

线颜色(常见)

引脚名称

功能

连接至 ESP32

​​红色​​

VDD

电源正极 (3.3V-5V)

3V3引脚

​​黑色​​

GND

电源地

GND引脚

​​黄色​​

DQ

数据线

任一 GPIO 引脚 (如 GPIO4)

表 29.1.1.1 DS18B20引脚说明

        与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。单总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新的概念,测试温度范围为-55~+125℃,精度为±0.5℃。现场温度直接以单总线的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9~12 位的数字值读数方式。它工作在 3~5.5V 的电压范围,采用多种封装形式,从而使系统设置灵活、方便,设定分辨率以及用户设定的报警温度存储在 EEPROM 中,掉电后依然保存。其内部结构如下图所示。

图 29.1.1.2 DS18B20 内部结构图

        ROM 中的 64 位序列号是出厂前被标记好的,它可以看作使该 DS18B20 的地址序列码,每个 DS18B20 的 64 位序列号均不相同。 64 位 ROM 的排列是:前 8 位是产品家族码,接着 48 位是 DS18B20 的序列号,最后 8 位是前面 56 位的循环冗余校验码(CRC=X8+X5+X4+1)。 ROM作用是使每一个 DS18B20都各不相同, 这样设计可以允许一根总线上挂载多个 DS18B20模块同时工作且不会引起冲突。

29.1.2 DS18B20 时序介绍

        所有单总线器件要求采用严格的信号时序,以保证数据的完整性。 DS18B20 共有 6 种信号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外,都是由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里介绍这几个信号的时序。

(1)复位脉冲和应答脉冲
 

图 29.1.2.1 复位脉冲和应答脉冲时序图

        单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少要在480us,以产生复位脉冲。接着主机释放总线, 4.7K 的上拉电阻将单总线拉高,延时时间要在15~60us,并进入接收模式(Rx)。接着 DS18B20拉低总线 60~240us,以产生低电平应答脉冲。

(2)写时序
 

图 29.1.2.2 写时序图

        写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在两次独立的写时序之间至少需要 1us的恢复时间,两种写时序均起始于主机拉低总线。写 1时序:主机输出低电平,延时2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线延时2us。

(3)读时序
 

图 29.1.2.3 读时序图

        单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后延时50us。
        在了解单总线时序之后,看一下 DS18B20 的典型温度读取过程, DS18B20 的典型温度读取过程为:

1)初始化总线​​:MCU 发送一个复位脉冲,DS18B20 回应一个存在脉冲;
​​2)发送 ROM 命令​​:例如,跳过 ROM 搜索 (0xCC),因为总线上通常只有一个器件;
​​3)发送功能命令​​:发送开始温度转换的命令 (0x44);
​​4)等待转换完成​​:与分辨率有关系,分辨率可配置(9-12 位),分辨率越高,转换时间越长。默认 12 位,温度转换需要时间(典型为 750ms);
​​5)再次初始化总线​​;
​​6)再次发送 ROM 命令​​ (0xCC);
​​7)发送读取暂存器命令​​ (0xBE);
​8)​读取数据​​:从总线上连续读取 2 个字节(共 16 位),即为温度数据;
​​9)数据转换​​:将读取的 16 位数据转换为实际的温度值。

        DS18B20 的详细说明参考其数据手册。

29.1.3 DS18B20 读取温度值数据转换

        DS18B20 温度传感器输出的 ​​16 位带符号二进制补码​​ 数据,是其温度测量的核心数字表示。下面解释这种格式的结构、含义以及如何将其转换为实际的温度值。
        为了更直观地理解其数据格式和转换过程,可以参考下面的流程图:

图 29.1.3.1 数据格式和转换过程

(1)16位数据格式详解:

        DS18B20 转换后的温度值以 ​​16 位带符号二进制补码​​ 形式存放在其高速暂存存储器的第0(低字节)和第1(高字节)个字节中。其结构如下:

位范围(高字节)​

​位范围(低字节)​

​含义​

​​bit15​​

​符号位 (S)​​。0表示正温度,1表示负温度。

​​bit14 ~ bit11​​

温度值的整数部分(2^3 到 2^6)

​​bit10 ~ bit4​​

温度值的整数部分(2^2 到 2^-3)

​​bit3 ~ bit0​​

温度值的小数部分(2^-4 = 0.0625)。分辨率最低位为 LSB。

表 29.1.3.1 16位数据格式详解

        读取顺序​​:单片机读取时,​​先读低字节(LSB),再读高字节(MSB)​​。

        符号位​​:最高位(bit15)表示正负(0正,1负)。
        ​​整数部分​​:由​​高字节的低7位(bit14~bit8)和低字节的高4位(bit7~bit4)​​ 共同组成一个11位的整数。
        ​​小数部分​​:由​​低字节的低4位(bit3~bit0)​​ 表示,其权重从高位到低位依次为 2⁻¹, 2⁻², 2⁻³, 2⁻⁴(即0.5, 0.25, 0.125, 0.0625)。

        最终温度值:温度值 = ​​(整数部分 + 小数部分) °C​​

(2)温度值转换方法

        转换的核心是识别符号位,并对数值部分进行处理。下面分别举例读取到正温度值和负温度值如何转换:

  • 正温度(S = 0)

        当符号位 ​​S=0​​ 时,表示温度值为正。
​​        转换公式​​:​​实际温度 = (raw_temp) × 0.0625​​,raw_temp​​ 是将高字节和低字节合并后的 ​​16 位无符号整数​​。
        ​​例如​​:输出数据高字节为0x07,低字节为0xD0,组合后的16位值为0x07D0(0000 0111 1101 0000)
        符号位(第15位) S=0,是正温度,0x07D0的十进制是 2000,实际温度 = 2000 × 0.0625 = ​​125°C​​。
        或者按整数和小数分开计算:
        整数部分​​:高字节的低7位 (000 0111) 和低字节的高4位 (1101) 组成 000 01111101(二进制) = 125(十进制)
        小数部分​​:低字节的低4位 (0000) = 0
        计算温度​​:125 + 0 * 0.0625 = 125°C​​

  • 负温度(S = 1)

        当符号位 ​​S=1​​ 时,表示温度值为负。
        ​​转换公式​:​​实际温度 = ([补码] 转换为 [原码] 后的数值) × 0.0625​​
​​        补码转原码​​:数值部分​​取反后加1​​(符号位不变)。
        ​​例如​​:输出数据高字节为0xFC,低字节为0x90,组合后的16位值为0xFC90(1111 1100 1001 0000)
        符号位(第15位) S=1,是负温度,0xFC90是补码。​​求原码​​:数值位取反得 0x036F(0000 0011 0110 1111),再加1得 0x0370(880)。
        实际温度 = - (880 × 0.0625) = ​​-55°C​​。

        或者按整数和小数分开计算:
        整数部分​​:高字节的低7位 (111 1100) 和低字节的高4位 (1001) 组成 111 11001001(二进制) -> 这部分是补码格式->取反(0x36) +1 = 0x37(原码)= 55(十进制)
        小数部分​​:低字节的低4位 (0000) = 0
        计算温度​​:-(55 + 0*0.0625) = -55°C​​

(3)在代码中处理
        在实际编程中,可以利用 C 语言的数据类型特性来简化操作:

// 1. 从DS18B20读取两个字节
uint8_t low_byte = read_byte();  // 温度低字节
uint8_t high_byte = read_byte(); // 温度高字节

// 2. 将两个字节组合成一个16位有符号整数
int16_t raw_temp = (high_byte << 8) | low_byte;

// 3. 转换为实际温度值(浮点数)
float temperature = raw_temp * 0.0625f;

        声明 raw_temp为 ​​int16_t​​(16位有符号整数类型)。
        编译器会自动将补码转换为原码。当 raw_temp为负值时(即最高位为1),raw_temp * 0.0625f的结果自动为负数。
        这种方法无需手动判断符号位和进行取反加一操作,​​代码更简洁,不易出错​​。

29.2 硬件设计

29.2.1 例程功能

        DS18B20 每隔 100ms 左右读取一次数据,并把温度显示在 LCD 上。 LED 闪烁用于提示程
序正在运行。

29.2.2 硬件资源
        1.LED 灯
                LED-IO1
        2.USART0
                U0TXD-IO43
                U0RXD-IO44
        3.XL9555
                IIC_SDA-IO41
                IIC_SCL-IO42
        4.SPILCD
                CS-IO21
                SCK-IO12
                SDA-IO11
                DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
                PWR- IO1_3(XL9555)
                RST- IO1_2(XL9555)
        5.DS18B20
                1WIRE_DQ-IO0(在 P5 端口,使用跳线帽将 IWIRE_DQ 和 IO0 相连)
29.2.3 原理图

        DS18B20 原理图,如下图所示。

图 29.2.3.1 DS18B20 原理图

从上图可以看出, 1WIRE_DQ并没有直接跟 ESP32-S3的 IO0相连,而是需要通过跳线帽进行连接。 1WIRE_DQ 和 IO0 连接图如下图所示。
 

图 29.2.3.2 WIRE_DQ 和 IO0 连接图
 

图 29.2.3.3 DS18B20 与开发板连接的位置

29.3 程序设计

29.3.1 程序流程图

        本实验的程序流程图:

图 29.3.1.1 DS18B20 实验程序流程图

29.3.2 DS18B20 函数解析

        这一章节除了涉及到 GPIO 的 API 函数,便没有再涉及到其他 API 函数。

29.3.3 DS18B20 驱动解析

        在 IDF 版 19_ds18b20 例程中,在 19_ds18b20\components\BSP 路径下新增了一个DS18B20 文件夹,分别用于存放 ds18b20.c、 ds18b20.h 这两个文件。其中, ds18b20.h 文件负责声明 DS18B20 相关的函数和变量,而 ds18b20.c 文件则实现了 DS18B20 的驱动代码。下面,详细解析这两个文件的实现内容。

(1)ds18b20.h 文件
        由于数据线会存在输入输出模式的切换以及高低电平的设置,所以为 DS18B20_DQ_PIN 做了相关宏函数供单总线时序函数调用。

/* 引脚定义 */
#define DS18B20_DQ_GPIO_PIN       GPIO_NUM_0

/* DS18B20引脚高低电平枚举 */
typedef enum 
{
    DS18B20_PIN_RESET = 0u,
    DS18B20_PIN_SET
}DS18B20_GPIO_PinState;

/* IO操作 */
#define DS18B20_DQ_IN     gpio_get_level(DS18B20_DQ_GPIO_PIN)   /* 数据端口输入 */

/* DS18B20端口定义 */
#define DS18B20_DQ_OUT(x) do{ x ?                                                   \
                            gpio_set_level(DS18B20_DQ_GPIO_PIN, DS18B20_PIN_SET) :  \
                            gpio_set_level(DS18B20_DQ_GPIO_PIN, DS18B20_PIN_RESET); \
                        }while(0)

/* 函数声明 */
void ds18b20_reset(void);               /* 复位DS18B20 */
uint8_t ds18b20_check(void);            /* 检测是否存在DS18B20 */
uint8_t ds18b20_read_bit(void);         /* 从DS18B20读取一个位 */
uint8_t ds18b20_read_byte(void);        /* 从DS18B20读取一个字节 */
void ds18b20_write_byte(uint8_t data);  /* 写一个字节到DS18B20 */
void ds18b20_start(void);               /* 开始温度转换 */
uint8_t ds18b20_init(void);             /* 初始化DS18B20 */
short ds18b20_get_temperature(void);    /* 获取温度 */

        DS18B20_DQ_OUT(x)宏函数的作用是设置 GPIO_NUM_0的电平状态, x为 0表示低电平,x 为 1 表示高电平。
        DS18B20_DQ_IN 宏函数的作用是获取 GPIO_NUM_0 的电平状态,返回值为 0 表示低电平,返回值为 1 表示高电平。

(3)ds18b20.c 文件

/**
 * @brief       初始化DS18B20
 * @param       无
 * @retval      0, 正常
 *              1, 不存在/不正常
 */
uint8_t ds18b20_init(void)
{
    gpio_config_t gpio_init_struct;

    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;                 /* 失能引脚中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT_OD;              /* 开漏模式的输入和输出 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;               /* 使能上拉 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;          /* 失能下拉 */
    gpio_init_struct.pin_bit_mask = 1ull << DS18B20_DQ_GPIO_PIN;    /* 设置的引脚的位掩码 */
    gpio_config(&gpio_init_struct);                                 /* 配置DS18B20引脚 */

    ds18b20_reset();
    return ds18b20_check();
}

        在 DS18B20初始化函数中,通过调用复位函数和自检函数,可以知道 DS18B20是否正常。

        下面看下在前面提及的几个信号类型。

/**
 * @brief       复位DS18B20
 * @param       data: 要写入的数据
 * @retval      无
 */
void ds18b20_reset(void)
{
    DS18B20_DQ_OUT(0);      /* 拉低DQ,复位 */
    esp_rom_delay_us(750);  /* 拉低至少750μs */
    DS18B20_DQ_OUT(1);      /* DQ=1,释放复位 */
    esp_rom_delay_us(15);   /* 主机拉高10~35us */
}

/**
 * @brief       等待DS18B20的回应
 * @param       无
 * @retval      0, DS18B20正常
 *              1, DS18B20异常/不存在
 */
uint8_t ds18b20_check(void)
{
    uint8_t retry = 0;
    uint8_t rval = 0;

    while (DS18B20_DQ_IN && retry < 200)      /* DS18B20会拉低200us */
    {
        retry++;
        esp_rom_delay_us(1);
    }

    if (retry >= 200)
    {
        rval = 1;
    }
    else
    {
        retry = 0;

        while (!DS18B20_DQ_IN && retry < 240) /* DS18B20拉低后会再次拉高240us */
        {
            retry++;
            esp_rom_delay_us(1);
        }
        if (retry >= 240)
        {
            rval = 1;
        }
    }
    
    return rval;
}

        以上两个函数分别为复位脉冲与应答信号,可以对照时序图理解。应答信号函数,函数主要是对于 DS18B20 传感器的回应信号进行检测,对此判断其是否存在。函数的实现也是依据时序图进行逻辑判断,例如当主机发送了复位信号之后,按照时序, DS18B20 会拉低数据线60~240us,同时主机接收最小时间为 480us,我们就依据这两个硬性条件进行判断,首先需要设置一个时限等待 DS18B20 响应,后面也设置一个时限等待 DS18B20 释放数据线拉高,满足这两个条件即DS18B20 成功响应。
        下面看写函数,其定义如下:

/**
 * @brief       写一个字节到DS18B20
 * @param       data: 要写入的字节
 * @retval      无
 */
void ds18b20_write_byte(uint8_t data)
{
    uint8_t j;

    for (j = 1; j <= 8; j++)
    {
        if (data & 0x01)
        {
            DS18B20_DQ_OUT(0);  /* 写0 */
            esp_rom_delay_us(2);
            DS18B20_DQ_OUT(1);  /* 写1 */
            esp_rom_delay_us(60);
        }
        else
        {
            DS18B20_DQ_OUT(0);  /* 写0 */
            esp_rom_delay_us(60);
            DS18B20_DQ_OUT(1);  /* 写1 */
            esp_rom_delay_us(2);
        }

        data >>= 1;             /* 右移,获取高一位数据 */
    }
}

        通过形参决定是写 1还是写 0,按照前面对写时序的分析,很容易理解函数的逻辑处理。
        下面看一下读函数:

/**
 * @brief       从DS18B20读取一个位
 * @param       无
 * @retval      读取到的位值: 0 / 1
 */
uint8_t ds18b20_read_bit(void)
{
    uint8_t data = 0;
    DS18B20_DQ_OUT(0);
    esp_rom_delay_us(2);
    DS18B20_DQ_OUT(1);
    esp_rom_delay_us(12);

    if (DS18B20_DQ_IN)
    {
        data = 1;
    }

    esp_rom_delay_us(50);
    return data;
}

/**
 * @brief       从DS18B20读取一个字节
 * @param       无
 * @retval      读到的数据
 */
uint8_t ds18b20_read_byte(void)
{
    uint8_t i, b, data = 0;

    for (i = 0; i < 8; i++)     /* 循环读取8位数据 */
    {
        b = ds18b20_read_bit(); /* DS18B20先输出低位数据,高位数据后输出 */
        data |= b << i;         /* 填充data的每一位 */
    }

    return data;
}

        ds18b20_read_bit 函数从 DS18B20 处读取 1位数据,前面对读时序已详细的分析,找照很好理解。
        下面介绍读取温度函数,其定义如下:

/**
 * @brief       从ds18b20得到温度值(精度:0.1C)
 * @param       无
 * @retval      温度值 (-550~1250)
 * @note        返回的温度值放大了10倍.
 *              实际使用的时候,要除以10才是实际温度.
 */
short ds18b20_get_temperature(void)
{
    uint8_t flag = 1;           /* 默认温度为正数 */
    uint8_t TL, TH;
    short temp;
    
    ds18b20_start();            /*  ds1820 start convert */
    ds18b20_reset();
    ds18b20_check();
    ds18b20_write_byte(0xcc);   /*  skip rom */
    ds18b20_write_byte(0xbe);   /*  convert */
    TL = ds18b20_read_byte();   /*  LSB */
    TH = ds18b20_read_byte();   /*  MSB */

    if (TH > 7)
    {/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
        正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
        所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
        我们这里先暂时没有作+1的处理,这里需要留意 */
        TH = ~TH;
        TL = ~TL;
        flag = 0;   
    }

    temp = TH;      /* 获得高八位 */
    temp <<= 8;
    temp += TL;     /* 获得底八位 */

    /* 转换成实际温度 */
    if (flag == 0)
    {   /* 将温度转换成负温度,这里的+1参考前面的说明 */
        temp = (double)(temp+1) * 0.625;
        temp = -temp;   
    }
    else
    {
        temp = (double)temp * 0.625;
    }
    
    return temp;
}

        介绍上面用到的 RAM 指令:
        跳过 ROM(0xCC),该指令只适合总线只有一个节点,它通过允许总线上的主机不提供 64位 ROM 序列号而直接访问 RAM,节省了操作时间;
        温度转换(0x44),启动 DS18B20 进行温度转换,结果存入内部 RAM;
        读暂存器(0xBE),读暂存器 9 个字节内容,该指令从 RAM 的第一个字节(字节 0)开始读取,直到九个字节(字节 8, CRC 值)被读出为止。如果不需要读出所有字节的内容,那么主机可以在任何时候发出复位信号以中止读操作。

        当然上面读取也可以优化为下面代码(外面调用点做相应适配),编译器会自动将补码转换为原码:

// 1. 将两个字节组合成一个16位有符号整数
int16_t raw_temp = (TH << 8) | TL;

// 2. 转换为实际温度值(浮点数)
float temperature = raw_temp * 0.0625f;

29.3.4 CMakeLists.txt 文件

        打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:

set(src_dirs
            DS18B20
            IIC
            LCD
            LED
            SPI
            XL9555)

set(include_dirs
            DS18B20
            IIC
            LCD
            LED
            SPI
            XL9555)

set(requires
            driver)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

29.3.5 实验应用代码

        打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

i2c_obj_t i2c0_master;

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    uint8_t err;
    uint8_t t = 0;
    short temperature;
    esp_err_t ret;
    
    ret = nvs_flash_init();                                                 /* 初始化NVS */

    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    led_init();                                                             /* 初始化LED */
    i2c0_master = iic_init(I2C_NUM_0);                                      /* 初始化IIC0 */
    spi2_init();                                                            /* 初始化SPI */
    xl9555_init(i2c0_master);                                               /* 初始化XL9555 */
    lcd_init();                                                             /* 初始化LCD */

    lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DS18B20 TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    /* 初始化DS18B20数字温度传感器 */
    err = ds18b20_init();

    if (err != 0)
    {
        while (1)
        {
            lcd_show_string(30, 110, 200, 16, 16, "DS18B20 Error", RED);
            vTaskDelay(200);
            lcd_fill(30, 110, 239, 130 + 16, WHITE);
            vTaskDelay(200);
        }
    }
    lcd_show_string(30, 110, 200, 16, 16, "DS18B20 OK", RED);
    lcd_show_string(30, 130, 200, 16, 16, "Temp:   . °C", BLUE);
    
    while (1)
    {
        if (t % 10 == 0)                                                    /* 每100ms读取一次 */
        {
            temperature = ds18b20_get_temperature();

            if (temperature < 0)
            {
                lcd_show_char(30 + 40, 130, '-', 16, 0, BLUE);              /* 显示负号 */
                temperature = -temperature;                                 /* 转为正数 */
            }
            else
            {
                lcd_show_char(30 + 40, 130, ' ', 16, 0, BLUE);              /* 去掉负号 */
            }
            lcd_show_num(30 + 40 + 8, 130, temperature / 10, 2, 16, BLUE);  /* 显示正数部分 */
            lcd_show_num(30 + 40 + 32, 130, temperature % 10, 1, 16, BLUE); /* 显示小数部分 */
        }

        vTaskDelay(10);
        t++;

        if (t == 20)
        {
            t = 0;
            LED_TOGGLE();                                                   /* LED闪烁 */
        }
    }
}

29.4 下载验证

        假定 DS18B20 传感器已经接上去正确的位置,将程序下载到开发板后,可以看到 LED0 不停的闪烁,提示程序已经在运行了。 LCD 显示当前的温度值的内容如下图所示(放了一个冰块温度续下降):

图 29.4.1 DS18B20 实验测试图

        待后续到零下温度可以测试显示负温度值。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐