图30.3.1.1 DHT11实验程序流程图
30.3.2 DHT11函数解析
这一章节除了涉及到GPIO的API函数,便没有再涉及到其他API函数。因此,有关GPIO的API函数介绍,请读者回顾此前的第十章的内容。接下来,笔者将直接介绍DHT11的驱动代码。
30.3.3 DHT11驱动解析
在IDF版20_dht11例程中,作者在20_dht11\components\BSP路径下新增了一个DHT11文件夹,分别用于存放dht11.c、dht11.h这两个文件。其中,dht11.h文件负责声明DHT11相关的函数和变量,而dht11.c文件则实现了DHT11的驱动代码。下面,我们将详细解析这两个文件的实现内容。
1,dht11.h文件
/* 引脚定义 */
#define DHT11_DQ_GPIO_PIN GPIO_NUM_0
/* DHT11引脚高低电平枚举 */
typedef enum
{
DHT11_PIN_RESET = 0u,
DHT11_PIN_SET
}DHT11_GPIO_PinState;
/* IO操作 */
#define DHT11_DQ_IN gpio_get_level(DHT11_DQ_GPIO_PIN) /* 数据端口输入 */
/* DHT11端口定义 */
#define DHT11_DQ_OUT(x) do{ x ? \
gpio_set_level(DHT11_DQ_GPIO_PIN, DHT11_PIN_SET) : \
gpio_set_level(DHT11_DQ_GPIO_PIN, DHT11_PIN_RESET); \
}while(0)
对DHT11的相关引脚以及IO操作进行宏定义,方便程序中调用。
2,dht11.c文件
/**
* @retval 0, 正常
* 1, 不存在/不正常
*/
uint8_t dht11_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 << DHT11_DQ_GPIO_PIN;
gpio_config(&gpio_init_struct); /* 配置DHT11引脚 */
dht11_reset();
return dht11_check();
}
在DHT11的初始化函数中,主要对用到的GPIO口进行初始化,同时在函数最后调用复位函数和自检函数,这两个函数在后面会解释到。
下面介绍的是复位DHT11函数和等待DHT11的回应函数,它们的定义如下:
/**
* @brief 复位DHT11
* @param data: 要写入的数据
* @retval 无
*/
static void dht11_reset(void) {
DHT11_DQ_OUT(0); /* 拉低DQ */
vTaskDelay(25); /* 拉低至少18ms */
DHT11_DQ_OUT(1); /* DQ=1 */
esp_rom_delay_us(30); /* 主机拉高10~35us */
}
/**
* @brief 等待DHT11的回应
* @param 无
* @retval 0, DHT11正常
* 1, DHT11异常/不存在
*/
uint8_t dht11_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DHT11_DQ_IN && retry < 100) /* DHT11会拉低约83us */
{
retry++;
esp_rom_delay_us (1);
}
if (retry >= 100)
{
rval = 1;
}
else
{
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低后会再次拉高87us */
{
retry++;
esp_rom_delay_us (1);
}
if (retry >= 100) rval = 1;
}
return rval;
}
以上两个函数分别代表着前面所说的复位脉冲与应答信号,大家可以对比前面的时序图进行理解。那么在上一章DS18B20的实验中,也对复位脉冲以及应答信号进行了详细的解释,大家也可以对比理解。
DHT11与DS18B20有所不同,DHT11是不需要写函数,只需要读函数即可,下面我们看一下读函数:
/**
* @brief 从DHT11读取一个位
* @param 无
* @retval 读取到的位值: 0 / 1
*/
uint8_t dht11_read_bit(void)
{
uint8_t retry = 0;
while (DHT11_DQ_IN && retry < 100) /* 等待变为低电平 */
{
retry++;
esp_rom_delay_us (1);
}
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */
{
retry++;
esp_rom_delay_us (1);
}
esp_rom_delay_us (40); /* 等待40us */
if (DHT11_DQ_IN) /* 根据引脚状态返回 bit */
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 从DHT11读取一个字节
* @param 无
* @retval 读到的数据
*/
static uint8_t dht11_read_byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++) /* 循环读取8位数据 */
{
data <<= 1; /* 高位数据先输出, 先左移一位 */
data |= dht11_read_bit(); /* 读取1bit数据 */
}
return data;
}
在这里dht11_read_bit函数从DHT11处读取1位数据,大家可以对照前面的读时序图进行分析,读数字0和1的不同,在于高电平的持续时间,所以这个作为判断的依据。dht11_read_byte函数就是调用一字节读取函数进行实现。
下面介绍读取温湿度函数,其定义如下:
/**
* @brief 从DHT11读取一次数据
* @param temp: 温度值(范围:-20~60°)
* @param humi: 湿度值(范围:5%~95%)
* @retval 0, 正常.
* 1, 失败
*/
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
dht11_reset();
if (dht11_check() == 0)
{
for (i = 0; i < 5; i++) /* 读取40位数据 */
{
buf = dht11_read_byte();
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0];
*temp = buf[2];
}
}
else
{
return 1;
}
return 0;
}
读取温湿度函数也是根据时序图进行实现的,在发送复位信号以及应答信号产生后,即可以读取5Byte数据进行处理,校验成功即读取数据有效成功。
30.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
set(src_dirs
DHT11
IIC
LCD
LED
SPI
XL9555)
set(include_dirs
DHT11
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)
上述的红色DHT11驱动需要由开发者自行添加,以确保DHT11驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了DHT11驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
30.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;
uint8_t temperature;
uint8_t humidity;
esp_err_t ret;
/* 初始化NVS */
ret = nvs_flash_init();
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 */
led_init();
/* 初始化IIC0 */
i2c0_master = iic_init(I2C_NUM_0);
/* 初始化SPI2 */
spi2_init();
/* 初始化XL9555 */
xl9555_init(i2c0_master);
/* 初始化LCD */
lcd_init();
lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DHT11 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
/* 初始化DHT11数字温湿度传感器 */
err = dht11_init();
if (err != 0)
{
while (1)
{
lcd_show_string(30, 110, 200, 16, 16, "DHT11 Error", RED);
vTaskDelay(200);
lcd_fill(30, 110, 239, 130 + 16, WHITE);
vTaskDelay(200);
}
}
lcd_show_string(30, 110, 200, 16, 16, "DHT11 OK", RED);
lcd_show_string(30, 130, 200, 16, 16, "Temp: C", BLUE);
lcd_show_string(30, 150, 200, 16, 16, "Humi: %", BLUE);
while (1)
{
/* 每100ms读取一次 */
if (t % 10 == 0)
{
/* 读取温湿度值 */
dht11_read_data(&temperature, &humidity);
/* 显示温度 */
lcd_show_num(30 + 40, 130, temperature, 2, 16, BLUE);
/* 显示湿度 */
lcd_show_num(30 + 40, 150, humidity, 2, 16, BLUE);
}
vTaskDelay(10);
t++;
if (t == 20)
{
t = 0;
LED_TOGGLE();/* LED闪烁 */
}
}
}
主函数代码比较简单,一系列硬件初始化后,如果DHT11初始化成功,那么在循环中调用dht11_get_temperature函数获取温湿度值,每隔100ms读取数据并显示在LCD上。
30.4 下载验证
假定DHT11传感器已经接上去正确的位置,将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示当前的温度值的内容如图30.4.1所示:
图30.4.1 程序运行效果图
至此,本章实验结束。大家可以将本章通过DHT11读取到的温度值,和前一章的通过DS18B20读取到的温度值对比一下,看看哪个更准确?