news 2026/4/22 10:01:51

中断下半部:延迟工作实验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中断下半部:延迟工作实验

一、该驱动程序的功能

这个驱动程序是基于 Linux 中断下半部的 “延迟工作” 机制,实现按键中断的消抖处理:当按键触发中断时,不会立即处理按键事件,而是调度一个 “延迟工作”(延迟指定时间,避开按键抖动的窗口期),待延迟时间结束后,再执行工作函数,以此过滤按键抖动导致的多次误触发,保证按键事件的有效识别。

二、实验完整步骤(含代码)

步骤 1:准备实验环境
  1. 确保已安装 Linux 内核源码(需与开发板内核版本一致)。
  2. 准备交叉编译工具链(适配开发板架构,如 ARM 架构的arm-linux-gnueabihf-)。
  3. 开发板端确保已开启 GPIO 中断支持(内核配置中启用对应 GPIO 子系统、工作队列子系统)。
步骤 2:编写驱动代码(文件名:interrupt.c
#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/delay.h> // 定义工作队列、延迟工作 struct workqueue_struct *test_workqueue; struct delayed_work test_workqueue_work; // 1. 延迟工作对应的工作函数(中断下半部,延迟后执行) void test_work(struct work_struct *work) { msleep(100); // 模拟实际按键处理(如读取GPIO状态) printk("This is test_work (按键抖动已过滤,执行有效处理)\n"); } // 2. 中断处理函数(中断上半部,快速响应) irqreturn_t test_interrupt(int irq, void *args) { printk("This is test_interrupt (按键触发中断,调度延迟工作)\n"); // 调度延迟工作:在自定义工作队列上,延迟3秒(3*HZ,HZ是内核时钟节拍,默认1000) queue_delayed_work(test_workqueue, &test_workqueue_work, 3 * HZ); return IRQ_HANDLED; } static int irq; // 中断号 // 3. 驱动初始化函数 static int __init interrupt_irq_init(void) { int ret; // (1)将GPIO4转换为对应的中断号(假设按键接在GPIO4) irq = gpio_to_irq(4); printk("irq is %d\n", irq); // (2)申请中断 ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL); if (ret < 0) { printk("request_irq is error\n"); return -1; } // (3)创建自定义工作队列 test_workqueue = create_workqueue("test_workqueue"); // (4)初始化延迟工作(绑定工作函数test_work) INIT_DELAYED_WORK(&test_workqueue_work, test_work); printk("interrupt_irq_init success\n"); return 0; } // 4. 驱动退出函数 static void __exit interrupt_irq_exit(void) { // (1)释放中断 free_irq(irq, NULL); // (2)同步取消延迟工作(确保工作函数执行完毕) cancel_delayed_work_sync(&test_workqueue_work); // (3)销毁工作队列 destroy_workqueue(test_workqueue); printk("bye bye\n"); } module_init(interrupt_irq_init); module_exit(interrupt_irq_exit); MODULE_LICENSE("GPL");
步骤 3:编写 Makefile(文件名:Makefile

makefile

obj-m += interrupt.o KERNELDIR ?= /path/to/your/kernel/source # 替换为你的内核源码路径 PWD := $(shell pwd) all: make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- # 适配开发板架构 clean: make -C $(KERNELDIR) M=$(PWD) clean
步骤 4:编译驱动模块

在主机端执行命令:

make

编译完成后,会生成interrupt.ko驱动模块文件。

步骤 5:加载驱动到开发板
  1. interrupt.ko通过 U 盘、SSH 等方式传到开发板。
  2. 开发板端执行加载命令:
insmod interrupt.ko
  1. 查看驱动加载状态(可选):
lsmod | grep interrupt
步骤 6:测试驱动功能
  1. 按下与 GPIO4 连接的按键,触发中断。
  2. 查看内核打印信息(开发板端执行):
dmesg -w
  • 会先打印This is test_interrupt (按键触发中断,调度延迟工作)(中断上半部)。
  • 延迟 3 秒后,打印This is test_work (按键抖动已过滤,执行有效处理)(延迟工作的下半部)。
步骤 7:卸载驱动模块

测试完成后,开发板端执行卸载命令:

rmmod interrupt

同时通过dmesg可查看退出打印的bye bye

我以RK3568 开发板为例,带你完成 “按键 + 延迟工作” 的完整实验,从硬件接线到软件验证一步到位:

一、先确认:开发板 GPIO 引脚对应(以 RK3568 为例)

RK3568 的 GPIO 编号规则是:编号 = 组索引*32 + 子组索引*8 + 引脚号,常用按键实验引脚(以 “GPIO0_A4” 为例):

开发板引脚丝印内核 GPIO 编号说明
GPIO0_A44对应内核中gpio=4

二、硬件接线(按键部分)

只需要 1 个按键 + 杜邦线:

  1. 按键的一端接开发板的GPIO0_A4引脚;
  2. 按键的另一端接开发板的GND引脚(接地);
  3. (可选)若开发板 GPIO 无内部上拉,需在GPIO0_A43.3V之间接一个 10KΩ 上拉电阻(大部分 RK 开发板 GPIO 默认内部上拉,可省略)。

三、实验步骤(分硬件验证→驱动修改→编译测试)

步骤 1:先验证按键硬件是否正常(用户空间临时测试)

先通过sysfs确认 GPIO 能识别按键状态,避免硬件问题:

  1. 开发板终端执行(导出 GPIO4):
    echo 4 > /sys/class/gpio/export
  2. 配置 GPIO4 为输入模式:
    echo in > /sys/class/gpio/gpio4/direction
  3. 读取 GPIO4 电平(未按按键时,内部上拉应为1;按下按键时变为0):
    cat /sys/class/gpio/gpio4/value
    → 按下按键再执行cat,若 value 从10,说明硬件接线正常。
步骤 2:完善驱动代码(补充 GPIO 申请逻辑)

之前的驱动缺少 GPIO 申请步骤,需修改interrupt.c(确保 GPIO 资源不冲突):

#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/delay.h> struct workqueue_struct *test_workqueue; struct delayed_work test_workqueue_work; int gpio_num = 4; // 对应GPIO0_A4 int irq; // 延迟工作函数 void test_work(struct work_struct *work) { int key_val = gpio_get_value(gpio_num); // 读取按键电平 printk("按键当前电平:%d(已过滤抖动)\n", key_val); } // 中断处理函数 irqreturn_t test_interrupt(int irq, void *args) { printk("按键触发中断,调度延迟工作(3秒后执行)\n"); // 调度延迟工作(延迟3秒,避开按键抖动) queue_delayed_work(test_workqueue, &test_workqueue_work, 3 * HZ); return IRQ_HANDLED; } static int __init interrupt_irq_init(void) { int ret; // 1. 申请GPIO if (gpio_request(gpio_num, "key_gpio") < 0) { printk("GPIO%d申请失败\n", gpio_num); return -EBUSY; } // 2. 配置GPIO为输入 if (gpio_direction_input(gpio_num) < 0) { printk("GPIO%d配置输入失败\n", gpio_num); gpio_free(gpio_num); return -EINVAL; } // 3. 获取GPIO对应的中断号 irq = gpio_to_irq(gpio_num); printk("按键对应的中断号:%d\n", irq); // 4. 申请中断(触发方式:下降沿,对应按键按下时电平从1→0) ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_FALLING, "key_irq", NULL); if (ret < 0) { printk("中断申请失败\n"); gpio_free(gpio_num); return -1; } // 5. 创建工作队列+初始化延迟工作 test_workqueue = create_workqueue("test_workqueue"); INIT_DELAYED_WORK(&test_workqueue_work, test_work); printk("驱动加载成功\n"); return 0; } static void __exit interrupt_irq_exit(void) { free_irq(irq, NULL); cancel_delayed_work_sync(&test_workqueue_work); destroy_workqueue(test_workqueue); gpio_free(gpio_num); // 释放GPIO printk("驱动卸载成功\n"); } module_init(interrupt_irq_init); module_exit(interrupt_irq_exit); MODULE_LICENSE("GPL");
步骤 3:编译驱动(同之前的 Makefile)

确保 Makefile 中KERNELDIR是你的 RK3568 内核源码路径,执行编译:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

生成interrupt.ko文件。

步骤 4:加载驱动并测试
  1. interrupt.ko传到开发板,执行加载:
    insmod interrupt.ko
  2. 查看内核日志(实时监控):
    dmesg -w
  3. 按下开发板上的按键,日志会输出:
    • 先打印:按键触发中断,调度延迟工作(3秒后执行)
    • 3 秒后打印:按键当前电平:0(已过滤抖动)(说明延迟工作执行,成功消抖)
步骤 5:卸载驱动(测试完成后)
rmmod interrupt

常见问题排查

  • 若中断不触发:检查 GPIO 引脚是否接错、中断触发方式是否匹配(比如改为IRQF_TRIGGER_RISING);
  • 若延迟工作不执行:确认工作队列是否成功创建(通过cat /proc/workqueue查看)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 19:29:32

从零实现工业网关通信:USB转485驱动实战

从零构建工业网关通信链路&#xff1a;深入实战USB转485驱动开发在某次现场调试中&#xff0c;我曾遇到一个“诡异”的问题——网关明明已经正确发送了Modbus查询指令&#xff0c;但PLC始终没有响应。抓包发现&#xff0c;每次数据只传出去一半就断了。排查数小时后才发现&…

作者头像 李华
网站建设 2026/4/18 20:31:32

PaddlePaddle开源平台全面解析:从入门到GPU加速训练

PaddlePaddle开源平台全面解析&#xff1a;从入门到GPU加速训练 在AI技术席卷各行各业的今天&#xff0c;深度学习框架早已不再是科研实验室里的“奢侈品”&#xff0c;而是企业构建智能系统的核心基础设施。面对图像识别、语音交互、文本理解等复杂任务&#xff0c;开发者需要…

作者头像 李华
网站建设 2026/4/20 13:49:06

MicroPython在ESP32上的定时器配置超详细版说明

MicroPython 在 ESP32 上的定时器配置&#xff1a;从原理到实战的完整指南你有没有遇到过这样的场景&#xff1f;想让一个 LED 每 500ms 闪烁一次&#xff0c;但time.sleep(500)却卡住了整个程序&#xff1b;需要每隔几秒读取一次温湿度传感器&#xff0c;却发现网络连接超时、…

作者头像 李华
网站建设 2026/4/19 10:37:21

图解说明Arduino Uno引脚功能及使用方法

从零搞懂Arduino Uno引脚&#xff1a;不只是接线&#xff0c;更是设计思维的起点你有没有过这样的经历&#xff1f;手握一块Arduino Uno&#xff0c;面对密密麻麻的引脚&#xff0c;心里默念&#xff1a;“D0到D13是数字口&#xff0c;A0到A5是模拟口……”然后把传感器一插、L…

作者头像 李华
网站建设 2026/4/19 19:45:16

PaddleOCR实战教程:基于PaddlePaddle镜像的高精度文字识别方案

PaddleOCR实战&#xff1a;基于PaddlePaddle镜像的高精度文字识别方案 在数字化转型浪潮中&#xff0c;如何快速、准确地从图像中提取文字信息&#xff0c;已成为金融、政务、制造等多个行业的共性需求。尤其是在中文场景下&#xff0c;传统OCR工具面对复杂字体、模糊背景或排版…

作者头像 李华