2.2 EC11 旋转编码器
旋转编码器是一种将旋转位移转换为一连串数字脉冲信号的旋转式传感器。这些脉冲用来控制角位移。读数系统通常采用差分方式,即将两个波形一样但相位差为 180° 的不同信号进行比较,以便提高输出信号的质量和稳定性。读数是在两个信号的差别基础上形成的,从而消除了干扰。
2.2.1 模块来源
采购链接: 旋转编码器模块 工作电压 5V 资料下载链接: https://pan.baidu.com/s/18pp1KaT2V_llizWvdIXtKA?pwd=8889 资料提取码:8889
2.2.2 规格参数
工作电压: 5V
工作电流: 1MA
模块尺寸: 18 x 25 mm
旋转角度: 360 度
通信协议: 相位差
管脚数量:5 Pin(2.54mm 间距排针)
2.2.3 移植过程
我们的目标是在梁山派 GD32F470 上能够判断旋转方向、旋转次数和是否按下的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
2.2.3.1 查看资料
旋转编码器是通过两个引脚的相位差,实现的旋转方向判断(以后的 CLK 引脚统一称呼为 A 相,DT 引脚为 B 相)
当是顺时针旋转时,A 相超前 B 相 90 度,即 A 相为下降沿时,B 相为低电平;A 相为上升沿时,B 相为高电平。
当是逆时针旋转时,B 相超前 A 相 90 度,即 A 相为下降沿时,B 相为高电平;A 相为上升沿时,B 相为低电平。
而 EC11 按旋转的输出动作可以分为两种。一种是转两格,A、B 端输出一个完整脉冲(转一格就只是由低电平-> 高电平或由高电平-> 低电平);另一种就是转一格,A、B 对 C 端输出一个完整脉冲。
因此我们只需检测 A 相或者 B 相有发生高低电平跳变时,就判断另一相状态,来决定旋转方向。根据以下真值表,可以发现:
- 当两相同时为上升沿或者同时为下降沿时,则为顺时针;
- 当两相不同时为上升沿或者不同时为下降沿时,则为逆时针;
下 B 相 \ 右 A 相 | 上升沿 | 下降沿 |
---|---|---|
上升沿 | 顺时针 | 逆时针 |
下降沿 | 逆时针 | 顺时针 |
旋转编码器是机械结构的,是机械结构就避免不了在旋转或者按下时有抖动,这里采用定时器每隔 10ms 扫描一次编码器是否有动作,实现 10ms 内的消抖。
在中断服务函数中,根据真值表确定旋转的方向。
2.2.3.2 引脚选择
该模块有 5 个引脚,具体引脚连接见 表 2.2.3.2 各引脚连接。
2.2.3.3 移植至工程
打开自己的工程。
新建两个文件。
点击生成的文件,分别保存并命名为 bsp_encoder.c 与 bsp_encoder.h。
将 bsp_encoder.c 添加至工程目录。
添加完成之后,则会看到新添的文件。
添加 bsp_encoder.h 路径至工程。
添加路径完成后,会显示出来。
完成之后全部点击 OK,退出配置。编译工程,可以看到在软件界面的左边,会显示我们新增的文件。
在文件 bsp_encoder.c 中,编写如下代码。
/********************************************************************************
* 文 件 名: bsp_encoder.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2023年04月06日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_encoder.h"
#include "systick.h"
#include "bsp_usart.h"
#include "stdio.h"
/******************************************************************
* 函 数 名 称:Encoder_GPIO_Init
* 函 数 说 明:旋转编码器引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:使用定时器每10Ms扫描一次是否有旋转,即通过定时器进行消抖
******************************************************************/
void Encoder_GPIO_Init(void)
{
timer_parameter_struct timere_initpara; // 定义定时器结构体
rcu_periph_clock_enable(RCU_ENCODER_LCK); // 开启CLK引脚时钟
rcu_periph_clock_enable(RCU_ENCODER_DT); // 开启DT引脚时钟
rcu_periph_clock_enable(RCU_ENCODER_SW); // 开启SW引脚时钟
rcu_periph_clock_enable(BSP_TIMER_RCU); // 开启定时器时钟
/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
/* 配置 SW 为上拉输入模式(必须上拉) */
gpio_mode_set(PORT_ENCODER_SW,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,GPIO_ENCODER_SW);
/* 配置 CLK 为输入模式 上拉模式 */
gpio_mode_set(PORT_ENCODER_LCK,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,GPIO_ENCODER_LCK);
/* 配置 DT 为输入模式 上拉模式 */
gpio_mode_set(PORT_ENCODER_DT,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,GPIO_ENCODER_DT);
timer_deinit(BSP_TIMER); // 复位定时器
/* 配置定时器参数 10Ms 扫描一次 */
/* f = 240,000,000 / (2400 * 1000) = 100Hz */
/* T = 1/f = 1/100 = 0.01 S = 10 MS */
timere_initpara.prescaler = 2400-1; // 时钟预分频值 0-65535
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = 1000-1; // 周期
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_TIMER,&timere_initpara); // 初始化定时器
/* 配置中断优先级 */
nvic_irq_enable(BSP_TIMER_IRQ,2,2); // 设置中断优先级为 2,2
/* 使能中断 */
timer_interrupt_enable(BSP_TIMER,TIMER_INT_UP); // 使能更新事件中断
/* 使能定时器 */
timer_enable(BSP_TIMER);
}
/******************************************************************
* 函 数 名 称:Encoder_Scanf
* 函 数 说 明:判断旋转编码器是否有往哪一个方向旋转
* 函 数 形 参:无
* 函 数 返 回:1=正转 2=反转
* 作 者:LC
* 备 注:哪一边正转哪一边反转不需要太在意,你说的算
******************************************************************/
char Encoder_Scanf(void)
{
static FlagStatus EC11_CLK_Last= RESET; //EC11的LCK引脚上一次的状态 (A相)
static FlagStatus EC11_DT_Last = RESET; //EC11的DT引脚上一次的状态(B相)
char ScanResult = 0;
//当A发生跳变时采集B当前的状态,并将B与上一次的状态进行对比。
if(GET_CLK_STATE !=EC11_CLK_Last)
{ //若A 0->1 时,B 1->0 正转;若A 1->0 时,B 0->1 正转;
//若A 0->1 时,B 0->1 反转;若A 1->0 时,B 1->0 反转
if(GET_CLK_STATE == 1) //EC11_A和上一次状态相比,为上升沿
{
//EC11_B和上一次状态相比,为下降沿
if((EC11_DT_Last == 1)&&(GET_DT_STATE == 0))
ScanResult = 1; //正转
//EC11_B和上一次状态相比,为上升沿
if((EC11_DT_Last == 0)&&(GET_DT_STATE == 1))
ScanResult = 2; //反转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<<<//
//A上升沿时,采集的B不变且为0
if((EC11_DT_Last == GET_DT_STATE)&&(GET_DT_STATE == 0))
ScanResult = 1; //正转
//A上升沿时,采集的B不变且为1
if((EC11_DT_Last == GET_DT_STATE)&&(GET_DT_STATE == 1))
ScanResult = 2; //反转
}
else //EC11_A和上一次状态相比,为下降沿
{
//EC11_B和上一次状态相比,为下降沿
if((EC11_DT_Last == 1)&&(GET_DT_STATE == 0))
ScanResult = 2; //反转
//EC11_B和上一次状态相比,为上升沿
if((EC11_DT_Last == 0)&&(GET_DT_STATE == 1))
ScanResult = 1; //正转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<<<//
//A上升沿时,采集的B不变且为0
if((EC11_DT_Last == GET_DT_STATE)&&(GET_DT_STATE == 0))
ScanResult = 2; //反转
//A上升沿时,采集的B不变且为1
if((EC11_DT_Last == GET_DT_STATE)&&(GET_DT_STATE == 1))
ScanResult = 1; //正转
}
EC11_CLK_Last = GET_CLK_STATE; //更新编码器上一个状态暂存变量
EC11_DT_Last = GET_DT_STATE; //更新编码器上一个状态暂存变量
return ScanResult; //返回值的取值: 0:无动作; 1:正转; 2:反转;
}
return 0;
}
/******************************************************************
* 函 数 名 称:Encoder_Sw_Down
* 函 数 说 明:判断编码器是否被按下
* 函 数 形 参:无
* 函 数 返 回:0=没有被按下 1=被按下
* 作 者:LC
* 备 注:请注意消抖
******************************************************************/
unsigned char Encoder_Sw_Down(void)
{
//没有按下
if( gpio_input_bit_get(PORT_ENCODER_SW, GPIO_ENCODER_SW) == SET )
{
delay_1ms(100);//消抖
return 0;
}
else//按下
{
delay_1ms(100);//消抖
// printf("down\r\n");
return 1;
}
}
/******************************************************************
* 函 数 名 称:Encoder_Rotation_left
* 函 数 说 明:左旋转服务函数。当编码器左转时,需要执行的操作
* 函 数 形 参:无
* 函 数 返 回:向左旋转次数
* 作 者:LC
* 备 注:无
******************************************************************/
int Encoder_Rotation_left(void)
{
static int left_num = 0;//左转次数
left_num++;
/* 你的代码写在此处 */
printf("left num = %d\r\n",left_num);
/* 你的代码写在此处 */
return left_num;
}
/******************************************************************
* 函 数 名 称:Encoder_Rotation_left
* 函 数 说 明:右旋转服务函数。当编码器右转时,需要执行的操作
* 函 数 形 参:无
* 函 数 返 回:向右旋转次数
* 作 者:LC
* 备 注:无
******************************************************************/
int Encoder_Rotation_right(void)
{
static int right_num = 0;//右转次数
right_num++;
/* 你的代码写在此处 */
printf("right num = %d\r\n",right_num);
/* 你的代码写在此处 */
return right_num;
}
/************************************************
函数名称 : BSP_TIMER_IRQHandler
功 能 : 基本定时器中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void BSP_TIMER_IRQHANDLER(void)
{
static char dat = 0;
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(BSP_TIMER,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(BSP_TIMER,TIMER_INT_FLAG_UP); // 清除中断标志位
/* 执行功能 */
dat = Encoder_Scanf();//扫描编码器是否扭动
if( dat != 0 )//如果有转动
{
if( dat == 2 )
{
Encoder_Rotation_left();
}
else
{
Encoder_Rotation_right();
}
}
}
}
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
在文件 bsp_encoder.h 中,编写如下代码。
#ifndef _BSP_ENCODER_H_
#define _BSP_ENCODER_H_
#include "gd32f4xx.h"
//SW引脚
#define RCU_ENCODER_SW RCU_GPIOA
#define PORT_ENCODER_SW GPIOA
#define GPIO_ENCODER_SW GPIO_PIN_7
//CLK引脚
#define RCU_ENCODER_LCK RCU_GPIOA
#define PORT_ENCODER_LCK GPIOA
#define GPIO_ENCODER_LCK GPIO_PIN_6
//DT引脚
#define RCU_ENCODER_DT RCU_GPIOA
#define PORT_ENCODER_DT GPIOA
#define GPIO_ENCODER_DT GPIO_PIN_4
//获取CLK引脚的状态
#define GET_CLK_STATE gpio_input_bit_get(PORT_ENCODER_LCK,GPIO_ENCODER_LCK)
//获取DT引脚的状态
#define GET_DT_STATE gpio_input_bit_get(PORT_ENCODER_DT,GPIO_ENCODER_DT)
//CLK引脚的外部中断
#define CLK_EXTI_IRQN EXTI5_9_IRQn // 外部中断6
#define CLK_EXTI_PORT_SOURCE EXTI_SOURCE_GPIOA // 外部中断端口资源
#define CLK_EXTI_PIN_SOURCE EXTI_SOURCE_PIN6 // 外部中断引脚资源
#define CLK_EXTI_LINE EXTI_6 // 外部中断
#define CLK_EXTI_IRQHANDLER EXTI5_9_IRQHandler // 外部中断函数名
//DT
#define DT_EXTI_IRQN EXTI4_IRQn // 外部中断4
#define DT_EXTI_PORT_SOURCE EXTI_SOURCE_GPIOA // 外部中断端口资源
#define DT_EXTI_PIN_SOURCE EXTI_SOURCE_PIN4 // 外部中断引脚资源
#define DT_EXTI_LINE EXTI_4 // 外部中断
#define DT_EXTI_IRQHANDLER EXTI4_IRQHandler // 外部中断函数名
//定时器扫描
#define BSP_TIMER_RCU RCU_TIMER5 // 定时器时钟
#define BSP_TIMER TIMER5 // 定时器
#define BSP_TIMER_IRQ TIMER5_DAC_IRQn // 定时器中断
#define BSP_TIMER_IRQHANDLER TIMER5_DAC_IRQHandler // 定时器中断服务函数
void Encoder_GPIO_Init(void);//旋转编码器初始化
unsigned char Encoder_Sw_Down(void);//编码器是否按下
int Encoder_Rotation_left(void);//左转服务函数
int Encoder_Rotation_right(void);//右转服务函数
#endif
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
2.2.4 移植验证
在自己工程中的 main 主函数中,编写如下。
/********************************************************************************
* 文 件 名: main.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2023年04月06日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_usart.h"
#include "bsp_encoder.h"
#include "stdio.h"
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); //滴答定时器初始化 1ms
usart_gpio_config(115200U);
Encoder_GPIO_Init();
printf("encoder demo start\r\n");
while(1)
{
if( Encoder_Sw_Down() == 1 )//旋转编码器被按下
{
printf("Encoder down\r\n");
}
}
}
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
移植现象:向右旋转 10 次,向左旋转 10 次,按下一次。
移植成功示例,见文件 2.2.4-1 。
文件 2.2.4-1 移植成功示例