☰
DHT11 温湿度传感器驱动与 I2C 子系统驱动开发详解
2026/10/14 19:30:49 网站建设 项目流程

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 字符设备驱动示例,使用内核定时器完成时序控制。

#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 驱动测试

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

sudo insmod dht11.ko cat /dev/dht11

3. I2C 子系统驱动开发

3.1 I2C 子系统架构

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

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

3.2 设备驱动核心结构

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

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 传感器为例,演示从注册到数据读取的完整流程。

#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 设备,驱动加载时内核会自动匹配:

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

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

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

5. 总结

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询