IMX257实现GPIO-IRQ中断按键驱动程序
2015-02-17 李海沿
昨天我们已经实现了中断查询的方式实现GPIO按键驱动程序,但是,有一个缺点就是,当我们把应用程序放在后台执行时,即便没有按键,应用程序while循环中的read函数也不断的在运行,严重的导致了CPU资源的浪费。
本文中,我们在前面按键查询驱动程序的基础上来修改。
大概介绍一下设计思路吧:
和前面的差不多,当我们加载驱动时,首先在init函数中,对GPIO功能进行模式设置,都设置为GPIO模式,然后申请GPIO引脚的内存,
接着,当我们应用程序使用ioctl函数的gpio_input命令时,将所有的GPIO引脚设置为输入,并且22K上拉模式,当应用程序使用read函数读取时,如果按键没有按下,则会在read函数中睡眠(可被中断唤醒),交出CPU的使用权,当按键按下时,首先会在中断程序中进行唤醒操作,在read函数中接着运行,读取GPIO引脚的电平,并且将数据从内核空间传递到用户空间中,并且打印出来。
最后,当我们不要用驱动程序时,rsmod会调用exit函数,释放所有我们已经申请的GPIO资源。
好了,大概的思路就是这样,和前面的查询驱动程序,差不多,只不过就加了一个中断,和睡眠而已。
附上驱动程序代码:
1 /****************************** 2 linux key_query 3 *****************************/ 4 #include <linux/module.h> 5 #include <linux/init.h> 6 #include <linux/kernel.h> 7 #include <linux/delay.h> 8 #include <linux/types.h> 9 #include <linux/ioctl.h> 10 #include <linux/gpio.h> 11 #include <linux/fs.h> 12 #include <linux/device.h> 13 #include <linux/uaccess.h> 14 #include <linux/irq.h> 15 #include <linux/wait.h> 16 #include <linux/sched.h>//error: 'TASK_INTERRUPTIBLE' undeclared 17 #include <linux/interrupt.h> 18 19 #include "mx257_gpio.h" 20 #include "mx25_pins.h" 21 #include "iomux.h" 22 23 #define Driver_NAME "key_interrupt" 24 #define DEVICE_NAME "key_interrupt" 25 26 #define GPIO2_21 MX25_PIN_CLKO 27 #define GPIO3_15 MX25_PIN_EXT_ARMCLK 28 #define GPIO2_10 MX25_PIN_A24 29 #define GPIO2_11 MX25_PIN_A25 30 #define GPIO2_8 MX25_PIN_A22 31 #define GPIO2_9 MX25_PIN_A23 32 #define GPIO2_6 MX25_PIN_A20 33 #define GPIO2_7 MX25_PIN_A21 34 //command 35 #define key_input 0 36 #define version 1 37 38 //interrupt head 39 static DECLARE_WAIT_QUEUE_HEAD(key_interrupt_wait); 40 static volatile unsigned char ev_press; 41 42 static int major=0; 43 44 //auto to create device node 45 static struct class *drv_class = NULL; 46 static struct class_device *drv_class_dev = NULL; 47 48 49 /* 应用程序对设备文件/dev/key_query执行open(...)时, 50 * 就会调用key_open函数*/ 51 static int key_open(struct inode *inode, struct file *file) 52 { 53 printk("<0>function open!\n\n"); 54 55 return 0; 56 } 57 58 /* 中断程序key_irq */ 59 static irqreturn_t key_irq(int irq, void *dev_id) 60 { 61 //发生了中断 62 printk("<0>function interrupt key_irq!\n\n"); 63 ev_press = 1; 64 wake_up_interruptible(&key_interrupt_wait); 65 66 return IRQ_RETVAL(IRQ_HANDLED); 67 } 68 69 70 static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) 71 { 72 int ret; 73 //nt cnt=0; 74 unsigned char key_vals[8]; 75 76 //如果按键没有按下,没有中断,休眠 77 wait_event_interruptible(key_interrupt_wait,ev_press); 78 79 // reading the pins value 80 key_vals[0] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_6)) ? 1 : 0; 81 key_vals[1] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_7)) ? 1 : 0; 82 key_vals[2] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_8)) ? 1 : 0; 83 key_vals[3] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_9)) ? 1 : 0; 84 key_vals[4] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_10)) ? 1 : 0; 85 key_vals[5] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_11)) ? 1 : 0; 86 key_vals[6] = gpio_get_value(IOMUX_TO_GPIO(GPIO2_21)) ? 1 : 0; 87 key_vals[7] = gpio_get_value(IOMUX_TO_GPIO(GPIO3_15)) ? 1 : 0; 88 89 ret = copy_to_user(buff,key_vals,sizeof(key_vals)); 90 if(ret){ 91 ; 92 } 93 ev_press = 0; 94 //printk("<0>%04d key pressed: %d %d %d %d %d %d %d %d\n",cnt++,key_vals[0],key_vals[1],key_vals[2],key_vals[3],key_vals[4],key_vals[5],key_vals[6],key_vals[7]); 95 96 return sizeof(key_vals); 97 } 98 99 static ssize_t key_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 100 { 101 printk("<0>function write!\n\n"); 102 103 return 1; 104 } 105 106 static int key_release(struct inode *inode, struct file *filp) 107 { 108 printk("<0>function release!\n\n"); 109 //释放中断 110 free_irq(IOMUX_TO_IRQ(GPIO2_21), (void *)1); 111 //free_irq(IOMUX_TO_IRQ(GPIO3_15), (void *)1); 112 free_irq(IOMUX_TO_IRQ(GPIO2_11), (void *)1); 113 free_irq(IOMUX_TO_IRQ(GPIO2_10), (void *)1); 114 free_irq(IOMUX_TO_IRQ(GPIO2_9), (void *)1); 115 //free_irq(IOMUX_TO_IRQ(GPIO2_8), (void *)1); 116 free_irq(IOMUX_TO_IRQ(GPIO2_7), (void *)1); 117 free_irq(IOMUX_TO_IRQ(GPIO2_6), (void *)1); 118 return 0; 119 } 120 121 static int key_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg) 122 { 123 int ret; 124 printk("<0>function ioctl!\n\n"); 125 switch (command) { 126 case key_input: 127 //设置所有的引脚为输入 128 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_21)); 129 gpio_direction_input(IOMUX_TO_GPIO(GPIO3_15)); 130 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_10)); 131 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_11)); 132 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_8)); 133 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_9)); 134 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_6)); 135 gpio_direction_input(IOMUX_TO_GPIO(GPIO2_7)); 136 printk("<0>have setting all pins to gpio input mod !\n"); 137 //设置GPIO引脚为上拉模式 138 mxc_iomux_set_pad(GPIO2_6, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 139 mxc_iomux_set_pad(GPIO2_7, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 140 //mxc_iomux_set_pad(GPIO2_8, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 141 mxc_iomux_set_pad(GPIO2_9, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 142 mxc_iomux_set_pad(GPIO2_10, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 143 mxc_iomux_set_pad(GPIO2_11, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 144 mxc_iomux_set_pad(GPIO2_21, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 145 //mxc_iomux_set_pad(GPIO3_15, PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_22K_PU); 146 147 //设置GPIO引脚中断 ,下降沿触发 148 request_irq(IOMUX_TO_IRQ(GPIO2_7), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_7", (void *)1); 149 request_irq(IOMUX_TO_IRQ(GPIO2_6), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_6", (void *)1); 150 request_irq(IOMUX_TO_IRQ(GPIO2_9), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_9", (void *)1); 151 request_irq(IOMUX_TO_IRQ(GPIO2_10), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_10", (void *)1); 152 request_irq(IOMUX_TO_IRQ(GPIO2_11), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_11", (void *)1); 153 request_irq(IOMUX_TO_IRQ(GPIO2_21), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_21", (void *)1); 154 //request_irq(IOMUX_TO_IRQ(GPIO3_15), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO3_15", (void *)1); 155 //request_irq(IOMUX_TO_IRQ(GPIO2_8), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_8", (void *)1); 156 printk("<0>have setting all pins to gpio interrupt mod by IRQF_TRIGGER_FALLING !\n"); 157 158 break; 159 case version: 160 printk("<0>hello,the version is 0.1.0\n\n"); 161 break; 162 default: 163 printk("<0>command error \n"); 164 printk("<0>ioctl(fd, (unsigned int)command, (unsigned long) arg;\n"); 165 printk("<0>command: <key_input> <version>\n\n"); 166 return -1; 167 } 168 return 0; 169 } 170 171 /* 这个结构是字符设备驱动程序的核心 172 * 当应用程序操作设备文件时所调用的open、read、write等函数, 173 * 最终会调用这个结构中指定的对应函数 174 */ 175 static struct file_operations key_fops = { 176 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 177 .open = key_open, 178 .read = key_read, 179 .write = key_write, 180 .release= key_release, 181 .ioctl = key_ioctl, 182 }; 183 184 /* 185 * 执行insmod命令时就会调用这个函数 186 */ 187 static int __init key_irq_init(void) 188 { 189 printk("<0>\nHello,this is %s module!\n\n",Driver_NAME); 190 //register and mknod 191 major = register_chrdev(0,Driver_NAME,&key_fops); 192 drv_class = class_create(THIS_MODULE,Driver_NAME); 193 drv_class_dev = device_create(drv_class,NULL,MKDEV(major,0),NULL,DEVICE_NAME); /*/dev/key_query*/ 194 195 //set all pins to GPIO mod ALF5 196 mxc_request_iomux(GPIO2_21, MUX_CONFIG_ALT5); 197 mxc_request_iomux(GPIO3_15, MUX_CONFIG_ALT5); 198 mxc_request_iomux(GPIO2_10, MUX_CONFIG_ALT5); 199 mxc_request_iomux(GPIO2_11, MUX_CONFIG_ALT5); 200 mxc_request_iomux(GPIO2_8, MUX_CONFIG_ALT5); 201 mxc_request_iomux(GPIO2_9, MUX_CONFIG_ALT5); 202 mxc_request_iomux(GPIO2_6, MUX_CONFIG_ALT5); 203 mxc_request_iomux(GPIO2_7, MUX_CONFIG_ALT5); 204 //request IOMUX GPIO 205 gpio_request(IOMUX_TO_GPIO(GPIO2_21), "GPIO2_21"); 206 gpio_request(IOMUX_TO_GPIO(GPIO3_15), "GPIO3_15"); 207 gpio_request(IOMUX_TO_GPIO(GPIO2_10), "GPIO2_10"); 208 gpio_request(IOMUX_TO_GPIO(GPIO2_11), "GPIO2_11"); 209 gpio_request(IOMUX_TO_GPIO(GPIO2_8), "GPIO2_8"); 210 gpio_request(IOMUX_TO_GPIO(GPIO2_9), "GPIO2_9"); 211 gpio_request(IOMUX_TO_GPIO(GPIO2_6), "GPIO2_6"); 212 gpio_request(IOMUX_TO_GPIO(GPIO2_7), "GPIO2_7"); 213 214 215 return 0; 216 } 217 218 /* 219 * 执行rmmod命令时就会调用这个函数 220 */ 221 static void __exit key_irq_exit(void) 222 { 223 printk("<0>\nGoodbye,%s!\n\n",Driver_NAME); 224 225 unregister_chrdev(major,Driver_NAME); 226 device_unregister(drv_class_dev); 227 class_destroy(drv_class); 228 229 /* free gpios */ 230 mxc_free_iomux(GPIO2_21, MUX_CONFIG_ALT5); 231 mxc_free_iomux(GPIO3_15, MUX_CONFIG_ALT5); 232 mxc_free_iomux(GPIO2_10, MUX_CONFIG_ALT5); 233 mxc_free_iomux(GPIO2_11, MUX_CONFIG_ALT5); 234 mxc_free_iomux(GPIO2_8, MUX_CONFIG_ALT5); 235 mxc_free_iomux(GPIO2_9, MUX_CONFIG_ALT5); 236 mxc_free_iomux(GPIO2_6, MUX_CONFIG_ALT5); 237 mxc_free_iomux(GPIO2_7, MUX_CONFIG_ALT5); 238 239 gpio_free(IOMUX_TO_GPIO(GPIO2_21)); 240 gpio_free(IOMUX_TO_GPIO(GPIO3_15)); 241 gpio_free(IOMUX_TO_GPIO(GPIO2_10)); 242 gpio_free(IOMUX_TO_GPIO(GPIO2_11)); 243 gpio_free(IOMUX_TO_GPIO(GPIO2_8)); 244 gpio_free(IOMUX_TO_GPIO(GPIO2_9)); 245 gpio_free(IOMUX_TO_GPIO(GPIO2_6)); 246 gpio_free(IOMUX_TO_GPIO(GPIO2_7)); 247 248 } 249 250 /* 这两行指定驱动程序的初始化函数和卸载函数 */ 251 module_init(key_irq_init); 252 module_exit(key_irq_exit); 253 254 /* 描述驱动程序的一些信息,不是必须的 */ 255 MODULE_AUTHOR("Lover雪"); 256 MODULE_VERSION("0.1.0"); 257 MODULE_DESCRIPTION("IMX257 key Driver"); 258 MODULE_LICENSE("GPL");
大概讲解一下中断的实现:
如上面的程序所示:
当我们要使用中断时,第一步肯定是要配置中断。所以,在我们的ioctl函数的gpio_input命令中,如下代码主要的功能就是配置各个引脚为中断模式,下降沿触发,中断的名称为key_GPIOm_n。
还有一个就是注册中断函数,告诉内核,我们中断的处理函数为key_irq。
1 Request_irq(IOMUX_TO_IRQ(GPIO2_7), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_7", (void *)1); 2 3 request_irq(IOMUX_TO_IRQ(GPIO2_6), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_6", (void *)1); 4 5 request_irq(IOMUX_TO_IRQ(GPIO2_9), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_9", (void *)1); 6 7 request_irq(IOMUX_TO_IRQ(GPIO2_10), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_10", (void *)1); 8 9 request_irq(IOMUX_TO_IRQ(GPIO2_11), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_11", (void *)1); 10 11 request_irq(IOMUX_TO_IRQ(GPIO2_21), key_irq, IRQF_TRIGGER_FALLING, "key_GPIO2_21", (void *)1);
中断配置好后,就开始使用中断,当我们用户程序read函数到来,而我们的按键却并没有按下时,为了节省资源,我们就可以让程序睡眠SLEEP,
1 //如果按键没有按下,没有中断,休眠 2 3 wait_event_interruptible(key_interrupt_wait,ev_press); 4 5 ev_press = 0; //清除中断时设置的ev_pre = 1
当中断到来时,由于前面我们的程序在read函数中已经处于睡眠态了,所以我们中断的作用就是把我们的程序唤醒,在read函数中继续运行,读取相应的数据
1 /* 中断程序key_irq */ 2 3 static irqreturn_t key_irq(int irq, void *dev_id) 4 { 5 //发生了中断 6 printk("<0>function interrupt key_irq!\n\n"); 7 8 ev_press = 1; 9 10 wake_up_interruptible(&key_interrupt_wait); 11 return IRQ_RETVAL(IRQ_HANDLED); 12 }
接下来,附上应用程序源代码;
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 #include <termios.h> 8 #include <errno.h> 9 #include <limits.h> 10 #include <asm/ioctls.h> 11 #include <time.h> 12 #include <pthread.h> 13 14 #include "mx257_gpio.h" 15 16 #define key_input 0 17 #define version 1 18 19 20 int main(int argc, char **argv) 21 { 22 int fd; 23 int i=0,cnt=0; 24 unsigned char key_vals[8]; 25 26 fd = open("/dev/key_interrupt",O_RDWR); 27 if(fd < 0){ 28 printf("can't open !!!\n"); 29 } 30 ioctl(fd,version,NULL); 31 ioctl(fd,key_input,NULL); 32 while(1){ 33 read(fd, key_vals,sizeof(key_vals)); 34 printf("%04d key pressed: %d %d %d %d %d %d %d %d\n",cnt++,key_vals[0],key_vals[1],key_vals[2],key_vals[3],key_vals[4],key_vals[5],key_vals[6],key_vals[7]); 35 } 36 return 0; 37 }
应用程序中,我们就不需要利用应用程序来检测是否按下,可以把检测的if判断语句去掉。
接下来的编译工作就不赘述:
Insmod ./test/key_test & (这里加 & 的意思是,在后台执行) cat /proc/interrupts
结果如图所示:
查看中断,可以看到我们前面设置的中断名字。
当我们的按键按下,应用程序在后台检测到,就会打印出我们当前的引脚电平
程序编写过程中碰到的错误:
error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token |
因为这个问题,耗费了我足足一个下午加一个晚上的时间,事实证明,写程序时一定要仔细,否则查找错误的过程真的很痛苦,花的时间远比重新编写程序的时间多得多。
分析:
开始,我们的init函数名称为key_init,
头文件包含为linux/inerrupt.h
经过测试使用 linux/rtc.h 或者 linux/serial_reg.h 时效果一样。
编译后结果如图所示:
而当我们将函数名修改掉:
编译通过,如图所示
这里出现问题的原因时,我们的key_init函数,和我们内核中包含有free_irq的头文件中的函数发生冲突。
总结一下第一个问题:
首先,要声明并不是所有的这样的错误都是如上面的一样。
有的时候,可能真的是因为前面的代码的语法错误而导致的,具体的情况要看具体的代码,具体分析。
error: 'TASK_INTERRUPTIBLE' undeclared |
因为在sched.h 头文件中包含了它的申明。
#include <linux/sched.h>
#include <linux/interrupt.h>
例外附上:
本人研究linux内核源代码时,发现很多文件中使用了 free_irq等中断申请和释放的函数,但是却未包含inrerrupt.h 的头文件,进过本人测试总结,
可以使用interrupt.h,rtc.h, seria_reg.h 达到同样的效果,因为这些头文件中包含了interrupt.h。
(ps:笔者认为,当我们碰到问题时,一定要有钻研精神,可能会花点时间,但这样当我们解决问题后,就会有一种豁然开朗的感觉,瞬间感觉自己又学到了知识,有助于自己的能力的提升,千万千万别一碰到问题就去问那些大神,大神会很无奈的,并且自己的查错能力也没法提升)