Yazar arşivleri: selimpehlivan

selimpehlivan hakkında

Sen anladın onu...

Basit Bir Kullanıcı Arayüzü

Bir projeyi değerlendirirken (onu karmaşıklık, zorluk, uygulanabilirlik vs. konularda “derecelendirirken” demek daha doğru olacak) kullandığım ölçütlerden biri “gereksinim duyulacak kullanıcı arayüzü” oluyor.
Aslında artık kullanıcı arayüzü için az zahmetle uygulamaya entegre edilebilecek seçenekler var. Ancak bu seçenekler bu işe ayırabileceğimizden fazla I/O gerektirebilir veya işimizin astarı yüzünü geçebilir.
Ben IO sayısının yeterli olmadığı bir board için sonradan bir arayüz modülü tasarlamıştım. Böyle bir şeyi yapmak zorunda olmasam oturup özellikle yapmazdım. Ama bir kere yapınca birden çok yerde kullanılabilecek bir şey olmuş oluyor, pek çok şey gibi.
Ben de RRUI isimli (adının pek önemi yok) board hakkında notlarımı temize çekip yedekleyeyim, başka yerde gerektiğinde hızla kullanabileyim diye düşündüm.

Donanım

Board üzerindeki LED’lerin anotları +5V olarak gösterdiğim uca bağlı. İşlemcinin LED süren portlarını open-drain olarak konfigüre ediyorum. Yakmak istediğimiz LED için bağlı olduğu porta 0 yazmamız gerek (Active-low).
Bargraph LED’leri işlemci portlarını ortaklaşa kullanırlar. Q1 ve Q2 transistörlerinin emetörleri de 5V’ta olduğu için bunların bazlarını süren çıkışlar da open-drain olmalı.

Bu devreyi iki amaçla kullanabilirsiniz:
Bir host controller olarak:
Üzerindeki SPI portunu master olarak konfigüre edip, SPI arayüzü olan bir sensor board’unu veya bir kablosuz haberleşme modülünü çalıştırmak için kullanabilirsiniz. Bu durumda butonlar kumanda için kullanılabilir. Bunun için gerekli ayarları ayrıca paylaşacağım.


Bir kullanıcı arayüzü modülü olarak:
Bu blog yazısı esasen bunu anlatıyor. Board üzerindeki SPI portu slave olarak konfigüre edilmiştir ve LED’ker belli SPI komutlarına göre belli yanma desenleri ile çalışırlar. Ayrıca her SPI erişiminde butonların basma durumu okunur.
Yukarıda şemasını paylaştığım board’un spi slave olarak çalışması için başlatım kodu şöyle:

void Initialize (void)
{
  // WDT'yi kapat:
  SFRPAGE = 0;
  WDTCN = 0xDE; 	 // First key
  WDTCN = 0xAD; 	 // Second key

  // Portlari ayarla:
  P0SKIP  = 0xF0;	 // P0.0 .. P0.3 : Crossbar'da!
  P0MDIN  = 0xFF;		
  P0MDOUT = 0x02;
  P0 = 0xFD;		 // P0.1 hariç hepsi digital giris
  P1SKIP = 0xFF;		
  P1MDIN = 0xFF;
  P1MDOUT = 0x00;	 // tum P1 portlari open drain 
  P1 = 0xFF;		 // hi-z durumdalar..	
  P2SKIP = 0x0F;
  P2MDIN = 0xFF;  	
  P2MDOUT = 0x00;	 // tum P2 portlari open drain
  P2 = 0x7F;	
  SFRPAGE = 0x20;
  P3MDIN = 0xFF;
  P3MDOUT = 0x00;	 // tum P3 pinleri de open drain!	
  P3 = 0x9F;

///// Crossbar:
  SFRPAGE = 0;
  XBR0 = 0x02;		// SPI sinyalleri crossbar'a çiksin!
  XBR2 = 0xC0;		// WEAKPUD ve XBARE = 1 yapildilar..
		
// SYSCLK ayari: 24,5MHz
  CLKSEL = 0;
  CLKSEL = 0;
				
/////////////////////////////////////////////////
/// Timer ayarlari:

CKCON0 = 0x01;	// TMR3, TMR2 <- EXTCLK;  TMR1,TMR0<- prescaler=SYSCLK / 4
TMOD = 0x11;		// TMR1,TMR0 : MODE-1 (16 bit timer) 
// TMR0,TMR1 artimi = 4/24,5M = 0,163us
TCON = 0;	
SPI0CN0 = 0x06;	 //  4 wire slave (NSS:input) modül henüz devreye alinmadi
// Kesmelerin ayari:
IE = 0xC0;	// kesmeleri yetkilendir, SPI kesmesini yetkilendir.
	
}

Board üzerinde iki grup LED var: Her biri 10 noktadan oluşan iki satır bargraph, bir düzey belirtici olarak kullanılabilir.
Yanda da 6 noktalı dairesel bir indikatör var. Buna beacon diyorum. Bir çalışma durumunu, durma durumunu ya da durum bildirimini basitçe bir LED’i yakıp söndürmek yerine bu çemberdeki noktaların animasyonu şeklinde göstermek daha dikkat çekici olabilir ve uzaktan algılama şansını arttırır.
Host uygulama beacon’a ve bargraph’ların her birine ayrı ayrı kumanda edebilmelidir. Yani bizim board açısından söylersek, bu kısımlar birbirinden bağımsız olarak çalıştırılmalıdırlar.

Beacon LED’leri

Beacon LED’leri belli bir hızla verilen canlandırma komutuna göre yanıp sönerler. Yani host uygulamadan beklediğimiz komut hangi canlandırmanın oynatılacağının söylenmesinden ibaret. Bu sürümde 6 farklı animasyon tanımladım:


Oynatma desenlerini flash’ta bir dizi olarak oluşturuyorum ve onları periyodik olarak beacon LED’lerini süren portları güncellemede kullanıyorum. Her animasyon 6 sahneden oluşuyor ve başka bir komut gelmediği sürece sürekli tekrar edip duruyor.

// beacon animasyonları:
unsigned char code beacon_pattern[42] = 
{
	0xDB, 0x6F, 0xB7, 0xDB, 0x6F, 0xB7,        // dönen çizgi
	0xDB, 0x27, 0xDB, 0x27, 0xDB, 0x27,        // unlem
	0x7B, 0x3F, 0x9F, 0xCF, 0xE7, 0xF3,        // dönen solucan
	0x73, 0x8F, 0x73, 0x8F, 0x73, 0x8F,        // yari-küre
	0xFB, 0x7F, 0xBF, 0xDF, 0xEF, 0xF7,        // dönen nokta
	0x03, 0xFF, 0x03, 0x00, 0x03, 0xFF,        // flash   
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF         // kapali  
};

Beacon LED’lerinin işletilmesini sağlayan fonksiyon aşağıda. Burada D26 tahmin edebileceğiniz üzere P3’te kalan beacon LED’i oluyor.
pattern_index ünite içinde tanımlı yerel bir değişken. Beacon thread’ini devreye alan fonksiyon parametre olarak buna atanması beklenen değeri alıyor. Bu değişkende beacon_pattern[] dizisinin seçilen animasyon için başlangıç indeksini tutuyorum.

static void Beacon_Program_Run(void)
{
  static unsigned char pix; // pattern sahne adım indeksi 
  unsigned char x;
	
	if (pix > 5) pix = 0;
	x = beacon_pattern[pattern_index + pix];
	
	LAT2 |= 0x7C;
	LAT2 &= x;
	if (x & 0x80) D26=1; else D26=0;	
	// çiki$i güncelleyelim:
	P2 = LAT2;
	++pix;
}

Ana programın beacon thread’ini açıp kapattığı global fonksiyonlar şunlar:

// arayüz fonksiyonlari:  STOP ve START
// kullanici program tarafindan herhangi bir anda çagrilabilirler

// Stop: Beacon animasyonunu durdurur. 
// turnoff > 0 olursa tüm led'ler söner,
// turnoff = 0 olursa son LED görüntüsü korunur.
void Beacon_Stop(unsigned char turnoff)
{
  if (turnoff)
	{
	  Disable_Beacon();
	}
	// artik beacon güncellenmeyecek..
	Beacon_Program = Beacon_Program_Idle;
}


void Beacon_Start(unsigned char animation)
{
   pattern_index = animation;
   Beacon_Program = Beacon_Program_Run;
}

Beacon işlerini yapan thread fonksiyonu şu şekilde tanımlanmış bir fonksiyon pointer’ıdır:

void (*Beacon_Program)();

Bunun ana programda systick timer’ının belli bir önbölücü ile çalıştırılması ile beacon işlerimiz halloluyor. Ana kodda da _Start() ve _Stop() fonksiyonları ile dilediğimiz zaman hareketi durdurup başlatabiliyoruz. Stop fonksiyonu son durumu korumak ya da tüm LED’leri söndürmek şeklinde davranabiliyor.

Bargraph LED’leri

Bargraph LED’leri zaman bölümlü çoğullama ile çalışıyorlar bu yüzden LED dirençlerini seçerken LED’lerin %50 duty cycle ile sürüldüklerini unutmayalım. Bu arada, bargraph’lardan birinde tüm LED’ler sönükse onun satırını boşu boşuna seçip parlaklığı yarı yarıya düşürmemek için gerekli yazılım desteğini sağlıyorum. Satırlardan biri kapalıysa diğeri sürekli seçili kalıyor.
Bargraph’lar için de, beacon’dakine benzer şekilde 3 farklı çalışma şekli kabul ediyorum. Biri, alışılageldik progressbar türü bargraph, diğeri gezen nokta ve sonuncusu da çift gezen nokta. Bunları bir sinyal seviyesi göstermede ya da bir menü/ayar değeri göstermede kullanabilirsiniz. İki bargraph’tan her biri ayrı ayrı ayarlanabilir.

Burada da her seviye için doğru LED desenini gösteren bir sabit dizi kullanıyorum. Ancak eleman sayısı 8’den büyük olduğu için _L ve _H suffix’li iki dizim var:

unsigned char code bargraph_L[33] =
{
  0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00,    // bar
  0xFF, 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F, 0xFF, 0xFF,    // dot
  0xFF, 0xFE, 0xFC, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3F, 0x7F, 0xFF     // doubledot
};

unsigned char code bargraph_H[33] =
{
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC,    // bar
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFD,    // dot
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC     // doubledot
};

_L dizisi doğrudan P1 portunu güncellerken _H dizisinin yalnızca LSB 2 biti anlamlı ve P2’nin 0. ve 1. bitlerini değiştiriyorlar. Diğer bitler 1 olduğu için bir mantıksal VE işlemi maskeleme yapmadan kullanılabiliyor. Doubledot desenleri için bit değerlerini gösterdiğim tabloda ne dediğim daha kolay anlaşılıyor:

Bargraph işlemlerini yürüttüğüm thread’in organizasyonu beacon’ın aynısı. Tek fark, her satır için ayrı bir task fonskiyonu yazmış olmam. Çalışma durumu için (*Bargraph_Program)() pointer’ı _Run1 ve _Run2 olarak iki farklı fonksiyon arasında değişir.

static void Bargraph_Program_Run1(void)
{
  unsigned char x;
  
  LAT1 = bargraph_L[bar_index1 + bar1];
  x = bargraph_H[bar_index1 + bar1];
  LAT2 |= 0x03;	// bargraph'la ilgili bitleri set et..
  LAT2 &= x;      // en dü$ük anlamli 2 bit reset edilecekse edilecek..
  // portlari güncelle:
  Select_Line_1();
  P1 = LAT1;
  P2 = LAT2;   
  
  // diger kanal devredeyse o programa atla:
  if (L2) Bargraph_Program = Bargraph_Program_Run2;
  
}


static void Bargraph_Program_Run2(void)
{
  unsigned char x;
  
  LAT1 = bargraph_L[bar_index2+bar2];
  x = bargraph_H[bar_index2+bar2];
  LAT2 |= 0x03;	// bargraph'la ilgili bitleri set et..
  LAT2 &= x;      // en dü$ük anlamli 2 bit reset edilecekse edilecek..
  // portlari güncelle:
  Select_Line_2();
  P1 = LAT1;
  P2 = LAT2;   
  
  // diger kanal devredeyse o programa atla:
 if (L1) Bargraph_Program = Bargraph_Program_Run1;

}

L1 ve L2 yerel bit değişkenleridir. Sırası gelen satırın etkin olup olmadığını belirtirler.
bar_index1, bar_index2, o satırı devreye alan _Start() fonksiyonunun parametre olarak kullanıcıdan aldığı, hangi tipte gösterge yürütüleceğini belirten, desen indeks değeridir.
bar1 ve bar2 değişkenleri de, bargraph’ın geçerli değerini gösterirler ve ana program tarafından gerektiğinde güncellenirler.
Söylemeye gerek yok, _Start ve _Stop fonksiyonları her bar için ayrı ayrı:

/////////////////////////////////////////////////////////////////////////////////////////
/// kullanici programin bargraph'lari çali$tirmak ve durdurmak için kullanacagi fonksiyonlar

void Bargraph_Start1(unsigned char bartype)
{
  L1 = 1;
  bar_index1 = bartype;
  Bargraph_Program = Bargraph_Program_Run1;
}


void Bargraph_Start2(unsigned char bartype)
{
  L2 = 1;
  bar_index2 = bartype;
  Bargraph_Program = Bargraph_Program_Run2;
}


void Bargraph_Stop1( void )
{
  L1 = 0;
  // diger kanal da kapaliysa bargraph'lari tamamen kapatabiliriz:
  if (L2==0) 
  {
    Disable_Lines();
    Bargraph_Program = Bargraph_Program_Idle;
  }

}


void Bargraph_Stop2( void )
{
  L2 = 0;
  if (L1 == 0)
  {
    Disable_Lines();
    Bargraph_Program = Bargraph_Program_Idle;
  }
}

Bargraph’lar beacon animasyon adımlarından daha hızlı güncellenmeliler. Aksi halde LED’lerdeki kırpışmalar görünür olabilir. Diğer taraftan, çok hızlı güncelleme de parlaklığı azaltacak bir şey. Ben ana programda 10,7ms periyotla (93Hz) ile satır anahtarlaması yapıyorum.

SPI Slave Portu

Bu program beacon, bargraph1 ve bargraph2 durumlarını SPI portundan aldığı komutlara göre ayarlar. Bunun için açılışta SPI modülü slave olarak ve mod0 faz ayarıyla konfigüre edilir. Yani, SCK’nın boşta durumu 0’dır ve MISO/MOSI veri durumu değişimi düşen kenarda yapılır.
Datasheet BB3’te 4 byte TX ve 4 byte RX FIFO’su olduğunu belirtse de biz daha önemli bir işimiz olmadığı için düşük gecikmeli bir kesme rutininde, byte geldikçe veri alması yapacağız.
Daha fazla uzatmadan, sözü interrupt handler’a bırakıyorum:

void ISR_SPI (void) interrupt 6 
{
	static unsigned char xdata *p = 0; 
	unsigned char spidata;
    
	spidata = SPI0DAT;      // rx buffer'i oku... 
	if ( SPI0CN0_RXOVRN )
	{
	   Disable_SPI();
	   SPI_ERR = 1;		// programa hata oldu bilgisi yolla!
	}
        else   // buffer overrun yoksa rx olmu$tur:
	{
	   SPI0CN0 &= 0x0F;  // flag'leri sifirla!	  
	   SPI0DAT = *(p+5);
	   *p = spidata;     // gelen veriyi siradaki yerel konuma yaz.
	   ++p;
	   if (p > 3)
	   {
	      p = 1;		     // pointer'i basa sar!
	      SPI_RX = 1;     // ana programa da durumu bildir
	      SPI0DAT = dev_type;
	   }   
	} 

}

SPI’ı slave olarak kullanırken karşı karşıya olduğumuz bir mantıksızlık var: Konuşmayı başlatan biz olmadığımız için, ve ilk byte gelinceye kadar bir konuşma olduğundan bile haberdar olmayacağımız için SPI kesmesi aldığımızda ilk byte’ı değil ikinciyi yollamakla karşı karşıyayızdır. Çünkü SPI full duplex, senkron bir haberleşme. Veri almaya başladığımız anda veri de yollamaya başlamış oluyoruz.
Bu sorunu aşmak için, SPI boşa çıktığı anda TX buffer’ına “değişmeyen” bir veri yazıyorum. Burada bu aygıt tipi sabiti.

Bir konuşmanın 4 byte olması uygun. Master şu an için kullanmadığımız bir byte yollayıp haberleşmeyi başlatsın, ardından beacon, 1. satır ve 2. satır için çalıştırma komutlarını yollasın. Biz de slave tarafı olarak, aygıt tipi, buton durumları, 1. satırın bizdeki durumu ve 2. satırın bizdeki durumu olarak MISO portunu yükleyelim.

RAM’de sıralı yerleşmiş değişkenler erişim sırasında kullandığım TX ve RX buffer’ı oluyor:

unsigned char xdata resd1        _at_ 0;		
unsigned char xdata cmd_beacon	 _at_ 1;		
unsigned char xdata cmd_line1	 _at_ 2;
unsigned char xdata cmd_line2	 _at_ 3;
unsigned char xdata resd2        _at_ 4;
unsigned char xdata input_state  _at_ 5;
unsigned char xdata bar1         _at_ 6;
unsigned char xdata bar2         _at_ 7;
unsigned char xdata dev_type     _at_ 8;

Değişkenlerin mutlak olarak bildirilmiş adresleri kesme kodundaki pointer değerlerini anlatıyor sanırım. İlk 4 byte RX konumları. Her veri alma kesmesinde o anda geçerli pointer adresinin 5 fazlasını sonraki haberleşmede yollamak için tx buffer’a yüklüyorum. Örneğin, p=1 iken, cmd_beacon’ı alıyorum ve bar1 içeriğini yüklüyorum. Unutmayın, yüklediğimiz veri, bir sonraki kesmede gitmesi tamamlanacak veri. Peki bu durumda ilk byte alınırken ne oluyor? Onun esprisi de dev_type değişkeninin 4 adresinde değil 8 adresinde olması işte. Son byte alındığında aslında biz TX buffer’a ilk byte’ı yazmış oluyoruz. (3+5)
En başta, yani SPI başlatılırken TX buffer’a dev_type’ı manual olarak yazmalıyız elbette.

SPI Komutları

Beacon Komutları

Bargraph Komutları

Komut byte’larının MSB 3 biti komut türünü belirtir. Beacon için “ÇALIŞ” komutu LSB 3 bit ile hangi canlandırma türünün yürütüleceğini belirtir:

  • 000 : Dönen Çizgi
  • 001 : Ünlem
  • 010 : Dönen Solucan
  • 011 : Yarı-Küre
  • 100 : Dönen Nokta
  • 101 : Hepsini Aç-Kapa
  • 110, 111 : Geçersiz

Bargraph için, belli bir anda L bitleri ile belirtilmiş düzey bilgisi (0..10 arası değer alabilir) gösterge türü ile birlikte, yanacak LED sayısını ya da konumunu tanımlar.

Kullanım Örneği

Aşağıdaki program her bir bargraph’ın farklı modlarda çalışmasını kumanda ediyor. Burada, SPI_RX=1 yapmakla sanki cihaza dışarıdan komut yollamış gibi yapıyoruz.

// başlangıçta:
 devicestatus = 0;
 level1 = 0;
 level2 = 0;
 cmd_beacon = 0;	// komut yok (beacon kapalı)
 cmd_line1 = 0x40;	// bargraph'i "bar" mode'da ba$lat..
 cmd_line2 = 0x60;      // bargraph'i "dot" mode'da ba$lat..
 SPI_RX = 1;

/// ANA PROGRAM:
/* input_state: buton basılı olma durumları
* level1, level2 : göstermek istedigimiz duzey bilgileri
* SPI_RX : "normal" çalışmada SPI modülünün set edecegi veri geldi bayrağı 
* BTN1 ve BTN2 ile üstteki, BTN3 ve BTN4 ile alttaki bargraph değiştirilir.
*/ 
 switch ( devicestatus )
  {
   case 0:
       // bargraph-1'i arttir:
       if (input_state & 0x01) 
       {
	  if ( level1 < 10 ) 
	  {
	    ++level1;
	    cmd_line1 = 0xA0 | level1;
	    SPI_RX = 1;
	  }
	  devicestatus = 1;
	}
        // bargraph-1'i azalt:
	else if (input_state & 0x02)
	{
	   if (level1)
	   {
	      --level1;
	      cmd_line1 = 0xA0 | level1;
	      SPI_RX = 1;
	   }
	   devicestatus = 1;
	 }
         // bargraph-2'yi arttir:		
	 else if (input_state & 0x04) 
         {
	    if ( level2 < 10 ) 
	    {
	       ++level2;
	       cmd_line2 = 0xA0 | level2;
	       SPI_RX = 1;
	    }
	    devicestatus = 1;
	  }
          // bargraph-2'yi azalt:	
	  else if (input_state & 0x08)
	  {
	     if (level2)
	     {
	       --level2;
	       cmd_line2 = 0xA0 | level2;
	       SPI_RX = 1;
	      }
	      devicestatus = 1;
	  }			 
	 break;
	  
	  
	  case 1:
	    if ( input_state == 0 )  devicestatus = 0;
	  break;
	  
  }

Yukarıdaki programın hex kodu için tıklayın.
Bu programı board’a yüklediğinizde ana programda yukarıdaki kod çalışmaya başlayacak ve bir host işlemciden spi komutu beklemeksizin butonlar ile bargraph’ı kumanda edebileceksiniz.

EFM8 DAC

EFM8 ‘in üstündeki DAC biriminden bahsetmeye başlamadan önce, digital-to-analog converter denen şeyler hakkında uzun süre devam eden bir kanaatimi not etmek isterim: DAC bana hep lüks bir şey gibi gelmiştir. ADC bir şekilde en başından beri erişilebilirliği olan bir birimdi. Ama DAC, ister bir mcu’da isterse de bir plc’de olsun, sanki hep maliyeti arttıran bir şey gibi geldi bana.

15 sene kadar önce bir iplik boyama makinesinde, boya desenleri oluşturmak için boya püskürten kafaların dönme hızlarını akan ipliğin uzunluğuna bağlı olarak değiştirilebilir yapmam istenmişti. Bulduğum çözüm küçük bir işlemciyle encoder darbeleri saymak ve 8 bitlik bir DAC’a çıkış vererek asenkron motor sürücülerinin hızlarını değiştirmekti. O zaman National’ın paralel bir DAC’ını kullanmıştım, çıkışa bir de opamp koyunca sonuç muhteşem olmuştu. DAC deyince aklıma hep bu iş gelir.

Yıllar sonra da ilginç bir proje için dalga şekli üretme işiyle uğraşmak zorunda kaldım. Artık işlemcilerin üzerinde oldukça hızlı DAC’lar var. Ayrıca çok yüksek çözünürlüklü PWM’lerin olması zaten pek çok durumda DAC ihtiyacını tamamen ortadan kaldırıyor. Ama ben yine de üzerinde DAC birimi olan mcu’lara ayrı bir gözle bakıyorum.

EFM8’de 4 kanal DAC var. Bunlar iki çift olarak kullanılıyor gibi gösterilse de tamamen bağımsız çıkışlar olarak kullanabiliyorsunuz. Bir çifti, birbirinin aynı ya da eşlenik çıkış üretmek istersek beraber kullanabiliriz. DAC çözünürlüğü 12 bit.

Anlatacağım örnek uygulamada DAC çıkışını ses üretmede kullanıyorum. Bunun için 2 DAC kanalını toplam şeklinde kullanmak için bir devre hazırladım. Bu şekilde basit dalgalardan daha çok harmonik üretmek mümkün.

DAC sinyal yolu (bazı şekiller sevgili kızım tarafından çizilmişlerdir)

Bir stereo kuvvetlendirici kullanıyor olsam da mono ses üretiyorum. Kuvvetlendiricinin yüksek giriş direncinin karşısında iki çıkışı dirençlerle birbirine bağlamak bir toplama kuvvetlendiricisi elde etmemizi sağlıyor. Benim kullandığım amplifikatörün bir bias off girişi de var. Bununla yükleme/geçiş vb. anlarda pop/click türü gürültüler olmasın diye çıkışı kapatabiliyorum.

Referans gerilimini, referans gerilim bölücüsünü (REFGN) ve çıkış sürücüsü kazancını (DRVGN) ayarlayarak çeşitli çıkış aralıkları elde edebiliriz. Ben bunlardan 4 tanesini kullanıcı konfigürasyonu ile seçilebilir yaptım:

Bir dalga üretmek iki eksende hareket etmemizi gerektirir: Y ekseni tahmin etmesi kolay olacağı üzere
V = Vref * REFGN * DRVGN * (DAC0H:DAC0L) / 4095
çarpanlarıyla belirlenir.
X ekseni ise zaman. Dalga şeklimizin çözünürlüğüne bağlı olarak, istediğimiz frekansı bir tam periyotta üretecek bir hızla DAC güncellemesi yapmamızı gerektirir.

Daha açık bir deyişle, bir dalga şeklini 12 örnekle tanımlamışsak DAC güncelleme frekansımız istenen dalga frekansının 12 katı kadar olmalı. Örneğin;

Yandaki dalga şeklini tanımlayan “normalize” sinüs dizisi;
32,48,60,64,60,48,32,16,4,0,4,16

Bu diziyi, DAC’a 208us periyotla yüklersek yaklaşık 2,5ms periyotlu bir sinüs çıkışı alırız. Bu da 400Hz kadar eder. Ses çıkışını test etmek için bunu deneyebiliriz.
Burada ilginç olan iki nokta var: Birincisi sample genlikleri unipolar. Çünkü DAC çıkışımız 0V ile seçtiğimiz pozitif Vref gerilimi arasında bir gerilim üretebilir. O yüzden, genliği simetrik bir işaret üretmek istiyorsak Vref/2 kadarlık bir offset eklememiz gerekir.
İkinci nokta, DAC sayısal değerimizle ilgili. Ben bir dalga şekli tanımlarken onu 0..64 gibi bir aralıkta normalize ederek üretiyorum. Eğer donanımsal değil, yazılımsal genlik ayarı yapmak isterseniz bu 0..64 aralığını 0dB kabul edip, sample değerlerini istediğiniz bir sabitle çarpıp, anlık bir çıkış genliği üretirsiniz.

Eğer bu bir ses işareti ise ters-logaritmik bir adım bölümlemesi volume ayarı için uygun olacaktır. 4095 verilebilecek en büyük çıkış değeridir. DAC çıkışını yüklüyorsanız ve 3V gibi bir range ayarı yaptıysanız çıkışın max. değerden önce doyuma ulaşması tehlikesi vardır.

Aşağıda, genel amaçlı kullanıma uygun bir dalga tablosu tanımı paylaşıyorum. Burada her bir dalga tanımı 24 byte’tan oluşuyor (her sample little endian yerleşmiş 12 word). Bunu denemek isterseniz dikkatinizi çekmek istediğim bir şey var: İlk 4 dalga haricindeki dalga tanımları kendisini 3 kez tekrar ediyor. Yani bunların frekansını hesaplarken x12 değil x4 hızla yükleme yapmalısınız.
code quailfier ile dizinin flash bellekte yerleşmesini sağlıyorum ancak mutlak adres veremiyorum. _at_ directive kullandığınızda değişkenleri başlatamazsınız.
Bu yüzden de, bu işleri denerken en başta yaptığım gibi, çalma kesmesi içinde doğrudan bir code pointer’ı çalıştıramıyorum. Bunun yerine waveTable dizi indeksini volatile bir değişken olarak kullanıp compiler’ın verimliliğine güveniyoruz.

unsigned char code waveTable[288] = 
{ 
	// wav-1 : sine +18dB
	0x00,0x01,0x80,0x01,0xE0,0x01,0xF8,0x01,
	0xE0,0x01,0x80,0x01,0x00,0x01,0x80,0x00,
	0x20,0x00,0x00,0x00,0x20,0x00,0x80,0x00,
       // wav-2 : sine +28dB
	0x00,0x03,0x80,0x04,0xA0,0x05,0xE8,0x05,
        0xA0,0x05,0x80,0x04,0x00,0x03,0x80,0x01,
        0x60,0x00,0x00,0x00,0x60,0x00,0x80,0x01,
	// wav-3 : sine +32dB
	0x40,0x05,0xE0,0x07,0xD8,0x09,0x56,0x0A,
        0xD8,0x09,0xE0,0x07,0x40,0x05,0xA0,0x02,
        0xA8,0x00,0x00,0x00,0xA8,0x00,0xA0,0x02,
	// wav-4: sine +36dB
	0x80,0x07,0x0B,0x40,0x10,0x0E,0xC4,0x0E,
        0x10,0x0E,0x40,0x0B,0x80,0x07,0xC0,0x03,
        0xF0,0x00,0x00,0x00,0xF0,0x00,0xC0,0x03,
	// wav-5: symm.sawtooth +12dB
	0xC0,0x00,0xFC,0x00,0x00,0x00,0x40,0x00,
        0xC0,0x00,0xFC,0x00,0x00,0x00,0x40,0x00,
	0xC0,0x00,0xFC,0x00,0x00,0x00,0x40,0x00,
	// wav-6 symm.sawtooth +24dB
	0x00,0x03,0xF0,0x03,0x00,0x00,0x00,0x01,
        0x00,0x03,0xF0,0x03,0x00,0x00,0x00,0x01,
	0x00,0x03,0xF0,0x03,0x00,0x00,0x00,0x01,
        // wav-7: symm.sawtooth +32dB
	0xE0,0x07,0x56,0x0A,0x00,0x00,0xA0,0x02,
        0xE0,0x07,0x56,0x0A,0x00,0x00,0xA0,0x02,
	0xE0,0x07,0x56,0x0A,0x00,0x00,0xA0,0x02,
	// wav-8: symm.sawtooth +36dB
	0x00,0x0C,0xC0,0x0F,0x00,0x00,0x00,0x04,
        0x00,0x0C,0xC0,0x0F,0x00,0x00,0x00,0x04,
        0x00,0x0C,0xC0,0x0F,0x00,0x00,0x00,0x04,
	// wav-9: sq.wave: +12dB
	0xFC,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,
	0xFC,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,
	0xFC,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,
	// wav-10: sq.wave: +24dB
	0xF0,0x03,0xF0,0x03,0x00,0x00,0x00,0x00,
        0xF0,0x03,0xF0,0x03,0x00,0x00,0x00,0x00,
        0xF0,0x03,0xF0,0x03,0x00,0x00,0x00,0x00,
	// wav-11: sq.wave: +32dB
	0x56,0x0A,0x56,0x0A,0x00,0x00,0x00,0x00,
        0x56,0x0A,0x56,0x0A,0x00,0x00,0x00,0x00,
	0x56,0x0A,0x56,0x0A,0x00,0x00,0x00,0x00,
	// wav-12: sq.wave: +36dB
	0xC0,0x0F,0xC0,0x0F,0x00,0x00,0x00,0x00,
        0xC0,0x0F,0xC0,0x0F,0x00,0x00,0x00,0x00,
	0xC0,0x0F,0xC0,0x0F,0x00,0x00,0x00,0x00
};

DAC güncelleme “frekans” ayarı konusuna geri dönelim: Ben bu iş için Timer-2 kesmesini kullanıyorum. TMR2’ye vereceğimiz bir reload değeri ile DAC güncelleme hızını ayarlarız, böylece dalga üretecimizin X ekseni hassas biçimde ayarlanmış olur.

CKCON0 |= 0x30;   // TMR2 clock source -> SYSCLK
TMR2CN0 = 0;      // 
// çalıştırmak:
TMR2CN0_TR2 = 1;  // bu register bit addressable
// çalma frekansını değiştirmek:
TMR2RLL = rld_L;
TMR2RLH = rld_H; 

TDAC (DAC Erişim Periyodu) = istenen işaret periyodu / N
N : Bir periyodun örnekleme sayısı (yukarıdaki örnekte bu 12)
T2 : (TMR2 tick period) = 1/49M
Reload Değeri = 65536 – (TDAC / T2)

Bir çalma ayarında istenen frekans değerini değil, TMR2 reload değerini saklamayı tercih ediyorum.

TMR2 kesmesinde yapılacak iş “sıradaki” sample’ı DAC’a yazmak. Oynatılacak (veya çalınacak diyelim) dalga örneklerini flash bellekte saklıyorum. Bu noktada iki farklı yaklaşım geliştirdim:
Birinci yöntemde, çalma ayarları yüklenirken (mesela bir sequencer’ın belli bir adımının zamanı geldiğinde) flash’tan, sequencer’ın belirttiği dalga setini okuyorum ve yukarıda anlattığım şekilde düzey ölçeklemesi yapıp (normalize sample’ların volume ayar değeriyle çarpılması) ram’de bir çalma tablosu oluşturuyorum (hatta iki tablo oluşturup biri çalarken diğerini yükleme ve geçişleri hızlı yapma gibi bir şey denedim ama 49MHz çalışmada bu çok kritik bir hızlandırma değil).
Çalınacak sesin genliği ya da dalga şekli değişinceye dek çalma tablosu artık değişmiyor.
İkinci yaklaşım daha sade: Çalınacak sample’ları flash’ta normalize halleriyle değil, ölçeklenmiş halleriyle saklıyorum ve çalma sırasında doğrudan flash’tan okuma yapıyorum. Okunacak dalga tablosu indeksini sequencer yüklemesinde bir rom pointer’ında belirliyorum ve sonra kesme kodunda doğrudan pointer’ı çalıştırıyorum. Bu yaklaşımda TMR2 kesme kodu şöyle:

// Play Kesmesi:
void ISR_TMR2 (void) interrupt 5
{ 
   TMR2CN0_TF2H = 0;
   // playBuffer ba$a sarmasi gerekiyor mu?:
   if ( smpIndex > 11 ) 
   {
     smpIndex = 0;
     playIndex = wavIndex;	// çalma indeksini de ba$a sar.
     // durdurma istegi verilmi$ mi?
     if (STOP_REQ) 
     {
       TMR2CN0_TR2 = 0;  // artik player kesmesi tetiklenmeyecek..
       STOP_REQ = 0;
       return;
     }
  }
    
  ///// DAC Update:
  SFRPAGE = 0x30;     
  DAC0L = waveTable[playIndex];  // yukarida anlattigim mevzu!!
  ++playIndex;
  DAC0H = waveTable[playIndex];
  SFRPAGE = 0;
  ++playIndex;
  ++smpIndex;
  
}

playIndex waveTable’ın sıradaki elemanına erişmek için kullandığımız dizi indeksi. Bunu, çalma adımını yüklediğimizde (ne çalacağımızı bize söyleyen veri) öğreniyoruz ve indeksi başlatıyoruz. Bunun yerine, muhtemelen daha efektif bir kesme kodu için
unsigned char code * volatile data playPtr;
şeklinde bir işaretçi tanımı yapıp bunu çalınacak rom sample’ları boyunca da koşturabilirdik.

Burada, özellikle dikkat edilecek bir nokta var: Kesme kodu içinde SFRPAGE değiştiriliyor. Interrupt handler yedeklemeyi hallediyor mu halletmiyor mu hiç endişe etmemek için, bu kesmeyi başka bir kesmenin kesmesi ihtimalini ortadan kaldıralım. (Böyle cümleleri şık hale getirmeye çalışmak işi sulandırmak olur, sonuçta anlatım bozukluğu yok).
Daha açık konuşursam, bu işlemci bir de UART kesmesi çalıştırıyor (native priority’si daha yüksek). Çipi başlatırken UART kesmesini TMR2 kesmesinden daha düşük öncelikli olacak şekilde ayarlıyorum. Çünkü gelen bir byte biraz bekleyebilir (baudrate düşük).
DAC yazması yaparken başka bir koda atlamak SFRPAGE anahtarlaması dışında da sorunlar yaratacaktır. Çünkü DAC güncelleme hızımız SYSCLK hızında. Bu arada yeri gelmişken onu da not edeyim: DAC güncelleme hızını SYSCLK yapmamızla TMR2 kesmesi içinde DAC yazması yapmamız arasında bir bağlantı yok. DAC’ın kendi güncellemesi başka bir şey. Sanırım yazılımdan daha bağımsız DAC otomasyonları yapmak istersek kullanışlı olur. Yine de, ben merak edip DAC update source ile benim DAC yazma kesmemi aynı timer ile çalıştırdım. Sonuç değişmedi. Sonuçta SYSCLK çok hızlı olsa da biz çıkışı 200kHz hızıyla güncelleyebiliriz (bunu da denedim). Bu, tıpkı ADC’de olduğu gibi, “elektriksel” bir kısıt. Kişisel görüşüm, 200k gayet yüksek bir hız!

Son olarak, bir sequencer (kullanıcı tarafından belirlenmiş dalga şekillerini verilen ayarlara göre bir diziden okumak ve art arda üretmek) yapacaksak, çalmayı anlık olarak durdurmak ve devam ettirmek için kullanabileceğimiz makroları da vereyim:

#define Play_Sequencer()  SFRPAGE = 0x30; DAC0CF0=0x80; SFRPAGE=0; \
			  TMR2L = seqData.pt_rld_L; \
                          TMR2H = seqData.pt_rld_H; \								  
                          TMR2CN0_TR2=1; IE_ET2=1; \ 					  
                          PLAYING=1; MUTE=0; LED1=0
								  
#define Stop_Sequencer()  MUTE=1; \
	  	          TMR2CN0_TR2 = 0; IE_ET2=0; \					  
                          SFRPAGE=0x30; \
			  DAC0L=0; DAC0H=0; \
			  DAC0CF0 = 0; \
			  SFRPAGE=0; PLAYING=0; LED1=1 

Sequencer’ın verilen adımındaki ayarlara bakarak DAC’ı konfigüre etmek de şöyle olabilir:

	// cfg.<pwr> bitlerine bak:
	 switch ( seqData.cfg & 0x03 )
	 {
		case 0:		// 2V çiki$
		  REF0CN = 0x80;	
		  DACGCF2 = 0x11;		  
		  DAC0CF1 = 0x00;
		break;
			
		case 1:	  // 1,2V çiki$
		  REF0CN = 0x40;
		  DACGCF2 = 0x10;
		  DAC0CF1 = 0x00;
		break;
			
		case 2:    // 1,6V çiki$
		  REF0CN = 0x80;
		  DACGCF2 = 0x12;			  
		  DAC0CF1 = 0x00;	  
		break;

		case 3:	 // 2,4V çiki$
		  REF0CN = 0x80;
		  DACGCF2 = 0x10;		  
		  DAC0CF1 = 0x00;
		break;
		
	}

	/// wav pointer'ini ba$latalim:
	wav_address = 0x3C00 + 24 * seqData.wav;	
	playPtr = (unsigned char xdata *) wav_address;
	smpIndex = 0;

        /// çalma frekansini güncelle:
        TMR2RLL = seqData.pt_rld_L;
        TMR2RLH = seqData.pt_rld_H;

Yukarıdaki örnek kodda 12 sample’lık bir wav dizisi kullanılıyor ve flash bellekteki 0x3C00 sayfası bu işe ayrılmış. Kullanıcının seçtiği wav indeksi seqData.wav parametresinden okunuyor.
smpIndex, sınır denetimi yaparken 16 bit sayılarla kendimizi yormayalım diye kullanılan yardımcı bir değişkendir. Kesme koduna bakabilirsiniz.

Sonuçta, gördüğünüz gibi, DAC kullanarak bir dalga şekli üretmek, eğer çıkışta analog bir işaret kullanma ihtiyacınız varsa PWM ile aynı sonucu elde etmeye çalışmaktan daha basit bir iştir. Yukarıda bir audio çıkışı (benim aklıma waveform generator deyince ilk bu geliyor) üzerinden örnek verdim. Ancak daha önce, programlanabilir bir sabit akım kaynağı işi için de bu DAC çıkışını kullanmıştım. Çalıştığımız alan, LED parlaklığını PWM ile ayarlamamızın mümkün olmayacağı bir uygulamaydı. Bu durumda EFM8’in bir çipte 4 adet bağımsız kullanılabilen DAC çıkışlarını bir sabit akım kaynağına referans girişi yaparak basit ama doğruluğu yüksek bir çözüm üretebildik. (Bunu da burada paylaşırım bir ara)

Sıcak bir zemheri

Bu sabah yataktan kalkınca saate baktım: 8.20’yi gösteriyordu.
Perdeyi araladım. Sokak lambaları henüz yanıyordu.
Sonra salona geçip gece lambasını yaktım.
Normalde ilk işimin sobayı yakmak olması gerekirdi ama kısık çalışan kombi yeterli oluyor evi ısıtmaya.
Bilgisayarı açtım. Windows’un başlat menüsündeki Hava Durumu canlı kutucuğuna tıklamışım yanlışlıkla. Ben masadaki notlarıma bakarken ekranda lacivert bir fon üzerinde 10 günlük hava durumu listelenmişti.
10 gün boyunca yağmur gözükmüyor ve sıcaklık bugün 18 derece verilmiş.
Tam şu satırı yazarken yine kontrol ettim: Tarih 26 Aralık.
Benim çocukluğumda yeni yılda kar yağacak mı diye bir heyecanımız olurdu. Hiç unutmuyorum, bir sene tam yılbaşı akşamı saat 8 gibi müthiş bir kar başlamıştı ve o gece her taraf bembeyaz olmuştu. Bunun hangi sene olduğunu sorup öğrenmem lazım. İnsan yaşlandıkça geride birbirine benzeyen çok senesi birikmeye başlıyor.

Kurak bir sonbahardan sonra kurak bir kışa girdik. Zemheri dedikleri dönemdeyiz ama kışın karanlığından başka bir şeyi yok. Ne yağış var ne de soğuk. Bu yazıyı yazarken aklıma 4 sene önce yazdığım 27 isimli yazım geldi. O zaman bir Kasım akşamında havanın 27 derece olması beni şaşırtmış. Bugün öğleden sonra dışarı çıktığımda sıcaklık 20 dereceydi. Aralık 26’da. Sadece dört sene içinde buna şaşırmama şaşırır bir duruma gelmişiz.

Bizimki gibi yerlerde küresel ısınmayı dert etmek gündelik koşuşturmacalarla ilgisi olmayan birinin özenti bir hobisi gibi duruyo olsa da bizim onu dert etmememiz onun gündeminde de bizim olmayacağımız anlamına hiç gelmiyor. Yaşadığımız coğrafya bu çalkantıdan en sert şekilde etkilenecek.

Bahçede çalışırken düşündüm: Gündemde kuraklık var ve büyük şehirlerde su tasarrufu yapmak için söylenen bir iki beylik laftan öte bir şey yok gündemde. Peki, ne yapılabilirdi? Buna yanıt veremiyorum. “İnsanlar” iklim değişirken ne yapabilirler ki? Bırakın bizimkisi gibi ilkel toplulukları, uygar toplumlar bile hiçbir şey yapamazlar bu gidişatı durdurmak için. İçinde doğduğumuz koşullar bu hale sokmuş dünyayı. Bundan ne derecede vazgeçebileceğimiz üstünde bile anlaşamayız ki. İnsanlık böyle yavaş ama güçlü değişimlere tepki verebilecek bir uygarlık kurmamış. Daha kötüsü insan doğası da buna uygun değil.

İşin uygarlık ve insan doğası yanına kadar inmeye gerek yok. Biz, koskoca bir ülkenin bir köşesine devasa bir şehir kurmuşuz ve tüm ekonomimizi o şehirdeki düşük katma değerli faaliyetlere bağlamışız. O şehri beslemeye, o şehrin suyunu karşılamaya ve o şehrin pisliğini bertaraf etmek için oldukça verimsiz bir şekilde çabalıyoruz.. Zaten kırılgan olan bir dünyada, kolayca üretebileceğimiz tarım ürünlerini bile ithal ederek bu kırılganlığı akıl almaz bir budalalıkla arttırmaktan başka bir şey yapmıyoruz. Görünen o ki, biz “muasır medeniyet” seviyesine çıkıp kentlileşene kadar, bildiğimiz anlamdaki kentler sürdürülebilirliğini yitirmeye başlamış olacaklar.

Bunca insanın beni bu kadar korkutan bir şey hakkında durup bir an bile düşünme gereği duymaması beni de kendimden şüphe duymak zorunda bırakıyor.
Kuraklık sezonluk bir şey olabilir mi? Belki de iklim düşündüğümüz şekilde değişmez ya da değişse bile bu beslenmemiz, ekonomimiz ve yaşam standardımız üzerinde olumsuz etki yapmaz, değil mi? Ben hayal ettiğim araziyi alırsam, bir ev daha yaparsam ya da arabamı değiştirirsem kızım için iyi bir gelecek inşa etmiş olabilirim belki?
Tüm bu soruların birer soru olarak kalmalarını isterdim ama en korktuğum kısmı burası: Bunların yantını çok yakında öğreneceğiz gibi duruyor.

ADC Auto-Scan

Bir analog-dijital dönüştürücünün çalışma hızı ölçüm devresine ve ADC’nin yapısına bağlıdır. Öte yandan, çok sayıda analog kanalı örneklemek gibi bir ihtiyacımız varsa “hız”a dair kaygımız genellikle, ADC’nin kendi ölçüm hızı değil bizim kanallar arasındaki geçiş hızımızla ilgili olur. Çünkü biz bir işlemcinin üstündeki ADC’yi kullanıyoruz ve kanallar arasında geçmek ve ölçüm sonuçlarını bir yere yazmak gibi işler dönüştürme işleminden daha çok zaman alıyor.

Eğer çok sayıda analog girişi okumak gerekiyorsa ve dahası, bu kanalların aynı anda alınmış örnekleri (mesela bir besleme hattının akım ve gerilim ölçümü gibi) anlamlıysa veya kesin bir bir zaman aralığında alınmış belli sayıda örnek toplamak istiyorsak bu işleri donanımsal olarak halletmek işimizi çok kolaylaştıracaktır.

EFM8’de bu tür durumlarda kullanabileceğimiz bir tarama fonksiyonu var. Bu fonksiyon ADC modülünün ayarlarından bağımsız olarak istediğimiz zaman kullanabileceğimiz bir örnekleme otomasyonu sağlıyor.

  • ADC’yi istediğiniz referans ve örnekleme/dönüştürme hızı ayarlarıyla ayarlıyorsunuz.
  • Sonra tarama işleminin başlangıç kanalını seçiyorsunuz. Taramada en çok 4 kanal ardışık olarak örneklenebiliyor.
  • Sonra kaç kez örnekleme yapılacağını belirliyorsunuz. Burada, komutla başlatılan tek bir örneklemenin sayısı belirleniyor. Bir örnekleme kendi içinde istenen sayıda oversampling içerebilir.
  • Dönüştürme işlem sonucunun xram’de yazılacağı konumu belirliyorsunuz.
  • İşlemi başlatıyorsunuz.

Tarama ayarını bir kez yaptıktan sonra işlemleri otomatik başlatabilirsiniz. Tarama içindeki iki dönüştürme işlemi arasına sabit bir süre koyabilirsiniz veya tarama içindeki dönüştürmeleri birbirine olabildiğince yakın yaptırabilirsiniz.
Bir tarama işlemi yapılırken sonraki işlem için istenen ayarları hemen berlirleyip ADC’yi durmaksızın arka arkaya farklı ayarlarla çalıştırabilirsiniz. Bu, mesela double-buffering gereken okuma durumlarında işin buffer güncelleme kısmını çok basitleştirir.

Aşağıdaki kod örneği, AN6..AN9 kanallarının birer kez ölçülmesi ile elde edilen sonuçları xram’in 16..23 konumuna big endian olarak yazdırır:

SFRPAGE = 0x30;
ADC0MX = 6;		  // AN6 seç (ba$langiç kanali)
ADC0ASAH = 0x00;
ADC0ASAL = 0x10;	  // sonuçlari xram=16 adresinden itibaren yaz
ADC0ASCT = 3;		  // tarama 4 sample sürer (her kanal 1 kez)
ADC0ASCF = 0xC3;          // 4 kanal örneklenecek..	
ADC0ASCF = 0x43;          // _ASEN = 0
SFRPAGE = 0;
ADC0CN0_ADINT = 0;
ADC0CN0_ADBUSY = 1;

Yukarıdaki kodda en ilginç kısım, ADC0ASCF_ASEN bitini 1 yapıp işlemi başlattıktan sonra hemen sıfırlamamız.
ASEN=1 yapıldığında donanım tarama ile ilgili tüm ayarları dahili belleğe kopyalar. Yani, ASEN=1 yaptıktan sonra tüm ayarları bir sonraki tarama için değiştirebiliriz.

Yukarıdaki ayarlarla, ADINT=1 olduğunda işlemler bitmiş ve sonuçlar xram’e yazılmış olacak. Eğer durmaksızın sonraki 4 kanala geçmek isteseydik _ASEN=0 yapar yapmaz ADC0MX’i ve ADC0ASAL’i değiştirmemiz yeterliydi.

Ben bu örnekte öyle yapmadım ama ADC0ASCF_STEN bitini 0 yapıp ADC’yi bir timer ile tetiklenecek şekilde ayarlasaydık (ADC tetikleme kaynağı ayarının tarama özelliğiyle ilgisi yok) zaman aralıklı tarama çalışması elde etmiş olurduk. Belki ileride bununla ilgili bir örnek de paylaşırım.

ADC işleminin sonucunda üretilen veriyi eşzamanlı olmayan başka bir süreç kullanıyorsa bu işi otomatize etmek ve kullanıcı sürecin sadece ram’deki datayla uğraşmasını kodlamak performansı oldukça arttıracak bir iyileştirme. Donanımı kullanmasını bilmek ile donanımdan bağımsız kod yazmakta konfor aramak arasındaki farka oldukça dramatik bir örnek olarak göz önüne alınabilir.

Ekmek Gramajı

Enflasyonun elbette pek çok sebebi var. Ama 2020’den bakınca 1950’leri 90’larda ve 00’larda baktığımızda gördüğümüzden çok daha kolayca anlayabiliyoruz.

Merkez sağ-muhafazakar iktidarlar daima enflasyon ve hayat pahalılığı getiriyorlar. Yanında bolca iki taşı üst üste koyma, hizmet getirme, kalkınma hikayesini de bedavaya veriyorlar. Gerileyen yalnızca ekmek ağırlığı değil, sosyal haklar, sabit gelirlinin alım gücü, eğitim kalitesi vb.

Bunu karşılığında da ülkenin zenginliği belli bir kesime transfer ediliyor. Yani siz boğazınızdan kısıyor, yiyeceğinizin bir kısmını ve sahip olmanız gerekenleri iktidarın o dönemki zengin sınıfına bağışlıyorsunuz. Karşılığında da akşam haberlerinde size kalitesi günden güne düşen bir prodüksiyon sunuluyor.

İşbirlikçi, hain, ahlaksız tipler merkez sağ üzerinden politikaya girdikleri için bunlar oluyor, solcularsa süper adamlar zaten demek istemiyorum. Bunu merkez sağ, muhafazakar, dinciler yapmayı başarıyorlar çünkü bu ülkede bunu yapabilecek güce sadece onlar erişebiliyorlar. Kısaca yapıyorlar, çünkü yapabiliyorlar. Sorun, 1950’lere bakınca bile aynı şeyi gördüğümüze göre yapmaları da değil zaten.

Not: Ekmek zamanla küçülmeliydi zaten, yeme alışkanlıkları değişti diyenlere şu anda dünyanın en çok ekmek tüketen ülkesi olduğumuzu hatırlatırım. Buna karşılık, et ve balık tüketiminde kaçıncıyız bilmiyorum ama üst sıralarda değilizdir diye tahmin ediyorum.

Toprak ve Piyasa

Önce talebi yaratıyorlar sonra da onu karşılayacak şeyleri”arz ediyorlar”(*) Buna da piyasanın serbestliği diyorlar. Arzın talebi karşılamak için çıktığını söyleyecek kadar da cüretkarlar.
Yukarıdaki resimdeki mantıksızlık bunu gösteriyor. O binalar oraya barınma ihtiyacına çözüm arz etmek için dikilmemiş. O binalar dikilmiş ve bir piyasa oluşturulmaya çalışılmış.
Binaların önündeki pis, iğrenç yeşillik de muhtemelen orada yapılması en verimli ekonomik faaliyetin, yani en rasyonel olan şeyin uzatmaları oynamasının görüntüsü.
Piyasa en rasyonel olanı yapmaz. En kârlı olanı yapar. Karnınızı doyurmak; talep icat edip, icat edilmiş talebe dayanarak yüksek bir fiyat belirleyip ev dikmekten daha az kârlı olduğu için oraya bina dikiyorlar. Ve herkes sadece bunun için yarıştığı için bok gibi kalitesiz ve sağlıksız şeylerle beslenmeye mecbur kalıp beton pisliklerinin içinde dolanıyorsunuz.
Toprağın yok oluşunu izleyin, kapitalizmin verimsizliğini göreceksiniz. Çünkü metalaştırma işinin en doğal kurbanı her zaman toprak ve onun üstündeki-altındakiler oluyor.

___________________________________________
(*) Yanılıyor da olabilirim. Belki de birileri bir sabah uyanmış ve ben akıllı telefon istiyoum, hatta onun ekranına bakmak zor geldiğinde koluma takacağım plastik, çirkin bir bileklik istiyorum, eşşek olduğum için sinekler gelince başımı sağa sola sallayıp kulaklığımın kablosuna dolaşıyorum, o yüzden kablosuz kulaklık da istiyorum ama sonra o fındık kadar zımbırtılar kaybolmasınlar diye onları birbirine bağlamak için 25 dolara da ip istiyorum demiştir belki. İddia çöker.

Aşının içindeki transistör

Türk “aydın”ından nefret eden, bunlarla uygarlaşacağımıza oturup onurumuzla ortadoğulu olalım diyen bir düşünce şekli var. Bu düşünceye mensup bir-iki arkadaşım var benim de. Onları trollerken, genellikle konuya giriş şeklim şu olur: Kendini aydın ya da entelektüel olarak görmek bile bir çabadır. Evet birbirlerinin kopyası gibiler, evet genelde halktan ve hatta yaşamdan kopuklar, evet pek bir halttan anladıkları yok.. Ama sandık kurup demokrasi bayramı yapıp idareci seçen ülkede yine de onlarla uğraşmaya devam etmeliyiz. Çünkü, bizimki gibi ülkelerde, kara kalabalığın karşıtı, onunla yaratıcı bir çatışmaya girecek olanlar yine onlar.

Bazen içtenlikle böyle düşünüyor olmama rağmen, Türk “aydın” sınıfının güncel eğilimlere bakışındaki temelsizliği gördüğümde ben de o arkadaşlarıma katılmaktan kendimi alamıyorum.

Güncel konu “aşı karşıtlığı”. Sosyal medyada sesi çok çıkan pek çok ünlü kişi aşı karşıtlığıyla dalga geçiyor. Bir şekilde aşıları yüksek teknoloji ile ürettiklerini, bu işte gerçekten akıllı insanların çalıştıklarını, bilimin iyi bir şey olduğunu falan biliyorlar. Buraya kadar eleştirecek bir şey yok. Herkes, etrafındaki her teknoloji hakkında bilgi sahibi olmak zorunda değil. Öte yandan, kendisini “bizim” tarafta görme çabasındaki bazı “aydın”ların duruşlarını belli etmek için amacını aşan şekilde çabaladıklarını görüyorum.

Çocuğun biri bir tweet atmış. covid19 aşılarında luciferase diye bir madde varmış, bu madde genlerimizi değiştirip bizim içimizde “nano” transistörler üretmemize neden oluyormuş. Bu transistörler de bizi radyo dalgalarını “toplayan” antenlere dönüştürecekmiş.

Yukarıdaki şeyler yazarken insanı gülümsetiyor. Aslında blogumun teknik kısmına, anten empedansı nasıl oluşur, onu süren devrenin rezonansı nasıl belirlenir, alttaki sürücü kat ne şekillerde olur konularını anlatan ilginç derecede basit bir makale eklemeyi düşünmüştüm birkaç gün önce. İnsana bir dna enjekte edip onu transistör üreten bir makineye dönüştürüyorsun. Bu transistörler sadece akımı ya da empedansı dönüştürebilirler. Onlarla frekans seçiciliği olan bir anteni fiziksel olarak nasıl gerçekleyeceksin. Ara-frekans katları, demodülasyon, bu dijital bir radyoysa mantıksal bloklar nasıl gerçekleniyor? Hangi modülasyonlar kullanılabiliyor (ki özellikle ilgimi çeker bunun yanıtı) ? Ben bu iddiayla ilgilendim. Her şey bir yana, bilim kurgu güzeldir. Ayıla bayıla izlediğimiz dizilerdeki saçmalıklar sanki bundan daha çok soru mu sordurabiliyordu bize?

Bu iddiaları biraz daha başka şekilde ve biraz daha ünlü birilerinden duysa hemen inanacak bir sürü kişi de bu çocukla dalga geçmeye başlamış. Buna inanmıyorum, saçmalık bu demekle kalmıyorlar. Akıllarınca espri yapıyorlar, bu konuda yarışa girmişler. Tek bir kişiden bile, genler değişince bedenimizin nasıl transistör ürettiğine dair bir espri göremedim. Ya da transistör ile alıcı anten nasıl oluyor diye sorana denk gelmedim. Oysa bir iddianın neden saçma olduğu konusunda basit bir iki düşünceniz olabilmeli, değil mi? Biriyle dalga geçerken, dalga geçtiğiniz konuda güveninizi sağlayacak kadarcık bilgi sahibi olmanız gerekmez mi? Bu adamlar muhtemelen radyo falan görmüşlerdir. Hatta cep telefonunun ya da bluetooth’un falan radyo dalgalarıyla çalıştığını duymuş olma ihtimalleri bile var. Ama bununla ilgili de pek espri yok.

Bilim, bilmekle ilgili bir şey. Bilmediğiniz bir şeyi savunuyorsanız onun doğruluğuna inanmışsınız demektir. Ki bu da dindarların uzmanlık alanı olur. Ama bizde Atatürkçülüğün, solculuğun ya da laikliğin kaderini paylaşan bir “akılcılık” var. Bizde dogmanın yerine bilimi koyan insanların çoğunun bir şeyler bilmek gibi bir kaygısı yok. Bizim coğrafyada cehaleti besleyen şey bilime inanan ama onun hakkında çok fikri olmayan yarı aydın güruhtur.

Yüksek frekans transistörü sentezlememize neden olan dna genlerinden bahseden çocuk naif bir örnek. Bu cephede daha vahşi, tehlikeli ve popüler oyuncular da var. Abdurrahman Dilipak gibi. O, aşıların kısırlık yaptığını iddia ediyor ve covid diye bir şey olmadığını, ahaliyi aşıya ikna etmek için gribin covid19 diye isimlendirildiğini savunuyor. Transistörcü çocukla dalga geçenlerin Dilipak karşısında pek şansı yok.

Lafı getireceğim yer ise şu: Burası ortadoğu. Bilimin bilinirliğinin en az olduğu yer. Aydın kesimi bunun istisnası değil. Bu açıdan bakınca bilim inkarcıları bilim inkarcılarıyla dalga geçenlerden çok daha içten ve anlaşılır gözüküyorlar. O yüzden genellikle kazanıyorlar. Deprem “şiddeti”nden işsizlik oranına, salgın hastalıktaki günlük yeni olgu sayısından yandaşa satılmış piyangodaki kazanma oranına kadar her konuda sayılarla oynamaktan çekinmeyen ve bu cüretinde haklı çıkan iktidarın taşıyıcı ayaklarından biri işte bizdeki bu bilimden arınmış aydınlık kafalar.

Ülkemiz iyi eğitim almış ve katma değeri olan bir mesleğin profesyoneli olmuş orta sınıflar için çekiciliğini yitirdikçe gericilik karşısında akılcılığı savunmak bu akılsızlara kalmaya devam edecek işte.

nRF9e5 Test Programı

Kablosuz bağlantı kullanan bir şeyler üstünde çalışıyorsanız ve daha da kötüsü donanım da size aitse işe basit haberleşme denemeleri yapmakla başlamak mantıklı.
Burada, nRF9e5 sub-GHz SoC ‘u test etmek için basit bir uygulama paylaşıyorum.

Gerekenler:
* Test edeceğiniz nRF9e5’li board (device under test),
* Bunun üstündeki EEPROM’u programlamak için bir programlayıcı,
* PCT, PC’ye bağlanan bir radyo terminali
* PCT’yi izlemek için bir PC programı veya bir seri terminal programı,
* Vereceğim firmware’ler

Bu programda, radyo cihazları rol değiştirmiyor. PCT daima dinlemede, test edeceğimiz 9e5 cihazı da sadece gönderme yapıyor.
Program 8MHz kristal kullanıyor.
Program 9e5’in P02 portunu (Pin:2) her göndermede kısa bir süreliğine açıp kapatıyor. Buraya LED bağlayabilirseniz TX anlarını gözlemleyebilirsiniz.
Her 786,4ms’de bir veri gönderiliyor. Bunun zamanlamasını Timer1 yapıyor.
Veri gönderilen kanal 866MHz bandında 218. kanal.
Hedef adresi : E2.E2.E2.E2
Alıcının 8 byte payload beklemesi, bu ayarlarda olması ve 16 bit CRC mode’un açık olması yeterli. PCT bu ayarlarla yüklü biçimde dinleme modunda çalışmaya başlayacak ve bir radyo paketi aldığında bunu 62500bps hızında host PC’ye aktaracak.
Veri paketinin yapısı :

byte indexveri içeriği
00x47
1tx_counter[H]
2tx_counter[L]
30x11
40x19
50x35
60x62
70x01

Aşağıda, bootloader yaması yapılmış nRF9e5 firmware hex dosyasını bulabilirsiniz. Bu program yüklenir yüklenmez çalışmaya başlar:

PC Terminal Radyosu için de aşağıdaki firmware’i kullanabilirsiniz. Bu program başlamak için bir komut istemeden, PC’ye bağlar bağlamaz çalışmaya başlayacaktır:

Benim PCT’lerden kullanmıyorsanız, EFM8SB1 kullandığınız sürece, yukarıdaki firmware’i kendi donanımlarınızda da çalıştırabilirsiniz. LED_U (P0.3) ve LED_R (P1.5) çıkışlarının kullanımına dikkat edin!

Alınan verileri gösteren bir PCT PC arayüzünü de burada paylaşacağım.

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?

Vertical Counter’lar Kullanarak Digital Giriş Filtreleme

Klasik Yönteme Bir Örnek

Girişlerin kontak durumlarını ya da butonları okumamız gereken durumlarda sık sık giriş düzeylerini filtrelememiz gerekir. Buna iyi bir örnek, push-buttonlara tepki veren programların tuş kontaklarının yarattığı sıçramalar yüzünden yanlış sayıda basmalar algılamasıdır. Bu sorun özellikle butonun anlık durumunun program içinde çeşitli yerlerde doğrudan sorgulandığı küçük uygulamalarda oldukça can sıkıcı olabilir.

Bu yazıda ilk olarak bir projemizde kullandığım basit bir tuş okuma arayüzünü anlatacağım. Sonra da tuş sayısının arttığı durumlar için etkin bir çözüm olarak kullanılabilecek vertical counter uygulamasının kodunu paylaşacağım.
İlerleyen zamanda, kendi tasarımım olan bir klavyenin girişlerini nasıl okuduğum bilgisini de bu yazıya ekleyeceğim.

Projemizde üretimi pek de başarılı olmayan, ayrı bir tuş takımı levhası var. Bunun üzerindeki 10 adet buton işlemcimizin üstünde olduğu kontrol board’una giriyor. Programın çalışması sırasında belli anlarda belli butonlar okunmalı. Butonlardan bazıları basılı tutulduklarında düz basma işevinden başka işlevler yerine getiriyorlar.

Buton tarama işlerini farklı buton grupları için benzer işler yapan 4 farklı fonksiyon olarak yazdım. Bu uygulamada iki farklı buton grubu sanki birbirinden bağımsız iki arayüzmüş gibi çalışıyor.

Fonksiyonlardan her biri sorumlu olduğu butonlar için kabaca yukarıda gösterdiğim basit algoritmayı çalıştırıyor. Aşağıda bir buton okuması için çalışan lojiği (ve aynı zamanda kullanılan yerel değişkenleri) görüyorsunuz:

    // A1: basma durumu sorgulama
    if ( button_mask & 0x0001 )
    {
        if ( button_pressed.A1 == 0 )
        {
            if ( pcnt_A1 > BTN_PRESSCNT ) 
            {
              // TODO: burada birden çok seferde sıfırlanma  eklentisi olabilir.
                if ( btn_A1 ) pcnt_A1 = 0;      
            }
            else 
            {   
                if ( btn_A1 ) pcnt_A1 = 0;
                else
                {
                    ++pcnt_A1;
                    if ( pcnt_A1 > BTN_PRESSCNT ) button_pressed.A1 = 1;
                }
            }
        }
        /*
         * kullanıcı programının pressed flag'i sıfırlaması sonrası butonu bırakmadan
         * işlemin tekrarlanmasını istiyorsak burada
         * if ( btn_A1 ) pcnt_A1 = 0; sıfırlamasını ekleyebiliriz.
         */
    }

Burada tüm butonların taramasının tek bir fonksiyon içinde değil her grup için iki fonksiyonda yapıyorum. Çünkü benim uygulamada tuş takımındaki butonlardan ikisinin basılı tutma için ayrı işlevler yaptırması gerekiyor (button hold fonksiyonu). Buna konu butonların tarama işlerini ayrı bir fonksiyonda yazdım.
Ayrıca, basılı tutma okuma mantığı yukarıdaki mantıktan biraz farklı:

Yukarıdaki algoritmanın yürütülmesi şu şekilde oluyor:

    if ( button_mask & 0x0008 )
    {
        if ( btn_A4 )           // buton basılı değilse
        {
            if ( pcnt_A4 )  
            {
               if ( pcnt_A4 > BTN_PRESSCNT )   button_pressed.A4 = 1; 
               pcnt_A4 = 0;
            }   
        }
        else if ( button_hold.AM == 0 )
        {
            ++pcnt_AM;
            if ( pcnt_AM > BTN_HOLDCNT ) 
            {
                button_hold.AM = 1;
                pcnt_AM = 0;
            }
        }
    }   

Bu task fonksiyonlarını bir fonksiyon pointer dizisi olarak grupladım:

void (* ButtonRead_Task[] ) () = 
{
    Button_Idle,
    Button_Read_A,
    Button_Read_AM,
    Button_Read_B,
    Button_Read_BM
};

Ana programımda 5ms periyotlu bir sistem timer’ı var. Bu timer’ı taramaları zamanlamada kullanıyorum. Timer tick’i geldiğinde ana programdaki buton okuma indeks değişkenini 1 yapıyorum:
button_read_index = 1;

Bunun sıfırdan farklı bir değere ayarlamak ButtonRead_Task[ ] fonksiyon dizisinin ilgili elemanının işaretlediği task’in o taramada çağrılmasını sağlıyor. Bu sıralama son task (4 indeksli olan) çağrılana kadar arka arkaya her çevrimde yapılıyor ve sonra thread kapanıyor (one-shot operation).

    if ( button_read_index )
    {
        ButtonRead_Task[button_read_index]();
        ++button_read_index;
        // one-shot bir task sıralaması olduğu için tüm task'ler taranınca işlemler biter:
        if ( button_read_index > 4 )  button_read_index = 0;    
    }

Burada butonları sistem çevrimi içinde, programın en sonunda, başka her şeyden bağımsız olarak okuduğuma dikkat edin. Programım içinde herhangi bir yerde bana herhangi bir butonun basma durumu gerekirse artık butonun bağlı olduğu portun lojik değerine değil yukarıda gördüğünüz task’in çıktısına bakacağım. Ki bu da, programım dahilindeki tüm modüllerin erişimine açık olan;
BUTTON_FLAGS button_pressed;
değişkenidir.
Buradaki BUTTON_FLAGS tip bildirimi, uygulamamızda butonlara verdiğimiz isimlerin tanımlandığı bir struct veri tipidir.
Son bir not da buton okumasının yetkilendirilmesi ya da devre dışına alınması mekanizması hakkında yazayım:
extern void Enable_Button( unsigned int button_index );
extern void Disable_Button( unsigned int button_index );
isminde iki fonksiyon tanımladım. Bunlar, macro olarak tanımladığım buton isimlerini parametre olarak alıp, o butonu taramaya açıyor ya da devre dışına alıyorlar. Bir butonu taramaya almak yukarıda lojiğini verdiğim koddaki ilgili button_mask bitini 1 ya da 0 yapmaktan ibaret. Ben bu değişkeni kendi ünitesi içinde static olarak saklıyorum. Ek olarak, o kanal devreye alınırken dahili sayacı ve durum flag’lerini de sıfırlıyorum.