目录一、DHT11 传感器基础概念1.1 硬件特性1.2 数据格式1.3 通信时序1.3.1 起始信号主机→从机1.3.2 从机响应从机→主机1.3.3 数据位传输从机→主机二、内核驱动程序实现2.1 设备树配置2.2 驱动架构设计2.3 核心函数拆解2.3.1 起始信号发送2.3.2 从机响应检测2.3.3 单比特读取2.3.4 40位数据读取2.3.5 文件操作接口2.3.6 Platform 驱动初始化2.4 驱动入口 / 出口三、应用层程序开发四、总结一、DHT11 传感器基础概念1.1 硬件特性DHT11是一款经济实用的数字温湿度传感器广泛应用于智能家居、环境监测等场景。其核心特性包括测量范围湿度 20-90%RH±5%RH 精度温度 0-50℃±2℃ 精度通信接口单总线Single-Wire协议仅需1根数据线响应速度采样周期不小于2秒硬件限制输出格式40 位数字信号无需 AD 转换直接解析时序即可采样频率最高 1Hz即每秒最多读取 1 次过快会导致数据错误。实物图dht11实物图电路图dht11电路图1.2 数据格式DHT11 每次输出 40 位5 字节数据字节含义如下字节序号含义备注第 1 字节湿度整数部分0~99第 2 字节湿度小数部分DHT11 固定为 0无小数第 3 字节温度整数部分0~50第 4 字节温度小数部分DHT11 固定为 0第 5 字节校验和 字节 1 字节 2 字节 3 字节 41.3 通信时序DHT11 的单总线通信由Linux 内核主动发起分为起始信号、从机响应、数据位传输三个阶段时序逻辑如下1.3.1 起始信号主机→从机主机将 DATA 线GPIO设为输出模式先拉高 2ms稳定电平拉低 DATA 线至少 18ms拉高 DATA 线 20~40us完成起始信号发送。1.3.2 从机响应从机→主机从机检测到起始信号后主动拉低 DATA 线 80us再拉高 DATA 线 80us作为 “响应成功” 的标识主机需切换 GPIO 为输入模式检测这两段电平超时则通信失败。起始信号与从机响应1.3.3 数据位传输从机→主机响应完成后从机逐位发送 40 位数据通过高电平时长区分 0/1发送 0拉低 DATA 线 50us → 拉高 26~28us发送 1拉低 DATA 线 50us → 拉高 70us主机通过检测高电平时长判断当前位是 0 还是 1。发送0信号发送1信号二、内核驱动程序实现2.1 设备树配置在设备树中添加 /dht11 节点。dht11 { #address-cells 1; #size-cells 1; compatible pt-dht11; pinctrl-names default; pinctrl-0 pinctrl_ptdht11; ptdht11-gpio gpio1 2 GPIO_ACTIVE_HIGH; status okay; }; pinctrl_ptdht11: ptdht11grp { fsl,pins MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10B0 ; };2.2 驱动架构设计本驱动采用Linux标准的Platform Driver Misc Device架构Platform 驱动适配设备树解析 GPIO 配置实现驱动与硬件解耦Misc 杂项设备简化字符设备注册快速生成 /dev/dht11 设备文件。// 驱动核心结构 static struct platform_driver pdrv { .probe probe, .remove remove, .driver { .name dht11, .of_match_table dht11_table // 支持设备树匹配 } }; // 杂项设备注册 static struct miscdevice misc_dev { .minor MISC_DYNAMIC_MINOR, // 动态分配次设备号 .name dht11, .fops fops // 文件操作接口 };2.3 核心函数拆解2.3.1 起始信号发送通过 gpio_set_value 控制电平msleep(20) 确保满足 DHT11 要求的 ≥18ms 低电平udelay(20) 精确控制 20-40μs 高电平窗口。static void dht11_start(void) { gpio_direction_output(dht11_gpio, 1); // GPIO设为输出初始拉高 mdelay(2); // 稳定电平2ms gpio_set_value(dht11_gpio, 0); // 拉低20ms msleep(20); gpio_set_value(dht11_gpio, 1); // 拉高20us完成起始 udelay(20); }2.3.2 从机响应检测DHT11响应信号包含 80μs 低 80μs 高需分三阶段检测超时返回不同错误码便于调试。static int dht11_wait_respon(void) { int time 0; gpio_direction_input(dht11_gpio); // 切换为输入模式 // 跳过主机起始后的高电平 time 30; while(gpio_get_value(dht11_gpio) time--) udelay(1); if(time 0) return -1; // 检测从机响应的低电平 time 100; while((!gpio_get_value(dht11_gpio)) time--) udelay(1); if(time 0) return -2; // 检测从机响应的高电平 time 100; while(gpio_get_value(dht11_gpio) time--) udelay(1); if(time 0) return -3; return 0; // 响应成功 }2.3.3 单比特读取在 50μs 低电平结束后延时 35μs 采样——此时 0 已结束26-28μs1 仍为高电平70μs实现可靠判别。static char dht11_get_bit(void) { int time 60; // 等待从机拉低的50us while(gpio_get_value(dht11_gpio) time--) udelay(1); if(time 0) return -4; // 检测从机拉低的电平 time 80; while((!gpio_get_value(dht11_gpio)) time--) udelay(1); if(time 0) return -5; // 延时35us区分0/1 udelay(35); if(!gpio_get_value(dht11_gpio)) return 0; // 高电平已结束是0 // 等待1的高电平结束 time 50; while(gpio_get_value(dht11_gpio) time--) udelay(1); if(time 0) return -6; return 1; }2.3.4 40位数据读取逐位拼接成字节最终生成 5 字节完整数据。static int dht11_read_data(unsigned char * data) { int i 0, j 0; for(j 0; j 5; j) // 读取5字节 { for(i 0; i 8; i) // 每字节8位 { char bit dht11_get_bit(); if(bit 0) return bit; // 位读取失败返回错误码 data[j] 1; // 左移1位腾出最低位 data[j] | bit; // 填充当前位 } } return 5; // 读取成功返回字节数 }2.3.5 文件操作接口串联时序函数完成数据读取通过 copy_to_user 实现内核态→用户态的数据传递。static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff) { int ret 0; unsigned char data[5]; dht11_start(); // 发送起始信号 ret dht11_wait_respon(); // 检测响应 if(ret 0) return ret; ret dht11_read_data(data); // 读取5字节数据 if(ret 0) return ret; copy_to_user(buf, data, sizeof(data)); // 内核数据拷贝到用户态 return 5; // 返回读取的字节数 }2.3.6 Platform 驱动初始化注册 Misc 设备生成 /dev/dht11解析设备树获取 GPIO 编号申请 GPIO 资源并初始化错误处理采用 goto 语句保证资源泄漏。static int probe(struct platform_device * pdev) { struct device_node * pdts; int ret misc_register(misc_dev); // 注册misc设备 if(ret) goto err_misc_register; // 解析设备树中/dht11节点 pdts of_find_node_by_path(/dht11); if(NULL pdts) { ret PTR_ERR(pdts); goto err_of_find; } // 获取设备树中指定的GPIO编号 dht11_gpio of_get_named_gpio(pdts, ptdht11-gpio, 0); if(dht11_gpio 0) { ret dht11_gpio; goto err_of_find; } // 申请GPIO资源 ret gpio_request(dht11_gpio, dht11); if(ret 0) goto err_gpio_request; gpio_direction_output(dht11_gpio, 1); // GPIO初始化为输出拉高 return 0; // 错误处理资源回滚 err_gpio_request: printk(dht11_driver gpio_request\n); err_of_find: printk(dht11_driver find node faidled\n); err_misc_register: misc_deregister(misc_dev); printk(dht11_driver misc register ret %d\n, ret) return ret; }2.4 驱动入口 / 出口static int __init dht11_driver_init(void) { int ret platform_driver_register(pdrv); // 注册Platform驱动 if(ret) goto err_platform_driver_register; return 0; err_platform_driver_register: return ret; } static void __exit dht11_driver_exit(void) { platform_driver_unregister(pdrv); // 注销Platform驱动 } module_init(dht11_driver_init); module_exit(dht11_driver_exit); MODULE_LICENSE(GPL); // 声明GPL协议避免内核警告三、应用层程序开发应用层通过操作 /dev/dht11 设备文件调用驱动接口核心是读取 5 字节数据并解析。#include stdio.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main(int argc, const char* argv[]) { // 打开设备节点 int fd open(/dev/dht11, O_RDONLY); if (fd 0) { perror(open dht11 failed); return 1; } unsigned char data[5] {0}; while (1) { // 读取5字节数据 if (read(fd, data, sizeof(data)) 5) { // 校验和检查 unsigned char checksum data[0] data[1] data[2] data[3]; if (checksum ! data[4]) { printf(Checksum error! Data: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n,data[0], data[1], data[2], data[3], data[4]); sleep(3); continue; } // 解析温湿度 float humidity data[0] data[1] * 0.1; float temperature data[2] data[3] * 0.1; printf(Humidity %.1f%% Temperature %.1fC\n, humidity, temperature); } else { printf(read failed\n); } sleep(3); // 间隔3秒满足DHT11采样频率要求 } close(fd); return 0; }运行结果如下应用程序运行结果四、总结本驱动完整实现了 DHT11 在 Linux 内核中的集成核心价值在于严格遵循单总线时序协议微秒级延时控制采用标准 Platform Driver 架构支持设备树配置通过 Misc Device 简化设备节点管理用户空间零依赖标准 read() 接口即可获取数据