一、DHT11 传感器基础DHT11 采用单总线串行通信方式仅需一根数据线即可完成温湿度数据的双向传输无需额外的时钟线硬件连接简单。其通信逻辑由主机MCU / 开发板主动发起从机DHT11响应并返回 40 位数据。数据格式为8 位湿度整数 8 位湿度小数 8 位温度整数 8 位温度小数 8 位校验和其中 DHT11 的湿度小数和温度小数位始终为 0实际有效数据为湿度整数和温度整数二、DHT11原理图1.电路原理图DHT11 温湿度传感器的单总线连接电路DATA 引脚通过 4.7K 上拉电阻接 VCC确保总线空闲时为高电平VCC 和 GND 分别接电源正负极。2.通信时许主机起始主机将数据线拉低至少 18ms再拉高 20~40us向 DHT11 发送起始指令从机响应DHT11 检测到起始信号后会将数据线拉低 80us再拉高 80us完成响应3.读取0和1的时序从机通过数据线高低电平的持续时间表示二进制位低电平 50us 后高电平 26~28us 表示 0依次传输 40 位数据。从机通过数据线高低电平的持续时间表示二进制位低电平 50us 后高电平 70us 表示 1依次传输 40 位数据。三、驱动代码核心实现解析3.1 宏定义与全局变量#define DEV_NAME dht11 static int dht11_gpio;定义设备名dht11作为杂项设备和平台驱动的标识全局变量dht11_gpio存储从设备树解析到的 DHT11 数据线对应的 GPIO 编号实现 GPIO 资源的动态获取。3.2 核心时序操作函数3.2.1 起始信号函数dht11_startstatic void dht11_start(void) { gpio_direction_output(dht11_gpio, 1); mdelay(2); gpio_set_value(dht11_gpio, 0); msleep(20); gpio_set_value(dht11_gpio, 1); udelay(20); }该函数实现主机向 DHT11 发送起始信号严格遵循 DHT11 时序要求先将 GPIO 配置为输出模式并拉高 2ms保证数据线初始状态稳定拉低数据线 20ms满足至少 18ms 的要求发送起始指令拉高数据线 20us等待 DHT11 的响应信号。3.2.2 响应等待函数dht11_wait_responstatic 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; }主机发送起始信号后将 GPIO 配置为输入模式等待 DHT11 的响应信号通过超时计数避免死循环等待 DHT11 将数据线拉低超时 30us 返回错误等待 DHT11 响应的 80us 低电平结束超时 100us 返回错误等待 DHT11 响应的 80us 高电平结束超时 100us 返回错误无超时则返回 0表示响应成功。3.2.3 位数据读取函数dht11_get_bitstatic char dht11_get_bit(void) { int time 60; 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; udelay(35); if(!gpio_get_value(dht11_gpio)) return 0; time 50; while(gpio_get_value(dht11_gpio) time--) { udelay(1); } if(time 0) return -6; return 1; }该函数实现单个二进制位的读取基于 DHT11 的位时序特征先等待数据位的起始低电平结束超时则返回错误延时 35us 后检测数据线电平若为低表示该位为 0若为高表示该位为 1对高电平做超时判断避免总线异常导致的死循环。3.2.4 完整数据读取函数dht11_read_datastatic int dht11_read_data(unsigned char * data) { int i 0; int j 0; for(j 0; j 5; j) { for(i 0; i 8; i) { char bit dht11_get_bit(); if(bit 0) return bit; data[j] 1; data[j] | bit; } } return 5; }循环读取 40 位5 个字节数据通过位运算将读取到的二进制位拼接为完整的字节数据外层循环遍历 5 个字节湿度整、湿度小、温度整、温度小、校验和内层循环遍历每个字节的 8 位通过左移和或运算拼接位数据若位读取失败立即返回错误码读取成功返回 5表示获取到 5 个字节有效数据。3.3 文件操作接口实现static int open(struct inode * inode, struct file * file) { printk(dht11 open\n); return 0; } 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); if(ret 0) return ret; ret copy_to_user(buf, data, sizeof(data)); printk(dht11 read\n); return 5; } static ssize_t write(struct file * file, const char __user * buf, size_t size, loff_t * loff) { printk(dht11 write\n); return 0; } static int close(struct inode * inode, struct file * file) { printk(dht11 close\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .write write, .release close };read核心接口用户态调用read函数读取设备文件时触发该接口依次执行起始信号、响应等待、数据读取通过copy_to_user将内核态的 5 字节温湿度数据拷贝到用户态缓冲区数据传输成功后返回 5表示读取到 5 个字节数据。3.4 杂项设备注册static struct miscdevice misc_dev { .minor MISC_DYNAMIC_MINOR, // 动态分配次设备号 .name DEV_NAME, // 设备名对应/dev/dht11 .fops fops // 绑定文件操作接口 };在平台驱动的probe函数中通过misc_register(misc_dev)完成注册驱动卸载时通过misc_deregister(misc_dev)注销。3.5.1 设备树匹配与 probe/remove 函数static int probe(struct platform_device * pdev) { struct device_node * pdts; int ret misc_register(misc_dev); if(ret) goto err_misc_register; // 解析设备树节点 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); printk(######################### dht11_driver probe\n); return 0; // 错误处理流程 err_gpio_request: err_of_find: misc_deregister(misc_dev); err_misc_register: printk(######################### dht11_driver misc register ret %d\n, ret); return ret; } static int remove(struct platform_device * pdev) { gpio_free(dht11_gpio); misc_deregister(misc_dev); printk(######################### dht11_driver remove\n); return 0; } // 设备树匹配表 static struct of_device_id dht11_table[] { {.compatible pt-dht11}, {} }; // 平台驱动结构体 static struct platform_driver pdrv { .probe probe, .remove remove, .driver { .name DEV_NAME, .of_match_table dht11_table } };probe 函数平台驱动匹配成功后执行的核心函数完成设备初始化remove 函数驱动卸载时执行释放 GPIO 资源并注销杂项设备避免资源泄漏。设备树匹配表通过compatible属性pt-dht11与设备树中的设备节点匹配实现驱动的自动加载。3.5.2 模块入口与出口static int __init dht11_driver_init(void) { int ret platform_driver_register(pdrv); if(ret) goto err_platform_driver_register; printk(dht11 platform_driver_register success\n); return 0; err_platform_driver_register: printk(dht11 platform_driver_register failed\n); return ret; } static void __exit dht11_driver_exit(void) { platform_driver_unregister(pdrv); printk(dht11 platofrm_driver_unregister\n); } module_init(dht11_driver_init); module_exit(dht11_driver_exit); MODULE_LICENSE(GPL);模块入口dht11_driver_init注册平台驱动驱动加载时执行模块出口dht11_driver_exit注销平台驱动驱动卸载时执行MODULE_LICENSE(GPL)声明模块遵循 GPL 协议避免内核加载时的版权警告。四、应用层程序int main(int argc, const char* argv[]) { // 1. 打开驱动生成的设备节点 int fd open(/dev/dht11, O_RDONLY); if (fd 0) { perror(open dht11 failed); return 1; } unsigned char data[5] { 0 }; while (1) { // 2. 读取 5 字节数据 if (read(fd, data, sizeof(data)) 5) { // data[0]: 湿度整数, data[1]: 湿度小数 // data[2]: 温度整数, data[3]: 温度小数 float h data[0] data[1] * 0.1; float t data[2] data[3] * 0.1; printf(Humidity %.1f %%, Temperature %.1f C\n, h, t); } else { printf(Read error or checksum failed\n); } // 3. 每 3 秒读一次DHT11 硬件限制不能读太快 sleep(3); } close(fd); return 0; }五、总结DHT11 温湿度传感器的单总线接口电路DHT11 的 VCC 和 GND 分别接电源正负极为传感器供电。DATA 引脚通过 4.7K 上拉电阻接 VCC保证总线空闲时为高电平并通过接口 P2 与 MCU 通信。整个电路实现了单总线双向通信用于 MCU 采集温湿度数据。