EC11编码器基于运算解码的算法(原创)
EC11旋转编码器是一种基于脉冲发生的装置,它的编码逻辑如下图
单片机解码方式主要有几种:
1、MCU定时器自带编码器模式,如GD32、STM32等;
2、使用外部IO中断,在中断时根据A或B的电平状态,来确定编码器的方向并计数;
3、在主程序里循环扫描或定时器中断扫描的方式;
我现在以第3种扫描算法方面来跟大家分享:
目前网上搜索到的代码,基本都是以时序AB相的状态,基于逻辑条件来解码,如:
- 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:正转; -1:反转;
- // 2:只按下按键; 3:按着按键正转; -3:按着按键反转
-
- //======================================================//
- 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 = -1;
- }
- EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
- EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
- }
- }
- return ScanResult; //返回值的取值: 0:无动作; 1:正转; -1:反转;
- }
复制代码 也有lkc8210发表的非常不错的方法:一定位一脉冲的EC11旋转编码器最简洁的单片机驱动代码
我通过研究,原创了基于运算解码的算法,先上代码//算法一
uint8_t KeyA_Last;
uint8_t KeyB_Last;
uint8_t KeyA_Now;
uint8_t KeyB_Now;
int EC_Counter;
int EC_CountTemp;
void Encoder_Ini()
{
KeyA_Last = P10;
KeyB_Last = P11;
}
void Encoder_Run()
{
KeyA_Now = P10;
KeyB_Now = P11; EC_Counter +=(1 ^ (KeyA_Last ^ KeyB_Last)) * (int)((KeyA_Last ^ KeyA_Now) - (KeyB_Last ^ KeyB_Now)); //更新计数
KeyA_Last = KeyA_Now;
KeyB_Last = KeyB_Now;
}
眼尖的网友可以看到,我的扫描解码程序里并没有逻辑判断,EC_Counter计数值会自动解码加减,什么情况?
我先来解析一下算法:
上次A、B电平相等时,A=1,B=1,或A=0,B=0时,KeyA_Last ^ KeyB_Last的异或运行结果会等于0,这时1^0就变成了1,
这个运算的作用就是如果上次AB相等,就可能会更新EC_Counter的值,因为这个结果后面跟着乘号,如果上次AB不相等时,运算结果为0,0乘以任何数仍为0,
EC_Counter的值就+=0,不变;
A相的电平发生变化时(上升沿或下降沿),KeyA_Last^KeyA_Now运算的结果=1;
A相的电平未发生变化,KeyA_Last^Key_Now运算的结果=0;
同理,B相的电平发生变化时(上升沿或下降沿),KeyB_Last^KeyB_Now运算的结果=1;
B相的电平未发生变化,KeyB_Last^KeyB_Now运算的结果=0;
现以反转时序来分析,从EC11编码器的波形可以看出,
反转时,初始状态要么A=1 ,B=1,要么A=0,B=0,在此以默认A=1,B=1来举例计算:
第一步: A=1,B=1,此时 KeyA_Last=1, KeyB_Last =1, KeyA_Now=1 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)= 1^(1^1)=1,
KeyA_Last^KeyA_Now=1^1=0,
KeyB_Last^KeyB_Now =1^1=0,
右边运算结果:1*(0-0)= 0,即EC_Counter+=0 ;
第二步: A=1,B=0 此时 KeyA_Last=1, KeyB_Last =1, KeyA_Now=1 , KeyB_Now=0
1 ^ (KeyA_Last ^ KeyB_Last)=1^(1^1)=1,
KeyA_Last^KeyA_Now=1^1=0,
KeyB_Last^KeyB_Now =1^0=1,
右边运算结果:1*(0-1)= -1,即EC_Counter+= -1;
第三步: A=0,B=0 此时 KeyA_Last=1, KeyB_Last =0, KeyA_Now=0 , KeyB_Now=0
1 ^ (KeyA_Last ^ KeyB_Last)=1^(1^0)=0,
KeyA_Last^KeyA_Now=1^0=1,
KeyB_Last^KeyB_Now =0^0=0,
右边运算结果:0*(1-0)=0,即EC_Counter+=0;
第四步: A=0,B=1 此时 KeyA_Last=0, KeyB_Last =0, KeyA_Now=0 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)=1^(0^0)=1,
KeyA_Last^KeyA_Now=0^0=0,
KeyB_Last^KeyB_Now =0^1=1,
右边运算结果:1*(0-1)=-1,即EC_Counter+=-1;
第五步: A=1,B=1 此时 KeyA_Last=0, KeyB_Last =1 KeyA_Now=1 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)=1^(0^1)=0,
KeyA_Last^KeyA_Now=0^1=1,
KeyB_Last^KeyB_Now =1^1=0,
右边运算结果:0*(1-0)=0,即EC_Counter+=0;
继续反转的话,又到了第二步;
正转时,初始状态要么A=1 ,B=1,要么A=0,B=0,在此以A=1,B=1来举例计算:
第一步: A=1,B=1,此时 KeyA_Last=1, KeyB_Last =1, KeyA_Now=1 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)= 1^(1^1)=1,
KeyA_Last^KeyA_Now=1^1=0,
KeyB_Last^KeyB_Now =1^1=0,
右边运算结果:1*(0-0)= 0,即EC_Counter+=0 ;
第二步: A=0,B=1 此时 KeyA_Last=1, KeyB_Last =1, KeyA_Now=0 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)=1^(1^1)=1,
KeyA_Last^KeyA_Now=1^0=1,
KeyB_Last^KeyB_Now =1^1=0,
右边运算结果:1*(1-0)= 1,即EC_Counter+= 1;
第三步: A=0,B=0 此时 KeyA_Last=0, KeyB_Last =1, KeyA_Now=0 , KeyB_Now=0
1 ^ (KeyA_Last ^ KeyB_Last)=1^(0^1)=0,
KeyA_Last^KeyA_Now=0^0=0,
KeyB_Last^KeyB_Now =1^0=1,
右边运算结果:0*(0-1)=0,即EC_Counter+=0;
第四步: A=1,B=0 此时 KeyA_Last=0, KeyB_Last =0, KeyA_Now=1 , KeyB_Now=0
1 ^ (KeyA_Last ^ KeyB_Last)=1^(0^0)=1,
KeyA_Last^KeyA_Now=0^1=1,
KeyB_Last^KeyB_Now =0^0=0,
右边运算结果:1*(1-0)=1,即EC_Counter+=1;
第五步: A=1,B=1 此时 KeyA_Last=1, KeyB_Last =0 KeyA_Now=1 , KeyB_Now=1
1 ^ (KeyA_Last ^ KeyB_Last)=1^(1^0)=0,
KeyA_Last^KeyA_Now=1^1=0,
KeyB_Last^KeyB_Now =0^1=1,
右边运算结果:0*(0-1)=0,即EC_Counter+=0;
继续正转的话,又到了第二步;
从上面运算可以,公式以双倍频(旋转嘀嗒一格,两次计数)解码EC11编码器;
有网友问,如果我只要单倍频(旋转嘀嗒一下,单次计数),又怎么解码?
这时,可以要根据你的编码器默认电平来改,默认A=1,B=1时,
加个条件即可
if(KeyA_Last==0 && KeyB_Last==0) //只有上次都为0时(要与默认值相反),才更新计数
{
EC_Counter += (int)((KeyA_Last ^ KeyA_Now) - (KeyB_Last ^ KeyB_Now)); //计数值运算
}
默认A=0,B=0时,
加个条件即可
if(KeyA_Last==1 && KeyB_Last==1) //只有上次都为1时(要与默认值相反),才更新计数
{
EC_Counter += (int)((KeyA_Last ^ KeyA_Now) - (KeyB_Last ^ KeyB_Now)); //计数值运算
}
单倍频的程序代码如下:
- uint8_t KeyA_Last;
- uint8_t KeyB_Last;
- uint8_t KeyA_Now;
- uint8_t KeyB_Now;
- int EC_Counter;
- int EC_CountTemp;
- void Encoder_Ini()
- {
- KeyA_Last = P10;
- KeyB_Last = P11;
- }
- void Encoder_Run()
- {
- KeyA_Now = P10;
- KeyB_Now = P11;
- if(KeyA_Last==1 && KeyB_Last==1) //注意此处与编码器默认的电平相
- {
- EC_Counter +=(int)((KeyA_Last ^ KeyA_Now) - (KeyB_Last ^ KeyB_Now));//如要调整加减方向,请修改此
- }
- KeyA_Last = KeyA_Now;
- KeyB_Last = KeyB_Now;
- }
复制代码 以上算法是不是很特别?以上代码我通过了验证,如果不用定时中断扫描,正常12格/秒手速旋转无问题,如果加1ms的定时中断扫描,80格/秒手速旋转无问题;编译后代码在N76E003里,汇编行数50行,大小增加100byte
如果网友只需要单倍频,也可以用我下面的算法,汇编行数更少40多行,大小增加72byte,
AB状态一起处理,具体原理大家去分析
//算法二 编译最小,但只适合单倍频
int EC_Counter;
int EC_CountTemp;
uint8_t KeyAB_Last;
uint8_t KeyAB_Now;
void Encoder_Ini()
{
KeyAB_Last =P1 & 0x03;
}
void Encoder_Run()
{
uint8_t Temp;
KeyAB_Now =(P1 & 0x03);
Temp=KeyAB_Now^KeyAB_Last;
if(KeyAB_Last==00) //注意此处与编码器默认的电平相反
{
EC_Counter +=(int)((Temp&0x01)-(Temp>>1)); //如要调整方向,请修改此处
}
KeyAB_Last=KeyAB_Now;
}
如果有网友说:你上面单倍频的算法,忽略了过程中的一部分,会不会有问题?
我可以放心的告诉大家,我验证的效果和算法一效果相当,可以使用,当然如果你不放心,我还有一种就是把所有的步骤都考虑进去的算法,如下:
//算法三 编译最大,但有考虑顺序步骤,只适合单倍
uint8_t EC_Index;
uint8_t EC_Last;
uint8_t EC_Now;
int EC_Counter;
int EC_CountTemp;
void Encoder_Ini()
{
EC_Last=P1 & 0x03;
EC_Counter=0;
EC_Index=0x40;
}
void Encoder_Run()
{
uint8_t temp;
EC_Now=P1 & 0x03;
temp=EC_Now^EC_Last;
EC_Last=EC_Now;
EC_Index>>=temp;
if(EC_Now==0x03) //注意是编码器默认的电平
{
EC_Index=0x40;
}
if(EC_Index==2 || EC_Index==4)
{
EC_Counter+=(EC_Index-3); //如要调整方向,请修改此处
EC_Index=0x40;
}
}
原理解析:
B对应P11 A对应P10
默认编码器值为高电平,即A=1,B=1,
反转分析:
第一步:A=1,B=1,EC_Last=0x03 , EC_Now=0x03 为什么会是0x03 , 因为P1 & 0x03=xxxxxx11 & 00000011=00000011=0x03
EC_Now^EC_Last=0x03^0x03=0,因此temp=0;
EC_Index>>=temp 即 EC_Index>>=0;此时EC_Index不变,还是0x40 二进制是0100 0000
而且,细心的网友会发现,这句程序:
if(EC_Now==0x03)
{
EC_Index=0x40; //重置EC_Index
}
因此EC_Index最后还是等于0x40
第二步:A=1,B=0,EC_Last=0x03 , EC_Now=0x01 为什么会是0x01 , 因为P1 & 0x03=xxxxxx01 & 00000011=00000001=0x01
EC_Now^EC_Last=0x01^0x03=2,因此temp=2;
EC_Index>>=temp 即 EC_Index>>=2;此时EC_Index=0x10 二进制是0001 0000
第三步:A=0,B=0,EC_Last=0x01 , EC_Now=0x00 为什么会是0x00 , 因为P1 & 0x03=xxxxxx00 & 00000011=00000000=0x00
EC_Now^EC_Last=0x00^0x01=1,因此temp=1;
EC_Index>>=temp 即 EC_Index>>=1;此时EC_Index=0x0F 二进制是0000 1000
第四步:A=0,B=1,EC_Last=0x00 , EC_Now=0x02 为什么会是0x02 , 因为P1 & 0x03=xxxxxx10 & 00000011=00000010=0x02
EC_Now^EC_Last=0x02^0x00=2,因此temp=2;
EC_Index>>=temp 即 EC_Index>>=2;此时EC_Index=0x02 二进制是0000 0010
此时,EC_Index=0x02 符合条件更新计数的条件 EC_Index==0x02 或 EC_Index==0x04
(EC_Index-3)=2-3=-1
EC_Counter+=(EC_Index-3) ,即EC_Counter+= - 1 ,自动减1了
第五步:A=1,B=1,EC_Last=0x02 , EC_Now=0x03 为什么会是0x03 , 因为P1 & 0x03=xxxxxx11 & 00000011=00000011=0x03
EC_Now^EC_Last=0x03^0x02=1,因此temp=1;
EC_Index>>=temp 即 EC_Index>>=1;此时EC_Index=0x01 二进制是0000 0001
细心的网友会发现,这句程序,
if(EC_Now==0x03)
{
EC_Index=0x40; //重置EC_Index
}因此EC_Index最后还是等于0x40;运行到此步后,会从第二步继续循环;
正转分析:
第一步:A=1,B=1,EC_Last=0x03 , EC_Now=0x03 为什么会是0x03 , 因为P1 & 0x03=xxxxxx11 & 00000011=00000011=0x03
EC_Now^EC_Last=0x03^0x03=0,因此temp=0;
EC_Index>>=temp 即 EC_Index>>=0;此时EC_Index不变,还是0x40 二进制是0100 0000
而且,细心的网友会发现,这句程序:
if(EC_Now==0x03)
{
EC_Index=0x40; //重置EC_Index
}
因此EC_Index最后还是等于0x40
第二步:A=0,B=1,EC_Last=0x03 , EC_Now=0x02 为什么会是0x02 , 因为P1 & 0x03=xxxxxx10 & 00000011=00000010=0x02
EC_Now^EC_Last=0x02^0x03=1,因此temp=1;
EC_Index>>=temp 即 EC_Index>>=1;此时EC_Index=0x20 二进制是0010 0000
第三步:A=0,B=0,EC_Last=0x02 , EC_Now=0x00 为什么会是0x00 , 因为P1 & 0x03=xxxxxx00 & 00000011=00000000=0x00
EC_Now^EC_Last=0x00^0x02=2,因此temp=2;
EC_Index>>=temp 即 EC_Index>>=2;此时EC_Index=0x0F 二进制是0000 1000
第四步:A=1,B=0,EC_Last=0x00 , EC_Now=0x01 为什么会是0x01 , 因为P1 & 0x03=xxxxxx01 & 00000011=00000001=0x01
EC_Now^EC_Last=0x01^0x00=1,因此temp=1;
EC_Index>>=temp 即 EC_Index>>=1;此时EC_Index=0x04 二进制是0000 0100
此时,EC_Index=0x04 符合条件更新计数的条件 EC_Index==0x02 或 EC_Index==0x04
(EC_Index-3)=4-3=1
EC_Counter+=(EC_Index-3) ,即EC_Counter+=1 ,自动加1了第五步:A=1,B=1,EC_Last=0x01 , EC_Now=0x03 为什么会是0x03 , 因为P1 & 0x03=xxxxxx11 & 00000011=00000011=0x03
EC_Now^EC_Last=0x03^0x01=2,因此temp=2;
EC_Index>>=temp 即 EC_Index>>=2;此时EC_Index=0x01 二进制是0000 0001
细心的网友会发现,这句程序,
if(EC_Now==0x03)
{
EC_Index=0x40; //重置EC_Index
}因此EC_Index最后还是等于0x40;运行到此步后,会从第二步继续循环;
从上面的分析可以看出,算法是有按顺序对每个步骤进行处理并关联到了temp和EC_Index的值,从而决定了EC_Counter的值
这个算法的核心是:
反转一格时,EC_Now^EC_Last的值是0,2,1,2, 1 的变化,第一步和第五步为默认状态,EC_Index重置为0x40,移位不起作用,
因此实际反转时,EC_Index右移了2+1+2=5次,最终等于2;
正转一格时,EC_Now^EC_Last的值是0,1,2,1, 2 的变化,第一步和第五步为默认状态,EC_Index重置为0x40,移位不起作用,
因此实际正转时,EC_Index右移了1+2+1=4次,最终等于4;
然后用 EC_Index-3 巧妙的得到了正负1,实现自动正反转加减;
现在可以解析EC_Index重置为0x40的意义:0x40二进制是0100 0000,右移位4或5次时能得到4和2的值,其他数值不行;
以上就是我原创的EC11编码器基于运算解码的算法,希望能给大家抛砖引玉。
|