例程代码路径:ELF 1开发板资料包\03-例程源码\03-2 驱动例程源码\05_按键中断驱动 上一节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 // 包含模块相关函数的头文件 #include // 包含文件系统相关函数的头文件 #include // 包含用户空间数据访问函数的头文件 #include //包含字符设备头文件 #include //包含设备节点相关的头文件 #include //包含gpio操作函数的相关头文件 #include //包含中断函数相关头文件 (二)创建相关宏定义和变量 #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 handler\n"); 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: %d\n", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %d\n",major); printk(KERN_INFO "minor number: %d\n",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 class\n"); 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 device\n"); 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 released\n"); } else { printk(KERN_INFO "Button pressed\n"); } return IRQ_HANDLED; } 使用gpio_get_value()函数获取gpio引脚的电平状态。函数原型如下: int gpio_get_value(unsigned int gpio); 参数gpio是要获取值的GPIO引脚号。该函数返回GPIO引脚的当前值,如果引脚处于高电平状态,则返回1;如果引脚处于低电平状态,则返回0。如果读取GPIO值失败,函数将返回一个负值。通过判断电平引脚的电平状态,来输出对应的打印信息。 完整的驱动myirq_key.c示例源码 #include // 包含模块相关函数的头文件 #include // 包含文件系统相关函数的头文件 #include // 包含用户空间数据访问函数的头文件 #include //包含字符设备头文件 #include //包含设备节点相关的头文件 #include //包含gpio子系统相关函数头文件 #include //包含中断函数相关头文件 #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 released\n"); } else { printk(KERN_INFO "Button pressed\n"); } 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 handler\n"); 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: %d\n", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %d\n",major); printk(KERN_INFO "minor number: %d\n",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 class\n"); 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 device\n"); 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。 |
575 浏览 0 评论
esp8266作为tcp客户端,服务器主动断开连接后,是会主动重连吗,如何设置不要主动重连呢
1712 浏览 0 评论
嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-platform总线驱动简单示例
669 浏览 0 评论
《DNESP32S3使用指南-IDF版_V1.6》第六十章 猫脸检测实验
995 浏览 2 评论
1290 浏览 2 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
12763 浏览 31 评论