Pulse Encoder Knob Interface

Keyes-KY040 encoder’dan gelen harekete göre değer girişi yapmak için bir arayüz hazırlamam gerekti, bununla ilgili düşüncelerimi burada paylaşmak istiyorum.

KY-040 modülünde 5 pinli bir header var. Bu pinler sırasıyla şöyle isimlendirilmişlerdir:

GND, + Besleme
SW Pushbutton NO ucu
DT Faz-B
CLK Faz-A

Modülün iç yapısını yukarıdaki gibi gösterebiliriz:

BQ’ları açık kollektör NPN çıkışlı endüktif proximity switch’ler olarak görelim. Rod Push Switch, knoba basınca kısa devre olan bir push-button. Bendeki modülde R3 olarak gözüken direnç takılı değil. Ama bu şekliyle bunu ham kontak olarak kullanamayız çünkü diğer uç (-) beslemeye gidiyor, yani her durumda bunu bir pull-down button olarak kullanacağız demektir.

İnternette yapılan bir aramada, beslemenin +5V’a bağlandığını gösteren şemalar geliyor ama sanırım bu, arduino modüllerinin bir kısmının 5V ile çalışması yüzünden böyle belirtilmiş. Ben modülü +3V ile besliyorum çünkü benim devrede, +3V’a bağlı harici pull-up dirençleri de var.
DT ve CLK uçları endüktif algılayıcıların pull-up yapılmış çıkışlarına bağlı ve aralarında dönme yönüne bağlı faz farkı olan encoder çarkı izleme sinyalleri.

Bu arada, modülün üstünde pull-up dirençleri olduğu için encoder sinyallerini işlemciye doğrudan bağlamak yeterli gibi gözükse de kenarlarda debounce olmasına tahammülü olmayacak kadar hızlı bir kod çalıştıracaksanız, pull-up kondansatörleri kullanmanız iyi olur. Ben işlemciye girmeden önce 10nF kondansatör ile pull-up yapıyorum.

ENC-A ve ENC-B olarak isimlendirdiğim encoder pulse sinyallerini P06 ve P07 portlarına giriyorum. P0’ın kullanılması şart değil, işaret fazlarını değerlendirmekte kullanacağım port-match fonksiyonu EFM8’in tüm pinlerinde var. Ancak, söylemeye gerek yok ama aynı portun pinlerini kullanmak register ayarlarını tek yazmada yapmamız açısından hızlandırıcı olabilir elbette.

Firmware

Eğer üzerinde çalıştığımız işlemcinin üstünde QEI (Quadrature Encoder Interface) modülü yoksa encoder fazlarını takip etmek için bir yol olarak port match fonksiyonunu kullanabiliriz.
Ben bu encoder’la ilk denemeleri yaparken doğrudan port değerini okuyan bir döngü ile sistemi çalıştırdım. Zaten örnek program diye bulacağınız şeylerin hepsinde de öyle yapılıyor.
Ancak pratik kullanımlarda çoğu zaman deneme programında olduğu gibi, port okumasına âmâde bomboş bir ana çevrim olmayacaktır. Bu durumda, özellikle yüksek encoder hızlarını doğru okumak için port-mismatch kesmesini kullanmak yerinde olur.

EFM8’de her bir portun MAT register’ıyla ayarlanan bir port match değeri var. Bunu portun “olmasını beklediğimiz” değeri, “boşta” değeri olarak düşünelim. Herhangi bir port bitinin durumu bu boşta değerden farklı olduğu an bir kesme tetiklenir. Elbette bu mekanizmayı sadece ilgilendiğimiz port bitleri için devreye almamız gerekir. Bunu da her portun MASK değerini ayarlayarak yaparız.
Port mismatch kesmesi EIE1<1> (_EMAT) biti ile devreye alınır ve 8 numaralı kesme vektörüne atlama yaptırır.
Kesmenin tetiklenme koşulunu şöyle yazabiliriz:
Py.x & PYMASK.x != PYMAT.X & PyMASK.x
Bu kesmenin bir flag’i yok. Kesmeye atlandığı an durum sıfırlanıyor. Microchip’teki gibi mismatch tetikleyen portu okuma zorunluluğu da yok.
Benim uygulamada, P0^6 ve P0^7 için mismatch kesmelerini devreye almak, sürekli olarak çağrılan bir kodla portların durumunu yoklama külfetinden (overhead) bizi kurtarıyor:

#define Enable_Port_Mismatch() P0MAT = 0xFF; P0MASK = 0xC0; EIE1 |= 0x02


Encoder’da bir hareket olduğunda kesme tetiklenecek. Ancak iki şeyi daha ayırt etmemiz gerekiyor: Hareketin yönü ve bitişi.
Bunun için kesme kodu içinde basit bir durum makinesi çalıştırmak gerek. Bu durum makinesinin çalışmasını şöyle özetleyebilirim:

0ENC_IDLEHangi girişin kesmeye neden olduğunu bul (önde olan faz). Bu, hareket yönünü belirlememizi sağlar. Ardından önde olan fazın kesme üretmesini iptal et. [Diyagramda 1 durumu]
1ENC_CW_LEADINGBu aşamada gelen kesme kesinlikle B girişinden geliyordur. Artık uygulamaya saat yönünde bir hareket olduğunu söyleyebiliriz. B fazının mismatch durumunu 1 yaparsak hareket fazının bitişinde kesme almış olacağız. [Diyagramda 2 durumu]
2ENC_CCW_LEADINGBu aşamada gelen kesme kesinlikle A girişinden geliyordur. Uygulamaya saat yönünün tersinde hareket olduğunu söyleyebiliriz. A fazının mismatch durumunu 1 yaparsak hareket fazının bitişinde kesme almış olacağız. [Diyagramda 2 durumu]
3ENC_PULS_TRAILINGGeride kalan fazın 0->1 gerçişi yapmasını bekle. Bu, hareketin bitişi demektir, yani durum makinesi başa dönebilir. [3] durumu. Her iki yön için de bu adım ortak.
4ENC_WAKEUPBu, sistemi uykudan uyandırmak için kullanılan adım. Bir hareket algılaması yapılmayacağı için durum makinesi çalışmaz.
void ISR_Port_Mismatch(void)  interrupt 8
{
 switch ( encoder_state )
  {
    case ENC_IDLE:
    // Encoder bo$ta iken bir mismatch kesmesi gelmi$se
    // önde olan faz =0 olmu$ demektir. Bu, cw/ccw ayrimini yapmamizi saglar
	  if ( ENC_A == 0 )
	  {
             P0MASK = 0x80;	// P0.6 (ENC_A port match fonksiyonu kapatildi)
	     encoder_state = ENC_CW_LEADING;
	  }
	  else
	  {
	     P0MASK = 0x40;	// P0.7 (ENC_B port match fonksiyonu kapatildi)
	     encoder_state = ENC_CCW_LEADING;
	  }
     break;
	
	
     case ENC_CW_LEADING:
     // bu a$amada gelen interrupt enc-b: 1->0 geçi$i sebebiyle tetiklenmi$tir..
     // $imdi, enc-b: 0->1 geçi$ini bekleyebiliriz:
	P0MAT = 0x40;
	ENC_PULS_CW = 1;
	/// todo: ENC_A = 1 durumu hata olarak algilanabilir!
	encoder_state = ENC_PULS_TRAILING;			
    break;
		
		
    case ENC_CCW_LEADING:
    // bu a$amada gelen interrupt enc-a: 1->0 geçi$i sebebiyle tetiklenmi$tir..
    // $imdi, enc-a: 0->1 geçi$ini bekleyebiliriz:		
	P0MAT = 0x80;
	ENC_PULS_CCW = 1;
    /// todo: ENC_B = 1 durumu hata olarak algilanabilir.
	encoder_state = ENC_PULS_TRAILING;
    break;
		
		
    case ENC_PULS_TRAILING: 
    // bu interrupt gelmi$se gerideki fazin: 0->1 geçi$i olmu$ demektir.
    // encoder fazi tamamlanmi$tir.
    // fiziksel olarak tamamlanmi$ olsa bile, bir fazin bitmesini 
    //bekleyen i$lem flag'inin sifirlanmasi ile mumkun kilalim:
	P0MAT = 0xC0;
	P0MASK = 0xC0;
	encoder_state = ENC_IDLE;
   break;		
		
   // CPU encoder hareketi ile IDLE durumdan çikmi$ demektir.
   // encoder okuma state mach. çali$mayacak.		
   case ENC_WAKEUP:
    // wake-up durumundan çikiliyor.
      EIE2 = 0;
   break;

   }
}

Bakınca, ana döngü içinde port poll eden “örnek” uygulamadan bile daha basit gözüküyor. Artık, pulse üreten encoder’ınızı,
ENC_PULS_CW ve ENC_PULS_CCW
adında, Port mask ve(/veya) _EMAT bitleri ile kolayca devreye alınıp çıkarılabilen mantıksal bit girişleri olarak düşünebilirsiniz.
Bu bitlerin sıfırlanması uygulama programının sorumluluğundadır. Siz ENC_PULS_XX bitini işlemeden yeni bir pulse algılanırsa bu hareket “atlanacaktır”. Ben uygulamamda bunu özellikle istiyorum çünkü ekrandaki bir değeri encoder hareketine göre değiştiriyorum. Öte yandan hız/konum algılama gibi bir iş yapacaksanız encoder kesmelerini ana programın işlemesi öncesi buffer’lamanın bir yolunu düşünmelisiniz. En basiti, hareket ile artan/azalan bir tamsayı sayaç kullanmaktır, değil mi?

Düşüncelerinizi yazın...

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google fotoğrafı

Google hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.