Linux 输入子系统实战:从零构建Rotary Encoder驱动(GPIO+设备树)

张开发
2026/4/18 7:14:40 15 分钟阅读

分享文章

Linux 输入子系统实战:从零构建Rotary Encoder驱动(GPIO+设备树)
1. 旋转编码器与Linux输入子系统基础第一次接触旋转编码器是在一个智能家居控制面板项目上。当时需要在嵌入式Linux系统中实现一个旋钮控制音量功能选用了常见的EC11旋转编码器。这种编码器通过两个相位差90度的信号输出旋转方向和步数硬件连接简单只需要两个GPIO引脚。Linux内核自带了旋转编码器的通用驱动rotary-encoder.c但网上能找到的资料基本都是官方文档的简单翻译缺少完整的实战教程。经过几天的摸索我总结出了这套从硬件连接到软件识别的完整流程。输入子系统是Linux对各类输入设备的抽象层。想象它是一个邮局系统设备驱动层如旋转编码器驱动就像各地的邮局分局负责收集原始邮件硬件信号核心层是邮局总部的分拣中心负责标准化处理事件处理层则是最终投递到用户手中的信件对于旋转编码器这类设备内核已经帮我们实现了90%的工作我们只需要正确配置设备树相当于给邮局写清楚收件地址确保GPIO和中断正常工作保证邮路畅通在用户空间读取事件最终收到信件2. 硬件连接与设备树配置我使用的开发板是i.MX6Q选择GPIO2_IO22和GPIO2_IO18两个引脚连接EC11的A、B相。这两个引脚需要支持中断触发能检测上升沿和下降沿未被其他功能占用设备树配置是关键步骤常见问题都出在这里。完整配置如下iomuxc { pinctrl_rotary: rotarygrp { fsl,pins MX6QDL_PAD_EIM_A16__GPIO2_IO22 0x1b0b0 MX6QDL_PAD_EIM_A20__GPIO2_IO18 0x1b0b0 ; }; }; rotary_encoder: rotary_encoder { compatible rotary-encoder; gpios gpio2 22 1, gpio2 18 1; linux,axis 0; /* REL_X */ rotary-encoder,relative-axis; pinctrl-names default; pinctrl-0 pinctrl_rotary; interrupt-parent gpio2; interrupts 22 IRQ_TYPE_EDGE_BOTH, 18 IRQ_TYPE_EDGE_BOTH; };几个容易出错的点gpios参数中最后的1表示GPIO默认高电平EC11常见配置interrupts必须配置为双边沿触发IRQ_TYPE_EDGE_BOTH一定要检查引脚复用情况避免冲突3. 内核驱动配置与编译在内核配置中启用旋转编码器驱动make menuconfig路径Device Drivers → Input device support → Generic input layer → Rotary encoders编译后可以在drivers/input/misc/rotary-encoder.c找到源码。驱动工作原理是通过设备树获取GPIO和中断配置初始化input_dev结构体注册中断处理函数检测旋转方向通过input子系统上报REL_X事件1或-1如果驱动没有自动加载可以手动加载echo rotary-encoder /sys/bus/platform/drivers_probe验证驱动是否成功加载ls /sys/bus/platform/devices/rotary_encoder/ cat /proc/bus/input/devices应该能看到类似这样的输出I: Bus0019 Vendor0000 Product0000 Version0000 N: Namerotary-encoder P: Phys S: Sysfs/devices/platform/rotary_encoder/input/input2 U: Uniq H: Handlersevent24. 用户空间测试与调试输入设备在用户空间表现为/dev/input/eventX节点。可以用evtest工具测试evtest /dev/input/event2旋转编码器时应该能看到类似输出Event: time 162000.123456, type 2 (EV_REL), code 0 (REL_X), value 1 Event: time 162000.123567, -------------- SYN_REPORT ------------ Event: time 162000.234567, type 2 (EV_REL), code 0 (REL_X), value -1如果没有输出按这个顺序排查检查dmesg看驱动probe是否成功用gpiodetect和gpiomon确认GPIO信号正常测量硬件电压是否稳定EC11通常需要上拉电阻检查设备树配置是否正确应用cat /proc/device-tree/rotary_encoder/gpios5. 实际应用开发在应用程序中标准的读取方式是通过libinput库或直接读取input事件。这里给出一个简单的C示例#include linux/input.h #include fcntl.h #include unistd.h int main() { struct input_event ev; int fd open(/dev/input/event2, O_RDONLY); while(1) { read(fd, ev, sizeof(ev)); if(ev.type EV_REL ev.code REL_X) { printf(Rotation: %s\n, ev.value 0 ? CW : CCW); } } close(fd); return 0; }对于需要精确计数的场景建议添加互斥锁防止多线程竞争使用epoll避免忙等待考虑防抖处理硬件或软件实现6. 进阶技巧与问题排查性能优化修改设备树中的debounce-interval减少抖动使用IRQF_ONESHOT标志优化中断处理考虑高精度定时器替代GPIO中断常见问题解决漏计数检查中断触发方式确保是双边沿方向相反交换设备树中gpios的两个引脚顺序无反应确认/sys/kernel/debug/gpio显示GPIO状态变化扩展功能通过rotary-encoder,steps参数实现绝对位置模式结合LED子系统实现旋钮背光控制添加按键功能EC11通常带下压按键记得每次修改设备树后要重新编译dtb并确保正确加载。通过这套方案我们成功在多个产品中实现了稳定可靠的旋钮控制累计出货量已超过10万台。

更多文章