?
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-gpio",?i);
??if?(imx6uirq.irqkeydesc[i].gpio?0)?{
???printk("can't?get?key%d
",?i);
??}
?}
?/*?初始化key所使用的IO,获取中断号?*/
?for?(i?=?0;?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?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?"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???
?
评论