2.2 EC11旋转编码器
旋转编码器是一种将旋转位移转换为一连串数字脉冲信号的旋转式传感器。这些脉冲用来控制角位移。读数系统通常采用差分方式,即将两个波形一样但相位差为180°的不同信号进行比较,以便提高输出信号的质量和稳定性。读数是在两个信号的差别基础上形成的,从而消除了干扰。
2.2.1 模块来源
采购链接:
资料下载链接:
【完整代码资料见:https://pan.quark.cn/s/1a0df70fbf80】
2.2.2 规格参数
工作电压:5V
工作电流:1MA
模块尺寸:18 x 25 mm
旋转角度: 360度
通信协议:相位差
管脚数量:5 Pin(2.54mm间距排针)
工作电流: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 各引脚连接。
旋转编码器 | 立创·梁山派 |
---|---|
GND | GND |
+ | 5V |
SW | PA7 |
DT | PA4 |
CLK | PA6 |
2.2.3.3 移植至工程
打开自己的工程。(这里工程参考见文件2.2.3.3-1)
文件2.2.3.3-1
新建两个文件。
点击生成的文件,分别保存并命名为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();
}
}
}
}
在文件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.2.4 移植验证
在自己工程中的main主函数中,编写如下:【完整代码资料见:https://pan.quark.cn/s/1a0df70fbf80】
移植成功示例,【完整代码资料见:https://pan.quark.cn/s/1a0df70fbf80】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律