Etiket arşivi: Function pointer

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.