目录
前言
最近在学习EC11旋转编码器时,看见有两篇质量很高的文章(1,2)大家可以先去看一下,大佬写的代码很好,但在搬运的过程,发现代码有些还有错误和存在不足的地方(个人意见),于是我自己在大佬代码的基础上修改和完善,经测试可实现相应功能。于是就打算出一篇关于大佬代码解读,让新手更快能懂,毕竟我自己在看代码时看了很久,接下来进入正题吧!
EC11旋转编码器的原理
首先我们简单了解一下EC11的工作原理,EC11利用机械结构,输出A、B两项不同相位的方波。顺时针方向旋转时,A相超前B相90度,逆时针方向旋转时,B相超前A 相90度。其输出信号如下图所示。(注意:因为EC11工作电压是5V,所以接线引脚必须兼容5V,可以在数据手册查是否支持FT)
其真实输出的方波可能存在杂波,这里可以进行硬件消除和软件消除,一般选用硬件消除,利用RC滤波,这里不在细节,如果想更深入的小伙伴可自己查找相关资料(其实是我不懂>-<)。还有一点旋转编码器有一定位一脉冲和两定位一脉冲,这段详解在大佬文章中有详细解释。(我们一般在淘宝购买的旋转编码器一般是一定位一脉冲,所以本章代码所讲述是关于这种选择编码器的)
拿出一段如何判断正反转的代码分析。这一段代码很巧妙利用新旧的不同的状态的翻转,可知道电平发生的跳变,再利用只判断A项为零时才进入判断,就可以模拟检测下降沿触发中断效果,最后根据B项的电平就知道 ,此时是正转还是反转。
if (EC11_A_Now != EC11_A_Last) //以A为时钟,B为数据。正转时AB反相,反转时AB同相
{
if (EC11_A_Now == 0)
{
if (EC11_B_Now == 1) //只需要采集A的上升沿或下降沿的任意一个状态,若A下降沿时B为1,正转
ScanResult = 1; //正转
else //反转
ScanResult = 2;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
编码器按键按下处理程序
//>>>>>>>>>>>>>>>>编码器按键按下处理程序<<<<<<<<<<<<<<<<//
if (EC11_Key == 0) //====检测到按键按下====//
{
if (EC11_KEY_COUNT<10000) //打开按键按下时间定时器
EC11_KEY_COUNT++;
if (EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //按下按键时间到达消抖时间时
{ //置位短按按键标志
FLAG_EC11_KEY_ShotClick = 1;
}
if ((EC11_KEY_DoubleClick_Count > 0) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //松开按键后,又在定时器在双击时间内按下按键
{ //置位双击按键标志
FLAG_EC11_KEY_DoubleClick = 1;
}
if (EC11_KEY_COUNT > KEY_COUNT_LONGTIME) //按下按键时间到达长按时间
{ //置位长按按键标志并复位短按按键标志
FLAG_EC11_KEY_LongClick = 1;
FLAG_EC11_KEY_ShotClick = 0;
}
}
else //====检测到按键松开====//
{
if (EC11_KEY_COUNT < KEY_COUNT_DESHAKING) //没到消抖时长就松开按键,复位所有定时器和按键标志
{
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
else
{
if (FLAG_EC11_KEY_ShotClick == 1) //短按按键定时有效期间
{
if ((FLAG_EC11_KEY_DoubleClick == 0) && (EC11_KEY_DoubleClick_Count >= 0))//记录第一次按下动作,使如果还能在规定时间内按下第二次,就可以执行满足下一行代
{
EC11_KEY_DoubleClick_Count++;
}
if ((FLAG_EC11_KEY_DoubleClick == 1) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //如果在规定双击时间内再次按下按键
{ //认为按键是双击动作
FLAG_EC11_KEY_DoubleClick = 2;
}
if ((FLAG_EC11_KEY_DoubleClick == 0) && (EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)) //如果没有在规定双击时间内再次按下按键
{
FLAG_EC11_KEY_ShotClick = 0;
} //认为按键是单击动作
}
if (FLAG_EC11_KEY_LongClick == 1)//检测到长按按键松开
{
Flag = 1;
}
}
}
先简单浏览一下整体, 短按和长按的那两段if语句应该很好懂,至于它为什么要在松开时置零,是为了下一段的编码器按键分析处理程序,知道它已经结束了这次动作,从而返回相应的标志位。双击程序是在第一次按下if不执行,在松开后EC11_KEY_DoubleClick_Count++,又继续按下,此时if语句执行,就可以将 FLAG_EC11_KEY_DoubleClick 标志位置1,又松开就可以FLAG_EC11_KEY_DoubleClick = 2,从而知道完成双击动作。
编码器按键分析处理程序
if (EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按键延时到了时间
{
//短按按键动作结束代码
if ((FLAG_EC11_KEY_ShotClick == 0) && (FLAG_EC11_KEY_DoubleClick != 2)&& (FLAG_EC11_KEY_LongClick != 1)) //短按按键动作结束代码
{
//--------短按按键动作结束代码--------//
ScanResult = 3;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
else if ((FLAG_EC11_KEY_LongClick == 1) && (EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //连续长按按键按下代码
{
//-------连续长按按键按下代码--------//
if (Flag == 0)
{
ScanResult = 5;
Flag = 2;
}
else if (Flag == 1)//检测长按松开时
{
Flag = 0;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
}
//双击按键动作结束代码
if ((FLAG_EC11_KEY_DoubleClick == 2) && (EC11_KEY_DoubleClick_Count > 0) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //双击按键动作结束代码
{
//--------双击按键动作结束代码--------//
ScanResult = 4;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
}
编码器按键分析处理程序这块,可以对比大佬之前程序,简单了很多,我删除了一些代码,增添了一个长按松开标志位,从而长按之后只会进行一次长按输出,只有经过松开后,才能进入下一次长按输出,这是我觉得应该长按效果。
代码存在不足和自己的一些改进
在原作者的代码基础上,我修改和删减了一些相关函数和参数,以及源代码中一些错误和参数没有调整。如源代码在按键分析那块,执行了处理正反转和一些功能性代码,又在里面细分了长按、短按和双击的代码,没有完整的将数据直接调用出来,于是我修改了代码,得到了一个我只想知道执行了某种程序的一个标志位,只返回对应了数据,比如说我正转了,我就返回一个1,再在另一个程序里面执行相应的正转后想实现程序,这样函数更有可移植和低耦合效果。还有一些参数的整定修改和一些功能的舍弃,这里就不多说了,最后也会附上我自己修改后的代码,效果可以见下图,还是很不错的(如果想要整个测试代码,请留言或者私信我)。
EC11.c
#include "sys.h"
u8 Num = 5;
u8 Num1 = 6;
void TIM4_Int_Init(u16 arr, u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//TIM2时钟使能
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //使能指定的TIM7中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
TIM_Cmd(TIM4, ENABLE);//开启定时器4
}
//定时器4中断服务程序
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)//是更新中断
{
EC11_Hander(Encoder_EC11_Scan());
TIM_ClearITPendingBit(TIM4, TIM_IT_Update); //清除TIM4更新中断标志
}
}
//*******************************************************************/
//功能:初始化EC11旋转编码器相关参数
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲。
//返回:无
//详解:对EC11旋转编码器的连接IO口做IO口模式设置。以及将相关的变量进行初始化
//*******************************************************************/
void EC11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1|GPIO_Pin_2);
//避免上电时EC11旋钮位置不确定导致一次动作误判
EC11_A_Last = EC11_A_Now;
EC11_B_Last = EC11_B_Now;
//--------清除按键计数器和标志位--------//
EC11_KEY_COUNT = 0; //EC11按键动作计数器
EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
TIM4_Int_Init(9,7199); //初始化定时器4 1ms中断
}
//*******************************************************************/
//功能:根据对应形参EC11_Value实现对应功能
//形参:EC11_Value
//返回:无
//详解:旋转编码器的功能封装函数,可以跟据自己的想法填入对应实现的功能
//*******************************************************************/
void EC11_Hander(u8 EC11_Value)
{
if (EC11_Value == 1) //正转
{
//--------编码器正转动作代码--------//
printf("正转!!");
Num++;
}
else if (EC11_Value == 2) //反转
{
//--------编码器反转动作代码--------//
printf("反转!!");
Num--;
}
else if (EC11_Value == 3) //按压一次
{
//--------编码器按键按压一次代码--------//
printf("按下一次!!");
Num1++;
}
else if (EC11_Value == 4) //双击
{
//--------编码器按键双击代码-------//
printf("双击!!");
Num1--;
}
else if (EC11_Value == 5) //长按
{
//--------编码器按键长按代码-------//
printf("长按!!");
}
}
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
//*******************************************************************/
//功能:扫描EC11旋转编码器的动作并将参数返回给动作分析函数使用
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲
//返回:EC11旋转编码器的扫描结果-->> char ScanResult -->> 0:无动作;1:正转; -1:反转;2:只按下按键;3:按着按键正转;-3:按着按键反转
//详解:只扫描EC11旋转编码器有没有动作,不关心是第几次按下按键或长按或双击。返回值直接作为形参传给 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函数使用
//*******************************************************************/
char Encoder_EC11_Scan(void)
{
//以下储存A、B上一次值的变量声明为静态全局变量,方便对EC11对应的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //返回编码器扫描结果,用于分析编码器的动作
//返回值的取值: 0:无动作; 1:正转; 2:反转;
// 3:只按下按键; 4:双击; 5:长按
//======================================================//
if (EC11_A_Now != EC11_A_Last) //以A为时钟,B为数据。正转时AB反相,反转时AB同相
{
if (EC11_A_Now == 0)
{
if (EC11_B_Now == 1) //只需要采集A的上升沿或下降沿的任意一个状态,若A下降沿时B为1,正转
ScanResult = 1; //正转
else //反转
ScanResult = 2;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
//>>>>>>>>>>>>>>>>编码器按键按下处理程序<<<<<<<<<<<<<<<<//
if (EC11_Key == 0) //====检测到按键按下====//
{
if (EC11_KEY_COUNT<10000) //打开按键按下时间定时器
EC11_KEY_COUNT++;
if (EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //按下按键时间到达消抖时间时
{ //置位短按按键标志
FLAG_EC11_KEY_ShotClick = 1;
}
if ((EC11_KEY_DoubleClick_Count > 0) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //松开按键后,又在定时器在双击时间内按下按键
{ //置位双击按键标志
FLAG_EC11_KEY_DoubleClick = 1;
}
if (EC11_KEY_COUNT > KEY_COUNT_LONGTIME) //按下按键时间到达长按时间
{ //置位长按按键标志并复位短按按键标志
FLAG_EC11_KEY_LongClick = 1;
FLAG_EC11_KEY_ShotClick = 0;
}
}
else //====检测到按键松开====//
{
if (EC11_KEY_COUNT < KEY_COUNT_DESHAKING) //没到消抖时长就松开按键,复位所有定时器和按键标志
{
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
else
{
if (FLAG_EC11_KEY_ShotClick == 1) //短按按键定时有效期间
{
if ((FLAG_EC11_KEY_DoubleClick == 0) && (EC11_KEY_DoubleClick_Count >= 0))//记录第一次按下动作,使如果还能在规定时间内按下第二次,就可以执行满足下一行代
{
EC11_KEY_DoubleClick_Count++;
}
if ((FLAG_EC11_KEY_DoubleClick == 1) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //如果在规定双击时间内再次按下按键
{ //认为按键是双击动作
FLAG_EC11_KEY_DoubleClick = 2;
}
if ((FLAG_EC11_KEY_DoubleClick == 0) && (EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)) //如果没有在规定双击时间内再次按下按键
{
FLAG_EC11_KEY_ShotClick = 0;
} //认为按键是单击动作
}
if (FLAG_EC11_KEY_LongClick == 1)//检测到长按按键松开
{
Flag = 1;
}
}
}
//>>>>>>>>>>>>>>>>编码器按键分析处理程序<<<<<<<<<<<<<<<<//
if (EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按键延时到了时间
{
//短按按键动作结束代码
if ((FLAG_EC11_KEY_ShotClick == 0) && (FLAG_EC11_KEY_DoubleClick != 2)&& (FLAG_EC11_KEY_LongClick != 1)) //短按按键动作结束代码
{
//--------短按按键动作结束代码--------//
ScanResult = 3;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
else if ((FLAG_EC11_KEY_LongClick == 1) && (EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //连续长按按键按下代码
{
//-------连续长按按键按下代码--------//
if (Flag == 0)
{
ScanResult = 5;
Flag = 2;
}
else if (Flag == 1)//检测长按松开时
{
Flag = 0;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
}
//双击按键动作结束代码
if ((FLAG_EC11_KEY_DoubleClick == 2) && (EC11_KEY_DoubleClick_Count > 0) && (EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //双击按键动作结束代码
{
//--------双击按键动作结束代码--------//
ScanResult = 4;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
}
return ScanResult;
}
EC11.h
#ifndef __ec11_H
#define __ec11_H
#include "sys.h"
//----------------IO口定义----------------//
#define EC11_A_Now PAin(2) //EC11的A引脚,视为时钟线
#define EC11_B_Now PAin(1) //EC11的B引脚,视为信号线
#define EC11_Key PAin(0) //EC11的按键
//----------------编码器参数微调宏定义----------------//
#define EC11_SCAN_PERIOD_MS 1 //EC11编码器扫描周期
#define KEY_COUNT_DESHAKING ( 6/EC11_SCAN_PERIOD_MS) //按键消抖时间
#define KEY_COUNT_LONGTIME (600/EC11_SCAN_PERIOD_MS) //长按按键判断时间
#define KEY_COUNT_DUALCLICKTIME (200/EC11_SCAN_PERIOD_MS) //双击按键判断时间
#define KEY_LONG_REPEAT_TIME (200/EC11_SCAN_PERIOD_MS) //长按按键的回报率的倒数,即一直长按按键时响应的时间间隔
//----------------局部文件内变量列表----------------//
static char EC11_A_Last = 0; //EC11的A引脚上一次的状态
static char EC11_B_Last = 0; //EC11的B引脚上一次的状态
static char Flag = 0; //长按结束标志符号
//所谓一定位对应一脉冲,是指EC11旋转编码器每转动一格,A和B都会输出一个完整的方波。
static int EC11_KEY_COUNT = 0; //EC11按键动作计数器
static int EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
static char FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
static char FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
static char FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
//----------------函数快速调用(复制粘贴)列表----------------//
//
/*******************************************************************
void Encoder_EC11_Init(void); //初始化EC11旋转编码器IO口和类型以及变量初始化
char Encoder_EC11_Scan(); //扫描旋转编码器的动作
void Encoder_EC11_Analyze(char EC11_Value); //分析EC11旋转编码器的动作以及动作处理代码
******************************************************************/
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//----------------函数声明列表----------------//
//
//*******************************************************************/
//功能:初始化EC11旋转编码器相关参数
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲。
//返回:无
//详解:对EC11旋转编码器的连接IO口做IO口模式设置。以及将相关的变量进行初始化
//*******************************************************************/
void EC11_Init(void);
//*******************************************************************/
//功能:扫描EC11旋转编码器的动作并将参数返回给动作分析函数使用
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲
//返回:EC11旋转编码器的扫描结果-->> char ScanResult -->> 0:无动作;1:正转; -1:反转;2:只按下按键;3:按着按键正转;-3:按着按键反转
//详解:只扫描EC11旋转编码器有没有动作,不关心是第几次按下按键或长按或双击。返回值直接作为形参传给 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函数使用
//*******************************************************************/
char Encoder_EC11_Scan(void);
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
void EC11_Hander(u8 EC11_Value);
#endif