驱动篇:字符设备驱动综合实例(一)
1.按键的设备驱动
在嵌入式系统中,按键的硬件原理比较简单,通过一个上拉电阻将处理器的外部中断(或 GPIO)引脚拉高,电阻的另一端连接按钮并接地即可实现。如图 12.1 所示,当按钮被按下时,EINT10、EIN13、EINT14、EINT15 上将产生低电平,这个低电平将中断 CPU(图中的 CPU 为 S3C2410),CPU 可以依据中断判断按键被按下。
如果按键对应的引脚本身不具备中断输入功能,则可以改为完全查询方式如图b
设备驱动中主要要设计的数据结构是设备结构体,按键的设备结构体中应包含一个缓冲区,因为多次按键可能无法被及时处理,可以用该缓冲区缓存按键。此外,在按键设备结构体中,还包含按键状态标志和一个实现过程中要借助的等待队列、cdev结构体。为了实现软件延时,定时器也是必要的,但可以不包含在设备结构体中。
#define MAX_KEY_BUF 16
typedef unsigned char KEY_RET;
typedef struct
{
unsigned int keyStatus[KEY_NUM];
KEY_RET buf[MAX_KEY_BUF];
unsigned int head, tail;
wait_queue_head_t wq;
struct cdev cdev;
} KEY_DEV;
static struct timer_list key_timer[KEY_NUM];
在按键设备驱动中,可用一个结构体记录每个按键所对应的中断/GPIO 引脚及键值
static struct key_info
{
int irq_no;
unsigned int gpio_port;
int key_no;
} key_info_tab[4] =
{
{
IRQ_EINT10, GPIO_G2, 1
},
{
IRQ_EINT13, GPIO_G5, 2
},
{
IRQ_EINT14, GPIO_G6, 3
},
{
IRQ_EINT15, GPIO_G7, 4
},
};
按键设备驱动文件操作结构体主要实现了打开、释放和读函数,因为按键只是一个输入设备,所以不存在写函数
static struct file_operations s3c2410_key_fops =
{
owner: THIS_MODULE,
open: s3c2410_key_open,
release: s3c2410_key_release,
read: s3c2410_key_read,
};
按键设备作为一种字符设备,在其模块加载和卸载函数中分别包含了设备号申请和释放、cdev 的添加和删除行为,在模块加载函数中,还需申请中断、初始化定时器和等待队列等,模块卸载函数完成相反的行为
模块加载函数
static int __init s3c2410_key_init(void)
{
...
request_irqs();
keydev.head = keydev.tail = 0;
for (i = 0; i < KEY_NUM; i++)
keydev.keyStatus[i] = KEYSTATUS_UP;
init_waitqueue_head(&(keydev.wq));
for (i = 0; i < KEY_NUM; i++)
setup_timer(&key_timer[i], key_timer_handler, i);
}
模块卸载函数
static void __exit s3c2410_key_exit(void)
{
free_irqs();
...
}
按键设备驱动的中断申请函数
static int request_irqs(void)
{
struct key_info *k;
int i;
for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++)
{
k = key_info_tab + i;
set_external_irq(k->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS);
if (request_irq(k->irq_no, &buttons_irq, SA_INTERRUPT,DEVICE_NAME, i))
{
return - 1;
}
}
return 0;
}
中断释放函数
static void free_irqs(void)
{
struct key_info *k;
int i;
for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++)
{
k = key_info_tab + i;
free_irq(k->irq_no, buttons_irq);
}
}
在键被按下后,将发生中断,在中断处理程序中,应该关闭中断进入查询模式,延迟 20ms 以实现去抖动这个中断处理过程只包含顶半部,无底半部。
static void s3c2410_eint_key(int irq, void *dev_id, struct pt_regs *reg)
{
int key = dev_id;
disable_irq(key_info_tab[key].irq_no);
keydev.keyStatus[key] = KEYSTATUS_DOWNX;
key_timer[key].expires == jiffies + KEY_TIMER_DELAY1;
add_timer(&key_timer[key]);
}
在定时器处理程序中,查询按键是否仍然被按下,如果是被按下的状态,则将该按键记录入缓冲区。同时启动新的定时器延迟,延迟一个相对于去抖更长的时间(如100ms),每次定时器到期后,查询按键是否仍然处于按下状态,如果是,则重新启用新的 100ms 延迟;若查询到已经没有按下,则认定键已抬起,这个时候应该开启对应按键的中断,等待新的按键。每次记录新的键值时,应唤醒等待队列。
定时器处理函数
static void key_timer_handler(unsigned long data)
{
int key = data;
if (ISKEY_DOWN(key))
{
if (keydev.keyStatus[key] == KEYSTATUS_DOWNX)
{
keydev.keyStatus[key] = KEYSTATUS_DOWN;
key_timer[key].expires == jiffies + KEY_TIMER_DELAY;
keyEvent();
add_timer(&key_timer[key]);
}
else
{
key_timer[key].expires == jiffies + KEY_TIMER_DELAY;
add_timer(&key_timer[key]);
}
}
else
{
keydev.keyStatus[key] = KEYSTATUS_UP;
enable_irq(key_info_tab[key].irq_no);
}
}
按键设备驱动的打开和释放函数比较简单,主要是设置 keydev.head、keydev.tail和按键事件函数指针 keyEvent 的值
static int s3c2410_key_open(struct inode *inode, struct file *filp)
{
keydev.head = keydev.tail = 0;
keyEvent = keyEvent_raw;
return 0;
}
static int s3c2410_key_release(struct inode *inode, struct file
{
keyEvent = keyEvent_dummy;
return 0;
}
按键设备驱动的读函数主要提供对按键设备结构体中缓冲区的读并复制到用户空间。当keydev.head ! = keydev.tail 时,意味着缓冲区有数据,使用 copy_to_user()拷贝到用户空间,否则,根据用户空间是阻塞读还是非阻塞读,分为如下两种情况。
l 若采用非阻塞读,则因为没有按键缓存,直接返回- EAGAIN;
l 若采用阻塞读,则在 keydev.wq 等待队列上睡眠,直到有按键被记录入缓冲区后被唤醒。
驱动读函数
static ssize_t s3c2410_key_read(struct file *filp, char *buf, ssize_t count,loff_t*ppos)
{
retry: if (keydev.head != keydev.tail)
{
key_ret = keyRead();
copy_to_user(..);
}
else
{
if (filp->f_flags &O_NONBLOCK)
{
return - EAGAIN;
}
interruptible_sleep_on(&(keydev.wq));
goto retry;
}
return 0;
}
在设备驱动的打开函数中,keyEvent 被赋值为 keyEvent_raw,这个函数完成记录键值, 并使用 wait_up_interrupt(&(keydev.wq))语句唤醒 s3c2410_key_read()第 17 行所期待的等待队列。而 keyRead()函数则直接从按键缓冲区中读取键值。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)