Etiket arşivi: Function pointer

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.

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.

Function Pointers in C

Fonksiyon işaretçisi, bir fonksiyonu bildirim referansını kullanarak çağırabilmemizi sağlar. Yani, bir fonksiyonu parametrik olarak çalıştırma imkanımız olur.

void (* func_name) ( ) = function_name;

Bunun bendeki en sık kullanma amacı Fonksiyon Listesi hazırlamak:


 int funcA(void);
 int funcB(void);
 int funcC(void);
 int funcD(void);
 int funcE(void);
 int funcF(void);

 int (* functionList[]) () = 
 {
  funcA, funcB, funcC, funcD, funcE, funcF 
 };

 // bu bildirimin cümle içinde kullanımı şöyle:
 ret_val = functionList[list_index]();

Her ne kadar bir kaynak dosyası içinde kullanılacak bir fonksiyon için deklarasyon yazmak zorunda olmasak da, fonksiyon listesi hazırlarken deklarasyon yazmalıyız. Deklarasyon bize yürütme kısmından önce o fonksiyona dair işaretçiyi kullanma şansı verir. Bu şekilde liste bildirimi yaparken liste elemanlarını sabit olarak kullanabiliriz.