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/dht113. 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_sensor48 { compatible vendor,xxx-sensor; reg 0x48; }; };4. 两种驱动开发方式对比对比项DHT11 驱动I2C 子系统驱动通信协议单总线需软件模拟时序标准 I2C 协议硬件控制器支持驱动框架字符设备驱动直接操作 GPIOI2C 子系统框架遵循总线-设备-驱动模型时序控制依赖 udelay/mdelay 精确延时由 I2C 控制器硬件完成设备匹配手动注册无自动匹配机制支持设备树和 id_table 自动匹配适用场景低成本、低速简单传感器标准总线外设多设备共享总线5. 总结DHT11 驱动开发的核心在于精确控制单总线时序适合理解底层 GPIO 操作和时序逻辑而 I2C 子系统驱动则充分利用内核标准框架代码结构清晰、可移植性强。实际项目中应根据传感器接口类型选择合适的驱动开发方式并优先复用内核提供的子系统框架。