EC11旋转编码器
旋转编码器是一种将旋转位移转换为一连串数字脉冲信号的旋转式传感器。这些脉冲用来控制角位移。读数系统通常采用差分方式,即将两个波形一样但相位差为180°的不同信号进行比较,以便提高输出信号的质量和稳定性。读数是在两个信号的差别基础上形成的,从而消除了干扰。
模块来源
采购链接:
https://item.taobao.com/item.htm?spm=a1z10.3-c-s.w4002-24706531953.10.211a6a4bFbOotE&id=522555699841
资料下载链接:
https://pan.baidu.com/s/18pp1KaT2V_llizWvdIXtKA?pwd=8889
资料提取码:8889
规格参数
工作电压:5V
工作电流:1MA
模块尺寸:18 x 25 mm
旋转角度: 360度
通信协议:相位差
管脚数量:5 Pin(2.54mm间距排针)
左侧信息见厂家资料产品规格书
移植过程
我们的目标是在天空星HC32F4A0PITB上能够判断旋转方向、旋转次数和是否按下的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
查看资料
旋转编码器是通过两个引脚的相位差,实现的旋转方向判断(以后的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相有发生高低电平跳变时,就判断另一相状态,来决定旋转方向。根据以下真值表,可以发现:
- 当两相同时为上升沿或者同时为下降沿时,则为顺时针;
- 当两相不同时为上升沿或者不同时为下降沿时,则为逆时针;
旋转编码器是机械结构的,是机械结构就避免不了在旋转或者按下时有抖动,这里采用定时器每隔10ms扫描一次编码器是否有动作,实现10ms内的消抖。
在中断服务函数中,根据真值表确定旋转的方向。
引脚选择
该模块有5个引脚,具体引脚连接见 表 各引脚连接。
移植至工程
移植步骤中的导入.c和.h文件与第二章的第1小节【DHT11温湿度传感器】相同,只是将.c和.h文件更改为ec11.c与ec11.h。这里不再过多讲述,移植完成后面修改相关代码。
在文件ec11.c中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-13 LCKFB-LP first version
*/
#include "ec11.h"
#include "board.h"
#include "stdio.h"
void TMR2_Cmp_IrqCallback(void);
/******************************************************************
* 函 数 名 称:Encoder_GPIO_Init
* 函 数 说 明:旋转编码器引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:lckfb.com
* 备 注:使用定时器每10Ms扫描一次是否有旋转,即通过定时器进行消抖
******************************************************************/
void Encoder_GPIO_Init(void)
{
stc_gpio_init_t stcGpioInit; // 定义GPIO结构体
(void)GPIO_StructInit(&stcGpioInit); // 使用默认参数配置结构体
stcGpioInit.u16PinState = PIN_STAT_SET; // 高电平
stcGpioInit.u16PinDir = PIN_DIR_IN; // 输入模式
stcGpioInit.u16PullUp = PIN_PU_ON; // 上拉开启
// SW
(void)GPIO_Init(PORT_GPIO, GPIO_ENCODER_SW, &stcGpioInit); // 初始化SW
// CLK
(void)GPIO_Init(PORT_GPIO, GPIO_ENCODER_LCK, &stcGpioInit); // 初始化CLK
// DT
(void)GPIO_Init(PORT_GPIO, GPIO_ENCODER_DT, &stcGpioInit); // 初始化DT
// 解除保护
LL_PERIPH_WE(LL_PERIPH_GPIO | LL_PERIPH_FCG | LL_PERIPH_PWC_CLK_RMU);
/* 使能 Timer2_1 时钟 */
FCG_Fcg2PeriphClockCmd(BSP_TIMER_FCG, ENABLE);
stc_tmr2_init_t stcTmr2Init;
/* 使用默认值配置缺省的参数 */
(void)TMR2_StructInit(&stcTmr2Init);
/* 结构体配置 */
/*
(120MHz / 1024) * 1000000 / 100 = 1171.875 Hz (10ms)
*/
stcTmr2Init.u32ClockSrc = TMR2_CLK_PCLK1; // 120MHz的时钟源
stcTmr2Init.u32ClockDiv = TMR2_CLK_DIV1024; // 分频1024
stcTmr2Init.u32Func = TMR2_FUNC_CMP; // 输出比较
stcTmr2Init.u32CompareValue = (uint32_t)(1172 - 1); // 比较值
(void)TMR2_Init(BSP_TIMER, BSP_TIMER_CH, &stcTmr2Init);
stc_irq_signin_config_t stcIrq;
stcIrq.enIntSrc = BSP_TIMER_INT; // 中断号
stcIrq.enIRQn = BSP_TIMER_IRQ; // 中断号定义
stcIrq.pfnCallback = &BSP_TIMER_IRQHANDLER; // 中断服务函数
(void)INTC_IrqSignIn(&stcIrq);
NVIC_ClearPendingIRQ(stcIrq.enIRQn);
NVIC_SetPriority(stcIrq.enIRQn, DDL_IRQ_PRIO_03); // 优先级
NVIC_EnableIRQ(stcIrq.enIRQn);
/* 启用 Timer2_1 指定 INT_SRC_TMR2_1_CMP_A 中断 */
TMR2_IntCmd(BSP_TIMER, BSP_TIMER_MATCH, ENABLE);
// 使能Timer2_1 CH_A
TMR2_Start(BSP_TIMER, BSP_TIMER_CH);
}
/******************************************************************
* 函 数 名 称:Encoder_Scanf
* 函 数 说 明:判断旋转编码器是否有往哪一个方向旋转
* 函 数 形 参:无
* 函 数 返 回:1=正转 2=反转
* 作 者:lckfb.com
* 备 注:哪一边正转哪一边反转不需要太在意,你说的算
******************************************************************/
char Encoder_Scanf(void)
{
static uint8_t EC11_CLK_Last= RESET; //EC11的LCK引脚上一次的状态 (A相)
static uint8_t 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=被按下
* 作 者:lckfb.com
* 备 注:请注意消抖
******************************************************************/
unsigned char Encoder_Sw_Down(void)
{
//没有按下
if( GET_SW_STATE == SET )
{
delay_ms(100);//消抖
return 0;
}
else//按下
{
delay_ms(100);//消抖
// printf("down\r\n");
return 1;
}
}
/******************************************************************
* 函 数 名 称:Encoder_Rotation_left
* 函 数 说 明:左旋转服务函数。当编码器左转时,需要执行的操作
* 函 数 形 参:无
* 函 数 返 回:向左旋转次数
* 作 者:lckfb.com
* 备 注:无
******************************************************************/
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
* 函 数 说 明:右旋转服务函数。当编码器右转时,需要执行的操作
* 函 数 形 参:无
* 函 数 返 回:向右旋转次数
* 作 者:lckfb.com
* 备 注:无
******************************************************************/
int Encoder_Rotation_right(void)
{
static int right_num = 0;//右转次数
right_num++;
/* 你的代码写在此处 */
printf("right num = %d\r\n",right_num);
/* 你的代码写在此处 */
return right_num;
}
/******************************************************************
* 函 数 名 称:TMR2_Cmp_IrqCallback
* 函 数 说 明:定时器中断服务函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:lckfb.com
* 备 注:
******************************************************************/
void TMR2_Cmp_IrqCallback(void)
{
char dat = 0;
dat = Encoder_Scanf();//扫描编码器是否扭动
if( dat != 0 )//如果有转动
{
if( dat == 2 )
{
Encoder_Rotation_left();
}
else
{
Encoder_Rotation_right();
}
}
// 清除 TMR2_FLAG_MATCH_CH_A 标志位
TMR2_ClearStatus(BSP_TIMER, BSP_TIMER_MATCH);
}
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
在文件ec11.h中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-13 LCKFB-LP first version
*/
#ifndef _BSP_ENCODER_H_
#define _BSP_ENCODER_H_
#include "hc32_ll.h"
#define PORT_GPIO GPIO_PORT_A
//SW引脚
#define GPIO_ENCODER_SW GPIO_PIN_07
//CLK引脚
#define GPIO_ENCODER_LCK GPIO_PIN_06
//DT引脚
#define GPIO_ENCODER_DT GPIO_PIN_04
//获取CLK引脚的状态
#define GET_CLK_STATE GPIO_ReadInputPins(PORT_GPIO, GPIO_ENCODER_LCK)
//获取DT引脚的状态
#define GET_DT_STATE GPIO_ReadInputPins(PORT_GPIO, GPIO_ENCODER_DT)
//获取SW引脚的状态
#define GET_SW_STATE GPIO_ReadInputPins(PORT_GPIO, GPIO_ENCODER_SW)
//定时器扫描
#define BSP_TIMER_FCG FCG2_PERIPH_TMR2_1 // 定时器时钟
#define BSP_TIMER CM_TMR2_1 // 定时器
#define BSP_TIMER_CH TMR2_CH_A // 定时器通道
#define BSP_TIMER_MATCH TMR2_INT_MATCH_CH_A // 中断类型
#define BSP_TIMER_INT INT_SRC_TMR2_1_CMP_A // 中断号
#define BSP_TIMER_IRQ INT050_IRQn // 中断号定义
#define BSP_TIMER_IRQHANDLER TMR2_Cmp_IrqCallback // 定时器中断服务函数
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
移植验证
在自己工程中的main主函数中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-04-13 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "ec11.h"
int32_t main(void)
{
board_init();
uart1_init(115200U);
// 关闭GPIO寄存器写保护
LL_PERIPH_WE(LL_PERIPH_GPIO);
Encoder_GPIO_Init();
printf("encoder demo start\r\n");
while(1)
{
if( Encoder_Sw_Down() == 1 )//旋转编码器被按下
{
printf("Encoder down\r\n");
//旋转编码器松开
while( Encoder_Sw_Down() == 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
移植现象:向右旋转10次,向左旋转10次,按下一次。
模块移植成功案例代码: