Linux下EC11旋转编码器驱动调试

news2025/3/31 8:53:17

文章目录

  • 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_none
                4 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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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() …

Unity Shader 学习17:合批渲染

一、基础概念 合批主要是针对这三个概念进行优化减少&#xff1a; ① SetPass Call&#xff1a;一次渲染状态切换&#xff0c;也就是每次切换 材质/Pass 时&#xff0c;就会触发一次SetPass Call ② Draw Call&#xff1a;cpu 调用一次 gpu 绘制函数 ③ Batch&#xff1a;表示…

带你从入门到精通——自然语言处理(十. BERT)

建议先阅读我之前的博客&#xff0c;掌握一定的自然语言处理前置知识后再阅读本文&#xff0c;链接如下&#xff1a; 带你从入门到精通——自然语言处理&#xff08;一. 文本的基本预处理方法和张量表示&#xff09;-CSDN博客 带你从入门到精通——自然语言处理&#xff08;二…

【计算机网络】DHCP工作原理

DHCP(动态主机配置协议) Dynamic Host Configuration Protocol 基于UDP协议传输 DHCP分配IP地址的过程 &#xff08;1&#xff09;DHCP DISCOVER客户机请求 IP 地址&#xff1a; 当一个 DHCP 客户机启动时&#xff0c;客户机还没有 IP 地址&#xff0c;所以客户机要通过 DHC…

Linux网站搭建(新手必看)

1.宝塔Linux面板的功能 宝塔面板是一款服务器管理软件&#xff0c;可以帮助用户建立网站&#xff0c;一键配置服务器环境&#xff0c;使得用户通过web界面就可以轻松的管理安装所用的服务器软件。 2. 宝塔Linux面板的安装 宝塔官网地址&#xff1a;宝塔面板 - 简单好用的Linu…

【C++初阶】---类和对象(上)

1.类的定义 1.1类的定义格式 • class为定义类的关键字&#xff0c;Data为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量;类中的函数称为类的⽅法或者成员函数。 •…

2-1 基本放大电路

放大的概念 mV →V mA→A 特征&#xff1a;放大功率&#xff08;电压与电流&#xff09;。 本质&#xff1a;能量在控制下的转换。&#xff08;外接供电电源&#xff09; 必要条件&#xff1a;有源元件&#xff08;能量控制原件&#xff09; 前提&#xff1a;不失真 测试的…

什么是矩阵账号

矩阵账号是指在同一平台或多个平台上&#xff0c;围绕同一品牌或个人&#xff0c;创建的多个相互关联、协同工作的账号组合。这些账号虽然独立&#xff0c;但在内容定位和运营策略上有所区分&#xff0c;同时又相互引流&#xff0c;共同形成一个网络结构&#xff0c;类似于矩阵…

【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大小

有时候元素之间可能存在很大的空间间隔&#xff0c;但是又不知道怎么产生的&#xff0c;无奈我又看不懂xcode里面的Debug View Hierarchy功能&#xff0c;只能使用笨方法&#xff0c;就是给不同的块元素设置上不同的背景色&#xff0c;然后看一下间隙区域到底是哪个背景色填充的…

信息安全的数学本质与工程实践

信息安全的本质是数学理论与工程实践的高度统一。在这个数字空间与物理世界深度融合的时代&#xff0c;信息安全已从简单的数据保护演变为维系数字社会正常运转的基础设施。对于计算机专业学习者而言&#xff0c;理解信息安全需要超越工具化认知&#xff0c;深入其数学内核与系…

Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出

Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档&#xff08;带图片&#xff09;预览&#xff0c;并导出 预览安装插件示例代码项目目录结构截图实际效果截图 动态渲染 .docx 文档&#xff08;带图片&#xff09;&#xff0c;预览、导出安装插件docx 模板文件内容完整代码…

ollama迁移已下载的单个模型到服务器

ollama迁移已下载的单个模型到服务器 场景 ollama是面向用户级的&#xff0c;部署和运行都很简单&#xff0c;是否高效就另说了。但最起码&#xff0c;他能充分利用用户的硬件设备&#xff0c;在GPU不足也能调用cpu和内存去加持。 ollama运行的模型基本是量化版本的&#xf…

Photoshop 2025安装教程包含下载安装包,2025最新版图文安装教程

文章目录 前言一、Photoshop 2025下载二、Photoshop 2025安装教程1. 安装包解压2. 找到安装程序3. 以管理员身份运行4. 安装选项设置5. 选择安装路径6. 开始安装7. 安装完成8. 启动软件9. 软件主界面 前言 无论你是专业设计师&#xff0c;还是刚接触图像处理的新手&#xff0c…

【Python · PyTorch】时域卷积网络 TCN

1. 概念 1.1 定义 TCN 是时域卷积网络&#xff08;Temporal Convolutional Network&#xff09;的简称。TCN是于2018年 Shaojie Bai 等人提出的一个处理时序数据的卷积模型。 TCN结合了CNN卷积并行性计算和RNN长期依赖的优势&#xff0c;CNN可在多个通道同时处理卷积核运算&…

Mysql update更新数据执行流程

update 的执行流程是以select查询为基础执行的&#xff01;&#xff01;你不明白select执行流程&#xff1f;没关系&#xff0c;这篇博客照样让你明白&#xff0c;update执行流程&#xff01; 存储引擎是什么&#xff1f; 如果把数据库比作一个大仓库&#xff0c;那么存储引擎…

WMS WCS系统架构

1.1立体仓库现场网络架构图 1.2立体仓库WMS系统与WCS系统架构 1.3系统技术选型 WEB端技术&#xff1a;node.js、vue 、element、jquery、html、js、css等 API端技术&#xff1a;spring boot 、msyql、redis、mybatis等 WCS技术&#xff1a;c#、winform、OPC、socket、S7等 …

23种设计模式-状态(State)设计模式

状态设计模式 &#x1f6a9;什么是状态设计模式&#xff1f;&#x1f6a9;状态设计模式的特点&#x1f6a9;状态设计模式的结构&#x1f6a9;状态设计模式的优缺点&#x1f6a9;状态设计模式的Java实现&#x1f6a9;代码总结&#x1f6a9;总结 &#x1f6a9;什么是状态设计模式…

kaggle上经典泰坦尼克项目数据分析探索

之前了解在kaggle上这个项目很火&#xff0c;最近想要加强一下python数据分析&#xff0c;所以在kaggle上找到这个项目进行学习探索&#xff0c;下面是将一些学习资料以及过程整理出来。 一、首先我们了解一下项目背景以及如何找到这个项目。 kaggle项目地址: https://www.k…

15 python 数据容器-字典

在 Python 的编程世界里&#xff0c;字典是一种超实用的数据类型&#xff0c;它就像打工人的工作资料夹&#xff0c;能把各种不同类型的信息有条理地存起来&#xff0c;还能快速找到你需要的内容。对于刚开始学习编程的小伙伴来说&#xff0c;掌握字典的用法&#xff0c;能让你…