DS18B20 实验
开发板:正点原子ESP32S3
外设:DS18B20
没有LCD屏可以用串口打印进行测试
例程源码在文章顶部可免费下载
1. DS18B20 介绍
1.1 DS18B20 简介
DS18B20 是由 DALLAS 半导体公司推出的一种“单总线”接口的温度传感器,实物图如下图所示。
与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。单总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新的概念,测试温度范围为-55-+125℃,精度为±0.5℃。现场温度直接以单总线的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9-12 位的数字值读数方式。它工作在 3-5.5V 的电压范围,采用多种封装形式,从而使系统设置灵活、方便,设定分辨率以及用户设定的报警温度存储在 EEPROM 中,掉电后依然保存。其内部结构如下图所示:
ROM 中的 64 位序列号是出厂前被标记好的,它可以看作使该 DS18B20 的地址序列码,每个 DS18B20 的 64 位序列号均不相同。 64 位 ROM 的排列是:前 8 位是产品家族码,接着 48 位是 DS18B20 的序列号,最后 8 位是前面 56 位的循环冗余校验码(CRC=X8+X5+X4+1)。 ROM作用是使每一个 DS18B20 都各不相同, 这样设计可以允许一根总线上挂载多个 DS18B20 模块同时工作且不会引起冲突。
1.2 DS18B20 时序介绍
所有单总线器件要求采用严格的信号时序,以保证数据的完整性。 DS18B20 共有 6 种信号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外,都是由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里我们简单介绍这几个信号的时序。
1.2.1 复位脉冲和应答脉冲
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少要在480us,以产生复位脉冲。接着主机释放总线, 4.7K 的上拉电阻将单总线拉高,延时时间要在15-60us,并进入接收模式(Rx)。接着 DS18B20 拉低总线 60-240us,以产生低电平应答脉冲。
1.2.2 写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在两次独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线延时 2us。
1.2.3 读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后延时 50us。
在了解单总线时序之后,我们来看一下 DS18B20 的典型温度读取过程, DS18B20 的典型温度读取过程为:复位→发 SKIP ROM(0xCC)→发开始转换命令(0x44)→延时→复位→发送 SKIP ROM 命令(0xCC)→发送存储器命令(0xBE)→连续读取两个字节数据(即温度)→结束。
DS18B20 的简介,我们就介绍到这里,关于该传感器的详细说明,请大家参考其数据手册。
2. 硬件设计
2.1 例程功能
DS18B20 每隔 100ms 左右读取一次数据,并把温度显示在 LCD 上。 LED 闪烁用于提示程序正在运行。
2.2 硬件资源
- LED 灯
LED-IO1 - USART0
U0TXD-IO43
U0RXD-IO44 - XL9555
IIC_SDA-IO41
IIC_SCL-IO42 - SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555) - DS18B20
1WIRE_DQ-IO0(在 P5 端口,使用跳线帽将 IWIRE_DQ 和 IO0 相连)
2.3 原理图
DS18B20 原理图,如下图所示:
从上图可以看出, 1WIRE_DQ 并没有直接跟 ESP32-S3 的 IO0 相连,而是需要通过跳线帽进行连接。 1WIRE_DQ 和 IO0 连接图如下图所示:
3. 软件设计
3.1 程序流程图
下面看看本实验的程序流程图:
3.2 程序解析
DS18B20 驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。 DS18B20 驱动源码包括两个文件: ds18b20.cpp 和 ds18b20.h。
本例实验直接使用 esp32 专门的底层引脚定义和底层函数来完成 DS18B20 的驱动。
下面我们先解析 ds18b20.h 的程序。 对 DS18B20 的数据引脚做了相关定义。
#define DS18B20_DQ_PIN GPIO_NUM_0
由于数据线会存在输入输出模式的切换以及高低电平的设置,所以为 DS18B20_DQ_PIN 做了相关宏函数供单总线时序函数调用。
#define DS18B20_DQ_OUT(x) gpio_set_level(DS18B20_DQ_PIN, x)
#define DS18B20_DQ_IN gpio_get_level(DS18B20_DQ_PIN)
#define DS18B20_MODE_IN gpio_set_direction(DS18B20_DQ_PIN, GPIO_MODE_INPUT)
#define DS18B20_MODE_OUT gpio_set_direction(DS18B20_DQ_PIN, GPIO_MODE_OUTPUT)
gpio_set_direction 函数是设置 GPIO 的模式为输入模式还是输出模式; gpio_set_level 函数是设置 GPIO 的高低电平状态; gpio_get_level 函数是获取 GPIO 的电平状态。 调用这些函数,需要包含“esp_system.h”头文件。
DS18B20_DQ_OUT(x)宏函数的作用是设置GPIO_NUM_0的电平状态, x为0表示低电平,x 为 1 表示高电平。
DS18B20_DQ_IN 宏函数的作用是获取 GPIO_NUM_0 的电平状态,返回值为 0 表示低电平,返回值为 1 表示高电平。
DS18B20_MODE_IN 宏函数的作用是设置 GPIO_NUM_0 为输入模式,该引脚在硬件上已经有上拉电阻。
DS18B20_MODE_OUT 宏函数的作用是设置 GPIO_NUM_0 为输出模式。
下面我们再解析 ds18b20.cpp 的程序,首先先来看一下初始化函数 ds18b20_init,代码如下:
/**
* @brief 初始化 DS18B20
* @param 无
* @retval 0:正常, 1:不存在/不正常
*/
uint8_t ds18b20_init(void)
{
ds18b20_reset();
return ds18b20_check();
}
在 DS18B20 初始化函数中,通过调用复位函数和自检函数,可以知道 DS18B20 是否正常。
下面介绍一下在前面提及的几个信号类型。
/**
* @brief 复位 DS18B20
* @param 无
* @retval 无
*/
static void ds18b20_reset(void)
{
DS18B20_MODE_OUT;
DS18B20_DQ_OUT(0); /* 拉低 DQ,复位 */
delayMicroseconds(750); /* 拉低 750us */
DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */
delayMicroseconds(15); /* 延迟 15US */
}
/**
* @brief 等待 DS18B20 的回应
* @param 无
* @retval 0, DS18B20 正常
* 1, DS18B20 异常/不存在
*/
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
DS18B20_MODE_IN;
while (DS18B20_DQ_IN && retry < 200) /* 等待 DQ 变低, 等待 200us */
{
retry++;
delayMicroseconds(1);
}
if (retry >= 240)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待 DQ 变高, 等待 240us */
{
retry++;
delayMicroseconds(1);
}
if (retry >= 240)
{
rval = 1;
}
}
return rval;
}
以上两个函数分别代表着前面所说的复位脉冲与应答信号,大家可以对比前面的时序图进行理解。由于复位脉冲比较简单,所以这里不做展开。现在看一下应答信号函数,函数主要是对于 DS18B20 传感器的回应信号进行检测,对此判断其是否存在。函数的实现也是依据时序图进行逻辑判断,例如当主机发送了复位信号之后,按照时序, DS18B20 会拉低数据线 60~240us,同时主机接收最小时间为 480us,我们就依据这两个硬性条件进行判断,首先需要设置一个时限等待 DS18B20 响应,后面也设置一个时限等待 DS18B20 释放数据线拉高,满足这两个条件即 DS18B20 成功响应。
下面介绍的是写函数,其定义如下:
/**
* @brief 写一个字节到 DS18B20
* @param data: 要写入的字节
* @retval 无
*/
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
DS18B20_MODE_OUT;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0);
delayMicroseconds(2);
DS18B20_DQ_OUT(1);
delayMicroseconds(60);
}
else
{
DS18B20_DQ_OUT(0);
delayMicroseconds(60);
DS18B20_DQ_OUT(1);
delayMicroseconds(2);
}
data >>= 1;
}
}
通过形参决定是写 1 还是写 0,按照前面对写时序的分析,我们可以很清晰知道写函数的逻辑处理。
有写函数肯定就有读函数,下面看一下读函数:
/**
* @brief 从 DS18B20 读取一个位
* @param 无
* @retval 读取到的位值: 0 / 1
* */
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_MODE_OUT;
DS18B20_DQ_OUT(0);
delayMicroseconds(2);
DS18B20_DQ_OUT(1);
delayMicroseconds(12);
DS18B20_MODE_IN;
if (DS18B20_DQ_IN)
{
data = 1;
}
delayMicroseconds(50);
return data;
}
/**
* @brief 从 DS18B20 读取一个字节
* @param 无
* @retval 读到的数据
*/
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20 先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充 data 的每一位 */
}
return data;
}
在这里, ds18b20_read_bit 函数从 DS18B20 处读取 1 位数据,在前面已经对读时序也进行了详细的分析,所以这里也不展开解释了。
下面介绍读取温度函数,其定义如下:
/**
* @brief 开始温度转换
* @param 无
* @retval 无
*/
static void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0x44); /* convert */
}
/**
* @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 值)被读出为止。如果不需要读出所有字节的内容,那么主机可以在任何时候发出复位信号以中止读操作。
17_ds18b20.ino 代码
在 17_ds18b20.ino 里面编写如下代码:
#include "led.h"
#include "uart.h"
#include "xl9555.h"
#include "spilcd.h"
#include "ds18b20.h"
short temperature; /* 温度值 */
uint8_t t = 0;
/**
* @brief 当程序开始执行时,将调用 setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED 初始化 */
uart_init(0, 115200); /* 串口 0 初始化 */
xl9555_init(); /* IO 扩展芯片初始化 */
lcd_init(); /* LCD 初始化 */
ds18b20_init(); /* DS18B20 初始化 */
lcd_show_string(30, 50, 200, 16, LCD_FONT_16, "ESP32-S3", RED);
lcd_show_string(30, 70, 200, 16, LCD_FONT_16, "DS18B20 TEST", RED);
lcd_show_string(30, 90, 200, 16, LCD_FONT_16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, LCD_FONT_16, "Temp: . C", BLUE);
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
if (t % 10 == 0)
{
temperature = ds18b20_get_temperature();
if (temperature < 0)
{
lcd_show_char(30 + 40, 110, '-', LCD_FONT_16, 0, BLUE);
temperature = -temperature;
}
else
{
lcd_show_char(30 + 40, 110, ' ', LCD_FONT_16, 0, BLUE);
}
lcd_show_num(30 + 40 + 8, 110, temperature / 10, 2, LCD_FONT_16, BLUE);
lcd_show_num(30 + 40 + 32, 110, temperature % 10, 1, LCD_FONT_16, BLUE);
}
delay(10);
t++;
if (t == 20)
{
t = 0;
LED_TOGGLE();
}
}
在 setup 函数中, 调用 led_init 函数完成 LED 初始化, 调用 uart_init 函数完成串口初始化,调用 xl9555_init 函数完成 XL9555 初始化,调用 lcd_init 函数完成 LCD 屏初始化, 调用ds18b20_init 函数完成 DS18B20 初始化, LCD 显示实验信息。
在 loop 函数中,间隔 100 毫秒调用 ds18b20_get_temperature 函数获取 DS18B20 传感器的温度数据,然后在 LCD 进行显示。 LED 灯每隔 200 毫秒状态翻转,实现闪烁效果。
4. 下载验证
假定 DS18B20 传感器已经接上去正确的位置,将程序下载到开发板后,可以看到 LED0 不停的闪烁,提示程序已经在运行了。 LCD 显示当前的温度值的内容如下图所示:.
该程序还可以读取并显示负温度值,具备零下温度条件可以测试一下。