EC11 参数

  1. 脉冲数:20个
  2. 带开关:有
  3. 操作寿命:50000次
  4. 最大额定:10mA 5V DC
  5. 轴型:半轴柄/梅花柄

EC11

EC11 原理图

EC11 一共有5个引脚:

  • S1 按钮引脚1
  • S2 按钮引脚2
  • A A相引脚
  • B B 相引脚
  • C 是 A和B的公共引脚。

3个10K电阻是上拉电阻,默认三个引脚都输出高电平;3个470电阻是输出电阻,也可以 不要。

旋转编码器正转时的时序波形

旋转编码器正反转判断

通过编码器旋转时的波形可以看到,如果 A 下降沿时,判断B的电平高低,如果为高是一个方向如果是低则为另一个方向。

分享一个国外的EC11 硬件消抖方案

博客网址:Strömlinge: Rotary Encoder Debouncer (stroemlinge.blogspot.com)

方案文档下载:EC11 debouncer - Google 云端硬盘

这个方案用到的两个芯片为非门反相器集成电路,一个一路的一个两路的。

由于该方案用到了反相器,所以默认3个IO口输出为低电平。单片机外部中断要设置成上升沿触发。

原理图

分享一个国外的EC11软件消抖解决方案

YouTube 地址: Coding a KY-040 Rotary Encoder on a Raspberry Pi Pico - Detailed Explanation & Step by Step Code

GitHub 地址:gurgleapps/rotary-encoder: Code to drive a rotary encoder in micropython (github.com)

该项目用的硬件是树莓派这里来分析下代码实现:

rotary.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import machine
import utime as time
from machine import Pin
import micropython

class Rotary:
ROT_CW = 1 # 顺时针旋转
ROT_CCW = 2 # 逆时针旋转
SW_PRESS = 4 # 中键按下
SW_RELEASE = 8 # 中键弹起释放

def __init__(self,dt,clk,sw):
# 一下3行为指定连接的硬件接口
self.dt_pin = Pin(dt, Pin.IN)
self.clk_pin = Pin(clk, Pin.IN)
self.sw_pin = Pin(sw, Pin.IN)

# 默认 dt 和 clk 都为1 ,dt 左移一位结果为 2(二进制10),再与 1 按位相或运算最后结果为 3(二进制11)
# (self.dt_pin.value() << 1) | self.clk_pin.value() 在这里的妙处是把两个位运算后的结果变为两位有效值
self.last_status = (self.dt_pin.value() << 1) | self.clk_pin.value()

# 中断配置
self.dt_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )
self.clk_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )
self.sw_pin.irq(handler=self.switch_detect, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )

self.handlers = []
self.last_button_status = self.sw_pin.value()

def rotary_change(self, pin):
# new_status 可能的结果为2(10)、1(01)、0(00)、3(11)
# dt==0 , clk==1 // 01
# dt==1 , clk==0 // 10
# dt==0 , clk== 0 // 00
# dt==1 , clk==1 // 10 | 01 = 11
new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()

# 初始化时:last_status 为二进制11 或 00(大概率正常情况下为:11),
# new_status == last_status 说明编码器没有发生旋转也为二进制11
# 其他时间: last_status 可能为4中状态:00\11\10\01
if new_status == self.last_status:
return # 跳出函数
# 下面代码是 dt 和 clk 都为0才执行的
# transition 等于把 last_status 和 new_status 的合并,last_status 当作高2位,new_status 当作低2位
transition = (self.last_status << 2) | new_status
# if 判断过滤掉 相同的情况: 00 或 11 也就是 new_status 的状态
if transition == 0b1110:
micropython.schedule(self.call_handlers, Rotary.ROT_CW)
elif transition == 0b1101:
micropython.schedule(self.call_handlers, Rotary.ROT_CCW)
self.last_status = new_status #把最后一次的值当作下一次的第一次值,可能包含4中状态 00|11|01|10

def switch_detect(self,pin):
if self.last_button_status == self.sw_pin.value():
return
self.last_button_status = self.sw_pin.value()
if self.sw_pin.value():
micropython.schedule(self.call_handlers, Rotary.SW_RELEASE)
else:
micropython.schedule(self.call_handlers, Rotary.SW_PRESS)

def add_handler(self, handler):
self.handlers.append(handler)

def call_handlers(self, type):
for handler in self.handlers:
handler(type)

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from rotary import Rotary
import utime as time

rotary = Rotary(0,1,2)
val = 0

def rotary_changed(change):
global val
if change == Rotary.ROT_CW:
val = val + 1
print(val)
elif change == Rotary.ROT_CCW:
val = val - 1
print(val)
elif change == Rotary.SW_PRESS:
print('PRESS')
elif change == Rotary.SW_RELEASE:
print('RELEASE')

rotary.add_handler(rotary_changed)

while True:
time.sleep(0.1)

我用 GD32 的实现

1
2
3
4
5
6
7
8
9
10
11
/*******************************************************************************
* @brief 2-3 外部中断
* @brief SW
************************************************************************** ****/
void EXTI0_1_IRQHandler(void)
{
if(SET == exti_interrupt_flag_get(ENCODER_CLK_EXTI_LINE)){
ec11_1.clk_flag = 1;
exti_interrupt_flag_clear(ENCODER_CLK_EXTI_LINE);
}
}

encoder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*******************************************************************************
* @brief 旋转编码器处理函数
******************************************************************************/
void encoder_handle(void)
{
FlagStatus SW_State = gpio_input_bit_get(ENCODER_SW_GPIO_PORT, ENCODER_SW_PIN);
if(ec11_1.sw_down_flag == 1 && ec11_1.sw_down_time > 10 && ec11_1.sw_down_count == 0)
{
if(SW_State == RESET)
{
ec11_1.sw_down_count = 1;
}
}

/* 按键长按 */
if(ec11_1.sw_down_count == 1 && ec11_1.sw_down_time > 500 && SW_State == RESET)
{
ec11_1.sw_mode_flag = 2; // 长按
}
if(ec11_1.sw_down_count == 1 && ec11_1.sw_down_time > 500 && SW_State == SET)
{
ec11_1.sw_down_count = 0; // 长按需要把 sw_down_count 清零
ec11_1.sw_down_time = 0; // 长按需要 sw_down_time 清零
ec11_1.sw_down_flag = 0; // 清除 sw_down_flag 标志
ec11_1.sw_mode_flag = 0; // 模式清除
}

/* 单击双击判断 */
if(ec11_1.sw_down_count == 1 && SW_State == SET && ec11_1.sw_down_time < 500)
{
ec11_1.sw_down_count = 2; // sw_down_time < 500 需要判断双击还是单击
ec11_1.sw_down_time = 0;
}

if(ec11_1.sw_down_flag == 1 && ec11_1.sw_down_count == 2 && ec11_1.sw_down_time > 100)
{
if(SW_State == RESET)
{
ec11_1.sw_mode_flag = 3; // 双击
ec11_1.sw_down_time = 0;
ec11_1.sw_down_flag = 0;
ec11_1.sw_down_count = 0;
} else {
ec11_1.sw_mode_flag = 1; // 单击
ec11_1.sw_down_time = 0;
ec11_1.sw_down_flag = 0;
ec11_1.sw_down_count = 0;
}
}

// 长按
if(ec11_1.sw_mode_flag == 2)
{
// 指定多少毫秒加1
if(ec11_1.sw_long_press_time == LED_AUTO_SETP)
{
test_number = 0;
ec11_1.sw_long_press_time = 0;
}

FlagStatus sw_state_temp = gpio_input_bit_get(ENCODER_SW_GPIO_PORT, ENCODER_SW_PIN);
if(sw_state_temp == SET)
{
ec11_1.sw_mode_flag = 0;
}

}
// 单击
if(ec11_1.sw_mode_flag == 1)
{
// test_number++;
ec11_1.sw_mode_flag = 0;
}

// 双击
if(ec11_1.sw_mode_flag == 3)
{
// test_number += 10;
ec11_1.sw_mode_flag = 0;
}

/* 旋转方向处理 */
if(ec11_1.clk_rotate_flag == 1)
{
FlagStatus clk_value = gpio_input_bit_get(ENCODER_CLK_GPIO_PORT, ENCODER_CLK_PIN);
FlagStatus dt_value = gpio_input_bit_get(ENCODER_DT_GPIO_PORT, ENCODER_DT_PIN);
uint8_t dt_temp = (uint8_t)dt_value;
uint8_t clk_temp = (uint8_t)clk_value;

rotary_chanage(dt_temp, clk_temp);

if(ec11_1.directron == 1)
{
test_number++;
ec11_1.directron = 0;
ec11_1.clk_rotate_flag = 0;
}
if(ec11_1.directron == 2)
{
test_number--;
ec11_1.directron = 0;
ec11_1.clk_rotate_flag = 0;
}
}

}


static void rotary_chanage(uint8_t clk, uint8_t dt)
{

ec11_1.new_status = (dt << 1) | (clk);

if(ec11_1.new_status == ec11_1.last_status)
{
return;
}

uint8_t transition = (ec11_1.last_status << 2) | ec11_1.new_status;

if(transition == 0x0e){
ec11_1.directron = 1;
}
if(transition == 0x0d){
ec11_1.directron = 2;
}

ec11_1.last_status = ec11_1.new_status;
}

一个国内论坛提供的软件消抖方案(推荐)

以上几个方案都不完美,国外那个用硬件(非门)实现的方案我没有经过实践效果还不知怎样;另一个基于树莓派的那个方案经过实践还是有微弱抖动,当没有旋转到位时也会有动作。最后这个国内论坛提供的方案,经过我的测试效果非常完美,特此再次分享给大家。

论坛内的代码例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void interrupt main_int(void)
{
if(RBIF)
{
if(int_nu==0 && KEY_A==0) //第一次中断,并且A相是下降沿
{
flag=0;
if(KEY_B) flag=1; //根据B相,设定标志
int_nu=1; //中断计数
}
if(int_nu && KEY_A) //第二次中断,并且A相是上升沿
{
if(KEY_B==0 && flag==1) --power_m;
if(KEY_B && flag==0) ++power_m;
int_nu=0; //中断计数复位,准备下一次
}
RBIF=0;
}

论坛地址:解决旋转编码抖动的方案 - Arduino - 极客工坊 - Powered by Discuz! (geek-workshop.com)

我的代码 GD32

encoder.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* EC11 编码器结构定义 */
typedef struct
{
uint16_t sw_down_time; /* >100ms 为单击,> 900为长按 */
uint8_t sw_mode_flag; /* 单击(1),长按(2),双击(3),未知(0)*/
uint8_t sw_down_flag; /* SW 下降沿标志,下降沿触发时为:1,用完后在适当的时候设置为:0 */
uint8_t sw_down_count; /* 下降沿计数,用户判断状态的临时计数值 */
uint16_t sw_long_press_time; /* sw 长按时间,用于判断长按时多长时间执行一次相应代码 */

uint8_t clk_flag; /* 旋转标志 */
uint8_t directron; /* 1: 顺时针 2:逆时针 0:默认 */
uint16_t clk_count;
} EC11_t;

// 创建结构体并初始化
EC11_t ec11_1 = {
.sw_down_count = 0,
.sw_down_flag = 0,
.sw_down_time = 0,
.sw_long_press_time = 0,
.sw_mode_flag = 0,

// 方向判断
.clk_flag = 0,
.directron = 0,
.clk_count = 0,
};

gd32f1x0_it.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*******************************************************************************
* @brief 0-1中断
* @brief CLK 与 DT
******************************************************************************/
void EXTI0_1_IRQHandler(void)
{
FlagStatus clk_value = gpio_input_bit_get(ENCODER_CLK_GPIO_PORT, ENCODER_CLK_PIN);
FlagStatus dt_value = gpio_input_bit_get(ENCODER_DT_GPIO_PORT, ENCODER_DT_PIN);
// 触发外部中断 CLK
if(SET == exti_interrupt_flag_get(ENCODER_CLK_EXTI_LINE) && clk_value == RESET && ec11_1.clk_count == 0)
{
ec11_1.clk_flag = 0;
if(dt_value) ec11_1.clk_flag = 1;

ec11_1.clk_count = 1;
exti_interrupt_flag_clear(ENCODER_CLK_EXTI_LINE);
}

if(SET == exti_interrupt_flag_get(ENCODER_CLK_EXTI_LINE) && clk_value == SET && ec11_1.clk_count == 1)
{
if(dt_value ==0 && ec11_1.clk_flag == 1) test_number++;

if(dt_value && ec11_1.clk_flag == 0)
{
test_number--;
}
ec11_1.clk_count = 0;
exti_interrupt_flag_clear(ENCODER_CLK_EXTI_LINE);
}
}