【前言】
在E起DIY中有些工程师可能是买到DHT11的,因此需要使用Zephyr来驱动他,但是我需要官方的库好象没有成功的驱动。因此写了个驱动来实现。
【硬件连接】
由于这块开发板的IO复用比较多,因此我偿试了好多个IO,最后选择了PTA16做为DHT11的数据接口。
是开发板上的J1第D0脚上,也方便查找。

【软件实现】
在zephyr\app\下面新建工程。并添加对应的文件:
app/dht11_test/ ├── CMakeLists.txt ├── prj.conf ├── west.yml ├── boards/ │ └── frdm_mcxw71.overlay # DHT11 on PTA16 └── src/ ├── main.c # 主程序 ├── dht11.c # DHT11 驱动 (DWT 微秒延时) └── dht11.h # 驱动头文件【关键配置】
【1. 设备树配置 (overlay)】
在 `boards/<board>.overlay` 中配置 GPIO:
/ {
aliases {
dht11 = &dht11_node;
};
dht11_node: dht11 {
compatible = "aosong,dht";
dio-gpios = <&gpioa 16 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
/* 确保 GPIO 控制器已启用 */
&gpioa {
status = "okay";
};
/* 如果该引脚被其他外设占用,需要禁用 */
&lpuart0 {
status = "disabled";
};【重要】:- 必须使用 `GPIO_ACTIVE_HIGH`,不能使用 `GPIO_ACTIVE_LOW`- 如果引脚被其他外设(如 UART)占用,必须禁用该外设
【2. 修改引脚】
在 `src/main.c` 中修改 GPIO 引脚号:
#define DHT11_PIN 16 /* PTA16 */
【DHT11通信协议】
DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次 通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零。
操作流程如下:
一次完整的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据+8bit湿度小数数据
+8bi温度整数数据+8bit温度小数数据
+8bit校验和
数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集, 用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集, 如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后 转换到低速模式。
1.通讯过程如图1所示

总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必 须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后, 等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束 后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换 到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。
总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉 高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定 了数据位是0还是1.格式见下面图示.如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常.当最后一bit数据传送完毕后,DHT11拉低总线 50us,随后总线由上拉电阻拉高进入空闲状态。
数字0信号表示方法如下图所示
数字1信号表示方法.如下图所示

通过上描数据通信协议的学习,工程中dht11.c的代码如下:
/*
* DHT11 Driver using ARM DWT cycle counter for precise microsecond delays
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
#include "dht11.h"
/* ARM Cortex-M DWT cycle counter */
static uint32_t dwt_cycles_per_us(void)
{
static uint32_t cycles_per_us = 0;
if (cycles_per_us == 0) {
cycles_per_us = SystemCoreClock / 1000000;
}
return cycles_per_us;
}
static inline void dwt_delay_us(uint32_t us)
{
volatile uint32_t *demcr = (uint32_t *)0xE000EDFC;
volatile uint32_t *dwt_ctrl = (uint32_t *)0xE0001000;
volatile uint32_t *dwt_cyccnt = (uint32_t *)0xE0001004;
/* Enable trace and cycle counter */
*demcr |= (1 << 24);
*dwt_ctrl |= (1 << 0);
uint32_t start = *dwt_cyccnt;
uint32_t cycles = us * dwt_cycles_per_us();
while ((*dwt_cyccnt - start) < cycles) {
__NOP();
}
}
static inline int get_pin(const struct device *gpio_dev, int pin)
{
return gpio_pin_get(gpio_dev, pin);
}
/* Wait for pin to reach target state, then measure how long it stays there (in us) */
static int wait_and_measure_us(const struct device *gpio_dev, int pin, int wait_high)
{
volatile uint32_t *demcr = (uint32_t *)0xE000EDFC;
volatile uint32_t *dwt_ctrl = (uint32_t *)0xE0001000;
volatile uint32_t *dwt_cyccnt = (uint32_t *)0xE0001004;
uint32_t cycles_per_us = dwt_cycles_per_us();
/* Enable DWT */
*demcr |= (1 << 24);
*dwt_ctrl |= (1 << 0);
/* First: wait for pin to reach target state */
int wait_count = 0;
while (get_pin(gpio_dev, pin) != (wait_high ? 1 : 0)) {
wait_count++;
if (wait_count > 100000) {
return 1000; /* Timeout */
}
}
/* Second: measure how long it stays in that state */
uint32_t start = *dwt_cyccnt;
int measure_count = 0;
while (get_pin(gpio_dev, pin) == (wait_high ? 1 : 0)) {
measure_count++;
if (measure_count > 100000) {
return 1000; /* Timeout */
}
}
uint32_t elapsed_cycles = *dwt_cyccnt - start;
return elapsed_cycles / cycles_per_us;
}
int dht11_init(const struct device *gpio_dev, int pin)
{
if (!gpio_dev || !device_is_ready(gpio_dev)) {
return -ENODEV;
}
int ret = gpio_pin_configure(gpio_dev, pin, GPIO_INPUT | GPIO_PULL_UP);
if (ret < 0) {
return ret;
}
return 0;
}
int dht11_read(const struct device *gpio_dev, int pin, uint8_t *humidity, uint8_t *temperature)
{
uint8_t data[5] = {0};
int ret;
if (!gpio_dev || !humidity || !temperature) {
return -EINVAL;
}
/* Send start signal: output low for 18ms */
ret = gpio_pin_configure(gpio_dev, pin, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
return ret;
}
dwt_delay_us(18000); /* 18ms */
/* Release bus: input with pull-up */
ret = gpio_pin_configure(gpio_dev, pin, GPIO_INPUT | GPIO_PULL_UP);
if (ret < 0) {
return ret;
}
/* Wait for DHT ACK low (~80us low) */
int ack_low = wait_and_measure_us(gpio_dev, pin, 0);
if (ack_low >= 1000) {
return -ETIMEDOUT; /* No response */
}
/* Wait for DHT ACK high (~80us high) */
int ack_high = wait_and_measure_us(gpio_dev, pin, 1);
if (ack_high >= 1000) {
return -ETIMEDOUT;
}
/* Read 40 bits */
for (int i = 0; i < 40; i++) {
/* Wait for bit start (low) */
int low_dur = wait_and_measure_us(gpio_dev, pin, 0);
if (low_dur >= 1000) {
return -EIO;
}
/* Measure high duration to determine bit value */
int high_dur = wait_and_measure_us(gpio_dev, pin, 1);
/* Store bit: 0 bit ~30us, 1 bit ~70us */
int byte_idx = i / 8;
int bit_idx = 7 - (i % 8);
if (high_dur > 40) {
data[byte_idx] |= (1 << bit_idx);
}
}
/* Verify checksum */
uint8_t sum = data[0] + data[1] + data[2] + data[3];
if (sum != data[4]) {
return -EIO; /* Checksum error */
}
*humidity = data[0];
*temperature = data[2];
return 0;
}DHT11 的 0/1 位通过高电平持续时间区分:0 bit: 高电平约 27-30μs1 bit: 高电平约 70-80μs/* 根据实测值调整阈值(应介于 0 bit 和 1 bit 之间)*/
if (high_dur > 40) { /* 阈值 ~40-50us */
data[byte_idx] |= (1 << bit_idx);
}在程序的设计是主要是需要一个精确的us的延时来实时对单总线的bit数据位的测量。
修改main.c中的代码实现测试:
/*
* DHT11 Test Application
*
* Hardware: FRDM-MCXW71, PTA16 connected to DHT11 DATA pin
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/device.h>
#include "dht11.h"
#define DHT11_PIN 16
int main(void)
{
int ret;
uint8_t humidity, temperature;
printk("DHT11 Test starting...\n");
const struct device *gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpioa));
if (!gpio_dev) {
printk("GPIOA not found\n");
return 0;
}
ret = dht11_init(gpio_dev, DHT11_PIN);
if (ret < 0) {
printk("DHT11 init failed: %d\n", ret);
return 0;
}
printk("DHT11 initialized\n");
while (1) {
ret = dht11_read(gpio_dev, DHT11_PIN, &humidity, &temperature);
if (ret == 0) {
printk("H=%d%% T=%d°C OK\n", humidity, temperature);
} else {
printk("Read failed: %d\n", ret);
}
k_sleep(K_SECONDS(3));
}
return 0;
}【效果】
编译下载后,通过串口打印如下:

附工程源码:
我要赚赏金
