例程代码路径:ELF 1开发板资料包3-例程源码3-2 驱动例程源码5_按键中断驱动
上一节LED驱动中,使用了GPIO子系统的API函数将引脚配置为输出来控制LED的亮灭,本节讲解将引脚配置为输入,来获取按键状态。并且还使用到了中断的概念。
接下来编写一个K1按键的驱动。
修改设备树
(一)查看原理图和引脚复用表格,可以得到K1由GPIO5_4控制,所以我们需要配置GPIO5_4引脚为输入,而且能够在用户空间来获取它的电平状态。
(二)在NXP内核源码的设备树中查找GPIO5_4,将用到的地方屏蔽掉,避免资源申请失败。打开arch/arm/boot/dts/imx6ull-elf1-emmc.dts可以看到sound节点下有用到GPIO5_4,所以先需要把这部分屏蔽掉。
(三)编译设备树
. /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 |
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码myirq_key.c编译
(一)头文件引用
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/uaccess.h> // 包含用户空间数据访问函数的头文件 #include <linux/cdev.h> //包含字符设备头文件 #include <linux/device.h> //包含设备节点相关的头文件 #include <linux/gpio.h> //包含gpio操作函数的相关头文件 #include <linux/interrupt.h> //包含中断函数相关头文件 |
(二)创建相关宏定义和变量
#define DEVICE_NAME "button_irq" // 设备名称 #define GPIO_KEY_PIN_NUM 132 //定义操作的GPIO编号 #define BUTTON_IRQ gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引脚中断号
static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针 int major; //主设备号 int minor; //次设备号 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()函数用于将GPIO引脚编号转换为对应的中断号。函数原型如下:
int gpio_to_irq(unsigned int gpio); |
参数gpio是要转换的GPIO引脚号。该函数返回与给定GPIO引脚相关联的中断号。如果转换失败或引脚没有关联的中断,函数将返回一个负值。
(三)mydevice_init(void)函数的实现
static int __init mydevice_init(void) { int ret;   gpio_free(GPIO_KEY_PIN_NUM); // 在这里执行驱动程序的初始化操作  if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) {   printk("request %s gpio faile n", "button_irq");    return -1;   }   //将引脚设置为输入模式   gpio_direction_input(GPIO_KEY_PIN_NUM);
  // 注册中断处理函数 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; }   // 注册字符设备驱动程序 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); // 创建设备类 button_irq = class_create(THIS_MODULE, "button_irq"); if (IS_ERR(button_irq)) { pr_err("Failed to create classn"); return PTR_ERR(button_irq); } // 创建设备节点并关联到设备类 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驱动的区别主要是使用gpio_direction_input函数将引脚配置为了输入模式,使用request_irq函数申请了中断,触发方式为:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。表示上升沿和下降沿都会触发中断。
(四)中断服务函数irqreturn_t button_interrupt_handler()实现
//中断服务函数 static irqreturn_t button_interrupt_handler(int irq, void *dev_id) { // 在此处执行按键中断处理代码
 // 检查按键状态 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()函数获取gpio引脚的电平状态。函数原型如下:
int gpio_get_value(unsigned int gpio); |
参数gpio是要获取值的GPIO引脚号。该函数返回GPIO引脚的当前值,如果引脚处于高电平状态,则返回1;如果引脚处于低电平状态,则返回0。如果读取GPIO值失败,函数将返回一个负值。通过判断电平引脚的电平状态,来输出对应的打印信息。
完整的驱动myirq_key.c示例源码
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/uaccess.h> // 包含用户空间数据访问函数的头文件 #include <linux/cdev.h> //包含字符设备头文件 #include <linux/device.h> //包含设备节点相关的头文件 #include <linux/gpio.h> //包含gpio子系统相关函数头文件 #include <linux/interrupt.h> //包含中断函数相关头文件
#define DEVICE_NAME "button_irq" // 设备名称 #define GPIO_KEY_PIN_NUM 132 #define BUTTON_IRQ gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引脚中断号
static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针 int major; //主设备号 int minor; //次设备号 static struct class *button_irq; static struct device *my_device;
//中断服务函数 static irqreturn_t button_interrupt_handler(int irq, void *dev_id) { // 在此处执行按键中断处理代码
 // 检查按键状态 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) { // 在这里处理设备打开的操作 printk(KERN_INFO "This is device_open.n"); return 0; }
static int device_release(struct inode *inode, struct file *file) { // 在这里处理设备关闭的操作 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); // 在这里执行驱动程序的初始化操作  if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) {   printk("request %s gpio faile n", "button_irq");    return -1;   }   //将引脚设置为输入模式   gpio_direction_input(GPIO_KEY_PIN_NUM);
    // 注册中断处理函数 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; }   // 注册字符设备驱动程序 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); // 创建设备类 button_irq = class_create(THIS_MODULE, "button_irq"); if (IS_ERR(button_irq)) { pr_err("Failed to create classn"); return PTR_ERR(button_irq); } // 创建设备节点并关联到设备类 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); // 在这里执行驱动程序的清理操作 gpio_free(GPIO_KEY_PIN_NUM); // 销毁设备节点 device_destroy(button_irq, MKDEV(major, minor)); // 销毁设备类 class_destroy(button_irq); // 删除字符设备 cdev_del(&my_cdev); // 注销字符设备驱动程序 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"); // 指定模块的描述信息 |
编译
复制7.5.4驱动中的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_按键中断驱动/myirq_key$ make |
将生成的.ko文件拷贝到开发板。
测试
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. |
加载驱动后,按下K1按键,打印Button pressed,抬起按键,打印Button released。