Linux下EC11旋转编码器驱动调试
文章目录
- 1、前言
- 2、使用gpio-keys驱动
- 2.1、dts配置
- 2.2、识别原理
- 2.3、应用层驱动实现
- 2.4、编译测试
- 3、使用rotary-encoder驱动
- 3.1、dts配置
- 3.2、app测试程序编写
- 3.3、编译测试
- 4、总结
1、前言
本来是没有这篇文章的。最近在rk3576下调试ec11旋转编码器时,一直没有效果,或者一开始可以,之后又不行了。首先我使用的ec11是基于AB相输出的,其次rk3576连接到AB相的引脚不是原生IO脚,是通过xl9535 gpio扩展芯片连接的,关于bug的调试可以参考《Linux下xl9535 gpio扩展芯片bug调试》。本文介绍基于原生IO引脚的ec11旋转编码器调试,总共有两种方式,一种是基于gpio-keys驱动,一种基于rotary-encoder驱动,这两个驱动都是内核自带的。
2、使用gpio-keys驱动
gpio-keys是按键驱动。所以使用此种方法只是把A相和B相的输出分别当作一个按键,并注册进input子系统。最终是在应用层实现驱动。
2.1、dts配置
# ec11的按键
Rotary_SW {compatible = "gpio-keys";status = "okay";#address-cells = <1>;#size-cells = <0>;rotary_sw {label = "rotary_sw";linux,code=<KEY_0>;debounce-interval = <5>;gpios = <&gpio3 6 GPIO_ACTIVE_LOW>;interrupt-parent = <&gpio3>;interrupts = <6 IRQ_TYPE_LEVEL_LOW>;};
};Rotary_A {compatible = "gpio-keys";status = "okay";#address-cells = <1>;#size-cells = <0>;rotary_a {label = "rotary_a";linux,code=<250>;debounce-interval = <1>;gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio4>;interrupts = <6 IRQ_TYPE_EDGE_RISING>;};
};Rotary_B {compatible = "gpio-keys";status = "okay";#address-cells = <1>;#size-cells = <0>;rotary_b {label = "rotary_b";linux,code=<251>;debounce-interval = <1>;gpios = <&gpio4 RK_PA4 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio4>;interrupts = <4 IRQ_TYPE_EDGE_RISING>;};
};
属性含义可参考:https://www.kernel.org/doc/Documentation/devicetree/bindings/input/gpio-keys.txt
2.2、识别原理
A相和B相输出的信号存在90度的相位差。当编码器顺时针旋转时,A相的信号会比B相的信号超前90度;而当编码器逆时针旋转时,A相的信号会比B相的信号滞后90度。
当检测到A相从低电平变为高电平时,如果此时B相为低电平,则编码器顺时针旋转:

当检测到B相从低电平变为高电平时,如果此时A相为低电平,则编码器逆时针旋转:

2.3、应用层驱动实现
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>#define EC11_SW_EVENT "/dev/input/event7"
#define EC11_A_EVENT "/dev/input/event6"
#define EC11_B_EVENT "/dev/input/event5" // 线程ID
pthread_t ec11_sw_obj, ec11_a_obj, ec11_b_obj; // 文件描述符
int ec11_sw_fd, ec11_a_fd, ec11_b_fd;
int count = 0; // 累计步数// 编码器状态变量
int ec11_sw_value = 0;
int ec11_a_value = 1;
int ec11_b_value = 1;
int ec11_direction = 0; // 0:不动作 1:顺时针旋转 2:逆时针旋转 3:按键按下顺时针旋转 4:按键按下逆时针旋转 5:按键按下
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void sigint_handler(int sig_num)
{ pthread_cancel(ec11_sw_obj);pthread_cancel(ec11_a_obj);pthread_cancel(ec11_b_obj);close(ec11_sw_fd); close(ec11_a_fd);close(ec11_a_fd);pthread_join(ec11_sw_obj, NULL);pthread_join(ec11_a_obj, NULL);pthread_join(ec11_b_obj, NULL);printf("all thread exit\n");exit(0);
}void *ec11_scan_thread(void *arg)
{ int fd = *(int*)arg; struct input_event ie; while (1) { if (read(fd, &ie, sizeof(struct input_event)) == sizeof(struct input_event)) { if (ie.type == EV_KEY) { pthread_mutex_lock(&lock); // 处理按键事件if (fd == ec11_sw_fd && ie.code == 4) { ec11_sw_value = ie.value; if (ec11_sw_value == 1 && ec11_a_value == 1 && ec11_b_value == 1) // 按键按下ec11_direction = 5; } // 处理A相事件else if (fd == ec11_a_fd && ie.code == 250) { if (ie.value == 0 && ec11_b_value == 1) // 顺时针旋转{ ec11_a_value = 0; ec11_direction = (ec11_sw_value == 1) ? 3 : 1; // 判断按键状态 } else if (ie.value == 1) ec11_a_value = 1; } // 处理B相事件else if (fd == ec11_b_fd && ie.code == 251) { if (ie.value == 0 && ec11_a_value == 1) // 逆时针旋转{ ec11_b_value = 0; ec11_direction = (ec11_sw_value == 1) ? 4 : 2; // 判断按键状态 } else if (ie.value == 1) ec11_b_value = 1; } pthread_mutex_unlock(&lock); } } }
} int main(int argc, char **argv)
{ int ret;ec11_sw_fd = open(EC11_SW_EVENT, O_RDONLY); ec11_a_fd = open(EC11_A_EVENT, O_RDONLY); ec11_b_fd = open(EC11_B_EVENT, O_RDONLY); if (ec11_sw_fd < 0 || ec11_a_fd < 0 || ec11_b_fd < 0) { printf("Failed to open input device\n"); return -1; }pthread_create(&ec11_sw_obj, NULL, ec11_scan_thread, &ec11_sw_fd); pthread_create(&ec11_a_obj, NULL, ec11_scan_thread, &ec11_a_fd); pthread_create(&ec11_b_obj, NULL, ec11_scan_thread, &ec11_b_fd); signal(SIGINT, sigint_handler); // 注册信号处理函数 while (1) { pthread_mutex_lock(&lock); switch (ec11_direction) { case 1: count++;printf("顺时针转 : %d\n", count); break; case 2: count--;printf("逆时针转 : %d\n", count); break; case 3: count++;printf("按键按下顺时针转 : %d\n", count); break; case 4: count--;printf("按键按下逆时针转 : %d\n", count); break; case 5: printf("按键按下\n"); break; default: break; } ec11_direction = 0; pthread_mutex_unlock(&lock); usleep(10000); } return 0;
}
2.4、编译测试
# 如果使用buildroot系统,需要交叉编译。
# 这里使用的是ubuntu系统,直接使用gcc编译。
gcc -o build ec11.c

3、使用rotary-encoder驱动
rotary-encoder驱动是一个在内核态实现旋转编码器驱动。可以输出相对位置或者绝对位置,以下以输出相对位置举例。最终也是注册进input子系统。
3.1、dts配置
rotary@0 {compatible = "rotary-encoder"; gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>, <&gpio4 RK_PA4 GPIO_ACTIVE_HIGH>;linux,axis = <0>; /* REL_X */rotary-encoder,encoding = "gray";rotary-encoder,relative-axis;interrupt-parent = <&gpio4>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_rotary>;
};&pinctrl {rotary {pinctrl_rotary:pinctrl_rotary {rockchip,pins = <4 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none4 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none>;};};
}
属性含义可参考:https://www.kernel.org/doc/Documentation/devicetree/bindings/input/rotary-encoder.txt
3.2、app测试程序编写
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>int main(int argc, char *argv[])
{struct input_event ie;int fd;if (argc != 2) {fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);exit(-1);}fd = open(argv[1], O_RDONLY);if (fd < 0) {fprintf(stderr, "can not open %s\n", argv[1]);exit(-1);}while (1){if (read(fd, &ie, sizeof(struct input_event)) == sizeof(struct input_event))printf("type:%d code:%d value:%d\n", ie.type, ie.code, ie.value);}
}
3.3、编译测试
# 如果使用buildroot系统,需要交叉编译。
# 这里使用的是ubuntu系统,直接使用gcc编译。
gcc -o build ec11_app.c

4、总结
参考文章:
Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器驱动(EC11)通用GPIO为例 挂载input输入子系统_rotary encoder with display-CSDN博客
相关文章:
Linux下EC11旋转编码器驱动调试
文章目录 1、前言2、使用gpio-keys驱动2.1、dts配置2.2、识别原理2.3、应用层驱动实现2.4、编译测试 3、使用rotary-encoder驱动3.1、dts配置3.2、app测试程序编写3.3、编译测试 4、总结 1、前言 本来是没有这篇文章的。最近在rk3576下调试ec11旋转编码器时,一直没…...
【无标题】Java的基础准备
一、cmd的常见命令 盘符名称冒号 说明:盘符切换dir 说明:查看当前路径下的内容cd目录 说明:进入单极目录cd.. 说明:回退到上一级目录cd目录1\目录2\... 说明:进入多级目录cd\ …...
【数学建模】(启发式算法)遗传算法:自然选择的计算模型
遗传算法:自然选择的计算模型 文章目录 遗传算法:自然选择的计算模型1. 引言2. 遗传算法的基本原理2.1 基本概念2.2 算法流程 3. 编码方式3.1 二进制编码3.2 实数编码3.3 排列编码 4. 选择操作4.1 轮盘赌选择4.2 锦标赛选择4.3 精英保留策略 5. 交叉操作…...
嵌入式八股,static在Linux驱动编写时的用处
1. 限制作用域 static关键字可以用来限制函数或变量的作用域,使其只能在当前文件内被访问。这有助于避免命名冲突,并提高代码的模块化和可维护性。 只能在当前文件里访问,或调用当前文件里有的函数。 // 文件 A.h static int globalVar 1…...
RCE——回调后门
目录 rce简述 rce漏洞 rce漏洞产生分类 rce漏洞级别 创造tips的秘籍——回调后门 call_user_func 解析 如何执行后门 call_user_func_array array_filter、array_map 解析 如何执行后门 php5.4.8中的assert——二参数的回调函数 uasort uksort array_reduce() …...
JavaScript 调试入门指南
JavaScript 调试入门指南 一、调试准备阶段 1. 必备工具配置 浏览器套件:安装最新Chrome102+,开启实验性功能(地址栏输入chrome://flags/#enable-devtools-experiments)编辑器集成:VS Code安装以下扩展: JavaScript Debugger:支持浏览器与Node.js双端调试Error Lens:实…...
字节真题,问a,b,c指的地址是否相同?
题目: class A{ int a, int d } class B { int b }class C: public A,public B { int b } C* c new C; A* a c; B* b c; 问a,b,c指的地址是否相同? 在 C 中,由于类的继承关系以及内存布局的规则,a、b 和 c 指针的地址可能不…...
2025年03月18日柯莱特(外包宁德)一面前端面试
目录 自我介绍你怎么从0到1搭建项目的webpack 的构建流程手写webpack插件你有什么想问我的吗 2. 你怎么从 0 到 1 搭建项目的 在面试中回答从 0 到 1 搭建前端项目,可按以下详细步骤阐述: 1. 项目前期准备 需求理解与分析 和产品经理、客户等相关人…...
OpenGL ES 2.0与OpenGL ES 3.1的区别
如果硬件支持且需要更高质量的图形效果,推荐3.1;如果兼容性和开发简便更重要,且效果需求不高,2.0更合适。不过现代车载系统可能越来越多支持3.x版本,所以可能倾向于使用3.1,但具体情况还需调查目标平台的硬…...
Unity Shader 学习17:合批渲染
一、基础概念 合批主要是针对这三个概念进行优化减少: ① SetPass Call:一次渲染状态切换,也就是每次切换 材质/Pass 时,就会触发一次SetPass Call ② Draw Call:cpu 调用一次 gpu 绘制函数 ③ Batch:表示…...
带你从入门到精通——自然语言处理(十. BERT)
建议先阅读我之前的博客,掌握一定的自然语言处理前置知识后再阅读本文,链接如下: 带你从入门到精通——自然语言处理(一. 文本的基本预处理方法和张量表示)-CSDN博客 带你从入门到精通——自然语言处理(二…...
vue3 数据监听(watch、watchEffect)
1、watch 1.1基本使用 作用:数据监听 语法: watch(监听的数据, (改变后的数据, 改变前的数据) > { console.log(newVal, oldVal); }) 注意点:watch写法上支持一个或者多个监听源,这些监听源必须只能是getter/effect函数…...
Vue 3中的Teleport:超越组件边界的渲染
Vue 3引入了许多新特性,其中之一便是Teleport。它为开发者提供了一种强有力的方式来控制组件的渲染位置,使得我们可以将组件的内容“传送”到DOM树的任何地方,而不仅仅局限于其父级组件的边界内。这在创建模态框、通知系统或任何需要脱离当前…...
【计算机网络】DHCP工作原理
DHCP(动态主机配置协议) Dynamic Host Configuration Protocol 基于UDP协议传输 DHCP分配IP地址的过程 (1)DHCP DISCOVER客户机请求 IP 地址: 当一个 DHCP 客户机启动时,客户机还没有 IP 地址,所以客户机要通过 DHC…...
Linux网站搭建(新手必看)
1.宝塔Linux面板的功能 宝塔面板是一款服务器管理软件,可以帮助用户建立网站,一键配置服务器环境,使得用户通过web界面就可以轻松的管理安装所用的服务器软件。 2. 宝塔Linux面板的安装 宝塔官网地址:宝塔面板 - 简单好用的Linu…...
JVM - 年轻代和老年代
通过一些问题来讨论 JVM 中年轻代和老年代的内容 为什么要区分年轻代和老年代?哪些对像会进入老年代?什么时候会进行年轻代GC?什么时候会进行老年代GC? 1. 为什么要区分年轻代和老年代? 年轻代中的对象大部分都是短期…...
【C++初阶】---类和对象(上)
1.类的定义 1.1类的定义格式 • class为定义类的关键字,Data为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的⽅法或者成员函数。 •…...
【数据库事务、消息队列事务、Redis 事务、Spring 事务 详细分析】
数据库事务、消息队列事务、Redis 事务、Spring 事务** 的详细分析 在分布式系统和应用开发中,事务管理是确保数据一致性和可靠性的关键机制。以下是针对 数据库事务、消息队列事务、Redis 事务、Spring 事务 的详细分析,包括原理、特点、适用场景和对比…...
2-1 基本放大电路
放大的概念 mV →V mA→A 特征:放大功率(电压与电流)。 本质:能量在控制下的转换。(外接供电电源) 必要条件:有源元件(能量控制原件) 前提:不失真 测试的…...
什么是矩阵账号
矩阵账号是指在同一平台或多个平台上,围绕同一品牌或个人,创建的多个相互关联、协同工作的账号组合。这些账号虽然独立,但在内容定位和运营策略上有所区分,同时又相互引流,共同形成一个网络结构,类似于矩阵…...
【Linux】Ubuntu 24.04 LTS 安装 OpenJDK 8
目录 通过 apt-get 直接安装 JDK 1. 更新 apt 软件源 2. 检查 JDK 是否已安装 3. 安装OpenJDK 4. 检查 JDK 是否成功安装 5. 设置 JAVA_HOME 环境变量 找到需要设置的 Java 路径 使用文本编辑器打开/etc/environment文件 添加 Java 安装路径 应用更改和验证配置 通过…...
xcode开发swiftui项目的时候,怎么调试ui占位和ui大小
有时候元素之间可能存在很大的空间间隔,但是又不知道怎么产生的,无奈我又看不懂xcode里面的Debug View Hierarchy功能,只能使用笨方法,就是给不同的块元素设置上不同的背景色,然后看一下间隙区域到底是哪个背景色填充的…...
测试用例的优先级划分规则
测试用例的优先级划分是根据 业务重要性、风险程度、测试资源 等因素,确定测试执行的顺序,以最大化测试效率和风险控制。以下是常见的优先级划分规则和操作方法: 一、优先级划分的核心原则 风险驱动 高风险功能(如核心支付流程&a…...
信息安全的数学本质与工程实践
信息安全的本质是数学理论与工程实践的高度统一。在这个数字空间与物理世界深度融合的时代,信息安全已从简单的数据保护演变为维系数字社会正常运转的基础设施。对于计算机专业学习者而言,理解信息安全需要超越工具化认知,深入其数学内核与系…...
第 6 章:优化动态分配内存的变量_《C++性能优化指南》_notes
优化动态分配内存的变量 第六章核心知识点详解总结第六章 动态内存优化 重点难点梳理 一、多选题(每题至少2个正确答案)二、设计题答案与详解多选题答案设计题答案示例 第六章核心知识点详解 动态内存分配的开销 知识点:动态内存分配需要调用…...
k8s kubernetes dashboard一直CarshLoopBackoff
使用 kubectl get pods -A -o wide 发现pod一直CarshLoopBackoff 通过 kubectl describe pod kubernetes-dashboard-7c4f8ff86d-7k7bd -n kubernetes-dashboard 获取详细信息 发现一直报错 Warning Unhealthy 10m (x31 over 34m) kubelet Liveness probe fail…...
[C++面试] 你了解视图吗?
一、入门 1、什么是 C 视图(View)?请简要说明其概念和用途 它提供了对序列(如数组、容器等)的非拥有性、只读或可写的访问。(就像是个透明的放大镜,它能让你去看一组数据,但它自己…...
Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出
Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出 预览安装插件示例代码项目目录结构截图实际效果截图 动态渲染 .docx 文档(带图片),预览、导出安装插件docx 模板文件内容完整代码…...
ollama迁移已下载的单个模型到服务器
ollama迁移已下载的单个模型到服务器 场景 ollama是面向用户级的,部署和运行都很简单,是否高效就另说了。但最起码,他能充分利用用户的硬件设备,在GPU不足也能调用cpu和内存去加持。 ollama运行的模型基本是量化版本的…...
Photoshop 2025安装教程包含下载安装包,2025最新版图文安装教程
文章目录 前言一、Photoshop 2025下载二、Photoshop 2025安装教程1. 安装包解压2. 找到安装程序3. 以管理员身份运行4. 安装选项设置5. 选择安装路径6. 开始安装7. 安装完成8. 启动软件9. 软件主界面 前言 无论你是专业设计师,还是刚接触图像处理的新手,…...
