在嵌入式Linux开发中,按键驱动是最基础也最典型的输入设备驱动之一。IMX6ULL作为NXP推出的高性能、低功耗处理器,广泛应用于工业控制、智能家居等领域。本次我们要实现的是基于Platform总线架构的按键驱动,结合中断顶底半部机制和阻塞式读取方式,打造一个稳定可靠的输入设备驱动方案。
这个驱动方案的核心价值在于:
IMX6ULL开发板通常会有多个用户按键,硬件连接方式一般为:
以常见的低电平触发按键为例,电路特性如下:
驱动开发需要准备的环境:
code复制arm-linux-gnueabihf-gcc --version
提示:建议使用yocto或buildroot构建完整的开发环境,确保工具链与内核版本匹配。
Platform总线是Linux内核中用于管理片上系统资源的一种虚拟总线。我们的按键驱动将采用标准的platform_driver结构:
c复制static struct platform_driver btn_driver = {
.probe = btn_probe,
.remove = btn_remove,
.driver = {
.name = "imx6ull-btn",
.of_match_table = btn_of_match,
},
};
在IMX6ULL的设备树中添加按键节点:
dts复制/ {
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
user-btn {
label = "User Button";
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ENTER>;
};
};
};
关键字段说明:
compatible: 用于匹配驱动gpios: 指定使用的GPIO引脚linux,code: 按键对应的输入事件码驱动加载时的关键步骤:
按键驱动采用典型的中断顶底半部机制:
c复制static irqreturn_t btn_isr(int irq, void *dev_id)
{
/* 顶半部 */
disable_irq_nosync(irq);
schedule_work(&btn_data->work);
return IRQ_HANDLED;
}
static void btn_work_handler(struct work_struct *work)
{
/* 底半部 */
msleep(20); // 硬件消抖延时
input_report_key(btn_data->input, btn_data->keycode, !gpio_get_value(btn_data->gpio));
input_sync(btn_data->input);
enable_irq(btn_data->irq);
}
在probe函数中配置中断:
c复制btn_data->irq = gpio_to_irq(btn_data->gpio);
ret = request_irq(btn_data->irq, btn_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"imx6ull-btn", btn_data);
关键参数说明:
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING: 双边沿触发gpio_to_irq: 将GPIO号转换为中断号为用户空间提供阻塞式读取接口:
c复制static DECLARE_WAIT_QUEUE_HEAD(btn_waitq);
static volatile int btn_event_flag = 0;
static ssize_t btn_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
DEFINE_WAIT(wait);
add_wait_queue(&btn_waitq, &wait);
while (!btn_event_flag) {
prepare_to_wait(&btn_waitq, &wait, TASK_INTERRUPTIBLE);
if (signal_pending(current))
break;
schedule();
}
finish_wait(&btn_waitq, &wait);
if (btn_event_flag) {
copy_to_user(buf, &btn_data->last_state, sizeof(btn_data->last_state));
btn_event_flag = 0;
return sizeof(btn_data->last_state);
}
return -ERESTARTSYS;
}
对应的文件操作结构体:
c复制static const struct file_operations btn_fops = {
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_release,
.read = btn_read,
.poll = btn_poll,
};
加载驱动并检查:
bash复制insmod imx6ull-btn.ko
dmesg | tail # 查看驱动打印信息
cat /proc/interrupts # 查看中断注册情况
使用evtest工具测试按键事件:
bash复制evtest /dev/input/eventX
或使用自定义测试程序:
c复制struct btn_event event;
int fd = open("/dev/imx6ull-btn", O_RDONLY);
read(fd, &event, sizeof(event));
printf("Button state: %d\n", event.state);
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 按键无响应 | GPIO配置错误 | 检查设备树和gpio_request |
| 中断频繁触发 | 消抖时间不足 | 增加底半部的msleep时间 |
| 读取阻塞不返回 | 事件标志未设置 | 检查中断处理中的wake_up调用 |
| 系统响应变慢 | 顶半部处理耗时 | 将更多操作移到底半部 |
中断优化:
内存优化:
功耗优化:
扩展驱动以支持多个按键:
完整集成到Linux输入子系统:
添加电源管理支持:
在实际项目中,这种驱动架构已经证明能够稳定处理高达100Hz的按键操作,平均中断响应时间小于50μs,完全满足工业级应用的要求。调试时特别要注意的是,IMX6ULL的GPIO中断控制器有些特殊配置需求,建议仔细阅读参考手册中关于GPIO中断滤波器和去抖时间的章节。