完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 zxl_zxl 于 2016-6-17 10:08 编辑 NanoPi M2试用体验之按键中断驱动程序+去抖详解(含源码) Nanopi M2除了价格便宜,还有就是它板载的丰富的GPIO资源。利用板载的GPIO资源可用编写轮询的驱动程序或中断驱动程序。显然中断驱动程序的效率要远远高于轮询的方式。 本篇试用报告主要介绍基于GPIO的按键中断驱动程序编写、测试,详解介绍GPIO外部中断与定时器去抖的实现方法,通过内核printk函数和外部的LED灯,以查看驱动是否正确运行。 注意GPIO区别编写采用标准的Linux GPIO资源接口函数,而不是采用自己配置寄存器,然后通过ioremap的方式进行虚拟映射,这样我们的驱动程序的可移植性就高了很多。 1. GPIO驱动概述 Linux内核已经帮我们设计好了相关GPIO驱动模型,并设计了相关的操作函数接口。主要函数介绍如下: int gpio_request(unsigned gpio, const char *tag) //申请一个空闲的GPIO int gpio_direction_input(unsigned gpio) //设置GPIO为输入 int gpio_direction_output(unsigned gpio, int value) //设置GPIO为输出或输入 void gpio_set_value(unsigned gpio, int value) //设置GPIO输出高电平或低电平 int gpio_get_value(unsigned gpio) //获取GPIO的当前状态,高电平或低电平 int gpio_to_irq(unsigned gpio) //返回的中断编号 以及request_irq()和free_irq()。以上这些函数的用法可以自己搜索查找,但是以上函数中的unsigned gpio这个参数如何与我们的GPIO引脚关联起来呢? 通过查看S5P4418源码中的plat-s5p4418目录下的内容,发现如下对应关系: 三星官方把在源码中把GPIO分为了A、B、C、D、E组,每组32个引脚,我们通过上述函数申请GPIO资源时,比如申请GPB31这个引脚为GPIO时,我们只需提交参数PAD_GPIO_B + 31就可以了。 GPIO的中断号在s5p4418_irq.h中定义,如下图所示: 就是说,我们既可以通过gpio_to_irq(PAD_GPIO_B +31)来获取GPB31引脚对应的中断号,也可以通过64+PAD_GPIO_B+31得到中断号。 有了上述知识,以及对三星S5P4418板级平台信息的了解,我们就可以根据Linux内核关于中断的驱动框架来编写程序了。 S5P4418与S3C2440的一个很大不同是,每一个GPIO都可以占用一条中断线,不存在共享中断的情况,这给我们编写中断驱动带来了一些方便,不用再为了区别共享中断而设置设备ID信息了。 2. 硬件定义 本报告中,采用GPB31作为GPIO的外部中断输入引脚,GPB30作为GPIO输出,在中断发生时通过LED的亮灭翻转,以反映中断驱动是否正常工作。硬件电路用画图板做了一个简单的电路示意图,如下图所示: 硬件连线效果如图所示: 3. 驱动代码分析 3.1 头文件 #include #include #include #include #include #include #include #include #include #include #include 红色标注的为S5P4418平台特有的头文件,必须包含 3.2 全局变量 #define LED_GPIO (PAD_GPIO_B + 30) //GPB30为LED输出控制引脚 #define KEY_GPIO (PAD_GPIO_B + 31) //GPB31为触发终端源 #define KEY_GPIO_IRQ gpio_to_irq(KEY_GPIO) //根据GPIO编号获取对应的引脚的中断编号 #define DEVICE_NAME "key_irq" static int major; static int minor; struct cdev *key_irq; /* cdev 数据结构 */ static dev_t devno; /* 设备编号 */ static struct class *key_irq_class; static int flag = 0; //屏蔽因驱动初始化中添加定时器导致的在没有按下按键,定时器计时到,出现没有中断而调用了LED点亮动作 static struct timer_list key_timer; /*消抖定时器*/ char const irq_types[5] = { IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_EDGE_BOTH, IRQ_TYPE_LEVEL_HIGH, IRQ_TYPE_LEVEL_LOW }; 3.3 中断服务函数 由于按键本身存在的抖动现象,必须滤除掉按键按下后20ms内的不稳定抖动信号,这里采用内核定时器,按键信号触发中断,并进入中断服务程序后,修改定时器的定时值,如果20ms内没有其他信号触发中断,则执行定时器定时结束后的回调函数,否则从新开始计时20ms过程,直到按键信号稳定为止。中断服务函数如下: static irqreturn_t key_irq_irq_handler(unsigned int irq, void *dev_id) { /*20ms后启动定时器*/ flag= 1; //只有flag为1时才能说明是按键中断导致的定时器函数调用 mod_timer(&key_timer,jiffies+ HZ/50); /*去抖时间设为20ms*/ returnIRQ_HANDLED; } 3.4 定时器回调函数 当中断信号确实稳定后,定时器20ms到时,才执行定时器回调函数。在本例程中,定时器回调函数主要执行打印信息,以及翻转LED的亮灭,说明驱动程序工作正常。 在试用过程中,发现一个奇怪现象,通过insmod加载驱动后,即使没有按下按键,LED自动点亮。后来发现,在驱动的初始化函数中,即使我们设置了LED的GPIO引脚为高电平,即LED灭,由于在初始化并添加内核定时器后,会自动执行一次内核定时器的回调函数,导致即使没有中断发生,也会出现加载驱动模块时的LED点亮的现象。 为了解决这一问题,我设置了一个全局的flag,只用flag为1时,才允许在定时器的回调函数中判断LED灯的翻转逻辑;并且只有在真正的中断发生时,在中断服务函数中将flag设置为1。 定时器回调函数如下: static void key_timer_function(unsignedlong data) { staticint count = 0; if(1== flag) { printk(KERN_INFO"KEY IRQ HAPPENED!n"); /*根据按键KEY按下的次数交替点亮/熄灭LED灯*/ if(count% 2 == 0) gpio_set_value(LED_GPIO,0); /*点亮LED灯*/ else gpio_set_value(LED_GPIO,1); /*熄灭LED灯*/ count++; } } 3.5 驱动初始化函数 驱动初始化函数完成GPIO资源申请,获取GPIO的中断号,将GPIO中断号与中断服务函数绑定。申请字符设备驱动主设备号,创建设备类,和设备实体,以自动在/dev目录下自动生成设备节点。 被例程,采用按键上升沿作为中断的触发条件,即按键松开后,触发中断。初始化函数如下: static int __init key_irq_init(void) { intret; /*注册定时器*/ init_timer(&key_timer); key_timer.function= key_timer_function; add_timer(&key_timer); /*申请GPIO*/ gpio_free(KEY_GPIO); /*首先释放GPIO*/ gpio_free(LED_GPIO); /*首先释放GPIO*/ ret= gpio_request_one(KEY_GPIO, GPIOF_IN, "KEY IRQ"); /* 申请 IO ,为输入*/ if(ret < 0) { printk(KERN_ERR"Failed to request GPIO for KEYn"); } ret= gpio_request_one(LED_GPIO, GPIOF_OUT_INIT_HIGH, "LED OUTPUT"); /* 申请 IO ,为输出*/ if(ret < 0) { printk(KERN_ERR"Failed to request GPIO for LEDn"); } gpio_direction_input(KEY_GPIO);/* 设置 GPIO 为输入 */ gpio_direction_output(LED_GPIO,0);/* 设置 GPIO 为输出,参数0代表输出*/ gpio_set_value(LED_GPIO,1); /*初始化LED为熄灭状态*/ /*申请GPB31对应的中断*/ if(request_irq(KEY_GPIO_IRQ, key_irq_irq_handler, IRQF_TRIGGER_RISING, "key_irqirq", NULL) ) {/* 申请中断 */ printk(KERN_WARNINGDEVICE_NAME": Can't get IRQ: %d!n", KEY_GPIO_IRQ); } irq_set_irq_type(KEY_GPIO_IRQ,irq_types[0]); /*按键上升沿出发中断*/ disable_irq(KEY_GPIO_IRQ); enable_irq(KEY_GPIO_IRQ); ret= alloc_chrdev_region(&devno, minor, 1, DEVICE_NAME); /* 从系统获取主设备号 */ major= MAJOR(devno); if(ret < 0) { printk(KERN_ERR"cannot get major %d n", major); return-1; } key_irq= cdev_alloc(); /* 分配 key_irq 结构 */ if(key_irq != NULL) { cdev_init(key_irq,&key_irq_fops); /* 初始化 key_irq 结构 */ key_irq->owner= THIS_MODULE; if(cdev_add(key_irq, devno, 1) != 0) { /* 增加 key_irq 到系统中 */ printk(KERN_ERR"add cdev error!n"); gotoerror; } } else{ printk(KERN_ERR"cdev_alloc error!n"); return-1; } key_irq_class= class_create(THIS_MODULE, "key_irq_class"); if(IS_ERR(key_irq_class)) { printk(KERN_INFO"create class errorn"); return-1; } device_create(key_irq_class,NULL, devno, NULL, DEVICE_NAME); printk("Initcompleted!n"); return0; error: unregister_chrdev_region(devno,1); /* 释放已经获得的设备号 */ returnret; } 3.6 驱动退出函数 在驱动退出函数中,释放申请到的GPIO资源,释放GPIO对应的中断号,释放系统自动分配的字符设备驱动主设备号,删除设备类和设备实体,删除去抖定时器函数等。 函数代码如下: static void __exit key_irq_exit(void) { gpio_set_value(LED_GPIO,1); /*退出时设置LED为熄灭状态*/ gpio_free(KEY_GPIO); /*释放为按键KEY申请的GPIO资源*/ gpio_free(LED_GPIO); /*释放为LED申请的GPIO资源*/ disable_irq(KEY_GPIO_IRQ); free_irq(KEY_GPIO_IRQ,NULL); cdev_del(key_irq);/* 移除字符设备 */ unregister_chrdev_region(devno,1); /* 释放设备号 */ device_destroy(key_irq_class,devno); class_destroy(key_irq_class); del_timer(&key_timer); /*删除定时器*/ printk(KERN_INFO"Exit completed!n"); } 3.6 驱动Makefile文件 为驱动的编译编写Makefile文件,如下: KERN_DIR = /home/vmuser/nanopisource/linux-3.4.y all: make-C $(KERN_DIR) M=`pwd` modules clean: make-C $(KERN_DIR) M=`pwd` modules clean rm-rf modules.order obj-m += keyinterrupt.o 注意,在Makefile文件中,上面红色标识的内容一定要修改成你自己的源码路径和驱动C文件名称。 3.7 编译驱动 在驱动C源码目录下,执行make命令,生成驱动程序模块keyinterrupt.ko文件。 执行命令: cp ./keyinterrupt.ko /nfsshare 将驱动模块拷贝到NFS共享目录,以备Nanopi M2挂载调用。 4. 驱动测试 1)挂载NFS系统 在NanoPi M2上执行命令: sudo mount -t nfs192.168.1.xxx:/nfsshare /mnt -o nolock cd /mnt 2)加载驱动模块 执行命令: sudo insmod keyinterrupt.ko 如图示: 我们在驱动的初始化函数中添加了一句打印命令: printk("Init completed!n"); 如果驱动加载成功,会打印这条信息。 我们通过命令查看一下是否有Init completed!这条信息,执行命令: dmesg | tail 如图所示: 发现确实打印了上述信息,说明驱动加载成功。 3)按下按键触发中断 我们按下面包板上的按键,然后,看看LED灯是否点亮,如图所示: 反复按下按键,LED能够交替翻转。然后我们再来看看中断服务程序打印的信息,输入命令:dmesg | tail 显示有KEY IRQ HAPPENED!这条信息输出,说明我们的按键中断驱动程序能够正常工作。 4)卸载驱动模块 执行命令:rmmod keyinterrupt.ko 命令行输出如下: 输出Exit completed!信息,说明驱动程序的卸载函数也成功的得到了执行。 5. 小结 本例程只是简单的实现了基于中断的按键驱动程序,还没有实现异步功能,在中断服务程序中想用户态的程序发送SIG,然后让用户态的驱动程序在信号处理函数中读取GPIO引脚状态。实现异步还是比较简单,参考一下程序的异步模型,然后在本例程的基础稍加修改即可。由于其他项目的关系,本菜鸟就不打算继续深入研究了,本文是我的第一个Linux下的驱动程序编写,拿出来与大家交流一下。
|
|
相关推荐
10 个讨论
|
|
gpio_to_irq(KEY_GPIO) 一直返回-22,不知道什么原因。
看了下linux/gpio.h里的代码: static inline int gpio_to_irq(unsigned gpio) { /* GPIO can never have been requested or set as input */ WARN_ON(1); return -EINVAL; } 直接返回了,是什么原因呢?麻烦大神解答下~~~ |
|
|
|
|
|
此文不错啊
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
370个成员聚集在这个小组
加入小组NanoPi m3适合刷什么系统,刚接触玩,我刷了一个比较卡
5450 浏览 1 评论
7180 浏览 1 评论
4777 浏览 1 评论
【NanoPC-T4试用体验】4、手把手教你从单片机移植驱动到ARM Linux上
7744 浏览 1 评论
【NanoPC-T4试用体验】NanoPC-T4控制步进电机
24595 浏览 1 评论
NanoPi m3适合刷什么系统,刚接触玩,我刷了一个比较卡
5450浏览 1评论
443浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-30 05:42 , Processed in 0.733461 second(s), Total 59, Slave 51 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号