DHT11 温湿度传感器驱动与 I2C 子系统驱动开发详解

1. 引言

在嵌入式 Linux 开发中,传感器驱动是常见的开发任务。DHT11 是一款数字温湿度传感器,采用单总线通信协议;而 I2C 子系统是 Linux 内核中用于管理 I2C 总线设备的标准化驱动框架。本文分别介绍 DHT11 驱动的实现思路,以及基于 Linux I2C 子系统编写设备驱动的完整流程。

2. DHT11 驱动开发

2.1 DHT11 简介

DHT11 是一款含有已校准数字信号输出的温湿度复合传感器,采用单总线(Single-Wire)协议与主机通信。其测量范围:湿度 20%-90% RH,温度 0-50 摄氏度,精度为湿度 ±5% RH、温度 ±2 摄氏度。

2.2 单总线时序分析

DHT11 通信时序分为以下几个阶段:

  • 起始信号:主机拉低总线至少 18ms,然后释放总线,等待 DHT11 响应。
  • 响应信号:DHT11 拉低总线 80us,再拉高 80us,表示准备发送数据。
  • 数据发送:每 bit 以 50us 低电平开始,随后高电平时间长短决定该位是 0 还是 1。
  • 数据格式:40 位数据,依次为湿度整数、湿度小数、温度整数、温度小数、校验和。

2.3 驱动代码实现

下面给出一个基于 GPIO 的 DHT11 字符设备驱动示例,使用内核定时器完成时序控制。

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DHT11_GPIO 17
#define DHT11_DATA_LEN 5

static int major;
static struct cdev dht11_cdev;
static struct class *dht11_class;
static unsigned char dht11_data[DHT11_DATA_LEN];

static int dht11_read_bit(void)
{
    int timeout = 100;
    while (gpio_get_value(DHT11_GPIO) == 0 && timeout--) {
        udelay(1);
    }
    timeout = 100;
    while (gpio_get_value(DHT11_GPIO) == 1 && timeout--) {
        udelay(1);
    }
    return timeout > 0 ? 1 : 0;
}

static int dht11_read_data(void)
{
    int i, j;
    unsigned char byte = 0;

    gpio_direction_output(DHT11_GPIO, 0);
    mdelay(20);
    gpio_set_value(DHT11_GPIO, 1);
    udelay(30);
    gpio_direction_input(DHT11_GPIO);

    while (gpio_get_value(DHT11_GPIO) == 1) {
        udelay(1);
    }
    while (gpio_get_value(DHT11_GPIO) == 0) {
        udelay(1);
    }
    while (gpio_get_value(DHT11_GPIO) == 1) {
        udelay(1);
    }

    for (i = 0; i < DHT11_DATA_LEN; i++) {
        byte = 0;
        for (j = 0; j < 8; j++) {
            byte <<= 1;
            if (dht11_read_bit()) {
                byte |= 1;
            }
        }
        dht11_data[i] = byte;
    }

    return (dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3]) == dht11_data[4] ? 0 : -EIO;
}

static ssize_t dht11_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    char temp[32];
    int ret;

    ret = dht11_read_data();
    if (ret < 0) {
        return ret;
    }

    snprintf(temp, sizeof(temp), "hum=%d.%d temp=%d.%d\n",
             dht11_data[0], dht11_data[1], dht11_data[2], dht11_data[3]);
    return simple_read_from_buffer(buf, len, off, temp, strlen(temp));
}

static struct file_operations dht11_fops = {
    .owner = THIS_MODULE,
    .read = dht11_read,
};

static int __init dht11_init(void)
{
    int ret;

    ret = gpio_request(DHT11_GPIO, "dht11");
    if (ret) {
        return ret;
    }

    major = register_chrdev(0, "dht11", &dht11_fops);
    if (major < 0) {
        gpio_free(DHT11_GPIO);
        return major;
    }

    dht11_class = class_create(THIS_MODULE, "dht11");
    device_create(dht11_class, NULL, MKDEV(major, 0), NULL, "dht11");
    return 0;
}

static void __exit dht11_exit(void)
{
    device_destroy(dht11_class, MKDEV(major, 0));
    class_destroy(dht11_class);
    unregister_chrdev(major, "dht11");
    gpio_free(DHT11_GPIO);
}

module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DHT11 temperature and humidity sensor driver");

2.4 驱动测试

编译并加载驱动后,通过读取设备节点即可获取温湿度数据:

bash 复制代码
sudo insmod dht11.ko
cat /dev/dht11

3. I2C 子系统驱动开发

3.1 I2C 子系统架构

Linux I2C 子系统采用三层架构:

  • I2C 核心层:提供总线管理、设备注册、地址分配等通用逻辑。
  • I2C 总线驱动:对应具体硬件控制器,负责底层时序收发。
  • I2C 设备驱动:针对挂在总线上的具体外设,如 EEPROM、触摸屏、传感器等。

3.2 设备驱动核心结构

I2C 设备驱动主要实现两个核心结构体:

c 复制代码
static const struct i2c_device_id xxx_id[] = {
    { "xxx_chip", 0 },
    { }
};

static struct i2c_driver xxx_driver = {
    .driver = {
        .name = "xxx_chip",
        .of_match_table = xxx_of_match,
    },
    .probe = xxx_probe,
    .remove = xxx_remove,
    .id_table = xxx_id,
};

3.3 完整驱动示例

下面以一款虚拟 I2C 传感器为例,演示从注册到数据读取的完整流程。

c 复制代码
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>

#define XXX_REG_DATA 0x00
#define XXX_REG_CTRL 0x01

struct xxx_dev {
    struct i2c_client *client;
    int value;
};

static int xxx_read_value(struct xxx_dev *dev)
{
    struct i2c_client *client = dev->client;
    int ret;

    ret = i2c_smbus_read_byte_data(client, XXX_REG_DATA);
    if (ret < 0) {
        return ret;
    }
    dev->value = ret;
    return 0;
}

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct xxx_dev *dev;
    int ret;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev) {
        return -ENOMEM;
    }

    dev->client = client;
    i2c_set_clientdata(client, dev);

    ret = i2c_smbus_write_byte_data(client, XXX_REG_CTRL, 0x01);
    if (ret < 0) {
        goto err_free;
    }

    ret = xxx_read_value(dev);
    if (ret < 0) {
        goto err_free;
    }

    dev_info(&client->dev, "xxx sensor probed, value=%d\n", dev->value);
    return 0;

err_free:
    kfree(dev);
    return ret;
}

static void xxx_remove(struct i2c_client *client)
{
    struct xxx_dev *dev = i2c_get_clientdata(client);
    kfree(dev);
}

static const struct of_device_id xxx_of_match[] = {
    { .compatible = "vendor,xxx-sensor" },
    { }
};
MODULE_DEVICE_TABLE(of, xxx_of_match);

static const struct i2c_device_id xxx_id[] = {
    { "xxx-sensor", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, xxx_id);

static struct i2c_driver xxx_driver = {
    .driver = {
        .name = "xxx_sensor",
        .of_match_table = xxx_of_match,
    },
    .probe = xxx_probe,
    .remove = xxx_remove,
    .id_table = xxx_id,
};
module_i2c_driver(xxx_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example I2C sensor driver");

3.4 设备树配置

在设备树中描述 I2C 设备,驱动加载时内核会自动匹配:

dts 复制代码
&i2c1 {
    status = "okay";

    xxx_sensor@48 {
        compatible = "vendor,xxx-sensor";
        reg = <0x48>;
    };
};

4. 两种驱动开发方式对比

对比项 DHT11 驱动 I2C 子系统驱动
通信协议 单总线,需软件模拟时序 标准 I2C 协议,硬件控制器支持
驱动框架 字符设备驱动,直接操作 GPIO I2C 子系统框架,遵循总线-设备-驱动模型
时序控制 依赖 udelay/mdelay 精确延时 由 I2C 控制器硬件完成
设备匹配 手动注册,无自动匹配机制 支持设备树和 id_table 自动匹配
适用场景 低成本、低速简单传感器 标准总线外设,多设备共享总线

5. 总结

DHT11 驱动开发的核心在于精确控制单总线时序,适合理解底层 GPIO 操作和时序逻辑;而 I2C 子系统驱动则充分利用内核标准框架,代码结构清晰、可移植性强。实际项目中,应根据传感器接口类型选择合适的驱动开发方式,并优先复用内核提供的子系统框架。

相关推荐
zbyyd1 小时前
Linux 驱动开发实战:从手写 LED 字符设备驱动到设备树
linux·驱动开发
昌原的儿子LEO2 小时前
嵌入式 Linux I2C 驱动开发核心知识总结:设备树与 platform 架构全解析
linux·驱动开发·架构
gwf2162 小时前
常见驱动问题定位与解决:设备识别、性能不达预期、CQ错误码 —— 资深RDMA驱动调试排错指南
驱动开发·linux内核·性能调优·rdma·故障排查·mlx5·irdma
万物智能信息科技3 小时前
气体传感器MQ-2芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·嵌入式硬件·华为·开源·harmonyos
硅基手札5 小时前
【I2C 技术系列 11】I2C Linux 驱动框架
linux·驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
碧海银沙音频科技研究院6 小时前
炬芯ATS3089智能手表烧录方法
人工智能·驱动开发·嵌入式硬件·算法
wuyk55521 小时前
嵌入式驱动开发:量产级工程化实战(第9篇)——低功耗下的通信可靠性
c语言·驱动开发·stm32·单片机·嵌入式硬件
硅基手札1 天前
【I2C 技术系列 08】I2C 控制器寄存器与 IP 架构
驱动开发·stm32·单片机·嵌入式硬件·mcu·架构·计算机外设
万物智能信息科技1 天前
人体感应芯片PIR模块驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·嵌入式硬件·华为·开源·harmonyos