今日为了调试ARM板上的GPIO引脚中断效果,以便在后续项目使用ARM与ZLG7290按键LED中断芯片连接中随意选择空闲的GPIO引脚来作为ZLG7290的中断信号线,特意编写了一个小的Linux GPIO中断驱动程序下载到开发板上做实验。经验证,这种软件中断方式也还差强人意。下面贴出自己编写的不成熟的代码,见笑(<-_->)。
实验的硬件电路为ARM GPIO的PB17连接一个共阴LED,PB18与PB19连接,PB18由中断驱动设置为低电平触发,PB19由GPIO驱动程序控制,上层应用程序通过驱动控制PB19高低电平变化,从而引发PB18发生中断,中断程序中控制PB17的LED亮和灭。
Linux中断驱动部分:
/* * PB18_IRQTest.c * This is a test program for sam9260, using PB19(J5_18 pin) input a signal to PB18(J5_16 pin), * PB18 receive this signal as IRQ and make the LED linking on PB17((J5_14 pin)) turn on or turn off * * @Author: Cun Tian Rui * @Date :March.18.2011 */ #include <linux/types.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/gpio.h> #include <asm/arch/hardware.h> #include <asm/arch/gpio.h> #include <linux/interrupt.h> #include <asm/io.h> #include <asm/arch/board.h> #include <linux/cdev.h> #include <asm/arch/gpio.h> #include <asm/uaccess.h> #include <asm/io.h> #include <asm/arch/at91_pio.h> #include <asm/arch/at91_aic.h> #include <asm/arch/at91_pmc.h> void led_on() { at91_set_gpio_output(AT91_PIN_PB17,1); } void led_off() { at91_set_gpio_output(AT91_PIN_PB17 ,0); } struct light_dev *light_devp; int light_major = 200; struct light_dev { struct cdev cdev; unsigned char value; }; MODULE_AUTHOR("Cun Tian Rui"); MODULE_LICENSE("Dual BSD/GPL"); static void io_init(void) { at91_set_gpio_input(AT91_PIN_PB18, 1); at91_set_deglitch(AT91_PIN_PB18, 1); at91_sys_write(1 + PIO_IDR, 1<<18); at91_sys_write(1 + PIO_IER, (~(1<<18))); at91_sys_write(AT91_PMC_PCER, 1 << 3); } struct gpio_irq_desc { int irq; unsigned long flags; char *name; }; static struct gpio_irq_desc PB18_IRQ={AT91_PIN_PB18,AT91_AIC_SRCTYPE_LOW,"PB18"}; static irqreturn_t PB18_intHandle(int irq, void *dev_id) { led_on(); return IRQ_RETVAL(IRQ_HANDLED); } int light_open(struct inode *inode,struct file *filp) { int err; struct light_dev *dev; dev = container_of(inode->i_cdev,struct light_dev,cdev); filp->private_data = dev; io_init(); err = request_irq(PB18_IRQ.irq,PB18_intHandle,PB18_IRQ.flags,PB18_IRQ.name,(void*)0); if(err) { free_irq(PB18_IRQ.irq,(void*)0); return -EBUSY; } return 0; } int light_release(struct inode *inode,struct file *filp) { free_irq(PB18_IRQ.irq,(void*)0); return 0; } // ioctl int light_ioctl(struct inode *inode,struct file *filp,unsigned int cmd, unsigned long arg) { struct light_dev *dev = filp->private_data; switch(cmd) { case 0: at91_set_gpio_output(AT91_PIN_PB19,0); break; case 1: at91_set_gpio_output(AT91_PIN_PB19,1); led_off(); break; default: return -ENOTTY; // break; } return 0; } struct file_operations light_fops = { .owner = THIS_MODULE, .ioctl = light_ioctl, .open = light_open, .release = light_release, }; static void light_setup_cdev(struct light_dev *dev,int index) { int err,devno = MKDEV(light_major,index); cdev_init(&dev->cdev,&light_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &light_fops; err = cdev_add(&dev->cdev,devno,1); if(err) { printk(KERN_NOTICE "Error %d adding LED%d",err,index); } } int light_init(void) { int result; dev_t dev = MKDEV(light_major,0); if(light_major) { result = register_chrdev_region(dev,1,"PB18_IRQTest"); } if(result < 0) { return result; } light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL); if(!light_devp) { result = - ENOMEM; goto fail_malloc; } memset(light_devp,0,sizeof(struct light_dev)); light_setup_cdev(light_devp,0); return 0; fail_malloc:unregister_chrdev_region(dev,light_devp); return result; } void light_cleanup(void) { cdev_del(&light_devp->cdev); kfree(light_devp); unregister_chrdev_region(MKDEV(light_major,0),1); } module_init(light_init); module_exit(light_cleanup);
Linux上层应用程序:
#include <stdio.h> //#include <conio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <linux/ioctl.h> #include <signal.h> int main(int argc ,char *argv[]) { int fd; char input; fd = open("/dev/PB18_IRQTest",O_RDWR); if(fd < 0) { perror("open PB18_IRQTest device"); return 0; } while(1) { printf("input 0 to trigger int/n"); scanf("%c",&input); switch(input) { case '0': ioctl(fd,0,0); printf("/n"); break; case '1': ioctl(fd,1,0); printf("/n"); break; default: printf("/n"); break; } } return 0; }
由上面的代码可以看出,Linux内核在中断程序处理方面已经做了很多抽象,对于驱动程序编写者只需要按照内核中断构架去实现一定的控制函数就可以,以后有时间会专门撰文剖析还原Linux内核里那些对中断实现的抽象。