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