嵌入式开发记录 day30 中断方式获取按键值

1、中断执行

  按键按下(产生了中断)→跳转到异常向量入口,执行中断函数
  中断函数要做什么:
  1.保护现场
  2.执行中断处理函数
  3.恢复现场

2、向内核申请中断,以及释放中断

// 注册中断:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
    参数1:irq,中断号。(和平台架构相关,结合datasheet以及平台文件)
    参数2:中断处理函数,回调函数,中断发生后调用的函数
    参数3:中断标记。上升/下降沿,高/低电平……
    参数4:中断名字。cat /proc/interrupts
    参数5:使用设备的设备结构体或者NULL。

// 释放中断:
free_irq(irq,*dev_id)    

3、硬件准备

需要注册中断:需要获取中断名称
        HOME对应管脚-->UART_RING对应GPIO-->GPX1_1对应宏定义--
            ->EXYNOS4_GPX1(1)对应的中断--->XEINT9--->IRQ_EINT(9)
        Back对应管脚-->SIM_DET对应GPIO---->GPX1_2对应宏定义--
            ->EXYNOS4_GPX1(2)对应的中断--->XEINT10-->IRQ_EINT(10)

4、keyirq.c

#include <linux/init.h>
#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>

//中断头文件
#include <linux/irq.h>
#include <linux/interrupt.h>

#define DPRINTK(x...) printk("POLLKEY_CTL DEBUG:" x)
#define DRIVER_NAME "keyirq"

// 中断处理函数1 
static irqreturn_t eint9_interrupt(int irq,void *dev_id)
{
    printk("receive a interrupt 9!\n");
    return IRQ_HANDLED;  // 中断处理程序已经被处理;IRQ_NONE:中断处理异常
}
// 中断处理函数2
static irqreturn_t eint10_interrupt(int irq,void *dev_id)
{
    printk("receive a interrupt 10!\n");
    return IRQ_HANDLED;
}

static int keyirq_probe(struct platform_device *pdev)
{
    char *banner = "keyirq Initialize\n";
    printk(banner);  // 初始化提示信息
    // 注册为中断不需要设置管脚输入输出属性
    // 1、 申请中断:
    //             中断好号    中断处理函数    上升沿下降沿 高低电平  中断名字 设备结构  
    request_irq(IRQ_EINT(9), eint9_interrupt, IRQ_TYPE_EDGE_FALLING,"my_eint9", pdev);
    request_irq(IRQ_EINT(10),eint10_interrupt,IRQ_TYPE_EDGE_FALLING,"my_eint10",pdev);
    
    return 0;
}

static int keyirq_remove (struct platform_device *pdev)
{
    // 释放中断:
    free_irq(IRQ_EINT(9), pdev);
    free_irq(IRQ_EINT(10),pdev);
    
    return 0;
}

static int keyirq_suspend (struct platform_device *pdev, pm_message_t state)
{
    DPRINTK("keyirq suspend:power off!\n");
    return 0;
}

static int keyirq_resume (struct platform_device *pdev)
{
    DPRINTK("keyirq resume:power on!\n");
    return 0;
}


// 平台驱动结构体
static struct platform_driver keyirq_driver = {
    .probe = keyirq_probe,
    .remove = keyirq_remove,
    .suspend = keyirq_suspend,
    .resume = keyirq_resume,
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
    },
};
static int __init keyirq_init(void)
{
    // 平台驱动注册
    return platform_driver_register(&keyirq_driver);
}

static void __exit keyirq_exit(void)
{
    // 驱动卸载
    platform_driver_unregister(&keyirq_driver);
}

module_init(keyirq_init);
module_exit(keyirq_exit);
MODULE_LICENSE("Dual BSD/GPL");

5、在平台文件中注册设备:keyirq,(平台文件)vim arch/arm/mach-exynos/mach-itop4412.c

struct platform_device s3c_device_keyirq_ctl = {
            .name   = "keyirq",
            .id     = -1,
};
// 在另一段添加
&s3c_device_keyirq_ctl,

6、测试

  1、注册为中断,在中断回调函数中没有实现设备的读写,所以只触发中断,执行中断函数就行;

  2、正常加载模块后,按下按键HOME和Back,看到打印信息,一切正常;

  3、查看注册的中断, cat /proc/interrupts查看中断

7、中断处理程序编写注意事项:

  1、中断处理函数不能调用可能睡眠的函数;copy_to_user() copy_from_user()

  2、不能把自己调度出去,没有current指针,保存不了堆栈信息;

  3、不能调用enable irq或者disable irq之类的函数,

  4、力求简单、快速,中断执行程序是在关闭中断的场景下运行的;

8、中断

  中断上半部和中断下半部:在中断上半部执行一些需要快速处理的操作,追求简洁;不能有睡眠;中断中断下半部处理一些复杂,耗时的操作;

8.1、中断下半部:

  下半部处理复杂任务有两种实现机制:tasklet(小任务机制)和workqueue

  通常使用tasklet_struct结构来描述小任务;小任务机制不能被线程调度,可睡眠;

// 1、根据GPIO请求一个中断号;
gpio_to_irq(unsigned gpio)

// 2. 根据中断号注册中断处理函数,此时的中断处理函数也称为中断上半部;
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

// 初始化并声明一个tasklet
#define DECLARE_TASKLET(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
    
// 3、初始化tasklet中断下半部,此处的func为中断下半部;
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)

// 4、tasklet中断下半部调度函数,一般在中断的上半部处理完的最后调度;执行中断下半部;
tasklet_schedule(struct tasklet_struct * t)

8.2、工作队列机制:可睡眠可调度

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
#ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
#endif
};

#define INIT_WORK(_work, _func)                        \
    __INIT_WORK((_work), (_func), 0)
    
// 工作队列回调函数
void (*work_func_t)(struct work_struct *work);

schedule_work(struct work_struct * work)

// 内核异步数据处理
kfifo_alloc(fifo, size, gfp_mask)

struct __kfifo {
    unsigned int    in;
    unsigned int    out;
    unsigned int    mask;
    unsigned int    esize;
    void        *data;
};

#define kfifo_free(fifo)
kfifo_in(fifo, buf, n)
kfifo_out(fifo, buf, n)
kfifo_len(fifo)
kfifo_size(fifo)
kfifo_is_empty(fifo)
kfifo_is_full(fifo)

 

  

 

posted @ 2020-08-12 00:15  笑不出花的旦旦  阅读(158)  评论(0)    收藏  举报