您的位置:首页 > 运维架构 > Linux

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

2016-01-17 19:58 525 查看
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,查处问题后修改也很简单了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: