嵌入式linux学习笔记 之 按键与中断

2019-07-12 16:31发布

1.查询方式获取按键     1.框架         头文件         file_operations结构体             .open =              .read = second_drv_read,         read函数的参数         入口函数注册结构体 second_drv_init             major = register_chrdev(0,”secon_drv”,&second_drv_fops);         出口函数second_drv_exit(void)             unregister_chrdev(major,”secon_drv”);         修饰             module_init(second_drv_init)             module_exit(second_drv_exit)         让udev自动创建设备节点(mdev是udev的简化版本)             定义类和设备             static struct class *second_drv_class;             创建class,创建class device             seconddrv_class = classs_create(THIS_MODULE, “second_drv”);             gbseconddrv_class_device = class_device_create(seconddrv_class,NULL,MKDEV(major,0),NULL,”buttons”);             卸载设备和class             class_device_unregister(firstdrv_class_dev);             class_destroy(seconddrv_class);         MODULE_LINCENS(“GPL”)     上传驱动文件,修改Makefile,编译,拷贝到根文件系统,加载驱动,检查驱动是否正常加载     2.硬件操作         看原理图,确定引脚         看2440手册,确定引脚如何操作,相关寄存器         编写程序,单片机编程直接用物理地址;Linux驱动中使用虚拟地址。             vA = ioremap(pA,len);         程序规划:             在open函数中配置引脚             在read中返回IO值             在inint中进行寄存器映射         编程:         init中建立映射             gpfcon = (volatile unsigned log *)ioremap(0x56000050,16);             gpfdat = gpfcon + 1;             gpgcon = (volatile unsigned log *)ioremap(0x56000060,16);             gpgdat = gpgcon + 1;         exit中取消映射             iounmap(gpfcon);             iounmap(gpgcon);         open中配置GPF0,2,GPG3,11为输入,对应位清零             *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));             *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));         read中返回引脚电平,ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loft_t *ppos)             unsigned char key_vals[4];             int regval;             regval = *gpfdat;             key_vals[0] = regval & BIT0;  或者 key_vals[0] = (regval & (1<<0)) ? 1: 0             key_vals[1] = (regval & (1<<2)) ? 1: 0             数据返回给用户空间             if (size != sizeof(key_vals))                 return -EINVAL;             copy_to_user(buf,key_vals,sizeof(key_vals));         测试程序             open(“/dev/buttons”,O_RDWR);             read(fd,keyvals,sezeof(key_vals));             if (!keyval[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])             {                 printf(“keyval: %4d %4d %4d %4d”,key_vals[0],,key_vals[1],key_vals[2],key_vals[3])                           }                           2.Linux的中断处理框架     Linux的中断处理与单片机本质上是一致的,单片机中,中断发生后硬件根据中断向量表跳转到对应的地址去执行;在ARM架构Linux中,中断产生后,CPU进入异常处理模式,即中断异常,调用中断总入口函数 asm_do_IRQ() ,该函数又会根据中断号调用对应的中断处理函数,这个处理函数就相当于我们在单片机编程中写的中断服务子程序。     而Linux的中断编程就是填充相关的数据结构和编写中断服务函数。     关键的数据结构(这里只列出了数据结构中比较关键的成员):     struct irq_desc {         irq_flow_handler_t  handle_irq; /*highlevel irq-events handler [if NULL, __do_IRQ()]*/         struct irq_chip     *chip;      /*low level interrupt hardware access*/         struct irqaction    *action;    /* IRQ action list */         unsigned int        status;     /* IRQ status */         unsigned int        irq_count;  /* For detecting broken IRQs */         const char      *name;          /*flow handler name for /proc/interrupts output*/     } ____cacheline_internodealigned_in_smp;     其中irq_chip 结构体用于定义与硬件相关的底层操作,如启动或关闭中断、使能或禁止中断、设置中断屏蔽设置中毒触发方式等。     irqaction 结构体用于存放用户定义的中断处理函数,对于共享中断,可以有多个中断处理函数,故irqaction是一个结构链表。     另外,在单片机低功耗编程中,我设置好中断后进入休眠状态,当中断发生后唤醒系统进行中断处理,中断完成后再次进入休眠等待中断发生;在Linux按键中断编程中,中断处理函数唤醒read函数,read函数将数据发送给用户应用后再次进入休眠,这样就不会一直占用CPU时间。则与单片机的低功耗编程很相似。      3.中断方式的按键驱动框架     与查询方式的按键驱动不一样的地方在于:         1.在open函数中使用request_irq()函数注册中断         2.在close函数中使用free_irq()函数释放中断         3.文件操作结构体 gbthird_drv_fops 中添加 .release = gbthird_drv_close, 即关闭设备时需要调用驱动的close函数,该函数中释放中断。         4.编写中断服务函数 buttons_irq()         其实对于Linux的中断编程,我们只需要做几件简单的事就可以,前面说到的数据结构填充都会有Linux自动完成。当然这是站在一个比较低的层次来看,我相信中断编程还有很多需要深入的地方。         1. gbthird_drv_open 函数中注册中断:             request_irq(IRQ_EINT8, buttons_irq,IRQT_BOTHEDGE,”S1″,&pins_desc[0]);             参数依次为:中断号,中断处理函数,触发方式,名称,设备号,这些参数与前面的结构体是对应的,request_irq() 函数会使用这些参数完成数据结构的填充。设备号可以是任意数,也可以是指针。         2. gbthird_drv_close 函数中释放中断             free_irq(IRQ_EINT8 , &pins_desc[0]);             参数依次为:中断号,设备号,与注册中断时一致。         3. 文件操作结构体 gbthird_drv_fops 中添加 .release = gbthird_drv_close         4. 编写中断服务子函数,当中断产生时将会调用该函数             static irqreturn_t buttons_irq(int irq, void *dev_id)             {                 struct pin_desc * pindesc = (struct pin_desc*)dev_id;                 unsigned int pinval;                 pinval = s3c2410_gpio_getpin(pindesc->pin);      //ok6410中是使用gpio_get_value(pindesc->pin); 括号中参数可以为 S3C64XX_GPN(0)等等                 if (pinval)                 {                     key_val = 0x80 | pindesc->key_val;  //relrese                 }                 else                 {                     key_val = pindesc->key_val;                 }                 ev_press = 1;                 wake_up_interruptible(&button_waitq);                 return  IRQ_RETVAL(IRQ_HANDLED);             }         5. 修改原有的Read函数,read函数等待中断唤醒,然后拷贝按键值到用户空间。             static ssize_t gbthird_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)             {                 unsigned char KeyVal[6];                 int RegVal = 0;                 if ( size != 1 )                     return -EINVAL;                 /* 如果没有按键动作, 休眠 */                 wait_event_interruptible(button_waitq, ev_press);                 /* 如果有按键动作, 返回键值 */                 copy_to_user(buf, &key_val, 1);                 ev_press = 0;                 return size;             }         这样就完成了中断方式的按键驱动。          PS:新接触的linux命令     cat /proc/interrupts    查看中断注册、使用情况     ./button_test &         后台方式运行程序     ps 显示运行的进程
///////////////////////////////////////////////////////////////////////////////////////// s3c2410_gpio_getpin()函数说明
unsigned int s3c2410_gpio_getpin(unsigned int pin) {     void __iomem *base = S3C24XX_GPIO_BASE(pin);     unsigned long offs = S3C2410_GPIO_OFFSET(pin);     return __raw_readl(base + 0x04) & (1<< offs); } s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值与所要读取的GPIO对应的bit mask相与以后的值,0表示该GPIO对应的bit为0, 非0表示该bit为1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9为低电平则返回的是0,如果是高电平则返回的是GPxDAT中的GPG9对应位的值为0x0100而不是0x0001,查处问题后修改也很简单了。