【ARM&Linux】按键中断驱动程序设计

《按键中断处理程序设计》

【环境】
  1. TQ2440 2M-NOR 256NAND W43
  2. Ubuntu14.04 LTS

                在按键驱动程序设计中,大体流程可分为:
1. 按键按下中断产生
2. 按键消抖
3. 再次判断IO口的状态
4. 如果有效,执行相应的事件
 
                在单片机中,消抖一般采用delay的方式,而2440已经用上了系统,为了效率则会采用定时器的方式。
                在Linux系统中,中断的功能更加的强大,共享中断:如果一个中断占用时间太长,其他同类中断事件就有可能被忽略,所以就要在中断处理程序中分成两部分,上半部分是对硬件相关的处理,下半部分是对数据的处理,而下半部分对数据的处理就可以在别的地方,由此引入工作队列的使用。


【内核定时器的使用】

//--- 内核定时器数据结构 ---//
struct timer_list {
    struct list_head entry;
    unsigned long expires;

    void (*function)(unsigned long);//定时器的回调函数指针,用于定时结束后处理相应的事物,需要自己创建
    unsigned long data;

    struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
    void *start_site;
    char start_comm[16];
    int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

头文件:#include <linux/timer.h>
1. 创建:
                struct timer_list timer;
2. 初始化:
                init_timer(&timer);
3. 绑定回调函数:
                timer.function = timer_func;
4. 添加到内核:
                add_timer(&timer);
5. 启动定时器:
                mod_timer(&timer, jiffies + HZ/10); //超时时间100毫秒*
                5.1. jiffies:linux系统全局变量,代表当前时间
                5.2. HZ / 10 : 1000/10,表示定时100ms
6. 从内核删除定时器:
                del_timer(&timer);
 


【工作队列的使用】

//--- 工作数据结构 ---//
struct work_struct {
    atomic_long_t data;
#define WORK_STRUCT_PENDING 0       /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    struct list_head entry;
    work_func_t func; //只需要管此参数即可,其他参数初始化会帮我们做,这是一个函数指针,回调函数,指向一项工作。
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

头文件:include <linux/workqueue.h>

1、创建工作

struct work_struct work_btn;    //创建一个处理按键中断事件的工作
static void work_btn_oper(struct work_struct* work);    //工作对应的操作函数

2、初始化工作

INIT_WORK(&work_btn, work_btn_oper);    //初始化工作

3、提交工作

schedule_work(&work_btn);       //提交工作到内核工作队列

【等待队列的使用】

1、创建

wait_queue_head_t my_queue;

2、初始化

init_waitqueue_head(&my_queue);

3、定义+初始化一键操作

DECLARE_WAIT_QUEUE_HEAD(my_queue);

4、等待事件发生

wait_event(queue,condition);    //condition为假时以TASK_UNINTERRUPTIBLE模式睡眠
wait_event_interruptible(queue,condition);  //condition为假时以TASK_INTERRUPTIBLE模式睡眠,此模式表示不可被打断
int wait_event_killable(queue, condition);  //condition为假时以TASK_KILLABLE模式睡眠

5、唤醒

wake_up(wait_queue_t *q);
//唤醒队列中TASK_UNINTERRUPTIBLE、TASK_INTERRUPTIBLE、TASK_KILLABLE三种模式的所有进程

wake_up_interruptible(wait_queue_t *q); //唤醒模式为TASK_INTERRUPTIBLE的进程

【按键设备驱动代码-使用工作队列】

/****************************************************************************************
* 文件名: button.c
* 创建者: 
* 时 间: 
* 联 系: 
* 简 介: ARM9 按键中断驱动程序,使用工作队列
*****************************************************************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <mach/irqs.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <linux/io.h>
#include <asm/uaccess.h>

//----------------------------- 宏定义 ---------------------------------//
#define DEVICE_NAME "IRQ-Key-Control"       //本设备名称

//-------------------------- 函数前导声明 -------------------------------//
static int btn_open (struct inode *node, struct file *fp);
static int btn_close (struct inode *node, struct file *fp);
static ssize_t btn_read (struct file *fp, char __user *buf,
                        size_t size, loff_t *offset);
static irqreturn_t btn_irq_handler(int irq, void *dev_id);


//---------------------- 当前按键被按下记录 ------------------------------//
static int current_keydown = -1;

//---------------------- TQ2440按键中断描述结构 --------------------------//
typedef struct _btn_irq_desc {
    int irq_src;            //中断源
    int pin;                //对应的硬件引脚
    int pin_setting;        //引脚功能设置
    int num;                //编号
    const char* name;       //名字
}BUTTON_IRQ_DESC;

//----------------------- 按键消抖定时器 --------------------------------//
struct timer_list btn_timer;
static void timer_func(unsigned long n); //定时器定时到时后要执行的操作函数

//-------------------------- 工作队列 -----------------------------------//
struct work_struct work_btn;    //创建一个处理按键中断事件的工作
static void work_btn_oper(struct work_struct* work);    //工作对应的操作函数

//-------------------------- 设备描述结构初始化 -------------------------//
struct file_operations btn_fops = {
    .open = btn_open,
    .read = btn_read,
    .release = btn_close,
};
struct miscdevice btn_miscdev = {

    .minor  =   MISC_DYNAMIC_MINOR, //次设备号,由系统自动选择
    .name   =   DEVICE_NAME,
    .fops   =   &btn_fops,
};
//-------------------------- TQ2440按键中断描述结构初始化 ---------------//
BUTTON_IRQ_DESC btn_irqs[] = {
    {IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"},
    {IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"},
    {IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"},
    {IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"},

};


int btn_init(void)
{
    int ret;
    int i;
    int err;

    ret = misc_register(&btn_miscdev);  //注册混杂设备

    //--- 注册四个按键中断 ---//
    for(i=0; i<sizeof(btn_irqs)/sizeof(btn_irqs[0]); i++)
    {
        err = request_irq(btn_irqs[i].irq_src,  //中断源
                    btn_irq_handler,            //中断处理函数
                    IRQF_TRIGGER_FALLING,       //标志:下降沿触发中断
                    btn_irqs[i].name,           //中断名字
                    (void *)&btn_irqs[i]);      //此参数传递给中断处理函数

        if(err)
            break;
    }

    //--- 注册出错处理 ---//
    if(err)
    {
        for(; i>=0; i--)
        {
            disable_irq(btn_irqs[i].irq_src);
            free_irq(btn_irqs[i].irq_src, (void *)&btn_irqs[i]);
        }
        printk(DEVICE_NAME" init failed. \n");
        return -EBUSY;
    }

    init_timer(&btn_timer);                 //初始化定时器
    btn_timer.function = timer_func;        //绑定超时后的操作函数
    add_timer(&btn_timer);                  //向系统添加一个定时器

    INIT_WORK(&work_btn, work_btn_oper);    //初始化工作
    printk(DEVICE_NAME" init. \n");
    return ret;
}

void btn_exit(void)
{
    int i;

    misc_deregister(&btn_miscdev);  //注销混杂设备

    //注销四个中断
    for(i=0; i<4; i++)
        free_irq(btn_irqs[i].irq_src,  (void *)&btn_irqs[i]);

    //删除消抖定时器
    del_timer(&btn_timer);
}

int btn_open (struct inode *node, struct file *fp)
{
    return 0;
}
int btn_close (struct inode *node, struct file *fp)
{
    return 0;
}
ssize_t btn_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int key_num = current_keydown + 1;
    copy_to_user(buf, &key_num, sizeof(key_num));
    return sizeof(key_num);
}


/**************************************************************************//***
 * 函数名称      : btn_irq_handler
 * 函数简介      : 中断事件回调函数,用于处理中断发生后的相应事物
 * 输入参数      : irq:中断源  dev_id:request_irq最后一个参数
 * 返 回 值         : 执行结果
 ******************************************************************************/
irqreturn_t btn_irq_handler(int irq,void *dev_id)
{
    BUTTON_IRQ_DESC* btn_irq = (BUTTON_IRQ_DESC *)dev_id;

    schedule_work(&work_btn);       //提交工作到工作队列
    current_keydown = btn_irq->num; //记录是哪个按键被按下
    return IRQ_RETVAL(IRQ_HANDLED);
}

/**************************************************************************//***
 * 函数名称      : timer_func
 * 函数简介      : 按键消抖定时器回调函数,用于处理定时器超时后的相应事物
 * 输入参数      : n:超时时间
 * 返 回 值         : 无
 ******************************************************************************/
void timer_func(unsigned long n)
{
    int isdown;

    /// 再次检测中断引脚电平状态,如果为0,则本次按下有效
    isdown = s3c2410_gpio_getpin(btn_irqs[current_keydown].pin);
    if(isdown == 0)
    {
        printk("%s down. \n", btn_irqs[current_keydown].name);
    }else
        current_keydown = -1;
}

/**************************************************************************//***
 * 函数名称      : work_btn_oper
 * 函数简介      : work的回调函数,用于执行对应work的事物处理
 * 输入参数      : work:一项具体work
 * 返 回 值         : 无
 ******************************************************************************/
void work_btn_oper(struct work_struct* work)
{
    /**
     * jiffies是linux中的全局变量,表示当前时间
     * HZ 系统嘀嗒时间,1000嘀嗒 = 1s
     * 此条设置表示定时10ms
     */
    mod_timer(&btn_timer, jiffies + HZ /10);    //启动消抖定时器
}

//----------------------------- 模块信息 ----------------------------------//
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Study");
MODULE_VERSION("v1.0");
MODULE_DESCRIPTION("This is a Button Driver.");

module_init(btn_init);
module_exit(btn_exit);

【运行结果】
                安装模块后,按下tq2440按键可以看见有信息打印出来;按下一个按键之后,运行应用程序,同样有信息打印出来,说明驱动编写成功。

这里写图片描述


【按键设备驱动代码-使用等待队列】

/****************************************************************************************
* 文件名: button_wait_queue.c
* 创建者: 
* 时 间: 
* 联 系: 
* 简 介: ARM9 按键中断驱动程序,使用等待队列
*****************************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <mach/irqs.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/wait.h>

#define DEVICE_NAME "IRQ-Key-Control"

//-------------------------- 函数前导声明 -----------------------------------//
static int btn_open (struct inode *node, struct file *fp);
static int btn_close (struct inode *node, struct file *fp);
static ssize_t btn_read (struct file *fp, char __user *buf,
                        size_t size, loff_t *offset);
static irqreturn_t btn_irq_handler(int irq, void *dev_id);


//---------------------- 当前按键被按下记录 ------------------------------//
static int current_keydown = -1;

//---------------------- TQ2440按键中断描述结构 --------------------------//
typedef struct _btn_irq_desc {
    int irq_src;            //中断源
    int pin;                //对应的硬件引脚
    int pin_setting;        //引脚功能设置
    int num;                //编号
    const char* name;       //名字
}BUTTON_IRQ_DESC;

//----------------------- 按键消抖定时器 --------------------------------//
struct timer_list btn_timer;
static void timer_func(unsigned long n); //定时器定时到时后要执行的操作函数

//-------------------------- 等待队列-------------------------//
wait_queue_head_t btn_wq;
static volatile int flag = 0;       //唤醒标志:1:按键按下了有数据存在 0:按键未按下过,无数据存在

//-------------------------- 设备描述结构初始化 -------------------------//
struct file_operations btn_fops = {
    .open = btn_open,
    .read = btn_read,
    .release = btn_close,
};
struct miscdevice btn_miscdev = {

    .minor  =   MISC_DYNAMIC_MINOR, //次设备号,由系统自动选择
    .name   =   DEVICE_NAME,
    .fops   =   &btn_fops,
};
//--------------------- TQ2440按键中断描述结构初始化 -------------------//
BUTTON_IRQ_DESC btn_irqs[] = {
    {IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"},
    {IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"},
    {IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"},
    {IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"},

};

int btn_init(void)
{
    int ret;
    int i;
    int err;

    ret = misc_register(&btn_miscdev);  //注册混杂设备

    //--- 注册四个按键中断 ---//
    for(i=0; i<sizeof(btn_irqs)/sizeof(btn_irqs[0]); i++)
    {
        err = request_irq(btn_irqs[i].irq_src,  //中断源
                    btn_irq_handler,            //中断处理函数
                    IRQF_TRIGGER_FALLING,       //标志:下降沿触发中断
                    btn_irqs[i].name,           //中断名字
                    (void *)&btn_irqs[i]);      //此参数传递给中断处理函数

        if(err)
            break;
    }

    //--- 注册出错处理 ---//
    if(err)
    {
        for(; i>=0; i--)
        {
            disable_irq(btn_irqs[i].irq_src);
            free_irq(btn_irqs[i].irq_src, (void *)&btn_irqs[i]);
        }
        printk(DEVICE_NAME" init failed. \n");
        return -EBUSY;
    }

    init_timer(&btn_timer);                 //初始化定时器
    btn_timer.function = timer_func;        //绑定超时后的操作函数
    add_timer(&btn_timer);                  //向系统添加一个定时器

    init_waitqueue_head(&btn_wq);           //初始化等待队列

    printk(DEVICE_NAME" init. \n");
    return ret;
}

void btn_exit(void)
{
    int i;

    misc_deregister(&btn_miscdev);  //注销混杂设备

    //注销四个中断
    for(i=0; i<4; i++)
        free_irq(btn_irqs[i].irq_src,  (void *)&btn_irqs[i]);

    //删除消抖定时器
    del_timer(&btn_timer);
}

int btn_open (struct inode *node, struct file *fp)
{
    return 0;
}
int btn_close (struct inode *node, struct file *fp)
{
    return 0;
}
ssize_t btn_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int key_num;
    wait_event(btn_wq, flag);       //等待数据存在
    key_num = current_keydown + 1;
    copy_to_user(buf, &key_num, sizeof(key_num));
    flag = 0;                       //设置数据读空标志
    return sizeof(key_num);
}


/**************************************************************************//***
 * 函数名称      : btn_irq_handler
 * 函数简介      : 中断事件回调函数,用于处理中断发生后的相应事物
 * 输入参数      : irq:中断源  dev_id:request_irq最后一个参数
 * 返 回 值         : 执行结果
 ******************************************************************************/
irqreturn_t btn_irq_handler(int irq,void *dev_id)
{
    BUTTON_IRQ_DESC* btn_irq = (BUTTON_IRQ_DESC *)dev_id;
    current_keydown = btn_irq->num; //记录是哪个按键被按下
    wake_up(&btn_wq);               //唤醒
    flag = 1;                       //设置数据存在标志
    return IRQ_RETVAL(IRQ_HANDLED);
}

/**************************************************************************//***
 * 函数名称      : timer_func
 * 函数简介      : 按键消抖定时器回调函数,用于处理定时器超时后的相应事物
 * 输入参数      : n:超时时间
 * 返 回 值         : 无
 ******************************************************************************/
void timer_func(unsigned long n)
{
    int isdown;

    /// 再次检测中断引脚电平状态,如果为0,则本次按下有效
    isdown = s3c2410_gpio_getpin(btn_irqs[current_keydown].pin);
    if(isdown == 0)
    {
        printk("%s down. \n", btn_irqs[current_keydown].name);
    }else
        current_keydown = -1;
}

//----------------------------- 模块信息 ----------------------------------//
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Study");
MODULE_VERSION("v1.0");
MODULE_DESCRIPTION("This is a Button Driver.");

module_init(btn_init);
module_exit(btn_exit);


【运行结果】
                安装模块后,运行应用程序,可以看见应用程序阻塞,按下tq2440按键,应用程序继续执行打印出了信息,由此,驱动编写成功。
这里写图片描述


【Makefile】


#####################################################
# Filename : Makefile
# Time :
# Creator:
#####################################################


# 用来指明单个文件
obj-m := button.o button_wait_queue.o

# 用来指明多个文件
# led-objs := led.o led_misc.o


# 开发板内核源代码路径
KDIR := /opt/EmbedSky/linux-2.6.30.4


# -C    :改变目录
# M=    :内核模块源代码路径
# CROSS_COMPILE : 编译器
# ARCH= :ARM平台
all:
    make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=/opt/EmbedSky/4.3.3/bin/arm-linux- ARCH=arm
    arm-linux-gcc -g -Wall -static button_app.c -o button.app
    cp *.ko /yaffs2/drives
    cp *.app /yaffs2/drives



.PHONY:clean
clean:
    rm *.mod.* *.o *.ko *.order *.symvers *.app 


end..

猜你喜欢

转载自blog.csdn.net/qq153471503/article/details/79341250