DHT11温湿度传感器驱动

一、DHT11传感器通信协议

1. 单总线协议特点

  • 单总线:只有一根数据线,双向传输

  • 时序要求严格:微秒级延时控制

  • 通信过程:主机发送开始信号 → DHT11响应 → 数据传送

2. DHT11数据格式

5字节数据(40位):
字节0:湿度整数部分
字节1:湿度小数部分(DHT11为0)
字节2:温度整数部分  
字节3:温度小数部分(DHT11为0)
字节4:校验和(= 字节0+字节1+字节2+字节3)

二、设备树配置详解

1. DHT11设备节点

dts

dht11 {
    #address-cells = <1>;      // 地址单元格大小为1
    #size-cells = <1>;         // 大小单元格大小为1
    compatible = "pt-dht11";   // 与驱动匹配的兼容字符串
    pinctrl-names = "default"; // 引脚控制状态名称
    pinctrl-0 = <&pinctrl_ptdht11>; // 引脚配置引用
    ptdht11-gpio = <&gpio1 2 GPIO_ACTIVE_HIGH>; // GPIO引脚定义
    status = "okay";           // 设备启用状态
};

字段解释

  • compatible:驱动匹配关键字,必须与驱动中的.compatible = "pt-dht11"一致

  • pinctrl-0:引用引脚复用配置节点

  • ptdht11-gpio:自定义属性,指定GPIO控制器、引脚号和有效电平

    • &gpio1:GPIO1控制器

    • 2:GPIO1_IO02引脚

    • GPIO_ACTIVE_HIGH:高电平有效

2. 引脚复用配置

dts

&iomuxc {
    pinctrl_ptdht11: ptdht11grp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10B0  /* 配置为GPIO功能 */
        >;
    };
};

配置说明

  • MX6UL_PAD_GPIO1_IO02__GPIO1_IO02:将引脚复用为普通GPIO

  • 0x10B0:电气特性配置值(上下拉、驱动能力等)

三、DHT11驱动核心时序分析

1. 开始信号(主机发送)

static void dht11_start(void)
{
    // 1. 设置为输出,初始高电平
    gpio_direction_output(dht11_gpio, 1);
    mdelay(2);           // 稳定高电平2ms
    
    // 2. 拉低总线,发送开始信号
    gpio_set_value(dht11_gpio, 0);
    msleep(20);         // 保持低电平20ms
    
    // 3. 释放总线,准备接收响应
    gpio_set_value(dht11_gpio, 1);
    udelay(20);         // 等待20微秒
}

延时函数区别

  • mdelay(2):毫秒级延时,2ms

  • msleep(20):进程睡眠20ms,可被调度

  • udelay(20):微秒级延时,20us,忙等待

时序要求

  • 高电平稳定:>1ms

  • 开始信号低电平:18-30ms

  • 释放后等待:20-40us

2. 等待DHT11响应

static int dht11_wait_respon(void)
{
    int time = 0;
    
    // 1. 切换为输入模式
    gpio_direction_input(dht11_gpio);
    
    // 2. 等待DHT11拉低总线(响应信号开始)
    time = 30;
    while(gpio_get_value(dht11_gpio) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -1;  // 超时错误
    
    // 3. 等待低电平结束(响应信号持续)
    time = 100;
    while((!gpio_get_value(dht11_gpio)) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -2;  // 超时错误
    
    // 4. 等待高电平结束(准备发送数据)
    time = 100;
    while(gpio_get_value(dht11_gpio) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -3;  // 超时错误
    
    return 0;  // 响应正常
}

响应信号时序

主机发送开始信号后释放总线
    ↓
DHT11拉低总线80us(响应开始)
    ↓
DHT11拉高总线80us(响应结束)
    ↓
开始发送40位数据

3. 读取单个数据位

static char dht11_get_bit(void)
{
    int time = 60;
    
    // 1. 等待高电平开始(数据位开始)
    while(gpio_get_value(dht11_gpio) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -4;
    
    // 2. 等待低电平结束(数据位持续)
    time = 80;
    while((!gpio_get_value(dht11_gpio)) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -5;
    
    // 3. 延迟35us,在高电平中间采样
    udelay(35);
    
    // 4. 判断数据位是0还是1
    if(!gpio_get_value(dht11_gpio))
        return 0;  // 低电平:数据0
    
    // 5. 等待高电平结束(数据1)
    time = 50;
    while(gpio_get_value(dht11_gpio) && time--)
    {
        udelay(1);
    }
    if(time < 0) return -6;
    
    return 1;  // 高电平:数据1
}

数据位识别

数据0:50us低电平 + 26-28us高电平
      ↓ 采样点在35us时,读到低电平
      
数据1:50us低电平 + 70us高电平
      ↓ 采样点在35us时,读到高电平

4. 读取完整数据

static int dht11_read_data(unsigned char * data)
{
    int i = 0, j = 0;
    
    // 读取5个字节(40位)
    for(j = 0; j < 5; j++)
    {
        for(i = 0; i < 8; i++)
        {
            char bit = dht11_get_bit();
            if(bit < 0)
                return bit;  // 错误返回
            
            data[j] <<= 1;   // 左移1位
            data[j] |= bit;  // 低位填入新位
        }
    }
    
    return 5;  // 返回读取的字节数
}

数据组装过程

第1位:bit=1 → data=00000000 << 1 = 00000000, |=1 → 00000001
第2位:bit=0 → data=00000001 << 1 = 00000010, |=0 → 00000010
第3位:bit=1 → data=00000010 << 1 = 00000100, |=1 → 00000101
...
最终得到完整的8位数据

四、驱动核心操作

1. 读取函数完整流程

c

static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff)
{
    int ret = 0;
    unsigned char data[5];
    
    // 1. 发送开始信号
    dht11_start();
    
    // 2. 等待DHT11响应
    ret = dht11_wait_respon();
    if(ret < 0)
        return ret;  // 响应超时
    
    // 3. 读取5字节数据
    ret = dht11_read_data(data);
    if(ret < 0)
        return ret;  // 数据读取错误
    
    // 4. 将数据复制到用户空间
    ret = copy_to_user(buf, data, sizeof(data));
    
    printk("dht11  read\n");
    return 5;  // 返回读取的字节数
}

2. Probe函数(设备探测)

static int probe(struct platform_device * pdev)
{
    struct device_node * pdts;
    
    // 1. 注册杂项设备
    int ret = misc_register(&misc_dev);
    if(ret) goto err_misc_register;
    
    // 2. 查找设备树节点
    pdts = of_find_node_by_path("/dht11");
    if(NULL == pdts) {
        ret = PTR_ERR(pdts);
        goto err_of_find;
    }
    
    // 3. 获取GPIO编号
    dht11_gpio = of_get_named_gpio(pdts, "ptdht11-gpio", 0);
    if(dht11_gpio < 0) {
        ret = dht11_gpio;
        goto err_of_find;
    }
    
    // 4. 申请GPIO
    ret = gpio_request(dht11_gpio, "dht11");
    if(ret < 0) goto err_gpio_request;
    
    // 5. 初始化为输出,高电平
    gpio_direction_output(dht11_gpio, 1);
    
    printk("#########################  dht11_driver  probe\n");
    return 0;
    
    // 错误处理标签
    err_gpio_request:
    err_of_find:
    err_misc_register:
        misc_deregister(&misc_dev);
        return ret;
}

错误处理流程:标签从下往上排列,错误发生时跳到对应标签,逐级释放已申请的资源

五、应用程序分析

1. 应用程序代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    // 1. 打开设备
    int fd = open("/dev/dht11", O_RDWR);
    if(fd < 0)
    {
        perror("open dht11 failed");
        return 1;
    }
    
    // 2. 循环读取数据
    while(1)
    {
        unsigned char data[5] = {0};
        int ret = read(fd, data, sizeof(data));
        
        if(ret < 0)
        {
            printf("ret = %d\n", ret);  // 打印错误码
        }
        
        // 3. 打印原始数据(十六进制)
        int i = 0;
        for(i = 0; i < 5; i++)
        {
            printf("%#x\t", data[i]);  // %#x:带0x前缀的十六进制
        }
        printf("\n");
        
        sleep(3);  // DHT11读取间隔至少1秒
    }
    
    close(fd);
    return 0;
}

2. 应用程序执行流程

1. open("/dev/dht11")
   ↓
2. 驱动open函数(仅打印"dht11 open")
   ↓
3. read(fd, data, 5)
   ↓
4. 驱动read函数
   ├─ dht11_start()       ← 发送开始信号
   ├─ dht11_wait_respon() ← 等待响应
   ├─ dht11_read_data()   ← 读取40位数据
   └─ copy_to_user()      ← 返回数据给应用程序
   ↓
5. 打印数据(十六进制格式)
   ↓
6. sleep(3) 等待
   ↓
7. 重复步骤3-6

六、错误码说明

错误码 发生位置 含义
-1 wait_respon 等待DHT11拉低总线超时(30us内无响应)
-2 wait_respon 等待低电平结束超时(100us内未结束)
-3 wait_respon 等待高电平结束超时(100us内未结束)
-4 get_bit 等待数据位高电平开始超时
-5 get_bit 等待数据位低电平结束超时
-6 get_bit 等待数据1高电平结束超时

七、U-Boot启动命令配置

1. 环境变量操作

bash

# 打印所有环境变量
printenv

# 设置环境变量(字符串类型)
setenv bootcmd_bk '原有bootcmd命令'

# 删除环境变量(设为空值)
setenv bootcmd_bk

# 保存环境变量到MMC
saveenv

2. 网络启动内核配置

bash

# 设置TFTP下载并启动内核
setenv bootcmd 'tftp 0x80800000 zImage; \
                tftp 0x83000000 pt.dtb; \
                bootz 0x80800000 - 0x83000000'

# 命令解析:
# tftp 0x80800000 zImage    ← 从TFTP服务器下载zImage到内存0x80800000
# tftp 0x83000000 pt.dtb    ← 下载设备树到内存0x83000000
# bootz 0x80800000 - 0x83000000 ← 启动内核(zImage地址 - dtb地址)

内存地址说明

  • 0x80800000:内核zImage加载地址

  • 0x83000000:设备树dtb加载地址

  • -:表示没有initrd(初始RAM磁盘)

八、数据解析示例

原始数据输出

text

0x1c    0x00    0x1e    0x00    0x3a

解析过程

字节 十六进制 十进制 含义
data[0] 0x1c 28 湿度整数部分:28%
data[1] 0x00 0 湿度小数部分:0%
data[2] 0x1e 30 温度整数部分:30°C
data[3] 0x00 0 温度小数部分:0°C
data[4] 0x3a 58 校验和

校验验证

text

28 + 0 + 30 + 0 = 58 ✓ 校验通过

实际温湿度

  • 湿度:28.0% RH

  • 温度:30.0°C

九、驱动框架总结

1. 驱动分层结构

text

应用程序 (app)
    ↓
文件操作接口 (open/read/close)
    ↓
DHT11设备驱动 (dht11.c)
    ├─ 开始信号 (dht11_start)
    ├─ 等待响应 (dht11_wait_respon)  
    ├─ 读取位数据 (dht11_get_bit)
    └─ 读取完整数据 (dht11_read_data)
    ↓
GPIO子系统 (gpio_request/set_value/get_value)
    ↓
硬件引脚 (GPIO1_IO02)
    ↓
DHT11传感器

2. 关键机制

机制 作用 实现函数
平台驱动 设备树匹配 platform_driver_register()
杂项设备 简化字符设备注册 misc_register()
GPIO控制 引脚电平操作 gpio_set_value()/gpio_get_value()
精确延时 微秒级时序控制 udelay()/mdelay()/msleep()
数据拷贝 内核-用户空间数据交换 copy_to_user()

3. 时序关键点

  • 开始信号低电平:20ms(msleep)

  • 响应等待超时:30us/100us

  • 位采样点:35us

  • 读取间隔:≥1秒(sleep(3))

总结

  1. DHT11通信:单总线协议,严格的微秒级时序要求

  2. 驱动核心:精确控制GPIO电平变化和延时,模拟DHT11通信时序

  3. 设备树配置:定义GPIO引脚和匹配属性,完成硬件描述

  4. 应用程序:通过标准文件操作接口读取温湿度原始数据

  5. 错误处理:完善的超时检测和错误返回机制

  6. 启动配置:U-Boot环境变量设置,网络启动内核

DHT11驱动是单总线设备驱动的典型实现,展示了如何在Linux驱动中使用GPIO模拟复杂通信时序,以及如何通过设备树完成硬件配置。

Logo

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

更多推荐