苏州做网站优化,百度广告商,让百度收录自己的网站,众包网站开发一、移植背景与目标1. 硬件环境主控芯片#xff1a;RK3568#xff08;ARM64架构#xff0c;Linux5.10内核#xff09;传感器#xff1a;DHT11温湿度传感器#xff08;单总线通信#xff0c;数字输出#xff09;硬件接线#xff1a;DHT11 VCC→3.3V、DATA→GPIO3_A1、G…一、移植背景与目标1. 硬件环境主控芯片RK3568ARM64架构Linux5.10内核传感器DHT11温湿度传感器单总线通信数字输出硬件接线DHT11 VCC→3.3V、DATA→GPIO3_A1、GND→GND2. 移植目标基于Linux平台驱动框架实现DHT11驱动开发适配设备树配置驱动支持两种用户态访问方式sysfs/sys/class/dht11、字符设备/dev/dht11稳定读取温湿度数据解决时序误判、数据校验失败、编译报错等问题。二、移植全流程阶段1设备树配置驱动与硬件绑定1. 设备树修改文件打开设备树cd /home/alientek/rk3568_linux5.10_sdk/kernel vim arch/arm64/boot/dts/rockchip/rk3568-atk-evb1-ddr4-v10.dtsi修改rk3568-atk-evb1-ddr4-v10.dtsi添加三类关键配置// 1. GPIO3控制器启用确保GPIO3_A1可被调用 gpio3 { status okay; }; // 2. pinctrl引脚配置GPIO3_A1为通用IO无上下拉 pinctrl { dht11 { dht11_pin: dht11-pin { rockchip,pins 3 RK_PA1 RK_FUNC_GPIO pcfg_pull_none; }; }; }; // 3. DHT11设备节点匹配驱动compatible绑定GPIO dht110 { compatible dht11,humidity-temp; pinctrl-names default; pinctrl-0 dht11_pin; gpios gpio3 RK_PA1 GPIO_ACTIVE_HIGH; status okay; };阶段2驱动开发sysfs版本/sys/class/dht111. 最终可运行的sysfs驱动代码#include linux/module.h #include linux/init.h #include linux/platform_device.h #include linux/gpio.h #include linux/delay.h #include linux/of_gpio.h #include linux/of_platform.h #include linux/sysfs.h #include linux/kobject.h #include linux/preempt.h #include linux/device.h // 严格匹配设备树配置 #define DHT11_COMPATIBLE dht11,humidity-temp #define DATA_GPIO_NAME data-gpios #define DEV_NAME dht11 // 遵循DHT11官方时序规范 #define START_LOW_DELAY 18 // 主机拉低≥18ms官方要求 #define START_HIGH_DELAY 30 // 主机释放30us官方20-40us推荐值 #define BIT_JUDGE_DELAY 35 // 数据位判定延迟35us区分0/1关键 #define BIT_WAIT_TIMEOUT 200 // 超时保护阈值 #define POST_READ_DELAY 1000 // 读取间隔≥1s官方要求 // 全局变量 static int dht11_gpio; static u8 dht11_temp, dht11_humi; static struct class *dht11_class; static struct device *dht11_dev; static unsigned long last_read_jiffies; // 记录上次读取时间 // DHT11读取核心函数修复C90语法官方时序 static int dht11_read_data(void) { u8 buf[5] {0}; u8 check_sum; // 修复变量声明移到函数开头符合C90标准 int i, j; int timeout; unsigned long flags; // 遵循官方要求读取间隔≥1s if (time_after(jiffies, last_read_jiffies msecs_to_jiffies(POST_READ_DELAY))) { last_read_jiffies jiffies; } else { dev_info(dht11_dev, Read interval too short (need ≥1s)); return -EAGAIN; } dev_info(dht11_dev, DHT11 read start (follow official timing) ); // 关闭中断禁止抢占确保时序不被打断 local_irq_save(flags); preempt_disable(); // 1. 发送启动信号严格按官方流程 gpio_direction_output(dht11_gpio, 0); dev_info(dht11_dev, Send start signal: pull low %d ms (official ≥18ms), START_LOW_DELAY); mdelay(START_LOW_DELAY); gpio_set_value(dht11_gpio, 1); dev_info(dht11_dev, Release start signal: pull high %d us (official 20-40us), START_HIGH_DELAY); udelay(START_HIGH_DELAY); // 2. 切换为输入检测传感器响应官方响应流程 gpio_direction_input(dht11_gpio); dev_info(dht11_dev, Switch to input mode, wait DHT11 response); // 等待传感器拉低80us响应信号 timeout 0; while (gpio_get_value(dht11_gpio) ! 0) { udelay(1); timeout; if (timeout BIT_WAIT_TIMEOUT) { dev_err(dht11_dev, ERROR: No response low level (timeout%d, check wiring/pull-up resistor), timeout); preempt_enable(); local_irq_restore(flags); return -EIO; } } dev_info(dht11_dev, Detect response low level (duration ~80us, timeout%d us), timeout); udelay(80); // 等待响应低电平结束 // 等待传感器拉高80us准备数据 timeout 0; while (gpio_get_value(dht11_gpio) ! 1) { udelay(1); timeout; if (timeout BIT_WAIT_TIMEOUT) { dev_err(dht11_dev, ERROR: No response high level (timeout%d), timeout); preempt_enable(); local_irq_restore(flags); return -EIO; } } dev_info(dht11_dev, Detect response high level (duration ~80us, timeout%d us), timeout); udelay(80); // 等待准备数据高电平结束 // 3. 读取40位数据严格按官方0/1判定规则 dev_info(dht11_dev, Start reading 40bit data (judge delay%d us), BIT_JUDGE_DELAY); for (i 0; i 5; i) { buf[i] 0; for (j 7; j 0; j--) { // 等待数据位起始低电平官方50us低电平 timeout 0; while (gpio_get_value(dht11_gpio) 0) { udelay(1); timeout; if (timeout BIT_WAIT_TIMEOUT) { dev_err(dht11_dev, ERROR: Data bit start low timeout (byte%d, bit%d), i, j); preempt_enable(); local_irq_restore(flags); return -EIO; } } dev_info(dht11_dev, Byte%d Bit%d: start low level (timeout%d us, official 50us), i, j, timeout); // 核心35us后判定电平官方规则28us为070us为1 udelay(BIT_JUDGE_DELAY); if (gpio_get_value(dht11_gpio) 1) { buf[i] | (1 j); dev_info(dht11_dev, Byte%d Bit%d: 1 (high level 35us, official ~70us), i, j); } else { dev_info(dht11_dev, Byte%d Bit%d: 0 (high level 35us, official ~26-28us), i, j); } // 等待数据位结束高电平 timeout 0; while (gpio_get_value(dht11_gpio) 1) { udelay(1); timeout; if (timeout BIT_WAIT_TIMEOUT) { dev_err(dht11_dev, ERROR: Data bit end high timeout (byte%d, bit%d), i, j); preempt_enable(); local_irq_restore(flags); return -EIO; } } } dev_info(dht11_dev, Read byte%d: 0x%02x (humidity/temp data), i, buf[i]); } // 恢复中断抢占 preempt_enable(); local_irq_restore(flags); // 4. 数据校验官方校验规则校验和前4字节之和的末8位 check_sum (buf[0] buf[1] buf[2] buf[3]) % 256; dev_info(dht11_dev, Data check: buf0buf1buf2buf30x%02x, check_sum0x%02x, buf40x%02x, buf[0]buf[1]buf[2]buf[3], check_sum, buf[4]); if (check_sum ! buf[4]) { dev_err(dht11_dev, ERROR: Data check failed (official rule violated)); return -EINVAL; } // 5. 解析温湿度官方数据格式 dht11_humi buf[0]; // 湿度整数DHT11小数部分为0 dht11_temp buf[2]; // 温度整数DHT11小数部分为0 dev_info(dht11_dev, Read success (official format): temp%d℃, humi%d%%RH, dht11_temp, dht11_humi); dev_info(dht11_dev, DHT11 read end \n); return 0; } // sysfs温度读取接口 static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret dht11_read_data(); if (ret 0) { dev_err(dev, Temp read failed (ret%d), ret); return sprintf(buf, -1\n); } return sprintf(buf, %d\n, dht11_temp); } // sysfs湿度读取接口 static ssize_t humi_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret dht11_read_data(); if (ret 0) { dev_err(dev, Humi read failed (ret%d), ret); return sprintf(buf, -1\n); } return sprintf(buf, %d\n, dht11_humi); } // 定义sysfs属性只读 static DEVICE_ATTR_RO(temp); static DEVICE_ATTR_RO(humi); // probe函数初始化硬件资源 static int dht11_probe(struct platform_device *pdev) { struct device_node *node pdev-dev.of_node; int ret; dev_info(pdev-dev, DHT11 driver probe start ); // 1. 从设备树获取GPIO匹配data-gpios属性 dht11_gpio of_get_named_gpio_flags(node, DATA_GPIO_NAME, 0, NULL); if (!gpio_is_valid(dht11_gpio)) { dev_err(pdev-dev, ERROR: Invalid GPIO pin (check device tree />正确修改 misc 目录下的Kconfig文件仅添加菜单定义1 进入misc目录打开Kconfigcd /home/alientek/rk3568_linux5.10_sdk/kernel/drivers/misc vim Kconfig2在文件末尾添加以下代码只加这个别写赋值# DHT11 Temperature and Humidity Sensor Driver config DHT11 tristate DHT11 Temperature and Humidity Sensor depends on OF_GPIO help This driver supports DHT11 temperature and humidity sensor on RK3568. It parses GPIO from device tree and exposes data via sysfs /sys/class/dht11/.改完Kconfig后menuconfig就能搜到DHT11了如果后续想通过图形界面修改执行以下命令就能在菜单里看到DHT11选项cd /home/alientek/rk3568_linux5.10_sdk/kernel export ARCHarm64 export CROSS_COMPILEaarch64-buildroot-linux-gnu- make menuconfig菜单路径Device Drivers→Misc devices→DHT11 Temperature and Humidity Sensor已被勾选为*对应.config里的y。内核配置make menuconfig→Device Drivers→Misc devices→ 勾选DHT11 Temperature and Humidity Sensor或直接在.config添加CONFIG_DHT11y法二在.config添加CONFIG_DHT11y在/home/alientek/rk3568_linux5.10_sdk/kernel下打开.config vim .config在文件末尾或别的地方添加CONFIG_DHT11yy代表编译进内核built-in开机自动加载不要写m我们要永久内置。注意内核编译用的不是.config而是rockchip_linux_defconfig要用当前内核已修改好的.config覆盖原厂的rockchip_linux_defconfig✅ 正确命令用.config 覆盖 rockchip_linux_defconfig# 先进入内核目录 cd /home/alientek/rk3568_linux5.10_sdk/kernel # 核心源文件(.config) → 目标文件(原厂默认配置) cp .config arch/arm64/configs/rockchip_linux_defconfig执行后原厂的rockchip_linux_defconfig就会被自定义.config含 CONFIG_DHT11y完全替换后续执行./build.sh kernel时脚本调用的就是修改的配置再也不会被原厂默认冲掉编译内核并烧写进开发板cd /home/alientek/rk3568_linux5.10_sdk/ ./build.sh kernel sudo ./rkflash.sh bootsysfs方式验证验证读取# 1. 查看驱动加载日志 dmesg | grep dht11 # 读取温度 cat /sys/class/dht11/dht11/temp # 读取湿度 cat /sys/class/dht11/dht11/humidmesg | grep dht11说明驱动和设备树匹配成功cat /sys/class/dht11/dht11/tempcat /sys/class/dht11/dht11/humi阶段3字符设备驱动版本/dev/dht111. 字符设备驱动代码#include linux/module.h #include linux/init.h #include linux/platform_device.h #include linux/gpio.h #include linux/delay.h #include linux/of_gpio.h #include linux/fs.h #include linux/cdev.h #include linux/uaccess.h #include linux/preempt.h #define DHT11_COMPATIBLE dht11,humidity-temp #define DATA_GPIO_NAME data-gpios #define DEV_NAME dht11 // 时序参数复用官方规范 #define START_LOW_DELAY 18 #define START_HIGH_DELAY 30 #define BIT_JUDGE_DELAY 35 #define BIT_WAIT_TIMEOUT 200 #define POST_READ_DELAY 1000 // 全局变量 static int dht11_gpio; static dev_t dht11_devno; // 设备号主次 static struct cdev dht11_cdev; // 字符设备核心结构体 static struct class *dht11_class;// 设备类 static struct device *dht11_dev; // 设备实例 static unsigned long last_read_jiffies; static u8 dht11_temp, dht11_humi; // DHT11核心读取函数复用sysfs版本逻辑 static int dht11_read_data(void) { u8 buf[5] {0}; u8 check_sum; int i, j, timeout; unsigned long flags; if (!time_after(jiffies, last_read_jiffies msecs_to_jiffies(POST_READ_DELAY))) { return -EAGAIN; } last_read_jiffies jiffies; local_irq_save(flags); preempt_disable(); // 发送启动信号 gpio_direction_output(dht11_gpio, 0); mdelay(START_LOW_DELAY); gpio_set_value(dht11_gpio, 1); udelay(START_HIGH_DELAY); // 检测响应 gpio_direction_input(dht11_gpio); timeout 0; while (gpio_get_value(dht11_gpio) ! 0 timeout BIT_WAIT_TIMEOUT) { udelay(1); timeout; } if (timeout BIT_WAIT_TIMEOUT) { preempt_enable(); local_irq_restore(flags); return -EIO; } udelay(80); timeout 0; while (gpio_get_value(dht11_gpio) ! 1 timeout BIT_WAIT_TIMEOUT) { udelay(1); timeout; } if (timeout BIT_WAIT_TIMEOUT) { preempt_enable(); local_irq_restore(flags); return -EIO; } udelay(80); // 读取40位数据 for (i 0; i 5; i) { buf[i] 0; for (j 7; j 0; j--) { timeout 0; while (gpio_get_value(dht11_gpio) 0 timeout BIT_WAIT_TIMEOUT) { udelay(1); timeout; } if (timeout BIT_WAIT_TIMEOUT) { preempt_enable(); local_irq_restore(flags); return -EIO; } udelay(BIT_JUDGE_DELAY); if (gpio_get_value(dht11_gpio) 1) buf[i] | (1 j); timeout 0; while (gpio_get_value(dht11_gpio) 1 timeout BIT_WAIT_TIMEOUT) { udelay(1); timeout; } } } preempt_enable(); local_irq_restore(flags); // 校验 check_sum (buf[0] buf[1] buf[2] buf[3]) % 256; if (check_sum ! buf[4]) return -EINVAL; // 解析数据 dht11_humi buf[0]; dht11_temp buf[2]; return 0; } // 字符设备read接口用户态读取入口 static ssize_t dht11_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int ret; char data[32] {0}; ret dht11_read_data(); if (ret 0) return ret; // 拼接数据格式temp:21,humidity:43 snprintf(data, sizeof(data), temp:%d,humidity:%d\n, dht11_temp, dht11_humi); // 拷贝数据到用户空间 if (copy_to_user(buf, data, strlen(data))) { return -EFAULT; } return strlen(data); } // 文件操作结构体字符设备核心 static const struct file_operations dht11_fops { .owner THIS_MODULE, .read dht11_read, // 仅实现read满足只读需求 }; // probe函数字符设备初始化 static int dht11_probe(struct platform_device *pdev) { int ret; struct device_node *node pdev-dev.of_node; // 1. 获取并申请GPIO dht11_gpio of_get_named_gpio_flags(node, DATA_GPIO_NAME, 0, NULL); if (!gpio_is_valid(dht11_gpio)) return -EINVAL; ret gpio_request_one(dht11_gpio, GPIOF_OUT_INIT_HIGH, DEV_NAME); if (ret 0) return ret; // 2. 动态分配设备号避免主设备号冲突 ret alloc_chrdev_region(dht11_devno, 0, 1, DEV_NAME); if (ret 0) { gpio_free(dht11_gpio); return ret; } // 3. 初始化并注册字符设备 cdev_init(dht11_cdev, dht11_fops); dht11_cdev.owner THIS_MODULE; ret cdev_add(dht11_cdev, dht11_devno, 1); if (ret 0) { unregister_chrdev_region(dht11_devno, 1); gpio_free(dht11_gpio); return ret; } // 4. 创建类和设备节点生成/dev/dht11 dht11_class class_create(THIS_MODULE, DEV_NAME); if (IS_ERR(dht11_class)) { cdev_del(dht11_cdev); unregister_chrdev_region(dht11_devno, 1); gpio_free(dht11_gpio); return PTR_ERR(dht11_class); } dht11_dev device_create(dht11_class, NULL, dht11_devno, NULL, DEV_NAME); if (IS_ERR(dht11_dev)) { class_destroy(dht11_class); cdev_del(dht11_cdev); unregister_chrdev_region(dht11_devno, 1); gpio_free(dht11_gpio); return PTR_ERR(dht11_dev); } last_read_jiffies jiffies; dev_info(pdev-dev, DHT11 char driver init success: /dev/dht11); return 0; } // remove函数资源释放 static int dht11_remove(struct platform_device *pdev) { device_destroy(dht11_class, dht11_devno); class_destroy(dht11_class); cdev_del(dht11_cdev); unregister_chrdev_region(dht11_devno, 1); gpio_free(dht11_gpio); dev_info(pdev-dev, DHT11 char driver removed); return 0; } // 设备树匹配表 static const struct of_device_id dht11_of_match[] { {.compatible DHT11_COMPATIBLE}, { } }; MODULE_DEVICE_TABLE(of, dht11_of_match); // 平台驱动结构体 static struct platform_driver dht11_driver { .probe dht11_probe, .remove dht11_remove, .driver { .name dht11-char-driver, .of_match_table of_match_ptr(dht11_of_match), .owner THIS_MODULE, }, }; module_platform_driver(dht11_driver); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(DHT11 Char Device Driver for RK3568 (/dev/dht11)); MODULE_ALIAS(platform:dht11-char-driver);2. 字符设备驱动验证# 查看/dev/dht11节点 ls /dev/dht11 # 直接读取数据 cat /dev/dht11 # 输出temp:21,humidity:43 # 用户态C程序读取示例 gcc dht11_char_test.c -o dht11_char_test ./dht11_char_test # 输出Read from /dev/dht11: temp:21,humidity:43三、移植过程中遇到的问题与解决方案问题1驱动编译报错ISO C90 forbids mixed declarations and code现象drivers/misc/dht11_driver.c:144:5: error: ISO C90 forbids mixed declarations and code [-Werrordeclaration-after-statement] 144 | u8 check_sum (buf[0] buf[1] buf[2] buf[3]) % 256;原因Linux内核默认使用C90标准要求所有变量声明必须放在函数开头不能在代码中间声明变量。解决方案将check_sum变量声明移至函数开头赋值逻辑保留在原位置// 修复前报错 static int dht11_read_data(void) { // 代码... check_sum (buf[0] buf[1] buf[2] buf[3]) % 256; // 赋值 } // 修复后正常编译 static int dht11_read_data(void) { u8 buf[5] {0}; u8 check_sum; // 声明移至开头 int i, j; // 代码... check_sum (buf[0] buf[1] buf[2] buf[3]) % 256; // 赋值 }问题2读取温湿度返回-1数据全1、校验失败现象cat /sys/class/dht11/dht11/temp -1 dmesg日志Data check: buf0buf1buf2buf30x37c, buf40xff → 数据全1校验失败原因时序判定延迟过小22usRK3568 GPIO电平切换后需要30-40us稳定时间22us时电平仍在跳变导致所有位误判为1校验和计算未取模DHT11校验位是8位0-255前4字节之和可能超过255需取模256后再对比。解决方案调整数据位判定延迟为35usBIT_JUDGE_DELAY35精准区分0/1信号校验和计算添加%256check_sum (buf[0]buf[1]buf[2]buf[3])%256严格遵循DHT11官方时序启动信号拉低18ms、释放30us读取间隔≥1s。问题3传感器无响应No response low level现象dmesg日志ERROR: No response low level (timeout200)原因硬件接线错误VCC接5V烧毁传感器、DATA未接4.7KΩ上拉电阻、GPIO接错引脚传感器损坏误接5V导致DHT11烧毁时序参数不符合官方规范启动信号拉低时间18ms传感器未检测到起始信号。解决方案核对接线VCC3.3V、DATAGPIO3_A1、GND共地、DATA引脚串联4.7KΩ上拉电阻替换损坏的DHT11传感器调整启动信号拉低时间为18ms符合官方≥18ms要求。问题4sysfs节点路径问题/sys/class vs /dev现象用户疑问“为什么节点在/sys/class下而不是/dev/”原因与解决方案路径类型核心区别适用场景/sys/class/sysfs虚拟文件系统暴露设备属性/状态轻量只读无需分配设备号传感器、LED、GPIO等简单只读设备/dev/字符设备文件系统提供用户态与硬件的双向交互支持open/read/write/ioctl串口、LCD、按键等复杂交互设备若需/dev/dht11节点需改用字符设备驱动框架分配设备号、实现file_operations若仅需简单读取sysfs方式更简洁无需实现复杂文件操作。四、关键技术要点总结1. 时序控制核心DHT11单总线通信对时序要求极高需严格遵循官方规范时序环节官方要求驱动配置作用启动信号拉低≥18ms18ms确保传感器检测到起始信号启动信号释放20-40us30us适配传感器响应窗口数据位判定026-28us高电平170us高电平35us判定精准区分0/1信号读取间隔≥1s1s避免传感器过载符合电气特性2. Linux设备模型sysfs驱动通过class_createdevice_createdevice_attribute暴露属性无需设备号字符设备驱动通过alloc_chrdev_region分配设备号cdev_add注册字符设备device_create生成/dev节点两种方式均基于platform_driver框架通过of_device_id匹配设备树节点。3. 稳定性保障关闭中断禁止抢占local_irq_savepreempt_disable避免内核调度打断时序超时保护每个电平等待环节添加超时阈值避免驱动卡死资源释放probe失败时按“反向顺序”释放资源GPIO→类→设备避免内存泄漏。五、移植总结与选型建议1. 移植结论基于RK3568 Linux5.10内核的DHT11驱动移植成功支持sysfs和字符设备两种访问方式核心问题集中在时序适配和C90语法规范硬件接线尤其是4.7KΩ上拉电阻是基础驱动完全遵循DHT11官方时序规范稳定读取温湿度数据无丢包、校验失败问题。2. 选型建议驱动类型优点缺点适用场景sysfs驱动代码简洁、无需设备号、直接cat读取仅支持只读、无复杂交互DHT11/DS18B20等只读传感器字符设备驱动支持read/write/ioctl、用户态程序交互代码量较大、需管理设备号串口/LCD/按键等复杂设备3. 扩展建议可添加ioctl接口支持字符设备驱动的温湿度单独读取增加数据缓存机制减少频繁读取传感器导致的功耗增加适配DHT22传感器仅需调整数据解析逻辑时序参数通用。本移植攻略覆盖了从设备树配置、驱动开发、问题排查到最终验证的全流程代码均经过实际验证可稳定运行可直接作为RK3568平台DHT11驱动开发的参考模板。