例程代碼路徑:ELF 1開(kāi)發(fā)板資料包3-例程源碼3-2 驅(qū)動(dòng)例程源碼5_按鍵中斷驅(qū)動(dòng)
上一節(jié)LED驅(qū)動(dòng)中,使用了GPIO子系統(tǒng)的API函數(shù)將引腳配置為輸出來(lái)控制LED的亮滅,本節(jié)講解將引腳配置為輸入,來(lái)獲取按鍵狀態(tài)。并且還使用到了中斷的概念。
接下來(lái)編寫(xiě)一個(gè)K1按鍵的驅(qū)動(dòng)。
修改設(shè)備樹(shù)
(一)查看原理圖和引腳復(fù)用表格,可以得到K1由GPIO5_4控制,所以我們需要配置GPIO5_4引腳為輸入,而且能夠在用戶(hù)空間來(lái)獲取它的電平狀態(tài)。
(二)在NXP內(nèi)核源碼的設(shè)備樹(shù)中查找GPIO5_4,將用到的地方屏蔽掉,避免資源申請(qǐng)失敗。打開(kāi)arch/arm/boot/dts/imx6ull-elf1-emmc.dts可以看到sound節(jié)點(diǎn)下有用到GPIO5_4,所以先需要把這部分屏蔽掉。
(三)編譯設(shè)備樹(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è)備樹(shù)文件為imx6ull-elf1-emmc.dtb,參考《01-0 ELF1、ELF1S開(kāi)發(fā)板_快速啟動(dòng)手冊(cè)_V1》4.4節(jié)單獨(dú)更新設(shè)備樹(shù)。
驅(qū)動(dòng)源碼myirq_key.c編譯
(一)頭文件引用
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶(hù)空間數(shù)據(jù)訪(fǎng)問(wèn)函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> ??????//包含設(shè)備節(jié)點(diǎn)相關(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è)備名稱(chēng) #define GPIO_KEY_PIN_NUM 132 ???//定義操作的GPIO編號(hào) #define BUTTON_IRQ ?gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引腳中斷號(hào) ? static dev_t dev_num; ??//分配的設(shè)備號(hào) struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號(hào) int minor; ?//次設(shè)備號(hào) static struct class *button_irq; static struct device *my_device; ? |
GPIO編號(hào):
在imx6ull上確定GPIO編號(hào)的公式為:GPIOn_IOx=(n-1)*32+x;因?yàn)檫x擇的引腳為GPIO5_IO4,所以通過(guò)(n-1)*32+x計(jì)算得到的編號(hào)為132。
gpio_to_irq()函數(shù)用于將GPIO引腳編號(hào)轉(zhuǎn)換為對(duì)應(yīng)的中斷號(hào)。函數(shù)原型如下:
int gpio_to_irq(unsigned int gpio); |
參數(shù)gpio是要轉(zhuǎn)換的GPIO引腳號(hào)。該函數(shù)返回與給定GPIO引腳相關(guān)聯(lián)的中斷號(hào)。如果轉(zhuǎn)換失敗或引腳沒(méi)有關(guān)聯(lián)的中斷,函數(shù)將返回一個(gè)負(fù)值。
(三)mydevice_init(void)函數(shù)的實(shí)現(xiàn)
static int __init mydevice_init(void) { ????int ret;   ????gpio_free(GPIO_KEY_PIN_NUM); ????// 在這里執(zhí)行驅(qū)動(dòng)程序的初始化操作  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); ?  ?// 注冊(cè)中斷處理函數(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; ????}   ????// 注冊(cè)字符設(shè)備驅(qū)動(dòng)程序 ????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è)備類(lèi) ????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é)點(diǎn)并關(guān)聯(lián)到設(shè)備類(lèi) ????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ū)動(dòng)的區(qū)別主要是使用gpio_direction_input函數(shù)將引腳配置為了輸入模式,使用request_irq函數(shù)申請(qǐng)了中斷,觸發(fā)方式為:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。表示上升沿和下降沿都會(huì)觸發(fā)中斷。
(四)中斷服務(wù)函數(shù)irqreturn_t button_interrupt_handler()實(shí)現(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引腳號(hào)。該函數(shù)返回GPIO引腳的當(dāng)前值,如果引腳處于高電平狀態(tài),則返回1;如果引腳處于低電平狀態(tài),則返回0。如果讀取GPIO值失敗,函數(shù)將返回一個(gè)負(fù)值。通過(guò)判斷電平引腳的電平狀態(tài),來(lái)輸出對(duì)應(yīng)的打印信息。
完整的驅(qū)動(dòng)myirq_key.c示例源碼
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶(hù)空間數(shù)據(jù)訪(fǎng)問(wèn)函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> ??????//包含設(shè)備節(jié)點(diǎn)相關(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è)備名稱(chēng) #define GPIO_KEY_PIN_NUM 132 #define BUTTON_IRQ ?gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引腳中斷號(hào) ? static dev_t dev_num; ??//分配的設(shè)備號(hào) struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號(hào) int minor; ?//次設(shè)備號(hào) 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è)備打開(kāi)的操作 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;  //申請(qǐng)GPIO前先釋放資源 ????gpio_free(GPIO_KEY_PIN_NUM); ????// 在這里執(zhí)行驅(qū)動(dòng)程序的初始化操作  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); ?  ?  ?// 注冊(cè)中斷處理函數(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; ????}   ????// 注冊(cè)字符設(shè)備驅(qū)動(dòng)程序 ????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è)備類(lèi) ????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é)點(diǎn)并關(guān)聯(lián)到設(shè)備類(lèi) ????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ū)動(dòng)程序的清理操作 ?gpio_free(GPIO_KEY_PIN_NUM); // 銷(xiāo)毀設(shè)備節(jié)點(diǎn) ?device_destroy(button_irq, MKDEV(major, minor)); // 銷(xiāo)毀設(shè)備類(lèi) ?class_destroy(button_irq); // 刪除字符設(shè)備 ?cdev_del(&my_cdev); ????// 注銷(xiāo)字符設(shè)備驅(qū)動(dòng)程序 ????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ū)動(dòng)中的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ū)動(dòng)/myirq_key$ make |
將生成的.ko文件拷貝到開(kāi)發(fā)板。
測(cè)試
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ū)動(dòng)后,按下K1按鍵,打印Button pressed,抬起按鍵,打印Button released。