例程代碼路徑:ELF 1開發(fā)板資料包3-例程源碼3-2 驅(qū)動例程源碼5_按鍵中斷驅(qū)動
上一節(jié)LED驅(qū)動中,使用了GPIO子系統(tǒng)的API函數(shù)將引腳配置為輸出來控制LED的亮滅,本節(jié)講解將引腳配置為輸入,來獲取按鍵狀態(tài)。并且還使用到了中斷的概念。
接下來編寫一個K1按鍵的驅(qū)動。
修改設(shè)備樹
(一)查看原理圖和引腳復(fù)用表格,可以得到K1由GPIO5_4控制,所以我們需要配置GPIO5_4引腳為輸入,而且能夠在用戶空間來獲取它的電平狀態(tài)。
(二)在NXP內(nèi)核源碼的設(shè)備樹中查找GPIO5_4,將用到的地方屏蔽掉,避免資源申請失敗。打開arch/arm/boot/dts/imx6ull-elf1-emmc.dts可以看到sound節(jié)點下有用到GPIO5_4,所以先需要把這部分屏蔽掉。
(三)編譯設(shè)備樹
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs |
編譯生成的設(shè)備樹文件為imx6ull-elf1-emmc.dtb,參考《01-0 ELF1、ELF1S開發(fā)板_快速啟動手冊_V1》4.4節(jié)單獨更新設(shè)備樹。
驅(qū)動源碼myirq_key.c編譯
(一)頭文件引用
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> ??????//包含設(shè)備節(jié)點相關(guān)的頭文件 #include <linux/gpio.h> ????????//包含gpio操作函數(shù)的相關(guān)頭文件 #include <linux/interrupt.h> ????//包含中斷函數(shù)相關(guān)頭文件 |
(二)創(chuàng)建相關(guān)宏定義和變量
#define DEVICE_NAME "button_irq" ?// 設(shè)備名稱 #define GPIO_KEY_PIN_NUM 132 ???//定義操作的GPIO編號 #define BUTTON_IRQ ?gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引腳中斷號 ? static dev_t dev_num; ??//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 static struct class *button_irq; static struct device *my_device; ? |
GPIO編號:
在imx6ull上確定GPIO編號的公式為:GPIOn_IOx=(n-1)*32+x;因為選擇的引腳為GPIO5_IO4,所以通過(n-1)*32+x計算得到的編號為132。
gpio_to_irq()函數(shù)用于將GPIO引腳編號轉(zhuǎn)換為對應(yīng)的中斷號。函數(shù)原型如下:
int gpio_to_irq(unsigned int gpio); |
參數(shù)gpio是要轉(zhuǎn)換的GPIO引腳號。該函數(shù)返回與給定GPIO引腳相關(guān)聯(lián)的中斷號。如果轉(zhuǎn)換失敗或引腳沒有關(guān)聯(lián)的中斷,函數(shù)將返回一個負值。
(三)mydevice_init(void)函數(shù)的實現(xiàn)
static int __init mydevice_init(void) { ????int ret;   ????gpio_free(GPIO_KEY_PIN_NUM); ????// 在這里執(zhí)行驅(qū)動程序的初始化操作  if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) { ?  printk("request %s gpio faile n", "button_irq");   ?return -1;  ?}  ?//將引腳設(shè)置為輸入模式  ?gpio_direction_input(GPIO_KEY_PIN_NUM); ?  ?// 注冊中斷處理函數(shù) ????ret = request_irq(BUTTON_IRQ, button_interrupt_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to register interrupt handlern"); ????????gpio_free(GPIO_KEY_PIN_NUM); ????????return ret; ????}   ????// 注冊字符設(shè)備驅(qū)動程序 ????ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to allocate device number: %dn", ret); ????????return ret; } ???major=MAJOR(dev_num); ???minor=MINOR(dev_num);  printk(KERN_INFO "major number: %dn",major);  printk(KERN_INFO "minor number: %dn",minor); ?  my_cdev.owner = THIS_MODULE; ????????cdev_init(&my_cdev,&fops); ????????cdev_add(&my_cdev,dev_num,1); ???????// 創(chuàng)建設(shè)備類 ????button_irq = class_create(THIS_MODULE, "button_irq"); ????if (IS_ERR(button_irq)) { ????????pr_err("Failed to create classn"); ????????return PTR_ERR(button_irq); } ??// 創(chuàng)建設(shè)備節(jié)點并關(guān)聯(lián)到設(shè)備類 ????my_device = device_create(button_irq, NULL, MKDEV(major, minor), NULL, DEVICE_NAME); ????if (IS_ERR(my_device)) { ????????pr_err("Failed to create devicen"); ????????class_destroy(button_irq); ????????return PTR_ERR(my_device); ????} ???????????printk(KERN_INFO "Device registered successfully.n"); ????return 0; } |
與前面LED驅(qū)動的區(qū)別主要是使用gpio_direction_input函數(shù)將引腳配置為了輸入模式,使用request_irq函數(shù)申請了中斷,觸發(fā)方式為:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。表示上升沿和下降沿都會觸發(fā)中斷。
(四)中斷服務(wù)函數(shù)irqreturn_t button_interrupt_handler()實現(xiàn)
//中斷服務(wù)函數(shù) static irqreturn_t button_interrupt_handler(int irq, void *dev_id) { ????// 在此處執(zhí)行按鍵中斷處理代碼 ?  // 檢查按鍵狀態(tài) ????int button_state = gpio_get_value(GPIO_KEY_PIN_NUM); ????if (button_state) { ????????printk(KERN_INFO "Button releasedn"); ????} else { ????????printk(KERN_INFO "Button pressedn"); ????} ? ????return IRQ_HANDLED; } |
使用gpio_get_value()函數(shù)獲取gpio引腳的電平狀態(tài)。函數(shù)原型如下:
int gpio_get_value(unsigned int gpio); |
參數(shù)gpio是要獲取值的GPIO引腳號。該函數(shù)返回GPIO引腳的當前值,如果引腳處于高電平狀態(tài),則返回1;如果引腳處于低電平狀態(tài),則返回0。如果讀取GPIO值失敗,函數(shù)將返回一個負值。通過判斷電平引腳的電平狀態(tài),來輸出對應(yīng)的打印信息。
完整的驅(qū)動myirq_key.c示例源碼
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> ??????//包含設(shè)備節(jié)點相關(guān)的頭文件 #include <linux/gpio.h> ????????//包含gpio子系統(tǒng)相關(guān)函數(shù)頭文件 #include <linux/interrupt.h> ???//包含中斷函數(shù)相關(guān)頭文件 ? #define DEVICE_NAME "button_irq" ?// 設(shè)備名稱 #define GPIO_KEY_PIN_NUM 132 #define BUTTON_IRQ ?gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引腳中斷號 ? static dev_t dev_num; ??//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 static struct class *button_irq; static struct device *my_device; ? ? //中斷服務(wù)函數(shù) static irqreturn_t button_interrupt_handler(int irq, void *dev_id) { ????// 在此處執(zhí)行按鍵中斷處理代碼 ?  // 檢查按鍵狀態(tài) ????int button_state = gpio_get_value(GPIO_KEY_PIN_NUM); ????if (button_state) { ????????printk(KERN_INFO "Button releasedn"); ????} else { ????????printk(KERN_INFO "Button pressedn"); ????} ? ????return IRQ_HANDLED; } ? static int device_open(struct inode *inode, struct file *file) { // 在這里處理設(shè)備打開的操作 printk(KERN_INFO "This is device_open.n"); ????return 0; } ? static int device_release(struct inode *inode, struct file *file) { // 在這里處理設(shè)備關(guān)閉的操作 printk(KERN_INFO "This is device_release.n"); ? ????return 0; } ? ? ? static struct file_operations fops = { ????.owner = THIS_MODULE, ????.open = device_open, ????.release = device_release, }; ? static int __init mydevice_init(void) { ????int ret;  //申請GPIO前先釋放資源 ????gpio_free(GPIO_KEY_PIN_NUM); ????// 在這里執(zhí)行驅(qū)動程序的初始化操作  if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) { ?  printk("request %s gpio faile n", "button_irq");   ?return -1;  ?}  ?//將引腳設(shè)置為輸入模式  ?gpio_direction_input(GPIO_KEY_PIN_NUM); ?  ?  ?// 注冊中斷處理函數(shù) ????ret = request_irq(BUTTON_IRQ, button_interrupt_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to register interrupt handlern"); ????????gpio_free(GPIO_KEY_PIN_NUM); ????????return ret; ????}   ????// 注冊字符設(shè)備驅(qū)動程序 ????ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to allocate device number: %dn", ret); ????????return ret; } ???major=MAJOR(dev_num); ???minor=MINOR(dev_num);  printk(KERN_INFO "major number: %dn",major);  printk(KERN_INFO "minor number: %dn",minor); ?  my_cdev.owner = THIS_MODULE; ????????cdev_init(&my_cdev,&fops); ????????cdev_add(&my_cdev,dev_num,1); ???????// 創(chuàng)建設(shè)備類 ????button_irq = class_create(THIS_MODULE, "button_irq"); ????if (IS_ERR(button_irq)) { ????????pr_err("Failed to create classn"); ????????return PTR_ERR(button_irq); } ??// 創(chuàng)建設(shè)備節(jié)點并關(guān)聯(lián)到設(shè)備類 ????my_device = device_create(button_irq, NULL, MKDEV(major, minor), NULL, DEVICE_NAME); ????if (IS_ERR(my_device)) { ????????pr_err("Failed to create devicen"); ????????class_destroy(button_irq); ????????return PTR_ERR(my_device); ????} ???????????printk(KERN_INFO "Device registered successfully.n"); ????return 0; } ? static void __exit mydevice_exit(void) { // 釋放中斷 ??free_irq(BUTTON_IRQ, NULL); // 在這里執(zhí)行驅(qū)動程序的清理操作 ?gpio_free(GPIO_KEY_PIN_NUM); // 銷毀設(shè)備節(jié)點 ?device_destroy(button_irq, MKDEV(major, minor)); // 銷毀設(shè)備類 ?class_destroy(button_irq); // 刪除字符設(shè)備 ?cdev_del(&my_cdev); ????// 注銷字符設(shè)備驅(qū)動程序 ????unregister_chrdev(0, DEVICE_NAME); ????printk(KERN_INFO "Device unregistered.n"); } ? module_init(mydevice_init); module_exit(mydevice_exit); ? MODULE_LICENSE("GPL"); ?????// 指定模塊的許可證信息 MODULE_AUTHOR("Your Name"); // 指定模塊的作者信息 MODULE_DESCRIPTION("A simple character device driver"); // 指定模塊的描述信息 |
編譯
復(fù)制7.5.4驅(qū)動中的Makefile文件,將其中的myled.o修改為myirq_key.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/test/05_按鍵中斷驅(qū)動/myirq_key$ make |
將生成的.ko文件拷貝到開發(fā)板。
測試
root@ELF1:~#?insmod myirq_key.ko major number: 247 minor number: 0 Device registered successfully. root@ELF1:~# Button pressed Button released Button pressed Button released Button pressed Button released Button pressed Button released root@ELF1:~#?rmmod myirq_key.ko Device unregistered. |
加載驅(qū)動后,按下K1按鍵,打印Button pressed,抬起按鍵,打印Button released。