旋转编码器是一种将旋转位移转换为一连串数字脉冲信号的旋转式传感器。这些脉冲用来控制角位移。读数系统通常采用差分方式,即将两个波形一样但相位差为180°的不同信号进行比较,以便提高输出信号的质量和稳定性。读数是在两个信号的差别基础上形成的,从而消除了干扰。
1. 模块来源
2. 规格参数
厂家资料下载请查看百度网盘链接
工作电压:5V
工作电流:1MA
模块尺寸:18 x 25 mm
旋转角度: 360度
通信协议:相位差
管脚数量:5 Pin(2.54mm间距排针)
3. 移植过程
我们的目标是在开发板上能够判断旋转方向、旋转次数和是否按下的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
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内的消抖。
在中断服务函数中,根据真值表确定旋转的方向。
3.2. 引脚选择
该模块有5个引脚,具体引脚连接见 表 各引脚连接。
3.3. 移植至工程
打开自己的工程。(这里工程参考见入门手册工程模板)
移植步骤中的导入.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-03-29 LCKFB-LP first version
*/
#include "ec11.h"
#include "board.h"
#include "stdio.h"
/******************************************************************
* 函 数 名 称:Encoder_GPIO_Init
* 函 数 说 明:旋转编码器引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:使用定时器每10Ms扫描一次是否有旋转,即通过定时器进行消抖
******************************************************************/
void Encoder_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_GPIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_ENCODER_SW|GPIO_ENCODER_LCK|GPIO_ENCODER_DT;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_Init(PORT_GPIO, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // TIMER初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; // 中断配置结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 100; // 10ms进入一次中断
TIM_TimeBaseStructure.TIM_Prescaler = 3600 - 1;
TIM_TimeBaseInit(BSP_TIMER, &TIM_TimeBaseStructure);
TIM_ITConfig(BSP_TIMER, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = BSP_TIMER_IRQ;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(BSP_TIMER, ENABLE); // 使能定时器
}
/******************************************************************
* 函 数 名 称: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( GET_SW_STATE == Bit_SET )
{
delay_ms(100);//消抖
return 0;
}
else//按下
{
delay_ms(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(TIM_GetITStatus(BSP_TIMER, TIM_IT_Update) == SET)
{
dat = Encoder_Scanf();//扫描编码器是否扭动
if( dat != 0 )//如果有转动
{
if( dat == 2 )
{
Encoder_Rotation_left();
}
else
{
Encoder_Rotation_right();
}
}
}
TIM_ClearITPendingBit(BSP_TIMER, TIM_IT_Update);
}
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
在文件ec11.h中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-03-29 LCKFB-LP first version
*/
#ifndef _BSP_ENCODER_H_
#define _BSP_ENCODER_H_
#include "stm32f10x.h"
#define RCC_GPIO RCC_APB2Periph_GPIOA
#define PORT_GPIO GPIOA
//SW引脚
#define GPIO_ENCODER_SW GPIO_Pin_7
//CLK引脚
#define GPIO_ENCODER_LCK GPIO_Pin_6
//DT引脚
#define GPIO_ENCODER_DT GPIO_Pin_4
//获取CLK引脚的状态
#define GET_CLK_STATE GPIO_ReadInputDataBit(PORT_GPIO, GPIO_ENCODER_LCK)
//获取DT引脚的状态
#define GET_DT_STATE GPIO_ReadInputDataBit(PORT_GPIO, GPIO_ENCODER_DT)
//获取SW引脚的状态
#define GET_SW_STATE GPIO_ReadInputDataBit(PORT_GPIO, GPIO_ENCODER_SW)
//定时器扫描
#define BSP_TIMER_RCC RCC_APB1Periph_TIM3 // 定时器时钟
#define BSP_TIMER TIM3 // 定时器
#define BSP_TIMER_IRQ TIM3_IRQn // 定时器中断
#define BSP_TIMER_IRQHANDLER TIM3_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
4 移植验证
在自己工程中的main主函数中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-03-29 LCKFB-LP first version
*/
#include "stm32f10x.h"
#include "board.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "ec11.h"
int main(void)
{
board_init();
uart1_init(115200);
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
34
35
移植现象:向右旋转10次,向左旋转10次,按下一次。