当前位置: 首页 > news >正文

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_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博客

相关文章:

  • C++入门五式——类和对象(下)
  • 【TensorRT】TensorRT从安装到推理——Python 环境下 MobileNetV4 三分类任务
  • 【AVRCP】AVRCP核心术语解析
  • LeeCode 383. 赎金信
  • 【Linux】深度解析Linux进程间通信:匿名管道原理、实战进程池与高频问题排查。
  • 05 Python 元组:不可变序列的解析和应用
  • 上位机知识篇---PythonPip安装与配置
  • Enovia许可管理系统的兼容性和集成性
  • PHP回调后门小总结
  • Python 数据可视化实战:多维度销售数据分析与图表绘制
  • 【蓝桥杯】好数
  • 青少年编程与数学 02-012 SQLite 数据库简介 01课题、数据库概要
  • 系统与网络安全------Windows系统安全(1)
  • MybatisPlus(SpringBoot版)学习第五讲:条件构造器和常用接口
  • java代码错误(二)
  • Episode, time step, batch, epoch
  • 数字人对嘴型Wav2Lip模型原理与源码详解(推理部分)
  • DFX架构详解:构建面向全生命周期的卓越设计体系
  • Nextjs15 - 服务端组件(RSC)与客服端组件
  • 线程状态及转换详解
  • 做网站和淘宝美工 最低电脑/企业seo优化
  • 网站开发及建设赔偿条款/免费推广有哪些
  • 百度网站提交入口/优化公司排名
  • 给别人做网站挣钱么/磁力宅
  • 凡科互动怎么发布/seo优化检测
  • 做收费课程网站/广州seo软件