?
Linux下的按鍵輸入驅動開發(fā)模板一文中介紹了基本的按鍵輸入捕獲流程,這里將進一步介紹如何使用中斷的方式來驅動按鍵,同時通過定時器實現(xiàn)按鍵消抖功能,應用程序讀取按鍵值并通過終端打印出來 下面根據(jù)Linux內核中斷框架一文中介紹的內核中斷使用模板,來進行代碼的編寫
?
?1. 修改設備樹文件
?在Linux按鍵驅動的設備樹key節(jié)點基礎上,添加中斷相關屬性
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,上升和下降沿觸發(fā)
?status?=?"okay";
};
設備樹編寫完成后使用make dtbs命令重新編譯設備樹,使用新的設備樹文件啟動 linux 系統(tǒng)
?
?2. 編寫驅動程序
? 設備樹準備好后就可以編寫驅動程序了,新建imx6uirq.c文件,編寫程序?定義按鍵設備結構體,以及中斷IO的描述結構體
#define?IMX6UIRQ_CNT?? 1???????????//設備號個數(shù)
#define?IMX6UIRQ_NAME??"imx6uirq"??//名字
#define?KEY0VALUE??????0X01????????//KEY0按鍵值
#define?INVAKEY??????? 0XFF?????? //無效的按鍵值
#define?KEY_NUM??????? 1???????????//按鍵數(shù)量
/*?中斷IO描述結構體?*/
struct?irq_keydesc?{
?int?gpio;?????????????????????????????//gpio
?int?irqnum;???????????????????????????//中斷號
?unsigned?char?value;??????????????????//按鍵對應的鍵值
?char?name[10];????????????????????????//名字
?irqreturn_t?(*handler)(int,?void?*);??//中斷服務函數(shù)
};
/*?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;?????? //設備節(jié)點
?atomic_t?keyvalue;???????????? //有效的按鍵鍵值
?atomic_t?releasekey;??????????? //標記是否完成一次完成的按鍵
?struct?timer_list?timer;?????? //定義一個定時器
?struct?irq_keydesc?irqkeydesc[KEY_NUM];?//按鍵描述數(shù)組
?unsigned?char?curkeynum;?????? //當前的按鍵號
};
struct?imx6uirq_dev?imx6uirq;?/*?irq設備?*/
?編寫中斷處理函數(shù)和定時器處理函數(shù),實現(xiàn)按鍵消抖
/*?中斷服務函數(shù),開啟定時器,延時?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);
}
/*?定時器服務函數(shù),用于按鍵消抖?*/
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;
??}
?}
?/*?創(chuàng)建定時器?*/
?init_timer(&imx6uirq.timer);
?imx6uirq.timer.function?=?timer_function;
?return?0;
}
?編寫設備操作函數(shù)
/*?打開設備?*/
static?int?imx6uirq_open(struct?inode?*inode,?struct?file?*filp){
?filp->private_data?=?&imx6uirq;?/*?設置私有數(shù)據(jù)?*/
?return?0;
}
/*?從設備讀取數(shù)據(jù)?*/
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;
}
/*?設備操作函數(shù)?*/
static?struct?file_operations?imx6uirq_fops?=?{
?.owner?=?THIS_MODULE,
?.open?=?imx6uirq_open,
?.read?=?imx6uirq_read,
};
?驅動入口函數(shù)中,創(chuàng)建按鍵設備
/*?驅動入口函數(shù)?*/
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、創(chuàng)建類?*/
?imx6uirq.class?=?class_create(THIS_MODULE,?IMX6UIRQ_NAME);
?if?(IS_ERR(imx6uirq.class))?{
??return?PTR_ERR(imx6uirq.class);
?}
?/*?4、創(chuàng)建設備?*/
?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;
}
?驅動出口函數(shù)中,刪除字符設備,釋放中斷
/*?驅動出口函數(shù)?*/
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)???//讀取到數(shù)據(jù)
???printf("key?value?=?%#X
",?data);
?}
?
?ret=?close(fd);?
?if(ret?0){
??printf("file?%s?close?failed!
",?argv[1]);
??return?-1;
?}
?return?0;
}
?
?4. 編譯測試
??編譯驅動程序:當前目錄下創(chuàng)建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???
?
評論
查看更多