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

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,查处问题后修改也很简单了。


你可能感兴趣的:(嵌入式)