旋转编码器是一种将旋转位移转换为一连串数字脉冲信号的旋转式传感器。这些脉冲用来控制角位移。读数系统通常采用差分方式,即将两个波形一样但相位差为180°的不同信号进行比较,以便提高输出信号的质量和稳定性。读数是在两个信号的差别基础上形成的,从而消除了干扰。
1.模块来源
模块实物展示:
资料下载链接:https://pan.baidu.com/s/18pp1KaT2V_llizWvdIXtKA?pwd=8889
资料提取码:8889
2.规格参数
模块的厂家资料下载请查看百度网盘链接
工作电压:5V
工作电流:1MA
模块尺寸:18 x 25 mm
旋转角度: 360度
通信协议:相位差
管脚数量:5 Pin(2.54mm间距排针)
3.移植过程
我们的目标是在立创·CW32F030C8T6开发板上能够判断旋转方向、旋转次数和是否按下的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
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中,编写如下代码。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#include "ec11.h"
#include "stdio.h"
/******************************************************************
* 函 数 名 称:Encoder_GPIO_Init
* 函 数 说 明:旋转编码器引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:使用定时器每10Ms扫描一次是否有旋转,即通过定时器进行消抖
******************************************************************/
void Encoder_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体
BTIM_TimeBaseInitTypeDef BTIM_TimeBaseInitStruct; // 定时器初始化结构体
RCC_GPIO_ENABLE(); // 使能GPIO时钟
BTIM_RCC_ENABLE(); // 使能BTIM时钟
// 禁止中断,以安全地配置NVIC
__disable_irq();
// 开启BTIM1中断,并关联到NVIC
NVIC_EnableIRQ(BSP_TIMER_IRQ);
// 允许中断,恢复中断状态
__enable_irq();
GPIO_InitStruct.Pins = GPIO_ENCODER_SW| // GPIO引脚
GPIO_ENCODER_LCK|
GPIO_ENCODER_DT;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉输入
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 输出速度高
GPIO_Init(PORT_GPIO, &GPIO_InitStruct); // 初始化
// 配置定时器模式、周期和预分频器
BTIM_TimeBaseInitStruct.BTIM_Mode = BTIM_Mode_TIMER; // 设置为定时器模式
BTIM_TimeBaseInitStruct.BTIM_Period = 625 - 1; // 设置周期,使得定时器每10ms产生一次溢出中断
BTIM_TimeBaseInitStruct.BTIM_Prescaler = BTIM_PRS_DIV1024; // 预分频器设置为1024,以降低时钟频
// 使用上述配置初始化定时器BTIM1
BTIM_TimeBaseInit(BSP_TIMER, &BTIM_TimeBaseInitStruct);
// 使能BTIM1的溢出中断
BTIM_ITConfig(BSP_TIMER, BTIM_IT_OV, ENABLE);
// 启动定时器BTIM1
BTIM_Cmd(BSP_TIMER, ENABLE);
}
/******************************************************************
* 函 数 名 称:Encoder_Scanf
* 函 数 说 明:判断旋转编码器是否有往哪一个方向旋转
* 函 数 形 参:无
* 函 数 返 回:1=正转 2=反转
* 作 者:LC
* 备 注:哪一边正转哪一边反转不需要太在意,你说的算
******************************************************************/
char Encoder_Scanf(void)
{
static GPIO_PinState EC11_CLK_Last= GPIO_Pin_RESET; //EC11的LCK引脚上一次的状态(A相)
static GPIO_PinState EC11_DT_Last = GPIO_Pin_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 == GPIO_Pin_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(BTIM_GetITStatus(BSP_TIMER, BTIM_IT_OV) == SET)
{
dat = Encoder_Scanf();//扫描编码器是否扭动
if( dat != 0 )//如果有转动
{
if( dat == 2 )
{
Encoder_Rotation_left();
}
else
{
Encoder_Rotation_right();
}
}
}
BTIM_ClearITPendingBit(BSP_TIMER, BTIM_IT_OV);
}
在文件ec11.h中,编写如下代码。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#ifndef _BSP_ENCODER_H_
#define _BSP_ENCODER_H_
#include "board.h"
#define RCC_GPIO_ENABLE() __RCC_GPIOA_CLK_ENABLE()
#define PORT_GPIO CW_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_ReadPin(PORT_GPIO, GPIO_ENCODER_LCK)
//获取DT引脚的状态
#define GET_DT_STATE GPIO_ReadPin(PORT_GPIO, GPIO_ENCODER_DT)
//获取SW引脚的状态
#define GET_SW_STATE GPIO_ReadPin(PORT_GPIO, GPIO_ENCODER_SW)
//定时器扫描
#define BTIM_RCC_ENABLE() __RCC_BTIM_CLK_ENABLE() // 使能定时器时钟
#define BSP_TIMER CW_BTIM1 // 定时器
#define BSP_TIMER_IRQ BTIM1_IRQn // 定时器中断
#define BSP_TIMER_IRQHANDLER BTIM1_IRQHandler // 定时器中断服务函数
void Encoder_GPIO_Init(void);//旋转编码器初始化
unsigned char Encoder_Sw_Down(void);//编码器是否按下
int Encoder_Rotation_left(void);//左转服务函数
int Encoder_Rotation_right(void);//右转服务函数
#endif
4.移植验证
在自己工程中的main主函数中,编写如下。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#include "board.h"
#include "stdio.h"
#include "bsp_uart.h"
#include "ec11.h"
int32_t main(void)
{
board_init(); // 开发板初始化
uart1_init(115200); // 串口1波特率115200
Encoder_GPIO_Init();
printf("encoder demo start\r\n");
while(1)
{
if( Encoder_Sw_Down() == 1 )//旋转编码器被按下
{
printf("Encoder down\r\n");
}
}
}
移植现象:向右旋转10次,向左旋转10次,按下一次。
模块移植成功案例代码:
链接:https://pan.baidu.com/s/18_4-IfR_pzyy1QvyQE7XkQ?pwd=LCKF
提取码:LCKF