- 论坛徽章:
- 0
|
经过为期一周左右的时间,参考《linux设备驱动开发详解》把mini2440开发板的按键驱动完成了。
程序可以分成两部分来看:
第一部分:按键侦测,主要包括中断的初始化、中断处理、按键去抖、等待按键松开,侦测
完成返回按键键值。
第二部分:按键事件处理,主要包括,将案件事件保存至循环链表(其实就是一个数组,可
以循环保存数据)、将案件事件反馈给应用程序(Read函数)。
其中涉及到的数据结构有以下几个:
第一个数据结构:设备驱动结构
struct KEY_DEV
{
unsigned int tkeystatus[KEY_NUM]; //6个按键的状态,每个key对应一个位置
unsigned char tbuf[MAX_KEY_BUF]; //按键缓冲区,保存按键事件,Read函数从这读取
unsigned int head,tail; //按键缓冲区头和尾,指向缓冲区数据的头和尾
wait_queue_head_t wq; //等待队列,当无数据可读时read函数将挂在这
struct cdev cdev; //没什么好解释啦,^_^
};
所以这个结构体里面各个变量分工是这样的:
unsigned int tkeystatus[KEY_NUM]:
当有中断触发的时候,先在tkeystatus[KEY_NUM]这里的对应位置记为
“待定(KEYSTATUS_X)”,当延时20ms后如果通过读IO口的方式发现该端口仍然处于低电平,那么就认为确实有按下这个按键,那么在tkeystatus[KEY_NUM]这里的对应位置记为“按下(KEYSTATUS_DOWN)”
unsigned char tbuf[MAX_KEY_BUF]:
按键缓冲区,当确认某一个按键被按下后,将按键编号(看你喜欢怎么标识某一个按键啦,随便定义,只要能区分就行了)记录在这里。
unsigned int head,tail;
指向缓冲区里面数据的头尾,比如:
![]()
当Read函数来读数据的时候就判断Head和Tail是不是指向同一个地方,如果指向同一个地方就表示无数据可读,反之,则把数据读出。
Wq:
等待队列,当应用程序的Read采用阻塞方式读取的时候,如果当前没按键按下,那么就不能让Read函数返回,所以就用一个等待队列把Read函数挂起来(Read函数),当有数据可读的时候再把队列上的Read函数唤醒(keyEvent函数)。
第二个数据结构:
static struct timer_list g_tkey_timer[KEY_NUM]; //6个按键去抖计时器
主要用于计时函数,比如去抖、等待按键松开等。
第三个数据结构:
struct KEY_INFO
{
int irq_no; //中断号
unsigned int gpio_port; //GPOI端口
int key_no; //自己安排的按键号
};
很简单明了的结构体,一看就知道啦,所以不多说咯!
下面开始讲程序架构
第一部分:按键侦测。
1、 程序框架图,左边是框图,右边相应的部分实现过程。
本程序所有按键都是采用中断低电平触发方式。
![]()
第二部分:按键事件处理
主要负责按键记录以及与应用层沟通,当应用层调用Read函数的时候,如果在缓冲区有数据则马上反馈,如果无数据则判断应用层是否用阻塞方式读取,如果阻塞方式读取则将Read函数挂起,否则返回。
涉及函数一:keyEvent()记录按键事件到缓冲区。
涉及函数二:key_read()很明白啦…^_^
完整驱动代码:
//********************************************//
// 书写规范 //
//结构体定义:一律大写字母,中间可用"_"区分 //
//全局变量 :全部用小写字母,加前缀"g_" //
//局部变量 :全部用小写字母组合,无其他前后缀 //
//指针变量 :在变量前加"p",优先级比"g_"低 //
//数组 :在变量前加"t",优先级比"g_"低 //
//自制函数 :自制函数名字都以"key_"作为前缀 //
//********************************************//
#include linux/module.h>
#include linux/kernel.h>
#include linux/fs.h>
#include linux/init.h>
#include linux/delay.h>
#include linux/poll.h>
#include linux/irq.h>
#include asm/irq.h>
#include linux/interrupt.h>
#include asm/uaccess.h>
#include mach/regs-gpio.h>
#include mach/hardware.h>
#include linux/platform_device.h>
#include linux/cdev.h>
#include linux/miscdevice.h>
//*******************************
//some macro define
//*******************************
#define KEY_NUM 6
#define MAX_KEY_BUF 16 //for circle link
#define MINI2440_KEY_MAJOR 250
#define KEY_BUF_CLR 0x01
#define KEY_DELAY_20MS (HZ/50) //delay 20ms
#define KEY_DELAY_100MS (HZ/10) //delay 100ms
//buf循环链表:让x在mod进制下循环
//要求(mod-1)转换为二进制后必须是所有的"1"是连续在低位放置
//比如:mod=8; mod-1=7; 7 = 0x0000 0111(B)
//而不能是比如mod=9; mod-1=8; 8 = 0x0000 1000(B)
#define INC_BUF_POINTOR(x,mod) ((++(x))&((mod)-1))
//判断Key所在端口的状态
#define ISKEY_DOWN(key) (s3c2410_gpio_getpin(g_tkey_info[key].gpio_port) == 0)
//*******************************
//key status define
//*******************************
#define KEYSTATUS_UP 0 //none action
#define KEYSTATUS_DOWN 1 //press key
#define KEYSTATUS_X 2 //unsure state
static int g_key_major = MINI2440_KEY_MAJOR;
struct KEY_DEV
{
unsigned int tkeystatus[KEY_NUM]; //6个按键的状态
unsigned char tbuf[MAX_KEY_BUF]; //按键缓冲区
unsigned int head,tail; //按键缓冲区头和尾
wait_queue_head_t wq; //等待队列
struct cdev cdev;
};
struct KEY_DEV *g_pkey_dev;
static struct timer_list g_tkey_timer[KEY_NUM]; //6个按键去抖计时器
struct KEY_INFO
{
int irq_no; //中断号
unsigned int gpio_port; //GPOI端口
int key_no;
};
struct KEY_INFO g_tkey_info[KEY_NUM] =
{
//定义按键所使用的资源
{ IRQ_EINT8, S3C2410_GPG0, 0 }, //NO. 1 2 3 ... 与电路图匹配
{ IRQ_EINT11, S3C2410_GPG3, 1 },
{ IRQ_EINT13, S3C2410_GPG5, 2 },
{ IRQ_EINT15, S3C2410_GPG7, 3 },
{ IRQ_EINT14, S3C2410_GPG6, 4 },
{ IRQ_EINT19, S3C2410_GPG11, 5 },
};
static irqreturn_t key_eint_handler(int irq, void *dev_id)
{
int cnt,key_index;
key_index = 0;
for(cnt=0; cntKEY_NUM; cnt++)
{
if(g_tkey_info[cnt].irq_no == irq)
{
key_index = g_tkey_info[cnt].key_no;
break;
}
}
// printk(KERN_NOTICE "Eint %d\n",key_index);
disable_irq(g_tkey_info[key_index].irq_no); //disable irq
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_X; //set key in unsure state
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_20MS; //set timer value
add_timer(&g_tkey_timer[key_index]); //start timer
return IRQ_HANDLED;
}
static int request_irqs(void)
{
//申请中断
struct KEY_INFO *key_info;
int i;
for(i=0; i(sizeof(g_tkey_info)/sizeof(g_tkey_info[1])); i++)
{
key_info = g_tkey_info + i;
// set_external_irq(key_info->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS); //set INT low voltage level target
// if(request_irq(key_info->irq_no, , SA_INTERRUPT, "Mini2440_Key", i))
// /include/linux/irq.h
// #define RQ_TYPE_NONE 0x00000000 /* Default, unspecified type */
// #define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */
// #define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */
// #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
// #define IRQ_TYPE_LEVEL_HIGH 0x00000004 /* Level high type */
// #define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */
// #define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */
// #define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */
// set_external_irq(key_info->irq_no, IRQ_TYPE_LEVEL_LOW, GPIO_PULLUP_DIS); //set INT low voltage level target
// int set_irq_type (unsigned int irq, unsigned int type);
set_irq_type(g_tkey_info.irq_no, IRQ_TYPE_LEVEL_LOW);
/* /include/linux/interrupt.h
* flags used only by the kernel as part of the irq handling routines.
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
*
* #define IRQF_DISABLED 0x00000020
* #define IRQF_SAMPLE_RANDOM 0x00000040
* #define IRQF_SHARED 0x00000080
* #define IRQF_PROBE_SHARED 0x00000100
* #define IRQF_TIMER 0x00000200
* #define IRQF_PERCPU 0x00000400
* #define IRQF_NOBALANCING 0x00000800
* #define IRQF_IRQPOLL 0x00001000
*/
if(request_irq(key_info->irq_no, key_eint_handler, IRQF_DISABLED,"Mini2440_Key", &i))
{
return -1;
}
}
return 0;
}
void free_irqs(void)
{
struct KEY_INFO *key_info;
int i;
for(i=0; i(sizeof(g_tkey_info)/sizeof(g_tkey_info[1])); i++)
{
key_info = g_tkey_info + i;
free_irq(key_info->irq_no, &i);
}
}
static void keyEvent(int key_index)
{
g_pkey_dev->tbuf[g_pkey_dev->head] = key_index;
g_pkey_dev->head = INC_BUF_POINTOR(g_pkey_dev->head,MAX_KEY_BUF);
wake_up_interruptible(&g_pkey_dev->wq);
}
static void key_timer_handler(unsigned long data)
{
int key_index = data;
//printk("B:get key %d\n",s3c2410_gpio_getpin(g_tkey_info[key_index].gpio_port));
if (ISKEY_DOWN(key_index))
{
// printk(KERN_NOTICE "B\n");
if(g_pkey_dev->tkeystatus[key_index] == KEYSTATUS_X)
{
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_DOWN; //change key state
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS; //re_initial timer
keyEvent(key_index);
add_timer(&g_tkey_timer[key_index]); //restart timer
}
else //wait for user release the key
{
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS;
add_timer(&g_tkey_timer[key_index]);
}
}
else //user have released the key
{
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_UP;
//del_timer(&g_tkey_timer[key_index]);
enable_irq(g_tkey_info[key_index].irq_no);
}
}
static int key_open(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "key opened\n");
g_pkey_dev->head = g_pkey_dev->tail = 0;
return 0;
}
static int key_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t key_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
unsigned int ret,temp;
unsigned long flag;
retry:
if(g_pkey_dev->head != g_pkey_dev->tail)
{
local_irq_save(flag); //进入临界区,关闭中断
ret = g_pkey_dev->tbuf[g_pkey_dev->tail]; //读取尾部指针所指内容
g_pkey_dev->tail = INC_BUF_POINTOR(g_pkey_dev->tail, MAX_KEY_BUF);
local_irq_restore(flag); //退出临界区
//printk(KERN_NOTICE "driver key_read,key no:%d\n",ret);
temp = copy_to_user(buf, &ret, sizeof(unsigned int));
//printk(KERN_NOTICE "copy to user return %d\n", temp);
return (sizeof(unsigned int));
}
else
{
//printk(KERN_NOTICE "A\n");
if(filp->f_flags & O_NONBLOCK)
{
return -EAGAIN;
}
//printk("E:test %d\n",s3c2410_gpio_getpin(g_tkey_info[0].gpio_port));
interruptible_sleep_on(&(g_pkey_dev->wq));
goto retry;
}
// return 0;
}
static int key_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned long flag;
switch(cmd)
{
case KEY_BUF_CLR:
local_irq_save(flag);
g_pkey_dev->head = g_pkey_dev->tail = 0;
local_irq_restore(flag);
printk(KERN_NOTICE "key buf is clear\n");
break;
default:
return - EINVAL;
}
return 0;
}
static struct file_operations g_tkey_fops =
{
.owner = THIS_MODULE,
.open = key_open, //打开设备
.release = key_release, //关闭设备
.read = key_read, //读取按键的键值
.ioctl = key_ioctl, //清除缓冲区
};
static void key_setup_cdev(struct KEY_DEV *pdev, int index)
{
//1. cdev init
//2. cdev bind fops
//3. cdev add
int err, devno;
devno = MKDEV(g_key_major, index);
cdev_init(&(g_pkey_dev->cdev), &g_tkey_fops);
pdev->cdev.owner = THIS_MODULE;
pdev->cdev.ops = &g_tkey_fops;
err = cdev_add(&pdev->cdev, devno, 1);
if(err)
{
printk(KERN_NOTICE "Error %d adding dev %d", err, index);
}
}
static int mini2440_key_init(void)
{
//**********************************
//申请设备号,添加设备
//**********************************
int ret,i;
dev_t devno = MKDEV(g_key_major, 0);
if(g_key_major)
{
ret = register_chrdev_region(devno, 1, "Mini2440_Key");
}
else
{
ret = alloc_chrdev_region(&devno, 0, 1,"Mini2440_Key");
g_key_major = MAJOR(devno);
}
if(ret 0)
{
return ret;
}
g_pkey_dev = kmalloc(sizeof(struct KEY_DEV), GFP_KERNEL);
if(!g_pkey_dev)
{
ret = -ENOMEM;
goto fail_malloc;
}
memset(g_pkey_dev, 0, sizeof(struct KEY_DEV));
key_setup_cdev(g_pkey_dev, 0);
//**********************************
//申请设备号,添加设备 完毕!
//下面初始化其他内容
//**********************************
request_irqs(); //request all the key irq
g_pkey_dev->head = g_pkey_dev->tail = 0; //initial key_dev
for(i=0; iKEY_NUM; i++)
{
g_pkey_dev->tkeystatus = KEYSTATUS_UP;
}
init_waitqueue_head(&(g_pkey_dev->wq)); //initial wait queue
for(i=0; iKEY_NUM; i++)
{
// setup_timer(&g_tkey_timer, key_timer_handler,i);
g_tkey_timer.function = key_timer_handler;
g_tkey_timer.data = i;
init_timer(&g_tkey_timer);
}
return 0;
fail_malloc:unregister_chrdev_region(devno, 1);
return ret;
}
static void key_exit(void)
{
free_irqs(); //free irq
cdev_del(&g_pkey_dev->cdev); //del cdev
kfree(g_pkey_dev); //free memory
g_pkey_dev = NULL;
unregister_chrdev_region(MKDEV(g_key_major,0), 1);
}
MODULE_AUTHOR("Benson");
MODULE_LICENSE("Dual BSD/GPL");
module_param(g_key_major, int, S_IRUGO);
module_init(mini2440_key_init);
module_exit(key_exit);
完整测试代码:
#include stdio.h>
#include stdlib.h>
#include unistd.h>
int main()
{
int fd,ret,key_num;
fd = open("/dev/Mini2440_Key", 0);
if(fd 0)
{
printf("open error!");
return -1;
}
while(1)
{
// printf("A\n");
ret = read(fd, &key_num, sizeof(int));
printf("you press the key %d\n", key_num);
}
close(fd);
return 0;
}
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/102839/showart_2060243.html |
|