ARM-驱动-06-DHT11

DHT11 温湿度传感器驱动学习笔记

对应代码:dht11_ioctl.c / pt.dts

平台:i.MX6ULL,数据线接 GPIO1_IO04


一、DHT11 硬件基础

1.1 引脚说明

Pin 名称 说明
1 VDD 供电 3~5.5V
2 DATA 单总线数据线(需 5K 上拉电阻)
3 NC 悬空
4 GND 接地

1.2 关键参数

参数
测温范围 0~50°C,精度 ±2°C,分辨率 1°C
测湿范围 20~90%RH,精度 ±5%RH,分辨率 1%RH
采样间隔 ≥ 1 秒(强制要求,否则数据不稳定)
上电等待 上电后等待 1 秒才能开始通信
单次通信 约 4ms,传输 40 bit 数据

二、通信协议与完整时序讲解

DHT11 采用单总线协议(1-Wire),同一根线既发又收,分三个阶段。

2.1 总线空闲状态

总线空闲时由上拉电阻维持高电平。DHT11 处于低功耗模式,不主动发数据。

复制代码
总线空闲:
DATA ─────────────────────── 高电平(上拉电阻维持)

2.2 阶段一:

主机(MCU)主动拉低总线,通知 DHT11 开始工作。

​​ 复制代码
主机发起信号时序:

DATA ──┐                    ┌─────┐
       │                    │     │
       └────────────────────┘     └── 切为输入,等 DHT11
       ← 拉低 ≥ 18ms(代码用20ms)→ ↑  ←20~40us→
                               拉高

对应代码

c 复制代码
static void dht11_start(void)
{
    gpio_set_value(dht11_gpio, 0);   // ① 拉低总线
    msleep(20);                      // ② 保持低电平 20ms(>18ms 即可)
    gpio_set_value(dht11_gpio, 1);   // ③ 拉高,结束起始信号
    udelay(30);                      // ④ 等待 20~40us
    // ⑤ 函数返回后,调用方切换为输入模式等待响应
}

为什么要拉低 ≥18ms? 这是 DHT11 能检测到起始信号的最短时间,低于 18ms DHT11 不会响应。


2.3 阶段二:DHT11 响应信号(代码:dht11_response()

主机拉高后,DHT11 检测到起始信号,发出响应:先拉低 80us,再拉高 80us,然后再次拉低,表示数据开始。

复制代码
DHT11 响应时序:

         主机拉高                    数据开始
           ↓                           ↓
DATA ──────┐    ┌──────────┐           ┌── 第1个bit低电平50us
           │    │          │           │
           └────┘          └───────────┘
           ←20~40us→← 80us低 →← 80us高 →
           主机信号  DHT11响应低  DHT11响应高

对应代码

c 复制代码
static int dht11_response(void)
{
    int time = 10;
    gpio_direction_input(dht11_gpio);    // ① 切为输入模式

    // ② 等待 DHT11 拉低(响应的 80us 低电平开始)
    while (gpio_get_value(dht11_gpio) && time--)
        udelay(10);
    if (time <= 0) { return -1; }       // 超时:DHT11 没有响应

    // ③ 等待 DHT11 拉高(80us 低电平结束,80us 高电平开始)
    time = 10;
    while (!dht11_pin_check() && time--)
        udelay(10);
    if (time <= 0) { return -2; }

    // ④ 等待 DHT11 再次拉低(80us 高电平结束,第一个数据 bit 开始)
    while (dht11_pin_check());

    return 0;
}

注意 :步骤 ① gpio_direction_input 写在 dht11_response() 里,而不是 dht11_start() 里。这是因为 dht11_start() 执行完后要有 30us 的等待,这 30us 间 GPIO 还是输出模式(高电平),进入 dht11_response() 后才切换为输入。


2.4 阶段三:数据传输(40 bit)

DHT11 发送 40 bit 数据,高位(MSB)先出

数据格式

复制代码
第1字节    第2字节    第3字节    第4字节    第5字节
湿度整数   湿度小数   温度整数   温度小数   校验和
 8 bit     8 bit     8 bit     8 bit     8 bit

当前 DHT11 小数部分固定为 0(保留扩展),实际只用整数部分。

bit 编码规则

每个 bit 以 50us 低电平开始,后面跟的高电平时长决定是 0 还是 1:

复制代码
bit = 0 的波形:
DATA ─┐         ┌──────┐
      │         │      │
      └─────────┘      └─ 下一个bit低电平
      ← 50us低 →←26~28us高→

bit = 1 的波形:
DATA ─┐         ┌──────────────────┐
      │         │                  │
      └─────────┘                  └─ 下一个bit低电平
      ← 50us低 →←    70us高       →

采样判断方法(延迟 60us 采样)

复制代码
等 50us 低电平结束(上升沿到来)
    ↓
延迟 60us
    ↓
此时采样:
  - 若还是高电平 → 高电平持续 70us > 60us → bit = 1
  - 若已经是低电平 → 高电平只有 26~28us < 60us → bit = 0

对应代码

c 复制代码
for (j = 0; j < 5; j++) {
    for (i = 0; i < 8; i++) {
        while (!dht11_pin_check());    // 等待 50us 低电平结束(上升沿)
        udelay(60);                    // 延迟 60us 后采样
        if (dht11_pin_check())
            dht_dat[j] |= (1 << (7 - i));  // 还是高 → bit=1(MSB 先出)
        while (dht11_pin_check());     // 等待低电平,准备下一个 bit
    }
}

(7 - i) 的含义:第一个收到的 bit 是最高位(bit7),所以 i=0 时存到第 7 位,i=7 时存到第 0 位,这就是"高位先出"的处理方式。


2.5 阶段四:数据结束

最后一个 bit 传完后,DHT11 拉低总线 50us,然后由上拉电阻将总线拉回高电平,恢复空闲状态。代码里在 dht11_read() 结尾调用 gpio_direction_output(dht11_gpio, 1) 主动拉高,准备下次采集。


2.6 完整时序总览

复制代码
总线空闲(高电平)
    │
    ▼
【主机发起】
拉低 ≥18ms → 拉高 → 等 20~40us → 切为输入
    │
    ▼
【DHT11 响应】
DHT11 拉低 80us → DHT11 拉高 80us → DHT11 拉低(数据开始)
    │
    ▼
【传输 40 bit】(每个 bit:50us低 + 高电平)
bit7 bit6 bit5 ... bit0 │ bit7 ... bit0 │ ... │ 校验和
   湿度整数(8bit)         湿度小数(8bit)   温度整数  温度小数  校验
    │
    ▼
【结束】
DHT11 拉低 50us → 上拉电阻拉高 → 总线空闲

三、校验和机制

计算方式:前 4 字节之和,取低 8 位

c 复制代码
checksum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3];
if (checksum != dht_dat[4]) {
    // 数据有误,丢弃
}

遇到的 Bug:校验一直失败

现象:加上校验代码后一直报错,删掉校验就能正常读数。

原因dht_dat 是全局数组,每次读取前没有清零。数据读取用的是 |=(按位或),只能把 0 变成 1,无法把 1 变回 0。第一次读完的数据留在数组里,第二次读进来的新 bit 和旧数据叠加,校验和必然出错。

c 复制代码
// 错误原因示意:
// 第一次读完:dht_dat = {0x55, 0x00, 0x1C, 0x00, 0x71}  正确
// 第二次读取,|= 叠加后:某些 bit 永远是 1,校验和不对
dht_dat[j] |= (1 << (7 - i));   // |= 只能置 1,不能清 0

修复 :在 dht11_read() 开头加一行清零

c 复制代码
static int dht11_read(void)
{
    memset(dht_dat, 0, sizeof(dht_dat));   // ← 加这一行,每次采集前清零
    // ... 后面不变
}

为什么删掉校验"看起来正常" :删掉校验后直接使用 dht_dat[0](湿度)和 dht_dat[2](温度),温湿度变化缓慢,叠加的旧数据影响不明显,所以"看起来"能读出数据。但实际上数据已经因累积错误而不准确了------校验是在暴露 bug,而不是制造问题。


四、DTS 配置(需新增到 pt.dts)

DHT11 使用 GPIO1_IO04(原 DTS 中 PWM3 为 disabled,该引脚空闲可用)。

第一处:在 &iomuxc { imx6ul-evk { 内添加引脚配置

dts 复制代码
pinctrl_dht11: dht11grp {
    fsl,pins = <
        /* GPIO1_IO04,使能内部上拉(0x1b0b0)
         * DHT11 数据线需要上拉,保证总线空闲时为高电平
         * 0x1b0b0 = HYS+PUS+PUE+PKE+SPEED_100MHz+DSE_48OHM
         */
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04    0x1b0b0
    >;
};

第二处:在根节点 / { 内添加设备节点

dts 复制代码
pt_dht11 {
    compatible    = "pt-dht11";
    status        = "okay";
    pinctrl-names = "default";
    pinctrl-0     = <&pinctrl_dht11>;
    dht11-gpio    = <&gpio1 4 GPIO_ACTIVE_HIGH>;
};

五、数据读取流程图

复制代码
应用层:read(fd, buf, 4)
    ↓
内核 read()
    ↓
dht11_read()
    ├─ memset(dht_dat, 0)        清零,防止旧数据干扰
    ├─ dht11_start()             主机发起:拉低20ms → 拉高 → 等30us
    ├─ dht11_response()          等DHT11响应:低80us → 高80us → 低
    ├─ 读 40 bit 循环             5字节 × 8bit,60us 延迟采样
    ├─ 校验和验证                 前4字节之和 == 第5字节?
    └─ copy_to_user(buf, dht_dat, 4)  返回4字节给用户
    ↓
应用层获得:
  buf[0] = 湿度整数
  buf[1] = 湿度小数(当前固定0)
  buf[2] = 温度整数
  buf[3] = 温度小数(当前固定0)

六、编译与验证步骤

bash 复制代码
# 1. 修改 DTS,重新编译 DTB
make pt.dtb
# 把 pt.dtb 部署到开发板,重启生效

# 2. 编译驱动模块
make
# 生成 dht11.ko

# 3. 加载驱动,确认 probe 执行
insmod dht11.ko
dmesg | tail          # 看到 "probe dht11 misc_register"
ls /dev/dht11         # 设备节点存在

# 4. 编写应用层测试程序
# read(fd, buf, 4) 读出 4 字节
# buf[0]=湿度整数  buf[2]=温度整数
# 交叉编译后传到开发板运行
arm-linux-gnueabihf-gcc dht11_app.c -o dht11_app
./dht11_app

# 5. 卸载
rmmod dht11

注意事项

  • DHT11 上电后必须等待 1 秒 才能通信(代码里 open 函数有 msleep(5),实际上建议应用层上电后等 1s 再 open)
  • 两次 read 之间至少间隔 1 秒,否则 DHT11 来不及完成下一次采样
  • 时序对抖动敏感,Linux 非实时系统有调度延迟,如遇偶发错误属正常现象
相关推荐
dddddppppp1233 小时前
arm32段+页映射 手撕mmu的行为之软件模拟
linux·服务器·网络
天赐学c语言3 小时前
MySQL - 数据库基础
linux·数据库·mysql
wwj888wwj3 小时前
Ansible基础(复习3)
linux·运维·服务器·git·ansible
senijusene3 小时前
IMX6ULL Linux 驱动开发:GPIO 子系统 + misc 框架实现按键输入驱动开发
linux·运维·驱动开发
捞的不谈~3 小时前
解决在Ubuntu系统下使用运行Lucid 相机(HTR003S-001)相应实例出现的依赖库缺失的问题
linux·运维·ubuntu
J超会运3 小时前
OpenEuler24.03 LVS+Keepalived实战指南
linux·服务器·前端
白毛大侠3 小时前
四表五链:Linux 防火墙的核心框架
linux·运维·网络
拾光Ծ3 小时前
吃透 Linux 静态库 / 动态库:ELF 文件、链接加载与进程地址空间详解
linux·动态库·静态库·elf·链接与加载·c/c++编程
好家伙VCC3 小时前
**TEE在嵌入式安全中的应用实践:基于ARM TrustZone的加密存储方案设计与实现*
java·arm开发·python·struts·安全
铅笔小新z3 小时前
【Linux】进程(中)
linux·运维·服务器