按键的设备驱动
1.按键的硬件原理
在嵌入式系统中,通过一个上拉电阻将处理器的外部中断引脚拉高,电阻的另一端连接按钮并接地即可实现。
当按钮被按下时,EINT10、EIN13、EINT14、EINT15 上将产生低电平,这个低电平将中断CPU,CPU可以依据中断判断按键被按下。
仅仅依据中断被产生就认定有一次按键行为是很不准确的,所有按键、触摸屏等机械设备都存在一个固有的问题,那就是“抖动”,按键从最初接通到稳定接通要经过数毫秒,其间可能发生多次“接通―断开”的过程。如果不消除“抖动”的影响,一次按键可能被理解为多次按键。
消除按键抖动影响的方法是:在判断有键按下后,进行软件延时(如20ms,在延时过程中要屏蔽对应中断),再判断键盘状态,如果仍处于按键按下状态,则可以断定该按键被按下。
2.按键驱动中的数据结构
设备驱动中主要要设计的数据结构是设备结构体,按键的设备结构体中应包含一个缓冲区,因为多次按键可能无法被及时处理,可以用该缓冲区缓存按键。此外,在按键设备结构体中,包含按键状态标志和一个实现过程中要借助的等待队列、cdev结构体。为了实现软件延时,定时器也是必要的。
【按键驱动的设备结构体、定时器】
1 #define MAX_KEY_BUF 16 //按键缓冲区大小
2 typedef unsigned char KEY_RET;
3 //设备结构体:
4 typedef struct
5 {
6 unsigned int keyStatus[KEY_NUM]; //4个按键的按键状态
7 KEY_RET buf[MAX_KEY_BUF]; //按键缓冲区
8 unsigned int head, tail; //按键缓冲区头和尾
9 wait_queue_head_t wq; //等待队列
10 struct cdev cdev; //cdev结构体
10 } KEY_DEV;
11 static struct timer_list key_timer[KEY_NUM];//4个按键去抖定时器
在按键设备驱动中,可用一个结构体记录每个按键所对应的中断/GPIO 引脚及键值。
【按键硬件资源、键值信息结构体】
1 static struct key_info
2 {
3 int irq_no; //中断号
4 unsigned int gpio_port; //GPIO端口
5 int key_no; //键值
6 }key_info_tab[4] =
7 {
8 /*按键所使用的CPU资源*/
9 {IRQ_EINT10, GPIO_G2, 1},{IRQ_EINT13, GPIO_G5, 2},{IRQ_EINT14, GPIO_G6, 3},{IRQ_EINT15, GPIO_G7, 4},};
按键设备驱动的文件操作结构体,主要实现了打开、释放和读函数,因为按键只是一个输入设备,所以不存在写函数。
【按键设备驱动文件操作结构体】
1 static struct file_operations s3c2410_key_fops =
2 {
3 owner: THIS_MODULE,
4 open: s3c2410_key_open, //启动设备
5 release: s3c2410_key_release, //关闭设备
6 read: s3c2410_key_read, //读取按键的键值
7 };
3.按键驱动的模块加载和卸载函数
按键设备作为一种字符设备,在其模块加载和卸载函数中分别包含了设备号申请和释放、cdev的添加和删除行为,在模块加载函数中,还需申请中断、初始化定时器和等待队列等,模块卸载函数完成相反的行为。
【按键设备驱动的模块加载函数】
1 static int __init s3c2410_key_init(void) //程序入口
2 {
3 ...//申请设备号,添加cdev
4
5 request_irqs(); //注册中断函数
6 keydev.head = keydev.tail = 0; //初始化结构体
7 for (i = 0; i < KEY_NUM; i++)
8 keydev.keyStatus[i] = KEYSTATUS_UP;
9 init_waitqueue_head(&(keydev.wq)); //等待队列
10
11 //初始化定时器,实现软件的去抖动
12 for (i = 0; i < KEY_NUM; i++)
13 setup_timer(&key_timer[i], key_timer_handler, i);
14 //把按键的序号作为传入定时器处理函数的参数
15 }
1 static void __exit s3c2410_key_exit(void) //程序出口
2 {
3 free_irqs(); //注销中断
4 ...//释放设备号,删除cdev
5 }
【按键设备驱动的中断申请函数】
1 /*申请系统中断,中断方式为下降沿触发*/
2 static int request_irqs(void)
3 {
4 struct key_info *k;
5 int i;
6 for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++)
7 {
8 k = key_info_tab + i;
9 set_external_irq(k->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS);
10 //设置低电平触发
11 if (request_irq(k->irq_no, &buttons_irq, SA_INTERRUPT,DEVICE_NAME,i)) //申请中断,将按键序号作为参数传入中断服务程序
13 {
14 return - 1;
15 }
16 }
17 return 0;
18 }
【按键设备驱动的中断释放函数】
1 /*释放中断*/
2 static void free_irqs(void)
3 {
4 struct key_info *k;
5 int i;
6 for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++)
7 {
8 k = key_info_tab + i;
9 free_irq(k->irq_no, buttons_irq); //释放中断
10 }
11 }
4.按键设备驱动中断、定时器处理程序
在键被按下后,将发生中断,在中断处理程序中,应该关闭中断进入查询模式,延迟20ms 以实现去抖动
【按键设备驱动的中断处理程序】
1 static void s3c2410_eint_key(int irq, void *dev_id, struct pt_regs*reg)
2 {
3 int key = dev_id;
4 disable_irq(key_info_tab[key].irq_no); //关中断,转入查询模式
56
keydev.keyStatus[key] = KEYSTATUS_DOWNX;//状态为按下
7 key_timer[key].expires == jiffies + KEY_TIMER_DELAY1;//延迟
8 add_timer(&key_timer[key]); //启动定时器
9 }
在定时器处理程序中,查询按键是否仍然被按下,如果是被按下的状态,则将该按键记录入缓冲区。同时启动新的定时器延迟,延迟一个相对于去抖更长的时间(如100ms),每次定时器到期后,查询按键是否仍然处于按下状态,如果是,则重新启用新的100ms 延迟;若查询到已经没有按下,则认定键已抬起,这个时候应该开启对应按键的中断,等待新的按键。每次记录新的键值时,应唤醒等待队列。
【按键设备驱动的定时器处理函数】
1 static void key_timer_handler(unsigned long data)
2 {
3 int key = data;
4 if (ISKEY_DOWN(key))
5 {
6 if (keydev.keyStatus[key] == KEYSTATUS_DOWNX)
7 //从中断进入
8 {
9 keydev.keyStatus[key] = KEYSTATUS_DOWN;
10 key_timer[key].expires == jiffies + KEY_TIMER_DELAY; //延迟
11 keyEvent(); //记录键值,唤醒等待队列
12 add_timer(&key_timer[key]);
13 }
14 else
15 {
16 key_timer[key].expires == jiffies + KEY_TIMER_DELAY; //延迟
17 add_timer(&key_timer[key]);
18 }
19 }
20 else //键已抬起
21 {
22 keydev.keyStatus[key] = KEYSTATUS_UP;
23 enable_irq(key_info_tab[key].irq_no);
24 }
25 }
5.按键设备驱动的打开、释放函数
按键设备驱动的打开和释放函数比较简单,主要是设置keydev.head、keydev.tail和按键事件函数指针keyEvent 的值
【按键设备驱动的打开、释放函数】
1 static int s3c2410_key_open(struct inode *inode, struct file *filp)
2 {
3 keydev.head = keydev.tail = 0; //清空按键动作缓冲区
4 keyEvent = keyEvent_raw; //函数指针指向按键处理函数keyEvent_raw
5 return 0;
6 }
7
8 static int s3c2410_key_release(struct inode *inode, struct file*filp)
9 {
10 keyEvent = keyEvent_dummy; //函数指针指向空函数
11 return 0;
12}
6.按键设备驱动读函数
按键设备驱动的读函数主要提供对按键设备结构体中缓冲区的读并复制到用户空间。当keydev.head ! = keydev.tail时,意味着缓冲区有数据,使用copy_to_user()拷贝到用户空间,否则,根据用户空间是阻塞读还是非阻塞读,分为如下两种情况。
1若采用非阻塞读,则因为没有按键缓存,直接返回- EAGAIN;
2若采用阻塞读,则在keydev.wq 等待队列上睡眠,直到有按键被记录入缓冲区后被唤醒。
【按键设备驱动的读函数】
1 static ssize_t s3c2410_key_read(struct file *filp, char *buf, ssize_t
count,loff_t*ppos)
3 {
4 retry: if (keydev.head != keydev.tail)
5 //当前循环队列中有数据
6 {
7 key_ret = keyRead(); //读取按键
8 copy_to_user(..); //把数据从内核空间传送到用户空间
9 }
10 else
11 {
12 if (filp->f_flags &O_NONBLOCK)
13 //若用户采用非阻塞方式读取
14 {
15 return - EAGAIN;
16 }
17 interruptible_sleep_on(&(keydev.wq));
18 //用户采用阻塞方式读取,调用该函数使进程睡眠
19 goto retry;
20 }
21 return 0;
22 }