news 2026/4/23 14:18:20

学习笔记——按键驱动代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
学习笔记——按键驱动代码

按键驱动代码

一、基本按键驱动 (key.c)

1. 驱动框架概述

这是一个基于平台驱动框架的按键驱动程序,采用GPIO子系统杂项设备架构。

2. 关键代码分析

(1) 文件操作结构定义
static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .write = write, .release = close };
  • owner:模块所有者,固定为THIS_MODULE

  • open:设备打开时的处理函数

  • read:从设备读取数据(获取按键状态)

  • write:向设备写入数据(按键驱动通常不需要)

  • release:设备关闭时的处理函数

(2) 获取按键状态函数
static inline int get_key_status(void) { return gpio_get_value(key_gpio); }
  • inline:内联函数,编译器会直接将函数体插入调用处,减少函数调用开销

  • gpio_get_value():读取GPIO引脚的电平状态

  • 返回值:0表示低电平,1表示高电平(具体取决于按键硬件连接)

(3) 读取函数实现
static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff) { int ret = 0; int status = get_key_status(); // 获取按键状态 ret = copy_to_user(buf, &status, 4); // 将状态复制到用户空间 printk("key read\n"); return ret; }
  • 流程:读取GPIO状态 → 通过copy_to_user()传递给应用程序

  • copy_to_user():内核空间到用户空间的数据复制

  • 返回值:成功复制到用户空间的字节数

(4) 平台驱动探测函数 (probe)
static int probe(struct platform_device * pdev) { // 1. 注册杂项设备 int ret = misc_register(&misc_dev); // 2. 查找设备树节点 pdts = of_find_node_by_path("/ptkey"); // 3. 获取GPIO编号 key_gpio = of_get_named_gpio(pdts, "ptkey-gpio", 0); // 4. 申请GPIO ret = gpio_request(key_gpio, "key"); // 5. 配置GPIO为输入模式 gpio_direction_input(key_gpio); }

关键步骤说明

  1. 设备树节点查找:查找路径为/ptkey的设备树节点

  2. GPIO获取:从设备树中获取名为ptkey-gpio的GPIO编号

  3. GPIO申请:申请使用该GPIO引脚,命名为 "key"

  4. 方向设置:配置为输入模式(按键是输入设备)

(5) 驱动匹配表
static struct of_device_id key_table[] = { {.compatible = "pt-key"}, {} };
  • compatible:与设备树节点中的compatible属性匹配

  • 匹配流程

    text

    设备树节点:compatible = "pt-key" ↓ 驱动匹配表:compatible = "pt-key" ↓ 匹配成功 → 执行probe函数

3. 应用程序 (key_app.c)

int main(int argc, const char *argv[]) { // 1. 打开设备 int fd = open("/dev/key", O_RDWR); // 2. 循环读取按键状态 while(1) { int ret = read(fd, &status, sizeof status); printf("ret = %d status = %d\n", ret, status); } // 3. 关闭设备 close(fd); }

执行流程

  1. 打开/dev/key设备文件

  2. 循环读取按键状态

  3. 打印状态信息

  4. 关闭设备

二、中断版本按键驱动 (key_irq.ckey_irq1.ckey_irq_sub.c)

1. 与基本版本的主要区别

基本版本采用轮询方式读取按键状态,而中断版本采用中断方式响应按键事件。

2. 中断处理核心机制

(1) 中断相关数据结构
static wait_queue_head_t wq; // 等待队列头 static int condition; // 条件变量(表示中断是否发生) static int key_irq; // 中断号
(2) 中断处理函数
static irqreturn_t key_irq_handler(int irq, void * dev) { int arg = *(int *)dev; if(100 != arg) // 验证设备参数 return IRQ_NONE; // 不是本设备的中断 condition = 1; // 设置条件为真 wake_up_interruptible(&wq); // 唤醒等待队列 printk("irq = %d dev = %d\n", irq, arg); return IRQ_HANDLED; // 中断已处理 }

中断处理流程

  1. 参数验证:检查是否是本设备的中断

  2. 设置条件:将condition设为1,表示中断已发生

  3. 唤醒等待:唤醒在等待队列上睡眠的进程

  4. 返回状态:返回IRQ_HANDLED表示中断已处理

返回值说明

  • IRQ_HANDLED:中断已成功处理

  • IRQ_NONE:不是本设备的中断,或者中断处理失败

(3) 阻塞式读取函数
static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff) { int ret = 0; int status = 0; printk("key read\n"); condition = 0; // 重置条件 wait_event_interruptible(wq, condition); // 等待条件成立(中断发生) status = 1; // 按键按下 ret = copy_to_user(buf, &status, sizeof(status)); return sizeof(status); }

关键函数

  • wait_event_interruptible(wq, condition)

    • 使当前进程进入睡眠状态

    • condition为真时被唤醒

    • 可被信号中断

执行流程

  1. 重置条件变量 (condition = 0)

  2. 进入等待状态,直到中断发生(condition被设为1)

  3. 被唤醒后,返回按键状态给用户空间

3. 中断注册的三种方式

方式1:key_irq.c/key_irq1.c
// 1. 获取GPIO对应的中断号 key_irq = gpio_to_irq(key_gpio); // 2. 申请中断 ret = request_irq(key_irq, key_irq_handler, IRQF_TRIGGER_FALLING, "key0_irq", &arg);

特点

  • gpio_to_irq():将GPIO编号转换为中断号

  • IRQF_TRIGGER_FALLING:下降沿触发(按键通常按下时为低电平)

方式2:key_irq_sub.c

c

// 1. 从设备树解析中断号 key_irq = irq_of_parse_and_map(pdts, 0); // 2. 申请中断 ret = request_irq(key_irq, key_irq_handler, IRQF_TRIGGER_FALLING, "key0_irq", &arg);

特点

  • irq_of_parse_and_map():从设备树节点解析中断号和映射

  • 设备树中需要定义中断相关属性

三种版本对比
文件中断获取方式特点
key_irq.cgpio_to_irq()需要先获取GPIO编号
key_irq1.cgpio_to_irq()key_irq.c,代码基本一致
key_irq_sub.cirq_of_parse_and_map()直接从设备树获取中断信息

4. 中断申请参数详解

request_irq(key_irq, // 中断号 key_irq_handler, // 中断处理函数 IRQF_TRIGGER_FALLING, // 触发方式 "key0_irq", // 设备名(出现在/proc/interrupts) &arg); // 传递给中断处理函数的设备参数

触发方式标志

  • IRQF_TRIGGER_RISING:上升沿触发

  • IRQF_TRIGGER_FALLING:下降沿触发

  • IRQF_TRIGGER_HIGH:高电平触发

  • IRQF_TRIGGER_LOW:低电平触发

5. 等待队列机制

(1) 初始化等待队列
init_waitqueue_head(&wq); // 在probe函数中初始化
(2) 等待和唤醒函数
// 等待条件成立(可被信号中断) wait_event_interruptible(wq, condition); // 唤醒等待队列(中断处理函数中调用) wake_up_interruptible(&wq);

工作机制

  1. 应用程序调用read()时,如果没有按键事件,进程进入睡眠

  2. 按键按下触发中断,中断处理函数设置condition = 1并唤醒进程

  3. 进程被唤醒,读取按键状态并返回给应用程序

6. 资源清理 (remove函数)

static int remove(struct platform_device * pdev) { disable_irq(key_irq); // 禁用中断 free_irq(key_irq, &arg); // 释放中断 gpio_free(key_gpio); // 释放GPIO misc_deregister(&misc_dev); // 注销杂项设备 }

清理顺序:与初始化顺序相反,确保安全释放资源

三、两种模式的对比

轮询模式 (key.c)

特点说明
实现方式应用程序主动读取GPIO状态
CPU占用高(需要不断循环读取)
响应速度取决于读取频率
适用场景简单应用,不频繁按键

中断模式 (key_irq*.c)

特点说明
实现方式GPIO中断触发,进程阻塞等待
CPU占用低(进程睡眠,不占用CPU)
响应速度实时响应(微秒级)
适用场景需要实时响应的应用

四、设备树配置示例

轮询模式设备树

ptkey { compatible = "pt-key"; ptkey-gpio = <&gpio1 5 GPIO_ACTIVE_LOW>; // GPIO1第5脚,低电平有效 };

中断模式设备树

ptkey { compatible = "pt-key"; ptkey-gpio = <&gpio1 5 GPIO_ACTIVE_LOW>; interrupts = <GIC_SPI 66 IRQ_TYPE_EDGE_FALLING>; // 中断配置 interrupt-parent = <&gpio1>; };

总结

  1. 基本按键驱动:通过轮询方式读取GPIO状态,实现简单但效率低

  2. 中断按键驱动:通过中断机制响应按键事件,效率高且实时性好

  3. 等待队列:实现进程的阻塞和唤醒,是中断驱动中的关键机制

  4. 两种中断获取方式gpio_to_irq()irq_of_parse_and_map()

  5. 应用程序:统一通过文件操作接口(open/read/close)与驱动交互

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 14:12:23

Google Antigrgravity支持Agent Skills:AI编程的新革命

Google Antigrgravity支持Agent Skills功能&#xff0c;通过简单的SKILL.md文件指导AI完成任务。测试显示&#xff0c;Claude Code、Codex和Antigravity等平台在基础功能上表现一致。Skills具有三大价值&#xff1a;一次编写全平台通用、降低创作门槛、启动生态效应。随着模型能…

作者头像 李华
网站建设 2026/4/13 13:30:28

15分钟教会AI记住你的工作方式,Agent Skills完全指南

Agent Skills是将知识和流程打包成可重用技能的技术&#xff0c;解决了AI提示词反复输入的问题。通过标准化"技能文件夹"(含用途说明、执行步骤和参考资料)&#xff0c;使AI能像可靠同事一样&#xff0c;学习一次后持续稳定执行任务。带来稳定输出、经验复用和团队传…

作者头像 李华
网站建设 2026/4/12 3:46:50

长周期Agent落地难题全攻略:从双Agent架构到记忆恢复机制,一篇搞定

本文探讨了长周期Agent落地的两大失效模式及其根源——任务切分粒度不准导致上下文传递断裂。作者提出双Agent架构(InitializerCoding)解决任务分工&#xff0c;通过向量数据库实现跨会话状态恢复&#xff0c;并以测试驱动功能验证。实操示例展示了LangGraph与Milvus协同实现&q…

作者头像 李华
网站建设 2026/4/21 9:05:20

multipart/form-data,深度解析

在Flask开发中&#xff0c;处理文件上传和复杂表单数据是常见需求&#xff0c;这直接涉及到对multipart/form-data格式的解析。下面我将从五个方面对此进行说明。1. 它是什么&#xff1a;数据的“混合快递包裹”你可以把HTTP请求中的multipart/form-data格式想象成一个快递包裹…

作者头像 李华
网站建设 2026/4/18 10:15:47

streaming-form-data 库,深度详解

在处理文件上传&#xff0c;尤其是大文件时&#xff0c;服务器通常需要等整个文件都从网络传过来&#xff0c;在内存里组装好&#xff0c;才能开始处理。这就像等一整条生产线组装完一辆汽车&#xff0c;才能开始检查&#xff0c;非常低效且占用大量资源。streaming-form-data …

作者头像 李华