EC11編碼器基于運算解碼的算法(原創)
EC11旋轉編碼器是一種基于脈沖發生的裝置,它的編碼邏輯如下圖
1.jpg (194.72 KB, 下載次數: 48)
下載附件
2023-12-13 19:51 上傳
單片機解碼方式主要有幾種:
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編碼器基于運算解碼的算法,希望能給大家拋磚引玉。
|