一、旋转编码器介绍
1.原理简介
本实验使用的是EC11旋转编码器,这是一种增量式旋转编码器,拥有A、B、C三个输出通道,其中A、B两相输出正交信号,相位差为90°,C相输出零脉冲信号,用于标识位置。当编码器正转时,A相的输出信号超前B相90°;当编码器反转时,A相滞后B相90°。我们在程序中可以根据A、B两相信号输出的先后顺序,来判断旋转编码器是正转还是反转。
2.波形图
(1)正转、反转波形:
理想的波形输出如上图所示(仅为示意图),A、B两相波形有一个90°的相位差。
问题描述
关于检测波形,江协科技有两个版本,第一个版本是课程视频中的方法:同时检测A、B两相的下降沿,若A相下降沿触发中断后,B相为低电平,则为反转;若B相下降沿触发中断后,A相为低电平,则为正转。第二个版本是文件资料中的代码:触发中断后,再次判断引脚电平来避免抖动。本人将两种方法均尝试后,发现转动编码器会经常发生误判,消抖效果不太理想。
(2)实际波形示意图:
在查找一些资料后发现旋转编码器确实存在抖动,具体可参考下面贴出的两篇文章。上图为一个简单的波形抖动示意图,如果用前文所述方法进行检测,确实会很容易发生误判。
参考文章:
EC11、EC16、ECxx旋转编码器按钮软件滤波程序滤除干扰杂波51单片机C程序
详解EC11编码器示波器波形图
二、解决方案
1.检测思路
第一种方法:在原代码中断服务函数的转动判断里,加一个很短的延时来实现消抖。但是我们一般不在中断函数里加延时,因为延时会占用CPU,对中断造成影响。这种方法虽然能解决抖动问题,但在编码器转动比较快时,容易漏判,不建议使用该方法。
第二种方法:A相下降沿触发中断,在A相低电平期间死循环,直到A相恢复高电平后循环结束,然后通过检测B相在这期间产生了上升沿还是下降沿来判断正、反转。判断B相是上升沿还是下降沿的方法为:每次进入中断后立即保存B相的上一个状态(Last_status),同时在A相低电平期间更新B相的当前状态(Current_status),若上一个状态是高电平,而当前状态是低电平,则为下降沿;若上一个状态是低电平,而当前状态是高电平,则为上升沿。
第三种方法:增加判断正、反转的条件,读取一个周期内的电平变化再进行判断。首先将最小系统板的PB0引脚与A相连接,触发方式选择上升/下降沿触发,用A相的输出信号来触发中断,然后在A相下降沿触发第一次中断后读取B相电平,紧接着A相上升沿触发第二次中断后读取B相电平,结合两次读取到的电平来判断是正转还是反转,这种检测方法和第二种方法的原理相同,即从A相的下降沿触发到上升沿触发期间,若B相电平发生了变化,则判定编码器转动,反之未转动,波形抖动时B相的电平保持不变,能够实现消抖。
第四种方法:STM32有一个专门的编码器接口,定时器开启编码器模式,调用标准库中的TIM_EncoderInterfaceConfig()编码器函数进行参数的配置,然后通过TIM_GetCounter()函数直接读取计数值即可。编码器模式下计数器的计数方式如下图所示,有三种模式可选择。例如:第一种模式中,当TI1FP1为上升沿(Rising)时,若TI2FP2为高电平,则计数器向下计数,若TI2FP2为低电平,则计数器向上计数;而当TI1FP1为下降沿(Falling)时,若TI2FP2为高电平,则计数器向上计数,若TI2FP2为低电平,则计数器向下计数;TI2FP2的边沿触发不计数。这种计数方式下,毛刺信号的下降沿和上升沿产生的误判之间能够相互抵消。
经测试,以上四种方法均能解决抖动问题,本文仅列出第三种方法的参考代码。
2.引脚连接
旋转编码器 | STM32最小系统板 |
---|---|
VCC | 3.3V |
GND | GND |
A | PB0 |
B | PB1 |
C | GND |
3.关键代码展示(Encoder.c)
#include "stm32f10x.h"
int16_t Encoder_Count, B_level, Cnt;
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
void EXTI0_IRQHandler(void)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0 && Cnt==0)//A相下降沿触发第一次中断
{
Cnt++;//计数值加一,表示已经触发了第一次中断
B_level=0;//读取B相电平,若为高电平则B_level置1,反之保持0
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1)
{
B_level=1;
}
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 1 && Cnt==1)//A相上升沿触发第二次中断
{
Cnt=0;//计数清零
if(B_level==1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Encoder_Count++;//正转
}
if(B_level==0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1)
{
Encoder_Count--;//反转
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
4.注意事项
在第三种方法中,中断服务函数中的if语句顺序只能是先检测A相的下降沿,再检测上升沿。如果是先检测A相的上升沿再检测下降沿,则转动编码器时就会出现单片机上电后第一次转动没反应,以及每次反方向旋转的第一次转动没反应的问题。这是因为旋转编码器转动一次,只会产生“一个波形”,而单片机上电后A、B相默认输出高电平,第一次转动后A相先产生下降沿(触发中断后不会进入if语句),再产生上升沿(触发中断后只进入第一个if语句,同时给Cnt和B_level赋值),这样产生的后果是,之后每次转动编码器,都是第一次下降沿触发中断后进入第二个if语句,然后第二次上升沿触发中断后进入第一个if语句,这样虽然也能计次,但程序的逻辑不合理,会出现转动编码器但不计次的问题。
总结
以上就是旋转编码器的简单介绍以及消抖方法的全部内容,希望对大家有所帮助!