ARM嵌入式学习(二十一)--- Platform总线结合dts、gpio子系统、中断和错误处理、dht11传感器实现
2026/4/15 12:08:18 网站建设 项目流程

目录

一 、Platform总线结合dts

与之前的区别:

1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配

2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)

二、gpio子系统

函数解释:

设备树:

1.这里面是引脚配置和电气属性

2.表示上电后的默认电平

三、中断

函数解释:

设备树:

睡眠与唤醒机制:

四、错误处理

五、总结与补充

遇到的问题:

(1)模块加载不进去,返回错误码-16(busy)

(2)模块加载不进去,返回错误码-2

(3)为什么按键不用of_get_named_gpio显式获取 GPIO

补充:

(1)Vim替换字符串:

(2)IRQF_ONESHOT中断宏:

六、扩展练习:dht11传感器实现

代码为:

遇到的问题:

问题1:

问题2:

问题3:


一 、Platform总线结合dts

在上一篇文章中,我们使用的是仅 Platform 总线和仅dts的方法。

现在我们使用如今最新的方法:Platform总线结合dts

  • 传统方式(仅 Platform ):每支持一块新板子,就要在arch/arm/mach-xxx/下新增一个 C 文件(或少则几百行,多则上千行),里面充满platform_device_registerresource定义、gpio数组等。这些代码大多重复且难以阅读。

  • DTS 方式(Platform总线结合dts:板级描述变成几百行结构化的文本,不再需要在内核源码里为每块板子增加 C 文件。主流架构的mach-xxx目录如今只剩下少量核心代码,大量板级文件已被删除。

结果:内核更干净,维护者更容易审查硬件描述。

设备树这种方式添加到总线的好处在于,它会匹配上了再寻找设备节点,少做很多无用功

匹配的是compatible变量,跟名字pt_led和name1没关系。

代码为:

#include <linux/init.h> #include <linux/printk.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/export.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/io.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/of.h> #define DEV_NAME "led" static volatile unsigned int * sw_mux; static volatile unsigned int * sw_pad; static volatile unsigned int * gpio1_dr; static volatile unsigned int * gpio1_gdir; static void led_init(void) { *sw_mux = 0x05; *sw_pad = 0x10b0; *gpio1_gdir |= (1 << 3); *gpio1_dr |= (1 << 3); } static void led_on(void) { *gpio1_dr &= ~(1 << 3); } static void led_off(void) { *gpio1_dr |= (1 << 3); } static int open(struct inode * node, struct file * file) { led_init(); printk("led open...\n"); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk("led read...\n"); return 0; } static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset) { // "ledon" on "ledoff" off unsigned char data[10] = {0}; size_t len_cp = len < sizeof(data) ? len : sizeof data; int size_cp = copy_from_user(data, buf, len_cp); if(size_cp < 0) return size_cp; if(!strcmp(buf, "ledon")) led_on(); else if(!(strcmp(buf, "ledoff"))) led_off(); else return -EINVAL; printk("led write...\n"); return size_cp; } static int close(struct inode * node, struct file * file) { led_off(); printk("led close...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .write = write, .release = close }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops }; static const struct of_device_id match_table[] = { [0] = {.compatible = "pt-led"} }; static struct platform_driver drv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME .of_match_table = match_table } }; static int __init led1_init(void) { int ret = platform_driver_register(&drv); if(ret < 0) goto err_reg; printk("platform_driver_register ...\n"); return 0; err_reg: platform_driver_unregister(&drv); printk("platform_driver_register failed\n"); return ret; } static int probe(struct platform_device * pdev) { struct device_node * pnode; const char * pcom; const char * pname1; u32 led_array[8] = {0}; int ret = misc_register(&misc); if(ret < 0) goto err_misc; pnode = of_find_node_by_path("/pt_led"); if(pnode == NULL) { printk("of_find_node_by_path err\n"); return -1; } of_property_read_string(pnode, "compatible", &pcom); of_property_read_string(pnode, "name1", &pname1); printk("led compatible = %s name1 = %s\n", pcom, pname1); of_property_read_u32_array(pnode, "reg", led_array, sizeof(led_array) / sizeof(led_array[0])); sw_mux = ioremap(led_array[0], led_array[1]); sw_pad = ioremap(led_array[2], led_array[3]); gpio1_gdir = ioremap(led_array[4], led_array[5]); gpio1_dr = ioremap(led_array[6], led_array[7]); printk("led_init ##############\n"); return 0; err_misc: misc_deregister(&misc); printk("led_init failed ret = %d\n", ret); return ret; } static void __exit led1_exit(void) { iounmap(gpio1_gdir); iounmap(gpio1_dr); iounmap(sw_pad); iounmap(sw_mux); misc_deregister(&misc); printk("led_exit ##############\n"); } module_init(led1_init); module_exit(led1_exit); MODULE_LICENSE("GPL");

与之前的区别:

1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配

2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)

二、gpio子系统

Linux 内核提供了GPIO 子系统,用于统一管理各个 SoC 的 GPIO 控制器,并为驱动开发者提供标准 API。

这样我就不需要使用 ioremap函数转换地址了,只需要调用 GPIO 子系统提供的标准 API 即可。GPIO 控制器驱动内部已经做了 ioremap。

需要用到这两个头文件

#include <linux/of_gpio.h>
#include <linux/gpio.h>

将上面的代码改写为:

#include <linux/init.h> #include <linux/printk.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/export.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/io.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #define DEV_NAME "led" static int led_gpio; #define LED_ON 0 #define LED_OFF 1 static void led_init(void) { gpio_direction_output(led_gpio, LED_OFF); } static void led_on(void) { gpio_set_value(led_gpio, LED_ON); } static void led_off(void) { gpio_set_value(led_gpio, LED_OFF); } static int open(struct inode * node, struct file * file) { led_init(); printk("led open...\n"); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk("led read...\n"); return 0; } static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset) { // "ledon" on "ledoff" off unsigned char data[10] = {0}; size_t len_cp = len < sizeof(data) ? len : sizeof data; int size_cp = copy_from_user(data, buf, len_cp); if(size_cp < 0) return size_cp; if(!strcmp(buf, "ledon")) led_on(); else if(!(strcmp(buf, "ledoff"))) led_off(); else return -EINVAL; printk("led write...\n"); return size_cp; } static int close(struct inode * node, struct file * file) { led_off(); printk("led close...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .write = write, .release = close }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret = misc_register(&misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode = of_find_node_by_path("/pt_gpioled"); if(IS_ERR(pnode)) { ret = PTR_ERR(pnode); goto err_find_node; } led_gpio = of_get_named_gpio(pnode, "led-gpio", 0); gpio_request(led_gpio, "red_led"); gpio_direction_output(led_gpio, LED_OFF); printk("probe led misc_register ##############\n"); return 0; err_find_node: printk("of_find_node_by_path err\n"); err_misc: printk("led probe failed ret = %d\n", ret); misc_deregister(&misc); return ret; } static int remove(struct platform_device * pdev) { gpio_free(led_gpio); misc_deregister(&misc); printk("remove led misc_deregister ##############\n"); return 0; } static const struct of_device_id match_table[] = { [0] = {.compatible = "pt-gpioled"} }; static struct platform_driver drv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME, .of_match_table = match_table } }; static int __init led1_init(void) { int ret = platform_driver_register(&drv); if(ret < 0) goto err_reg; printk("platform_driver_register ...\n"); return 0; err_reg: printk("platform_driver_register failed ret = %d\n", ret); platform_driver_unregister(&drv); return ret; } static void __exit led1_exit(void) { platform_driver_unregister(&drv); printk("platform_driver_unregister ...\n"); } module_init(led1_init); module_exit(led1_exit); MODULE_LICENSE("GPL");

函数解释:

(1)led_gpio = of_get_named_gpio(pnode, "led-gpio", 0);//查找设备树的led信息获取gpio编号:

  • np:设备树节点指针

  • propname:属性名,如"led-gpio"这个必须和设备树的变量名一致

  • index(可以理解为数组下标:索引(因为属性可能包含多个 GPIO,如gpios = <&gpio1 3 0>, <&gpio1 4 0>;

  • 返回值:GPIO 编号(整型),负数表示错误。

(2)gpio_request(led_gpio, "red_led"); //获取gpio,后面的是标签随意写

(3)gpio_direction_output(led_gpio, LED_OFF);//设置为输出模式,后面是输出1(宏)

(4)gpio_direction_output(led_gpio)//设置为输入模式

(5)gpio_free(led_gpio);//释放gpio

设备树:

1.这里面是引脚配置和电气属性

2.表示上电后的默认电平

三、中断

通过led的学习我们同样可以写一个按键的驱动代码:

#include <linux/init.h> #include <linux/printk.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/export.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/io.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/irqreturn.h> #define DEV_NAME "key" static int key_gpio; static unsigned int irq_key_num; #define key_ON 0 #define key_OFF 1 static int arg = 100; static irqreturn_t key_irq_handler(int dev_num, void * dev) { printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev); return IRQ_HANDLED; } static int open(struct inode * node, struct file * file) { printk("key open...\n"); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk("key read...\n"); return 0; } static int close(struct inode * node, struct file * file) { printk("key close...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .release = close }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret = misc_register(&misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode = of_find_node_by_path("/pt_key"); if(IS_ERR(pnode)) { ret = PTR_ERR(pnode); goto err_find_node; } key_gpio = of_get_named_gpio(pnode, "key-gpio", 0); if(key_gpio < 0) { ret = key_gpio; goto err_get_gpio; } ret = gpio_request(key_gpio, "pt_key"); if(ret < 0) goto err_gpio_request; irq_key_num = gpio_to_irq(key_gpio); if(irq_key_num < 0) { ret = irq_key_num; goto err_gpio_to_irq; } ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg); if(ret < 0) goto err_request_irq; printk("probe key misc_register ##############\n"); return 0; err_request_irq: disable_irq(irq_key_num); free_irq(irq_key_num, &arg); printk("key err_request_irq\n"); err_gpio_to_irq: printk("key err_gpio_to_irq\n"); err_gpio_request: printk("key err_gpio_request\n"); err_get_gpio: printk("key err_get_gpio\n"); err_find_node: printk("of_find_node_by_path err\n"); err_misc: printk("key probe faikey ret = %d\n", ret); misc_deregister(&misc); return ret; } static int remove(struct platform_device * pdev) { disable_irq(irq_key_num); free_irq(irq_key_num, &arg); gpio_free(key_gpio); misc_deregister(&misc); printk("remove key misc_deregister ##############\n"); return 0; } static const struct of_device_id match_table[] = { [0] = {.compatible = "pt-key"} }; static struct platform_driver drv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME, .of_match_table = match_table } }; static int __init key1_init(void) { int ret = platform_driver_register(&drv); if(ret < 0) goto err_reg; printk("platform_driver_register ...\n"); return 0; err_reg: printk("platform_driver_register faikey ret = %d\n", ret); platform_driver_unregister(&drv); return ret; } static void __exit key1_exit(void) { platform_driver_unregister(&drv); printk("platform_driver_unregister ...\n"); } module_init(key1_init); module_exit(key1_exit); MODULE_LICENSE("GPL");

函数解释:

(1)static irqreturn_t key_irq_handler(int dev_num, void * dev)中断触发后的句柄,后面的void*dev是中断请求传的参数

(2)irq_key_num = gpio_to_irq(key_gpio);//获取中断号

(3)ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);

参数1:中断号

参数2:中断句柄

参数3:触发方式

参数4:中断名

参数5:传入的参数,代码这里写的全局变量static int arg = 100;

(4)结束中断

disable_irq(irq_key_num); //关闭中断
free_irq(irq_key_num, &arg);//释放中断

设备树:

在我们的dts中添加这两个信息

睡眠与唤醒机制:

我们可以在原来的代码里面使用标志位然后用while来循环判断标志位来实现各种功能,但这种方法非常浪费资源,CPU一直空转,这里我们加入一个wait_queue_head_t,它是 Linux 内核中等待队列头的类型,定义在<linux/wait.h>中。

相关函数:

init_waitqueue_head(&wq); //初始化等待队列

wait_event_interruptible(wq, condition); //睡眠,condition为我们的标志位,为0则一直休眠,为1则唤醒

wake_up_interruptible(&wq); //唤醒,判断标志位是否为1,不为1继续休眠

加入机制后的中断代码为:

#include <linux/init.h> #include <linux/printk.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/export.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/io.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/irqreturn.h> #include <linux/of_irq.h> #include <linux/wait.h> #include <linux/sched.h> #define DEV_NAME "key" static unsigned int irq_key_num; static int arg = 100; static wait_queue_head_t wq; static int condition; static irqreturn_t key_irq_handler(int dev_num, void * dev) { condition = 1; wake_up_interruptible(&wq); printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev); return IRQ_HANDLED; } static int open(struct inode * node, struct file * file) { printk("key open...\n"); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { condition = 0; wait_event_interruptible(wq, condition); //copy_to_user(); printk("key read...\n"); return 0; } static int close(struct inode * node, struct file * file) { printk("key close...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .release = close }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret = misc_register(&misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode = of_find_node_by_path("/pt_key"); if(IS_ERR(pnode)) { ret = PTR_ERR(pnode); goto err_find_node; } irq_key_num = irq_of_parse_and_map(pnode, 0); if(irq_key_num < 0) { ret = irq_key_num; goto err_of_parse; } ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg); if(ret < 0) goto err_request_irq; init_waitqueue_head(&wq); printk("probe key misc_register ##############\n"); return 0; err_request_irq: disable_irq(irq_key_num); free_irq(irq_key_num, &arg); printk("key err_request_irq\n"); err_of_parse: printk("key err_of_parse\n"); err_find_node: printk("of_find_node_by_path err\n"); err_misc: printk("key probe faikey ret = %d\n", ret); misc_deregister(&misc); return ret; } static int remove(struct platform_device * pdev) { disable_irq(irq_key_num); free_irq(irq_key_num, &arg); misc_deregister(&misc); printk("remove key misc_deregister ##############\n"); return 0; } static const struct of_device_id match_table[] = { [0] = {.compatible = "pt-key"} }; static struct platform_driver drv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME, .of_match_table = match_table } }; static int __init key1_init(void) { int ret = platform_driver_register(&drv); if(ret < 0) goto err_reg; printk("platform_driver_register ...\n"); return 0; err_reg: printk("platform_driver_register faikey ret = %d\n", ret); platform_driver_unregister(&drv); return ret; } static void __exit key1_exit(void) { platform_driver_unregister(&drv); printk("platform_driver_unregister ...\n"); } module_init(key1_init); module_exit(key1_exit); MODULE_LICENSE("GPL");

这样在我们应用层read的时候函数就会阻塞(休眠),当我们中断后就会继续执行了。

四、错误处理

内核里面的出错必须处理,不处理系统会崩

某些函数的返回值是指针,这种怎么判断它的错误码?

这里需要用到我们的错误处理函数:

宏/函数输入类型输出/判断适用场景
IS_ERR_VALUE(ret)unsigned long是否为错误值(来自负数错误码)整数返回值函数
IS_ERR(pnode)void *是否为错误指针指针返回值函数
PTR_ERR(pnode)void *错误码(负数)从错误指针提取错误码

示例:

五、总结与补充

遇到的问题:

(1)模块加载不进去,返回错误码-16(busy)

解决办法:把默认设备树中的key和led相关的全部删掉,使用我们自己写的即可

(2)模块加载不进去,返回错误码-2

解决办法:检查driver是否与设备节点匹配(compatible、of_find_node_by_path和of_get_named_gpio)

(3)为什么按键不用of_get_named_gpio显式获取 GPIO

我们的按键中断代码只关心中断事件,并不需要直接读取按键的 GPIO 电平(比如做消抖、轮询)。

补充:

(1)Vim替换字符串:

(2)IRQF_ONESHOT中断宏:

数据过来的时候会触发中断,在没有把数据读空之前,再有数据来就不触发中断

六、扩展练习:dht11传感器实现

现在可以自己实现一个驱动程序了,这里用dht11传感器举例

步骤如下:

代码为:

#include <linux/init.h> #include <linux/printk.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/export.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/io.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <asm/ioctl.h> #include <linux/delay.h> #define DEV_NAME "dht11" static int dht11_gpio; #define DHT11_PIN_HIGH 1 #define DHT11_PIN_DOWN 0 // 主机发送起始信号 static void master_start(void) { gpio_set_value(dht11_gpio, 0); msleep(20); gpio_set_value(dht11_gpio, 1); udelay(30); } static int dht11_response(void) { int time = 0; gpio_direction_input(dht11_gpio); // 等待低电平到来 while (gpio_get_value(dht11_gpio) && time < 4) { udelay(30); time++; } if (time >= 4) { printk("wait low_level failed,timeout!\n"); return -1; } // 等待高电平到来 time = 0; while(!gpio_get_value(dht11_gpio) && time < 4) { udelay(30); time++; } if (time >= 4) { printk("wait high_level failed,timeout!\n"); return -2; } // 等待低电平到来 while (gpio_get_value(dht11_gpio)); return 1; } static int open(struct inode * node, struct file * file) { gpio_direction_output(dht11_gpio, 1); msleep(5); printk("dht11 open...\n"); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { int i = 0; int j = 0; unsigned char dht_dat[5] = {0}; // 湿度整数 + 湿度小数 + 温度整数 + 温度小数 + 校验和 unsigned char checksum = 0; master_start(); // 主机发送起始信号 if (dht11_response() != 1) // 等待dht11回复响应信号 { printk("dht11 no response!\n"); return -3; } // 接收dht11 40位数据 for (j = 0; j < 5; j++) { for (i = 0; i < 8; i++) { // 等待高电平到来 while (!gpio_get_value(dht11_gpio)); // 延时60us udelay(60); // 代表dht11发送了一个'1' if (gpio_get_value(dht11_gpio)) { dht_dat[j] |= (1 << (7 - i)); } // 等待低电平到来,接收下一个bit while (gpio_get_value(dht11_gpio)); } } // 判断校验是否通过 checksum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]; if (checksum != dht_dat[4]) { printk("dht11 check failed!\r\n"); return -4; } //copy_to_user(); /*unsigned char dht_dat[5] = 0; 湿度整数 + 湿度小数 + 温度整数 + 温度小数 + 校验和*/ if (copy_to_user(buf,dht_dat,sizeof dht_dat)) return -EFAULT; gpio_direction_output(dht11_gpio, 1); printk("dht11 read...\n"); return sizeof(dht_dat); } static int close(struct inode * node, struct file * file) { printk("dht11 close...\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .release = close }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret = misc_register(&misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode = of_find_node_by_path("/dht11"); if(IS_ERR(pnode)) { ret = PTR_ERR(pnode); goto err_find_node; } dht11_gpio = of_get_named_gpio(pnode, "dht11-gpio", 0); gpio_request(dht11_gpio, "dht11"); printk("probe dht11 misc_register ##############\n"); return 0; err_find_node: printk("of_find_node_by_path err\n"); err_misc: printk("dht11 probe failed ret = %d\n", ret); misc_deregister(&misc); return ret; } static int remove(struct platform_device * pdev) { gpio_free(dht11_gpio); misc_deregister(&misc); printk("remove dht11 misc_deregister ##############\n"); return 0; } static const struct of_device_id match_table[] = { [0] = {.compatible = "dht11"} }; static struct platform_driver drv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME, .of_match_table = match_table } }; static int __init dht11_init(void) { int ret = platform_driver_register(&drv); if(ret < 0) goto err_reg; printk("platform_driver_register ...\n"); return 0; err_reg: printk("platform_driver_register failed ret = %d\n", ret); platform_driver_unregister(&drv); return ret; } static void __exit dht11_exit(void) { platform_driver_unregister(&drv); printk("platform_driver_unregister ...\n"); } module_init(dht11_init); module_exit(dht11_exit); MODULE_LICENSE("GPL");

遇到的问题:

问题1:

我在主机发送信号的时候,直接把引脚拉低,没有延迟,引脚之前的电平并不清楚是否为高

解决办法:在主机发送信号的时候,必须在open函数先置高引脚(最好延迟 5ms),然后每次读完数据都要把引脚拉高置为输出模式

问题2:

第一次数据能读,后续报错check failed

解决办法:全局变量的数组在重新读数据的时候没有清零,导致后面校验不成功,清零就行

问题3:

设备树的引脚配置不一致,引脚控制里面写的gpio1_io04 ,然后在gpio变量中我写的是&gpio1 2(2号引脚)。

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

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

立即咨询