//相比之下poll更为实用,但是这个代码实在太具有代表性了...涉及了下面几个知识。代码很大部分是linux设备驱动开发详解的内容。
/*
1、阻塞读取,队列概念
2、睡眠等待中断产生
3、定时器产生及相应函数
疑问:去除按键缓冲
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-irq.h>
#define KEYSTATUS_DOWNX 2 // 定义按键不确定状态
#define KEYSTATUS_DOWN 0 // 定义按键按下后电平
#define KEYSTATUS_UP 1 // 定义按键抬起后电平
#define BUF_CLEAR _IO(0xFF, 0) // 清除键盘缓冲区命令
#define DEVICE_NAME "utukey"
#define MAX_KEY_BUF 16 // 按键缓冲区大小
#define KEY_NUM 6 // 按键个数
#define BUF_HEAD (utukey_dev.buf[utukey_dev.head]) //缓冲头
#define BUF_TAIL (utukey_dev.buf[utukey_dev.tail]) //缓冲尾
#define ISKEY_DOWN(key) (s3c2410_gpio_getpin(key_info_tab[key].gpio_port) == KEYSTATUS_DOWN)
#define INCBUF(x,mod) ((++(x))&((mod)-1))
#define KEY_TIME_DELAY (HZ/10) // 100ms
#define KEY_TIME_DELAY1 (HZ/100) // 10ms
#define UTUKEY_MAJOR 0 // 定义0使用自动分配设备号
unsigned int utukey_major = UTUKEY_MAJOR;
/* 定义按键设备结构体 */
struct utukey_dev
{
struct cdev cdev; // cdev结构体
unsigned int key_status[KEY_NUM]; // 记录按键状态
unsigned int buf[MAX_KEY_BUF]; // 按键环形缓冲区
unsigned int head, tail; // 按键缓冲区头和尾
wait_queue_head_t wq; // 等待队列
};
struct utukey_dev utukey_dev; // 定义设备结构体
struct utukey_dev *utukey_devp; // 定义设备结构体指针
struct timer_list key_timer[KEY_NUM]; // 定义6个按键去抖动定时器
static struct key_info // 定义按键所用资源结构体
{
int irq_no; // 占用的中断号
int irq_type; // 中断类型
unsigned int gpio_port; // 占用的引脚
unsigned int gpio_setting; // 引脚设置值
int key_code; // 按键值
char *name; // 按键的对应字符串
}key_info_tab[] =
{
{
IRQ_EINT11, IRQT_FALLING, S3C2410_GPG3, S3C2410_GPG3_INP, 1, "Key Up" //下降沿触发
},
{
IRQ_EINT0, IRQT_FALLING, S3C2410_GPF0, S3C2410_GPF0_INP, 2, "Key Down"
},
{
IRQ_EINT19, IRQT_FALLING, S3C2410_GPG11,S3C2410_GPG11_INP, 3, "Key Left"
},
{
IRQ_EINT2, IRQT_FALLING, S3C2410_GPF2,S3C2410_GPF2_INP, 4, "Key Right"
},
{
IRQ_EINT6, IRQT_FALLING, S3C2410_GPF6,S3C2410_GPF6_INP, 5, "Key Enter"
},
{
IRQ_EINT5, IRQT_FALLING, S3C2410_GPF5,S3C2410_GPF5_INP, 6, "Key Exit"
},
};
/* 按键中断服务程序 */
static irqreturn_t utukey_irq(int irq, void *dev_id)
{
int key = (int)dev_id; // 传递key_info_tab[]索引
int i;
int found = 0;
for (i = 0; i < ARRAY_SIZE(key_info_tab); i++) // 查找产生中断的按键
{
if (key_info_tab.irq_no == irq)
{
found = 1;
break;
}
}
if (!found) // 没找到
{
printk(KERN_NOTICE"bad irq %d in button\n", irq);
return IRQ_NONE; //错误的中断
}
disable_irq(key_info_tab[key].irq_no); // 找到,关闭对应中断
utukey_dev.key_status[key] = KEYSTATUS_DOWNX; // 按键处于不确定状态
key_timer[key].expires = jiffies + KEY_TIME_DELAY1; // 去抖动延时
add_timer(&key_timer[key]); //add_timer
return IRQ_HANDLED; //正确的中断
}
/* 申请irq中断 */
static int request_irqs(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)
{
s3c2410_gpio_cfgpin(key_info_tab.gpio_port, key_info_tab.gpio_setting); // 设置按键引脚的模式(输入)
set_irq_type(key_info_tab.irq_no, key_info_tab.irq_type); // 设置中断类型
if (request_irq(key_info_tab.irq_no, utukey_irq, SA_INTERRUPT, DEVICE_NAME, (void *)i)) // 向内核注册中断
{
printk(KERN_WARNING "buttons:can't get irq no.%d\n", key_info_tab.irq_no);
return 1;
}
}
return 0;
}
/* 释放irq中断 */
static void free_irqs(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)
{
free_irq(key_info_tab.irq_no, (void *)i);
}
}
/* 记录键值并唤醒等待队列 */
static void keyEvent(unsigned key)
{
BUF_HEAD = key_info_tab[key].key_code; // 记录键值
utukey_dev.head = INCBUF(utukey_dev.head, MAX_KEY_BUF); // 调整缓冲区头指针
wake_up_interruptible(&(utukey_dev.wq)); // 唤醒等待队列
}
/* 驱动读函数 */
static ssize_t utukey_read(struct file *filp,char __user *buffer, size_t count, loff_t *ppos)
{
unsigned int key_ret;
unsigned long flags;
retry:
if (utukey_dev.head != utukey_dev.tail) // 缓冲区有数据?
{
local_irq_save(flags); // 进入临界区 ,关中断
key_ret = BUF_TAIL; // 读出键值
utukey_dev.tail = INCBUF(utukey_dev.tail, MAX_KEY_BUF); // 调整缓冲区尾指针
local_irq_restore(flags); // 退出临界区,开中断
copy_to_user(buffer, &key_ret, sizeof(unsigned int)); // 拷贝到用户空间
return sizeof(unsigned int);
}else // 缓冲区没数据
{
if (filp->f_flags & O_NONBLOCK) // 若采用非阻塞方式读取则返回错误
{
return -EAGAIN;
}
interruptible_sleep_on(&(utukey_dev.wq)); // 使进程睡眠
if (signal_pending(current)) //在这里等中断
{ // 如果是信号中断
return -ERESTARTSYS;
}
goto retry;
}
return sizeof(unsigned int);
}
/* ioctl设备控制函数 */
static int utukey_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
unsigned long flags;
switch (cmd)
{
case BUF_CLEAR: // 清除按键缓冲区
local_irq_save(flags);
utukey_dev.head = utukey_dev.tail = 0;
local_irq_restore(flags);
printk(KERN_INFO "key buffer is cleared\n");
break;
default:
return -EINVAL;
}
return 0;
}
/* 定时器中断回调函数 */
static void utukey_timer_callback(unsigned long data) //定时器时间到了调用 参数代表哪个定时器
{
int key = data;
if (ISKEY_DOWN(key)) // 按键处于按下状态?
{
if (utukey_dev.key_status[key] == KEYSTATUS_DOWNX) // 已经延时10ms,完成去抖动?
{
utukey_dev.key_status[key] = KEYSTATUS_DOWN;
key_timer[key].expires = jiffies + KEY_TIME_DELAY;
keyEvent(key); // 记录键值,唤醒等待队列
add_timer(&key_timer[key]); //抬起按键去抖动延时
}else
{ // 已经完成去抖动延时
key_timer[key].expires = jiffies + KEY_TIME_DELAY;
add_timer(&key_timer[key]);
}
}else
{ // 按键已经抬起
utukey_dev.key_status[key] = KEYSTATUS_UP;
enable_irq(key_info_tab[key].irq_no);
}
}
static int utukey_open(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "utukey opened\n");
return 0;
}
static int utukey_release(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "utukey released\n");
return 0;
}
static const struct file_operations utukey_fops =
{
.owner = THIS_MODULE,
.read = utukey_read,
.ioctl = utukey_ioctl,
.open = utukey_open,
.release = utukey_release,
};
/* 初始化并注册cdev */
static void utukey_setup_cdev(void)
{
int err,devno = MKDEV(utukey_major,0);
cdev_init(&utukey_dev.cdev,&utukey_fops);
utukey_dev.cdev.owner = THIS_MODULE;
utukey_dev.cdev.ops = &utukey_fops;
err = cdev_add(&utukey_dev.cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding utukey", err);
}
/* 初始化函数 */
static int __init utukey_init(void)
{
int result, i;
dev_t devno = MKDEV(utukey_major,0); // 用主次设备号生成设备号
/* 申请中断 */
result = request_irqs();
if (result) {
unregister_chrdev_region(devno,1);
return result;
}
/* 申请设备号 */
if (utukey_major)
result = register_chrdev_region(devno, 1, DEVICE_NAME);
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
utukey_major = MAJOR(devno);
printk(KERN_INFO "Todo: mknod /dev/%s c %d 0\n", DEVICE_NAME, utukey_major);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存 */
utukey_devp = kmalloc(sizeof(struct utukey_dev), GFP_KERNEL);
if (!utukey_devp) /* 申请失败 */
{
result = -ENOMEM;
goto fail_malloc;
}
memset(utukey_devp, 0, sizeof(struct utukey_dev)); /* 清零分配的设备结构体内存 */
utukey_setup_cdev();
init_waitqueue_head(&(utukey_dev.wq)); /* 初始化等待队列头 */
utukey_dev.head = utukey_dev.tail = 0;
/* 初始化按键状态 */
for(i = 0; i < KEY_NUM; i++)
{
utukey_dev.key_status = KEYSTATUS_UP;
}
/* 初始化定时器 */
for(i = 0; i < KEY_NUM; i++)
{
key_timer.function = utukey_timer_callback;
key_timer.data = i;
init_timer(&key_timer);
}
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/* 退出函数 */
static void __exit utukey_exit(void)
{
int i;
cdev_del(&utukey_dev.cdev); // 注销cdev
kfree(utukey_devp); // 释放结构体内存
unregister_chrdev_region(MKDEV(utukey_major, 0), 1); // 释放设备号
free_irqs(); // 释放中断
for(i = 0; i < KEY_NUM; i++) // 注销定时器
{
del_timer(&key_timer);
}
}
MODULE_AUTHOR("lxm<lxm650@163.com>");
MODULE_LICENSE("Dual BSD/GPL");
module_init(utukey_init);
module_exit(utukey_exit);
//测试程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#define BUF_CLEAR _IO(0xFF, 0)
int main(void)
{
int buttons_fd;
int key_value;
buttons_fd = open("/dev/utukey", 0);
if (buttons_fd < 0) {
perror("cann't open device /dev/utukey");
exit(1);
}
else
{
if (ioctl(buttons_fd, BUF_CLEAR, 0) < 0)
printf(" ioctl command failed\n");
}
while(1)
{
int ret = read(buttons_fd, &key_value, sizeof key_value);
printf("You pressed buttons %d\n", key_value);
}
close(buttons_fd);
return 0;
}
文章评论(0条评论)
登录后参与讨论