目录
一 、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_register、resource定义、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号引脚)。