按键中断处理程序设计

#####【环境】

  1. TQ2440 2M-NOR 256NAND W43
  2. Ubuntu14.04 LTS

基于S3C2440的按键中断驱动程序设计_工作队列在按键驱动程序设计中,大体流程可分为:

  1. 按键按下中断产生
  2. 按键消抖
  3. 再次判断IO口的状态
  4. 如果有效,执行相应的事件
    基于S3C2440的按键中断驱动程序设计_按键驱动程序_02
    基于S3C2440的按键中断驱动程序设计_等待队列_03在单片机中,消抖一般采用delay的方式,而2440已经用上了系统,为了效率则会采用定时器的方式。
    基于S3C2440的按键中断驱动程序设计_等待队列_03在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. 创建:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​struct timer_list timer;​
  2. 初始化:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​init_timer(&timer);​
  3. 绑定回调函数:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​timer.function = timer_func;​
  4. 添加到内核:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​add_timer(&timer);​
  5. 启动定时器:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​mod_timer(&timer, jiffies + HZ/10); //超时时间100毫秒*​​​基于S3C2440的按键中断驱动程序设计_等待队列_03 5.1. jiffies:linux系统全局变量,代表当前时间
    基于S3C2440的按键中断驱动程序设计_等待队列_03 5.2. HZ / 10 : 1000/10,表示定时100ms
  6. 从内核删除定时器:
    基于S3C2440的按键中断驱动程序设计_等待队列_03​​​del_timer(&timer);​​​基于S3C2440的按键中断驱动程序设计_按键驱动程序_02

【工作队列的使用】

//--- 工作数据结构 ---//
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);

【运行结果】
基于S3C2440的按键中断驱动程序设计_工作队列安装模块后,按下tq2440按键可以看见有信息打印出来;按下一个按键之后,运行应用程序,同样有信息打印出来,说明驱动编写成功。

基于S3C2440的按键中断驱动程序设计_内核定时器_15


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

/****************************************************************************************
* 文件名: 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);
【运行结果】
基于S3C2440的按键中断驱动程序设计_工作队列安装模块后,运行应用程序,可以看见应用程序阻塞,按下tq2440按键,应用程序继续执行打印出了信息,由此,驱动编写成功。

基于S3C2440的按键中断驱动程序设计_工作队列_17


【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…