核心结论:以下代码均适配 rk3399 平台(ARM64 架构),基于 Linux 4.4+/5.4 + 内核,包含顶半部中断、timer、tasklet、workqueue 四大核心模块,每个模块提供 “API 汇总 + 适用场景 + 可直接编译运行的代码片段”,兼顾实用性和可移植性。
一、通用准备:rk3399 平台适配说明
- GPIO 编号规则:rk3399 的 GPIO 分 bank(A~I),每个 bank 有 8 个引脚,编号公式为 “bank 号 ×32 + 引脚号”(如 GPIO0_A5 对应编号 5,GPIO1_B3 对应编号 32+11=43,与用户代码一致)。
- 编译依赖:需配置 rk3399 交叉编译工具链(如 aarch64-linux-gnu-gcc),Makefile 需指定内核源码路径。
- 运行方式:编译生成.ko 文件后,通过
insmod加载,dmesg查看日志,rmmod卸载。
二、模块 1:顶半部中断(rk3399 按键中断示例)
1. 核心 API 汇总
| API 函数 | 功能描述 | 参数说明 |
|---|---|---|
gpio_to_irq(unsigned gpio) | 将 GPIO 编号转换为中断编号(IRQ) | gpio:rk3399 GPIO 编号(如 5=GPIO0_A5) |
request_irq(irq, handler, flags, name, dev_id) | 注册中断 | irq:中断编号;handler:中断服务函数;flags:触发方式;name:设备名;dev_id:设备标识(共享中断必填) |
free_irq(irq, dev_id) | 注销中断 | 与 request_irq 的 irq、dev_id 完全一致 |
gpio_get_value(unsigned gpio) | 读取 GPIO 电平 | gpio:GPIO 编号(0 = 低电平,1 = 高电平) |
2. 适用场景
- 外设异步触发需求(按键、传感器、串口接收中断);
- rk3399 平台常用场景:板载按键、触摸芯片中断、红外接收中断等。
3. rk3399 可运行代码片段(按键中断:GPIO0_A5 = 按键 K1)
c
运行
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>
// rk3399按键配置:GPIO0_A5(编号5),双边沿触发,共享中断
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key1"
static int key_irq;
static char key_state = '0'; // 0=松开,1=按下
// 顶半部中断服务函数(ISR)
irqreturn_t key_irq_handler(int irq, void *dev_id) {
// 快速读取电平(仅做必要操作,不耗时)
key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0'; // 按键低电平按下
printk("[rk3399-key] 顶半部:按键状态=%c\n", key_state);
return IRQ_HANDLED;
}
// 文件操作:应用层读取按键状态
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
if (copy_to_user(buf, &key_state, 1)) {
return -EFAULT;
}
return 1;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
static struct miscdevice key_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = KEY_NAME,
.fops = &key_fops,
};
// 模块初始化
static int __init rk3399_key_init(void) {
int ret;
// 1. 申请GPIO
ret = gpio_request(KEY_GPIO, KEY_NAME);
if (ret < 0) {
printk("[rk3399-key] GPIO申请失败\n");
return ret;
}
// 2. 转换中断编号
key_irq = gpio_to_irq(KEY_GPIO);
printk("[rk3399-key] 中断编号=%d\n", key_irq);
// 3. 注册中断:双边沿触发(按下+松开),共享中断
ret = request_irq(key_irq, key_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
KEY_NAME, &key_misc);
if (ret < 0) {
printk("[rk3399-key] 中断注册失败\n");
gpio_free(KEY_GPIO);
return ret;
}
// 4. 注册杂项设备(应用层访问/dev/rk3399-key1)
ret = misc_register(&key_misc);
if (ret < 0) {
printk("[rk3399-key] 设备注册失败\n");
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
return ret;
}
printk("[rk3399-key] 模块加载成功\n");
return 0;
}
// 模块卸载
static void __exit rk3399_key_exit(void) {
misc_deregister(&key_misc);
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
printk("[rk3399-key] 模块卸载成功\n");
}
module_init(rk3399_key_init);
module_exit(rk3399_key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 按键中断顶半部示例");
三、模块 2:内核定时器(rk3399 按键消抖示例)
1. 核心 API 汇总
| API 函数 | 功能描述 | 参数说明 |
|---|---|---|
setup_timer(timer, func, data) | 动态初始化定时器 | timer:定时器对象;func:超时处理函数;data:传递给 func 的参数 |
add_timer(struct timer_list *timer) | 启动定时器 | timer:定时器对象(需先设置 expires) |
mod_timer(timer, jiffies + delay) | 修改定时器超时时间(未启动则启动) | delay:延时(单位:jiffies,如 msecs_to_jiffies (20) 表示 20ms) |
del_timer_sync(timer) | 注销定时器(等待执行完成) | timer:定时器对象 |
msecs_to_jiffies(unsigned int m) | 毫秒转换为 jiffies(内核时间单位) | m:毫秒数 |
2. 适用场景
- 中断消抖(按键、触摸传感器);
- 周期性任务(如每隔 1 秒读取传感器数据);
- rk3399 平台常用场景:按键消抖、LED 呼吸灯、周期性数据采集。
3. rk3399 可运行代码片段(按键消抖:20ms 延时)
c
运行
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-debounce"
#define DEBOUNCE_MS 20 // 消抖延时20ms
static int key_irq;
static char key_state = '0';
static struct timer_list debounce_timer; // 消抖定时器
// 定时器超时处理函数(底半部逻辑)
void debounce_timer_func(unsigned long data) {
// 消抖后读取真实电平
key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
printk("[rk3399-debounce] 定时器底半部:按键真实状态=%c\n", key_state);
}
// 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
// 启动/重置消抖定时器(20ms后执行)
mod_timer(&debounce_timer, jiffies + msecs_to_jiffies(DEBOUNCE_MS));
return IRQ_HANDLED;
}
// 应用层读取接口
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
if (copy_to_user(buf, &key_state, 1)) {
return -EFAULT;
}
return 1;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
static struct miscdevice key_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = KEY_NAME,
.fops = &key_fops,
};
static int __init rk3399_key_debounce_init(void) {
int ret;
// 1. 申请GPIO
ret = gpio_request(KEY_GPIO, KEY_NAME);
if (ret < 0) return ret;
// 2. 转换中断编号
key_irq = gpio_to_irq(KEY_GPIO);
// 3. 初始化定时器
setup_timer(&debounce_timer, debounce_timer_func, (unsigned long)"key-debounce");
// 4. 注册中断
ret = request_irq(key_irq, key_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
KEY_NAME, &key_misc);
if (ret < 0) {
gpio_free(KEY_GPIO);
return ret;
}
// 5. 注册设备
ret = misc_register(&key_misc);
if (ret < 0) {
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
return ret;
}
printk("[rk3399-debounce] 模块加载成功\n");
return 0;
}
static void __exit rk3399_key_debounce_exit(void) {
del_timer_sync(&debounce_timer); // 注销定时器
misc_deregister(&key_misc);
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
printk("[rk3399-debounce] 模块卸载成功\n");
}
module_init(rk3399_key_debounce_init);
module_exit(rk3399_key_debounce_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 按键消抖(中断+定时器)示例");
四、模块 3:底半部 - tasklet(rk3399 中断后数据处理)
1. 核心 API 汇总
| API 函数 | 功能描述 | 参数说明 |
|---|---|---|
DECLARE_TASKLET(name, func, data) | 静态初始化 tasklet(激活状态) | name:tasklet 对象;func:处理函数;data:传递参数 |
tasklet_init(t, func, data) | 动态初始化 tasklet | t:tasklet 对象指针;其余同 DECLARE_TASKLET |
tasklet_schedule(t) | 调度 tasklet(加入内核执行链表) | t:tasklet 对象指针 |
tasklet_kill(t) | 注销 tasklet(等待执行完成) | t:tasklet 对象指针 |
2. 适用场景
- 中断后快速数据处理(无需睡眠);
- 比 workqueue 开销小,适合轻量级底半部任务;
- rk3399 平台常用场景:中断后数据解析、状态上报、简单滤波。
3. rk3399 可运行代码片段(中断 + tasklet 处理)
c
运行
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-tasklet"
static int key_irq;
static char key_state = '0';
// 1. 定义tasklet处理函数(底半部)
void key_tasklet_func(unsigned long data) {
// 处理耗时操作(如打印详细日志、简单数据处理)
printk("[rk3399-tasklet] 底半部:data=%s,按键状态=%c\n", (char*)data, key_state);
}
// 2. 静态初始化tasklet
DECLARE_TASKLET(key_tasklet, key_tasklet_func, (unsigned long)"tasklet-data");
// 3. 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
printk("[rk3399-tasklet] 顶半部:触发中断,调度tasklet\n");
tasklet_schedule(&key_tasklet); // 调度底半部
return IRQ_HANDLED;
}
// 应用层读取接口
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
if (copy_to_user(buf, &key_state, 1)) return -EFAULT;
return 1;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
static struct miscdevice key_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = KEY_NAME,
.fops = &key_fops,
};
static int __init rk3399_tasklet_init(void) {
int ret;
ret = gpio_request(KEY_GPIO, KEY_NAME);
if (ret < 0) return ret;
key_irq = gpio_to_irq(KEY_GPIO);
ret = request_irq(key_irq, key_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
KEY_NAME, &key_misc);
if (ret < 0) {
gpio_free(KEY_GPIO);
return ret;
}
ret = misc_register(&key_misc);
if (ret < 0) {
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
return ret;
}
printk("[rk3399-tasklet] 模块加载成功\n");
return 0;
}
static void __exit rk3399_tasklet_exit(void) {
tasklet_kill(&key_tasklet); // 注销tasklet
misc_deregister(&key_misc);
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
printk("[rk3399-tasklet] 模块卸载成功\n");
}
module_init(rk3399_tasklet_init);
module_exit(rk3399_tasklet_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 中断底半部-tasklet示例");
五、模块 4:底半部 - workqueue(rk3399 耗时任务处理)
1. 核心 API 汇总(共享队列 + 私有队列 + delayed_work)
| 类型 | API 函数 | 功能描述 | 参数说明 |
|---|---|---|---|
| 普通 work(共享队列) | INIT_WORK(work, func) | 动态初始化 work 对象 | work:work 对象;func:处理函数 |
schedule_work(work) | 调度 work 到内核共享队列 | work:work 对象指针 | |
cancel_work_sync(work) | 注销 work | work:work 对象指针 | |
| 私有队列 | create_workqueue(name) | 创建私有工作队列 | name:队列名称,返回队列指针 |
queue_work(wq, work) | 将 work 加入私有队列 | wq:私有队列指针;work:work 对象 | |
destroy_workqueue(wq) | 销毁私有队列 | wq:私有队列指针 | |
| 延迟 work | INIT_DELAYED_WORK(dwork, func) | 初始化延迟 work(带定时器) | dwork:delayed_work 对象;func:处理函数 |
schedule_delayed_work(dwork, delay) | 延迟调度 work(共享队列) | delay:延时(msecs_to_jiffies 转换) | |
cancel_delayed_work_sync(dwork) | 注销延迟 work | dwork:delayed_work 对象指针 |
2. 适用场景
| 队列类型 | 适用场景 | rk3399 平台示例 |
|---|---|---|
| 共享队列 | 非实时、不耗时的普通任务(省资源) | 日志打印、状态上报 |
| 私有队列 | 实时性要求高、避免阻塞的任务 | 传感器数据读取、外设控制 |
| 延迟 work | 延迟执行的耗时任务 | 中断后延迟 3 秒保存数据、定时清理缓存 |
3. rk3399 可运行代码片段(3 种 workqueue 示例)
(1)共享队列 - work(基础示例)
c
运行
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-work"
static int key_irq;
static char key_state = '0';
// 1. 定义work处理函数(进程上下文,可睡眠)
void key_work_func(struct work_struct *work) {
// 耗时任务:模拟延迟(workqueue支持msleep)
msleep(100);
printk("[rk3399-work] 共享队列:按键状态=%c,耗时处理完成\n", key_state);
}
// 2. 定义并初始化work对象
static DECLARE_WORK(key_work, key_work_func);
// 3. 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
printk("[rk3399-work] 顶半部:触发中断,调度共享work\n");
schedule_work(&key_work); // 加入内核共享队列
return IRQ_HANDLED;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read, // 复用之前的read函数
};
static struct miscdevice key_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = KEY_NAME,
.fops = &key_fops,
};
static int __init rk3399_work_init(void) {
int ret;
ret = gpio_request(KEY_GPIO, KEY_NAME);
if (ret < 0) return ret;
key_irq = gpio_to_irq(KEY_GPIO);
ret = request_irq(key_irq, key_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
KEY_NAME, &key_misc);
if (ret < 0) {
gpio_free(KEY_GPIO);
return ret;
}
ret = misc_register(&key_misc);
if (ret < 0) {
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
return ret;
}
printk("[rk3399-work] 共享队列模块加载成功\n");
return 0;
}
static void __exit rk3399_work_exit(void) {
cancel_work_sync(&key_work); // 注销work
misc_deregister(&key_misc);
free_irq(key_irq, &key_misc);
gpio_free(KEY_GPIO);
printk("[rk3399-work] 模块卸载成功\n");
}
module_init(rk3399_work_init);
module_exit(rk3399_work_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-共享队列示例");
(2)私有队列 - work(实时性要求高)
c
运行
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-private-work"
#define WQ_NAME "rk3399-private-wq"
static int key_irq;
static struct workqueue_struct *private_wq; // 私有队列
static struct work_struct key_work;
// work处理函数
void key_private_work_func(struct work_struct *work) {
char state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
printk("[rk3399-private-wq] 私有队列:按键状态=%c\n", state);
}
// 顶半部中断处理
irqreturn_t key_irq_handler(int irq, void *dev_id) {
printk("[rk3399-private-wq] 顶半部:触发中断,加入私有队列\n");
queue_work(private_wq, &key_work); // 加入私有队列
return IRQ_HANDLED;
}
static int __init rk3399_private_wq_init(void) {
int ret;
// 1. 创建私有工作队列
private_wq = create_workqueue(WQ_NAME);
if (!private_wq) {
printk("[rk3399-private-wq] 队列创建失败\n");
return -ENOMEM;
}
// 2. 初始化work对象
INIT_WORK(&key_work, key_private_work_func);
// 3. 申请GPIO和中断
ret = gpio_request(KEY_GPIO, KEY_NAME);
if (ret < 0) return ret;
key_irq = gpio_to_irq(KEY_GPIO);
ret = request_irq(key_irq, key_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
KEY_NAME, private_wq);
if (ret < 0) {
gpio_free(KEY_GPIO);
destroy_workqueue(private_wq);
return ret;
}
printk("[rk3399-private-wq] 私有队列模块加载成功\n");
return 0;
}
static void __exit rk3399_private_wq_exit(void) {
cancel_work_sync(&key_work);
destroy_workqueue(private_wq); // 销毁私有队列
free_irq(key_irq, private_wq);
gpio_free(KEY_GPIO);
printk("[rk3399-private-wq] 模块卸载成功\n");
}
module_init(rk3399_private_wq_init);
module_exit(rk3399_private_wq_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-私有队列示例");
(3)延迟 work(delayed_work)
c
运行
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#define DELAY_MS 3000 // 延迟3秒执行
static struct delayed_work delay_work;
// 延迟work处理函数
void delay_work_func(struct work_struct *work) {
printk("[rk3399-delay-work] 延迟3秒后执行:工作完成\n");
}
static int __init rk3399_delay_work_init(void) {
// 初始化延迟work
INIT_DELAYED_WORK(&delay_work, delay_work_func);
// 调度延迟work(3秒后执行)
schedule_delayed_work(&delay_work, msecs_to_jiffies(DELAY_MS));
printk("[rk3399-delay-work] 模块加载成功,延迟3秒执行任务\n");
return 0;
}
static void __exit rk3399_delay_work_exit(void) {
cancel_delayed_work_sync(&delay_work); // 注销延迟work
printk("[rk3399-delay-work] 模块卸载成功\n");
}
module_init(rk3399_delay_work_init);
module_exit(rk3399_delay_work_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-延迟work示例");
六、rk3399 平台编译与运行脚本
1. Makefile(通用模板,需修改内核路径)
makefile
obj-m += rk3399_key.o rk3399_key_debounce.o rk3399_tasklet.o rk3399_work.o rk3399_private_wq.o rk3399_delay_work.o
KERNELDIR ?= /home/user/rk3399-linux-5.4.y # 替换为你的rk3399内核源码路径
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
2. 运行步骤
bash
运行
# 1. 编译(需安装aarch64交叉编译器)
make
# 2. 拷贝.ko文件到rk3399开发板(如通过scp)
scp *.ko root@rk3399-ip:/root/
# 3. 开发板上加载模块(以按键消抖为例)
insmod rk3399_key_debounce.ko
# 4. 查看日志
dmesg | grep rk3399
# 5. 应用层读取按键状态
cat /dev/rk3399-key-debounce
# 6. 卸载模块
rmmod rk3399_key_debounce
这就是本文的全部内容,也感谢耐心看到这里的读者,我会持续更新,希望你能够多多关注,如果本文有帮组到你的话,还请三连加关注,你的支持就是我创作的最大动力!希望这份手册能帮你少走弯路,欢迎在评论区补充你的调试经验!
743

被折叠的 条评论
为什么被折叠?



