Linux下的按键输入驱动开发模板一文中介绍了基本的按键输入捕获流程,这里将进一步介绍如何使用中断的方式来驱动按键,同时通过定时器实现按键消抖功能,应用程序读取按键值并通过终端打印出来 下面根据Linux内核中断框架一文中介绍的内核中断使用模板,来进行代码的编写
1. 修改设备树文件
在Linux按键驱动的设备树key节点基础上,添加中断相关属性
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "andyxi-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>; //设置gpio1为中断控制器
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; //GPIO1组的18号IO,上升和下降沿触发
status = "okay";
};
设备树编写完成后使用make dtbs命令重新编译设备树,使用新的设备树文件启动 linux 系统
2. 编写驱动程序
设备树准备好后就可以编写驱动程序了,新建imx6uirq.c文件,编写程序定义按键设备结构体,以及中断IO的描述结构体
#define IMX6UIRQ_CNT 1 //设备号个数
#define IMX6UIRQ_NAME "imx6uirq" //名字
#define KEY0VALUE 0X01 //KEY0按键值
#define INVAKEY 0XFF //无效的按键值
#define KEY_NUM 1 //按键数量
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; //gpio
int irqnum; //中断号
unsigned char value; //按键对应的键值
char name[10]; //名字
irqreturn_t (*handler)(int, void *); //中断服务函数
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; //设备号
struct cdev cdev; //cdev
struct class *class; //类
struct device *device; //设备
int major; //主设备号
int minor; //次设备号
struct device_node *nd; //设备节点
atomic_t keyvalue; //有效的按键键值
atomic_t releasekey; //标记是否完成一次完成的按键
struct timer_list timer; //定义一个定时器
struct irq_keydesc irqkeydesc[KEY_NUM]; //按键描述数组
unsigned char curkeynum; //当前的按键号
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
编写中断处理函数和定时器处理函数,实现按键消抖
/* 中断服务函数,开启定时器,延时 10ms */
static irqreturn_t key0_handler(int irq, void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数,用于按键消抖 */
void timer_function(unsigned long arg){
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
atomic_set(&dev->keyvalue, keydesc->value);
}
else{
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); //标记松开按键
}
}
初始化所使用的IO,获取中断号,并请求中断
/* 按键 IO 初始化 */
static int keyio_init(void){
unsigned char i = 0;
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!
");
return -EINVAL;
}
/* 提取 GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d
", i);
}
}
/* 初始化key所使用的IO,获取中断号 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d
",i,
imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
/* 申请中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!
",imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
return 0;
}
编写设备操作函数
/* 打开设备 */
static int imx6uirq_open(struct inode *inode, struct file *filp){
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/* 从设备读取数据 */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
驱动入口函数中,创建按键设备
/* 驱动入口函数 */
static int __init imx6uirq_init(void){
/* 1、构建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid, NULL,IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、 初始化按键 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
驱动出口函数中,删除字符设备,释放中断
/* 驱动出口函数 */
static void __exit imx6uirq_exit(void){
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer);
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
3. 编写测试程序
测试程序通过不断的读取/dev/imx6uirq文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上。新建imx6uirqApp.c文件,并编写代码
int main(int argc, char *argv[]){
int fd;
int ret = 0;
char *filename;
unsigned char data;
if (argc != 2) {
printf("Error Usage!
");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s
", filename);
return -1;
}
while (1) {
read(fd, &data, sizeof(data));
if (data) //读取到数据
printf("key value = %#X
", data);
}
ret= close(fd);
if(ret < 0){
printf("file %s close failed!
", argv[1]);
return -1;
}
return 0;
}
4. 编译测试
编译驱动程序:当前目录下创建Makefile文件,并make编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := imx6uirq.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15中,加载驱动
depmod #第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko #加载驱动
加载成功后可查看/proc/interrupts文件来检查对应的中断是否注册成功
cat /proc/interrupts
运行测试程序,按下KEY0按键,imx6uirqApp会获取并且输出按键信息
./imx6uirqApp /dev/key
评论
查看更多