一. 简介
本次将基于AuroraFOC开发板,来教大家如何将旋转编码器按键优雅地使用起来,为大家开发多功能按键提供一种思路。
开发环境
- STM32CubeMX HAL库
- Clion
作者: FPGA之旅(ValentineHP)
二. 原理(图)介绍
旋转编码器按键原理图如下,它有左旋转、右旋转和按下这三种状态。在默认情况下,EC_AR和EC_BR均为高电平,旋转的时候,它们会依次变为低电平,并且持续一小段时间后变为高电平,不同方向旋转的时候,它们电平的变化会有一个相位差,根据这个方向就可以来判断旋转的方向了。
EC_KEYR默认情况应该是悬空的,在按键按下的时候会拉低,使用的时候需要注意,单片机内部需要将IO口设置为上拉模式,否则读到的值一直为0。
作者: FPGA之旅(ValentineHP)
三. 编程实现
- 定义旋转编码器的状态类型。状态的扩展上主要是在按键上,多了长按和双击,多的两种状态的实现是设计的难点。
//EncoderKey 状态
typedef enum EncoderKeyState{
EncoderKey_None = 0,
EncoderKey_Click, //单击
EncoderKey_LClick, //长按
EncoderKey_DClick, //双击
EncoderKey_Left, //左边
EncoderKey_Right //右边
}EncoderKeyState;
- 先挑简单的实现: 左右旋转检测(在FPGA驱动电机的文章中已经介绍过了,原理一样)。设置旋转编码器的A相为电平中断,中断类型为上下边沿,那么第一次进入中断一定是下降沿,第二次进入中断一定是上升沿,通过这两次进入中断来判断B相的电平,结合上图,就可以判断出编码器的方向了。
else if( GPIO_Pin == EC_AR_Pin){ //旋转检测
if( ec_flag == 0){
if( HAL_GPIO_ReadPin(EC_AR_GPIO_Port,EC_AR_Pin) == 1) //消除误差,防止误判
ec_flag = 0;
else if( HAL_GPIO_ReadPin(EC_BR_GPIO_Port,EC_BR_Pin) == 1)
ec_flag = 1;
else
ec_flag = 2;
}
else{
if( HAL_GPIO_ReadPin(EC_AR_GPIO_Port,EC_AR_Pin) == 0) //消除误差,防止误判
;//ec_flag = 0;
else if( ec_flag == 1 && HAL_GPIO_ReadPin(EC_BR_GPIO_Port,EC_BR_Pin) == 0)
key_state = EncoderKey_Right;
else if(ec_flag == 2 && HAL_GPIO_ReadPin(EC_BR_GPIO_Port,EC_BR_Pin) == 1)
key_state = EncoderKey_Left;
ec_flag = 0;
}
// usb_printf("%d %d %d \r\n",ec_flag,HAL_GPIO_ReadPin(EC_AR_GPIO_Port,EC_AR_Pin),HAL_GPIO_ReadPin(EC_BR_GPIO_Port,EC_BR_Pin));
}
-
终于到了按键检测。需要检测三种不同的按键状态,就需要使用到定时器了,CubeMX默认是使用了SysTick滴答定时器作为了基本的时钟源,所以就不需要使用额外的定时器了。我们可以通过HAL_GetTick这个函数获取当前系统运行的时间戳,这个函数至关重要,也是检测三种不同按键状态的关键。
同样在CubeMX设置按键为电平中断,中断类型为下降上升沿中断(按键默认电平为高电平)。
- 第一次按键按下,这个时候会进入中断,我们获取当前系统运行的时间戳,第一次按键释放,也会进入中断,通过两次获取的时间戳差值判断这次按键是否有效(进行消抖处理),这就是检测单击。
- 第一次按键释放,通过两次获取的时间戳差值,是否达到了长按的时间的标准,来判断是否为长按。
- **双击啦!!!**第二次按键按下,进入中断后,将第一次按键释放时的时间戳 ,与当前时间戳进行比较,两个差小于设置值,说明这次是双击。双击中包含了单击的这一操作,这就会有个冲突,要解决这个冲突其实非常简单,那就是在获取按键状态的时候,如果按键的状态为单击,就来判断当前的时间戳和第一次按键释放时的时间戳的差值是否大于了双击的标准 ,是的话,就返回单击,否则的话就返回没有动作发生。
/* 获取按键状态 如果按键按下 则会清除按键状态 */ EncoderKeyState EncoderKey_Driver::get_key_state(){ EncoderKeyState state; if( key_state == EncoderKey_Click){ if((int32_t)HAL_GetTick() - (int32_t)key_tick < 400 || (int32_t)key_tick - (int32_t)HAL_GetTick() > 400) return EncoderKey_None; } #ifdef DEBUG_PRINTF if( key_state != EncoderKey_None){ usb_printf("%d\r\n",key_state); } #endif state = key_state; key_state = EncoderKey_None; return state; } void EncoderKey_Driver::EncoderKey_EXTI_Callback(uint16_t GPIO_Pin) { //按键 if( GPIO_Pin == EC_KEY_Pin){ if(HAL_GPIO_ReadPin(EC_KEY_GPIO_Port,EC_KEY_Pin) == 0) { if (HAL_GetTick() - key_tick < 400) dclick_flag = 1; else dclick_flag = 0; key_tick = HAL_GetTick(); } else{ if( HAL_GetTick() - key_tick > 800) //长按 key_state = EncoderKey_LClick; else if(HAL_GetTick() - key_tick > 5){ key_state = (dclick_flag == 0)? EncoderKey_Click : EncoderKey_DClick; } dclick_flag = 0; } } }
是不是总体是还是比较简单的,如果大家有更好的实现方法,可以一起交流交流哦!!