按键驱动代码
一、基本按键驱动 (key.c)
1. 驱动框架概述
这是一个基于平台驱动框架的按键驱动程序,采用GPIO子系统和杂项设备架构。
2. 关键代码分析
(1) 文件操作结构定义
static struct file_operations fops = { .owner = THIS_MODULE, .open = open, .read = read, .write = write, .release = close };owner:模块所有者,固定为
THIS_MODULEopen:设备打开时的处理函数
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); }关键步骤说明:
设备树节点查找:查找路径为
/ptkey的设备树节点GPIO获取:从设备树中获取名为
ptkey-gpio的GPIO编号GPIO申请:申请使用该GPIO引脚,命名为 "key"
方向设置:配置为输入模式(按键是输入设备)
(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); }执行流程:
打开
/dev/key设备文件循环读取按键状态
打印状态信息
关闭设备
二、中断版本按键驱动 (key_irq.c、key_irq1.c、key_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; // 中断已处理 }中断处理流程:
参数验证:检查是否是本设备的中断
设置条件:将
condition设为1,表示中断已发生唤醒等待:唤醒在等待队列上睡眠的进程
返回状态:返回
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为真时被唤醒可被信号中断
执行流程:
重置条件变量 (
condition = 0)进入等待状态,直到中断发生(
condition被设为1)被唤醒后,返回按键状态给用户空间
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.c | gpio_to_irq() | 需要先获取GPIO编号 |
key_irq1.c | gpio_to_irq() | 同key_irq.c,代码基本一致 |
key_irq_sub.c | irq_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);
工作机制:
应用程序调用
read()时,如果没有按键事件,进程进入睡眠按键按下触发中断,中断处理函数设置
condition = 1并唤醒进程进程被唤醒,读取按键状态并返回给应用程序
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>; };总结:
基本按键驱动:通过轮询方式读取GPIO状态,实现简单但效率低
中断按键驱动:通过中断机制响应按键事件,效率高且实时性好
等待队列:实现进程的阻塞和唤醒,是中断驱动中的关键机制
两种中断获取方式:
gpio_to_irq()和irq_of_parse_and_map()应用程序:统一通过文件操作接口(open/read/close)与驱动交互