**先给大家看看我选用的EC11元器件**
代码在最后,复制可直接食用
以及我的电路图
在研究EC11的时序之前首先要了解一点,EC11按旋转的输出动作可以分为两种。一种是转两格,A、B对C端输出一个完整脉冲(转一格就只是由低电平->高电平或由高电平->低电平);另一种就是转一格,A、B对C端输出一个完整脉冲。
一定位一脉冲的EC11按测试电路图的接法,在静止的时候AB两线输出都是高电平。转动一格,AB两线各自输出一个低电平脉冲,然后又回到高电平状态。对应于EC11内部AB两个触点开关的动作为断开–>闭合–>断开。
两定位一脉冲的EC11稍微复杂一些,转动一格只会输出半个脉冲。静止时,AB触点开关可以是断开的也可以是闭合的。若初始状态时AB都是高电平,转动一格就输出从高电平到低电平的下降沿,随后一直输出低电平。对应于EC11内部AB两个触电开关的动作为断开–>闭合。
若初始状态时AB都是低电平,转动一格就输出从低电平到高电平的上升沿,随后一直输出低电平。对应于EC11内部AB两个触点开关的动作为闭合–>断开。
由于两脉冲一定位的EC11会有两种初始状态,写驱动程序就需要考虑多一些情况。再者,这类EC11在转动到内部AB触点一直闭合的时候,就相当于把上拉电阻的另一端接地,无形中加大了系统的功耗(若外接10K上拉电阻到5V电源就是500uA的电流),这对于低功耗应用来说是非常不利的。
因此对于无特殊要求的人机输入应用来说,我都推荐使用一定位一脉冲的EC11。
当然了,有一些质量比较差的EC11会有一些额外的问题要考虑,例如开关的抖动问题,例如转动定位不清晰,静止时AB两个触点都要闭合或者都要断开才对,但是定位点不清晰,转动的角度不到位导致一个触点已经闭合(断开)了,另一个触点却还保持在断开(闭合)。对于这些问题我们在后面再做考虑。
时序图
要写驱动程序,得先了解EC11的工作过程。使用逻辑分析仪(LA)抓取时序可以很方便的从单片机的角度了解EC11的工作过程并依此来编写驱动程序。
EC11的编码器部分有3个引脚,A,B,和C。通常可以把C端接GND,A,B端接到输入上拉模式的IO口。可以取A或B任意一根线作为时钟线,另一根作为信号输出线。我个人习惯把A线作为时钟线,B线作为信号线。
本文中出现的逻辑分析仪抓取的时序图中均是最上方通道为EC11的A线,视为时钟;下方一个通道为EC11的B线,视为数据输出。
就不啰嗦太多,我就直接上代码,我使用STM32F103系列
#include "ec11.h"
#include "sys.h"
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)//是更新中断
{
Encoder_EC11_Analyze(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(unsigned char Set_EC11_TYPE)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9);
//EC11类型选择:0-一定位一脉冲;1-两定位一脉冲
if (Set_EC11_TYPE == 0)
{
EC11_Type = 0;
}
else
{
EC11_Type = 1;
}
//避免上电时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旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value)
{
char AnalyzeResult = 0;
static unsigned int TMP_Value = 0; //中间计数值,用于连续长按按键的动作延时间隔
//>>>>>>>>>>>>>>>>编码器正转处理程序<<<<<<<<<<<<<<<<//
if(EC11_Value == 1) //正转
{
//--------编码器正转动作代码--------//
printf("正转!!")
}
//>>>>>>>>>>>>>>>>编码器反转处理程序<<<<<<<<<<<<<<<<//
if(EC11_Value == 8) //反转
{
//--------编码器反转动作代码--------//
printf("反转!!");
}
//>>>>>>>>>>>>>>>>编码器按键按下并正转处理程序<<<<<<<<<<<<<<<<//
if(EC11_Value == 3)
{
//--------编码器按键按下并正转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下并反转处理程序<<<<<<<<<<<<<<<<//
if(EC11_Value == 9)
{
//--------编码器按键按下并反转动作代码--------//
// printf("按下反转!!");
}
//>>>>>>>>>>>>>>>>编码器按键按下处理程序<<<<<<<<<<<<<<<<//
if(EC11_Value == 2) //====检测到按键按下====//
{
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_EC11_KEY_LongClick = 0;
}
}
}
//>>>>>>>>>>>>>>>>编码器按键分析处理程序<<<<<<<<<<<<<<<<//
if(EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按键延时到了时间
{
//短按按键动作结束代码
if((FLAG_EC11_KEY_ShotClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)&&(EC11_KEY_COUNT < KEY_COUNT_LONGTIME)) //短按按键动作结束代码
{
//--------短按按键动作结束代码--------//
EC11_NUM_SW++;
if(EC11_NUM_SW >= 4)
EC11_NUM_SW = 1;
AnalyzeResult = 1;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//双击按键动作结束代码
if((FLAG_EC11_KEY_DoubleClick == 2)&&(EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //双击按键动作结束代码
{
//--------双击按键动作结束代码--------//
if(EC11_NUM_SW == 5)
EC11_NUM_SW = 0;
if(EC11_NUM_SW == 4)
EC11_NUM_SW = 5;
if(EC11_NUM_SW <4)
{
EC11_NUM_SW = 4;
}
AnalyzeResult = 2;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//连续长按按键按下代码
if((FLAG_EC11_KEY_LongClick == 1)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //连续长按按键按下代码
{
TMP_Value ++;
if(TMP_Value % KEY_LONG_REPEAT_TIME == 0)
{
TMP_Value = 0;
//-------连续长按按键按下代码--------//
AnalyzeResult = 4;
}
}
//长按按键动作结束代码
if((FLAG_EC11_KEY_LongClick == 0)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //长按按键动作结束代码
{
//--------长按按键按下动作结束代码--------//
EC11_NUM_SW = 0;
AnalyzeResult = 3;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
}
}
return AnalyzeResult;
}
//*******************************************************************/
//功能:扫描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()
{
//以下储存A、B上一次值的变量声明为静态全局变量,方便对EC11对应的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //返回编码器扫描结果,用于分析编码器的动作
//返回值的取值: 0:无动作; 1:正转; 8:反转;
// 2:只按下按键; 3:按着按键正转; 9:按着按键反转
//======================================================//
if(EC11_Type == 0) //================一定位对应一脉冲的EC11================//
{ //======================================================//
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 = 8;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
//======================================================//
else //================两定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now !=EC11_A_Last) //当A发生跳变时采集B当前的状态,并将B与上一次的状态进行对比。
{ //若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(EC11_A_Now == 1) //EC11_A和上一次状态相比,为上升沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = 1; //正转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = 8; //反转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<<<//
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = 1; //正转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = 8; //反转
}
else //EC11_A和上一次状态相比,为下降沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = 8; //反转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = 1; //正转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<<<//
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = 8; //反转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = 1; //正转
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
if(EC11_Key == 0) //如果EC11的按键按下,并且没有EC11没有转动,
{
// if(ScanResult == 0) //按下按键时未转动
ScanResult = 2; //返回值为2
// else
// {
// if(ScanResult == 1) //按下按键时候正转
// ScanResult = 3; //返回值为3
// if(ScanResult == 8) //按下按键时候反转
// ScanResult = 9; //返回值为-3
// }
}
return ScanResult; //返回值的取值: 0:无动作; 1:正转; 8:反转;
} // 2:只按下按键; 3:按着按键正转; 9:按着按键反转
#ifndef __ec11_H
#define __ec11_H
#include "sys.h"
#include "stm32f10x.h"
//----------------IO口定义----------------//
#define EC11_A_Now PBin(9) //EC11的A引脚,视为时钟线
#define EC11_B_Now PBin(8) //EC11的B引脚,视为信号线
#define EC11_Key PBin(7) //EC11的按键
//----------------编码器动作代码相关定义----------------//
static unsigned char EC11_NUM_SW = 0;
//----------------编码器参数微调宏定义----------------//
#define EC11_SCAN_PERIOD_MS 1 //EC11编码器扫描周期
#define KEY_COUNT_DESHAKING ( 10/EC11_SCAN_PERIOD_MS) //按键消抖时间
#define KEY_COUNT_LONGTIME (150/EC11_SCAN_PERIOD_MS) //长按按键判断时间
#define KEY_COUNT_DUALCLICKTIME (150/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 EC11_Type = 1; //定义变量暂存EC11的类型---->>>>---- 0:一定位对应一脉冲; 1:两定位对应一脉冲
//所谓一定位对应一脉冲,是指EC11旋转编码器每转动一格,A和B都会输出一个完整的方波。
//而 两定位对应一脉冲,是指EC11旋转编码器每转动两格,A和B才会输出一个完整的方波,只转动一格只输出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(unsigned char Set_EC11_TYPE); //初始化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(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);
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value);
#endif
这个程序我只做了正转,反转和按下,有需要自行修改,我这里利用的是定时器1毫秒扫描,所以刷新函数Encoder_EC11_Analyze(Encoder_EC11_Scan());
我写在中断里面,初始化函数 EC11_Init(1);
在这里需要注意一定位对应一脉冲;1(或非0)----两定位对应一脉冲,前面有说过了,放心食用文章来源:https://www.toymoban.com/news/detail-781068.html
转自:https://www.jianshu.com/p/41fa67ecb248文章来源地址https://www.toymoban.com/news/detail-781068.html
到了这里,关于STM32 EC11 旋转编码器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!