Kategori arşivi: EFM8

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?

EFM8BB3 Sıcaklık Sensörü

Sıcaklık ölçümü ile ilgili hikayelerimizde sıra hiçbir sensör kullanmadan sıcaklık ölçmeye kadar geldi.

İşlemcilerin üzerinde bir sıcaklık sensörü olması alışıldık bir durumdur. Silabs EFM8 serisinde de analog modülde, giriş multiplexer’ını ayarlayarak ADC bağlantısını yapabildiğimiz bir sıcaklık sensörü var.

Bu sensörü ortam sıcaklığı ölçmede kullanmayı düşünüyorsanız bunun çoğu durumda pek iyi bir fikir olmayacağını baştan söylemem gerek. Daha önce anlattığım kendi kendini ısıtma etkisi, tahmin edeceğiniz gibi işlemci üstündeki sensör için ziyadesiyle geçerli olacaktır. Elbette koskoca işlemciyi yalnızca sıcaklık ölçmek için kullanmayacağınızı varsayıyorum.

İşlemci üstünde sıcaklık ölçmenin bize donanımsal bir maliyeti yok. Sıcaklık ölçmek için ADC’nin herhangi bir dış bağlantısına ihtiyaç duymuyoruz. Yazılımsal olarak da büyük bir maliyet yok. Birkaç satırlık kod ve birkaç ms içinde işimiz halloluyor. Herkes adına ve her durumu açıklamak için konuşamam ancak ben daha önce onboard sensörü iki sebeple kullandım:

Birincisi board’umun sağlıklı çalışacağı bir sıcaklıkta olup olmadığını kontrol etmek için. Zamanında tasarladığım bir DC sürücü oldukça küçük bir board üstünde çıkış katı işlemci regülatör vs. bir aradaydı. İşlemcinin sıcaklığının 60 küsur dereceyi geçmesi durumunda önce çıkış gücünü sınırlamak sonra da aleti tamamen kapatmak gibi bir iş için onboard sensörü kullanmıştım.

İkinci kullanımda da aslında başka bir şeyi ölçmek için bir donanım kurmuşuzdur ama ölçeceğimiz şey sıcaklığa bağlıdır. Bu durumda sıcaklığa göre düzeltme yapmak için eğer uygunsa board sıcaklığını referans alabiliriz.

Vereceğim örnek kod ADC’yi hali hazırda kullanıyor olup olmamamızdan bağımsızdır çünkü ADC’nin güç bağlantılarının belli bir durumda olmasını gerektirir:

ADC0CF2 = 0x70;	 // Vref= internal ref. (1,65V) gnd= GND
ADC0MX = AMUX_TSENS;
ADC0CN0_TEMPE = 1;  // onboard temp. sensor enabled
Delay(96);  // 1ms bekle
ADC0CN0_ADBUSY = 1;

Onboard sıcaklık sensörünün çalışması için TEMPE kontrol bitini 1 yapmak lazım. Fakat öncesinde, ADC referansını 1,65V dahili referansa, eksi bağlantıyı da çip GND’ına bağlıyorum.
ADC giriş multiplexer’ına 0x14 (AMUX_TSENS) yazınca girişi de bağlamış oluyoruz.
Sıcaklık sensörünün açılma süresine bakarsanız 1,8us gibi bir süre görüyorsunuz ancak buna referansın açılmasını ve SAR girişinin settle olmasını da eklemek gerek.
Ben çeşitli donanımsal sebelerle ADC’nin kendi power-up delay zamanlamasını kullanmıyorum. O yüzden sıcaklık sensörünü açtıktan sonra, alışkanlıktan, 1ms bekliyor ve ADC dönüştürme işlemini sonra başlatıyorum.

ADC sequencer’ı, çözünürlüğü, örnekleme hızı vs. mevcut ayarlarımızda olabilir. Onları burada yeniden yazmadım. Sonuçta onboard sıcaklık sensörünün parametreleri gerilim cinsinden tanımlanmışlardır:

Sensörün V / *C kazancının offset hatasına oranına bakarsanız, çipten çipe 6 *C’lik bir 0 *C noktası farkı olduğunu göreceksiniz ki bunun kullanacağınız 100 çipten 68’i için garanti edildiğini de göz önüne almalısınız. Uzun lafın kısası, eğer mutlak değer doğruluğu sizin için önemliyse her bir board için 0*C offset’ini ölçüp flash’ta saklamanız gerekir. (Bununla kim uğraşır bilemiyorum)

Bir termostat uygulaması için sıcaklık okuması yapıyorsak *C skalası çalışma esnasında bizim için gerekli değil. Ancak yaptığımız ölçümün *C karşılığını bilmek istersek, yaptığımız ADC ölçümünden, 757mV ‘a karşılık gelen ADC okuma sayısını çıkarıp sonucu da 2,85 mV’a karşılık gelen ADC sayısına bölerek bir *C sonucuna “yaklaşabiliriz.

Sıfır noktası konusundaki belirsizliğe karşılık mV / *C doğruluğu oldukça belirlidir. Bunun sizin için anlamı şu: Bir noktada, bilinen bir değere ölçekleme yapmanız doğruluk açısından yeterli olacaktır.

Onboard sensörü kullanmanın en güzel yanı, self heating’in ne kadar dramatik bir etki olduğunu gözlerinizle görmenizi sağlayacak olması. Bunu olumsuz bir şey olarak düşünmüyorum. İşlemcinin kendi sıcaklığını görmesi sistemin toplam güvenliği için her zaman çok iyi bir şeydir. Bu arada, buzlu suyla 0 noktasını ayarlarsanız bu sensörün oldukça iyi bir doğruluğu olduğunu keşfedeceksiniz. Teşekkürler Silabs..