"Enter"a basıp içeriğe geçin

PIC Mikrodenetleyici ve XC8: Buton Kullanımı

Merhaba sevgili ziyaretçiler. Bu yazımda  PIC18F4550 mikrodenetleyicisinin buton ile nasıl kullanılacağını anlatmaya çalışacağım. Programı Microchip’in kendi Mplab IDE’sinde yazacağız ve kendi derleyicisi olan XC8 ile derleyeceğiz. Önceki yazımda Mplab ve XC8 konusuna giriş yapmış ve bir de ufak led yak söndür uygulaması yapmıştım. O yazımda proje oluşturma konusunu da anlattığımdan burada tekrar ele almayacağım.

Önceki yazımda TRISx (x: A, B, C, D, vs.) yazmaçlarının ne olduğunu ve ne işe yaradığından bahsetmiştim. Tekrar hatırlatmak gerekirse TRIS yazmaçları portlarda giriş veya çıkış yönlendirmesi yaparlar. Yani bir portun tamamını veya istediğiniz bitini porta ait TRIS yazmacına verdiğiniz değere göre giriş ya da çıkış yapabiliyorsunuz. Burada giriş için “1” (Input anlamında), çıkış için de “0” (Output anlamında) kullanıyoruz. Giriş olan pin yüksek empedans durumunda olacaktır.

Portun bir pini çıkış durumunda iken o pine “1” yazmak pine +5V verirken, “0” yazmak da o pini 0V durumuna çeker.

PORTx (x: port adı A, B, C, D, vs.) yazmacı portun fiziksel durumunu (aslında gerilim seviyesini) okumak için de kullanılabilir. Eğer giriş durumundaki bir pinden VDD’ye (+5v) yakın bir gerilim değeri okunuyorsa PORT’un o bitinin değeri 1 (HIGH), eğer Vss’ye (0v) yakın bir gerilim değeri okunuyorsa PORT’un o bitinin değeri 0 (LOW) olur.

Buradan hareketle devremize bir buton bağlamak istediğimizde tasarımını bu gerçeği göz önüne alarak yapmamız gerekiyor. Birincisi butona basılı olmadığında bağlı olduğu pine HIGH gönderip, basıldığında pinin durumunu LOW’a çektiği Pull-up durumudur. İkincisi ise butona basılı olmadığında bağlı olduğu pini LOW durumunda tutup, butona basıldığında pini HIGH’a çeken Pull-down durumudur. Bu konuda daha önce hazırlamış olduğum yazımı inceleyebilirsiniz.

Şimdi iki farklı durum için de devrelerimizi ve programımızı yazmaya başlayalım.

PULL-UP BAĞLANTI

Pull-up bağlantıda butona basılı olmadığında bağlı olduğu pine HIGH, basıldığında LOW gönderir.

Butonun PORTD’nin 0. pinine pull-up bağlanması

Mikrodenetleyicinin programını yazarken, devresini göz önüne alarak yazmamız gerekiyor. Yukarıdaki devrede PORTB’nin 0. bitine (RB0) bir adet led bağladık. O halde RB0 pini çıkış olmalı. Aynı zamanda PORTD’nin 0. bitine (RD0) de bir adet buton bağladık bu durumda RD0 pini de giriş olacak. Şimdi buna göre kodumuzu yazmaya başlayalım.

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;

    while (1) {
        if (PORTDbits.RD0 == 0) { // Eğer PORTD'nin 0. biti LOW ise
            PORTBbits.RB0 = 1; // LED'i yak
            while(PORTDbits.RD0 == 0); // PORTD'nin 0. biti LOW olduğu sürece bekle
            PORTBbits.RB0 = 0; // LED'i söndür
        }
    }
}

Kodlarda gördüğünüz üzere

TRISD0 = 1;

satırında PORTD’nin 0. bitini “1” olarak ayarladık. Böylece mikrodenetleyiciye PORTD’nin 0. pininden giriş alacağımı söyledim.

TRISB0 = 0;

satırında ise PORTB’nin 0. bitine “0” yazdık. Böylece mikrodenetleyiciye PORTB’nin 0. pininden çıkış vereceğimi söyledim.

if (PORTDbits.RD0 == 0) {

satırında PORTD’nin 0. pininin değeri okunarak “0” değeri ile karşılaştırılıyor. Program da bu koşula göre dallanıyor.

Burada if yapısı içindeki

while(PORTDbits.RD0 == 0);

döngüsü dikkatinizi çekmiştir. Bu döngü buton bırakılana kadar bekleme sağlıyor. Böylece buton basılı olduğu süre boyunca yazılım, butona tekrar tekrar basılıyormuş gibi davranmayacak. Farklı çözümler olabilir ancak bildiğim en etkili yöntem bu.

PULL-DOWN BAĞLANTI

Bu bağlantıda ise butona basılı olmadığında bağlı olduğu pine LOW, basıldığında HIGH gönderir. O zaman buna göre devremizi yeniden hazırlayalım.

Butonun PORTD’nin 0. pinine LOW olarak bağlanması

Yeni devrede dikkat ettiyseniz pine giden bağlantı yoluna göre dirençle butonun yeri değişti. Bu bağlantıya göre artık PORTD’nin 0. pini butona basılmadığı sürece hep LOW seviyesinde olacak. Butona basıldığında pinle Vdd arasındaki devre tamamlanacak ve akım artık buton üzerinden akacak. böylece pinin değeri HIGH olacak.

Bu durumun aslında en basit açıklaması “elektrik akımı her zaman dirençsiz (ya da en kolay akabileceği) yolu tercih eder” olacaktır.

Şimdi programımızı yeni devreye göre güncelleyelim:

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;

    while (1) {
        if (PORTDbits.RD0 == 1) { //Eğer PORTD'nin 0. biti HIGH ise
            PORTBbits.RB0 = 1; // LED'i yak
            while(PORTDbits.RD0 == 1); PORTD'nin 0. biti HIGH olduğu sürece bekle
            PORTBbits.RB0 = 0; // LED'i söndür
        }
    }
}

Evet kodlardan da göreceğiniz üzere sadece

if (PORTDbits.RD0 == 1) {

koşulu ve

while(PORTDbits.RD0 == 1);

döngüsü üzerinde yaptığımız ufak değişiklikle kodumuzu donanıma uygun hale getirdik.

Ark Oluşumu ve Ark Söndürme

Elektronik bir düzeneğe bağlı olan bir butona basıldığında basma esnasında Mantıksal “1” seviyesinden Mantıksal “0” seviyesine geçiş sırasında kısa süreli de olsa (mikro saniyeler seviyesinde) dalgalanmalar oluşur. Bu da anahtar (buton) kontakları arasında birçok küçük bas-çek durumuna neden olur. Bu duruma literatürde ark (bounce) denir. Oluşan bu arklar nedeniyle sinyalin ark kısımlarında birçok 1-0-1-0 geçişleri oluşur. Bu da mikrodenetleyicinin butonu hatalı algılamasına neden olur. Ark oluşumunu önlemek için (debounce) donanım ve yazılım tabanlı olmak üzere iki farklı metot  mevcuttur.

Donanım Tabanlı Ark Söndürme

Evet geldik butonlarla çalışırken en fazla saç baş yolduran kısma. Devrenize butonu eklediniz, programı da yazdınız ancak devreyi çalıştırıp butona bastığınızda o da ne devre alakasız şeyler yapmaya başladı. İşte bu başlıkta butonun bu alakasız çalışmasını nasıl engelleriz ona değineceğiz.

Elektronik bir devrede ark istenmeyen bir durumdur. Bu nedenle bu istenmeyen sinyali yok etmek için filtre kullanırız. Sinyal içindeki arkın frekansı sinyalin frekansından yüksek olduğundan yüksek frekanslı olan arkı eleyip alçak frekanslı sinyali elde etmek için alçak geçiren filtre bu iş için uygun olacaktır. Bunu da basitçe butonun uçlarına bir adet kondansatör bağlayarak gerçekleyebiliriz.

Donanımsal Ark Söndürme (Hardware Debounce)
Donanımsal Ark Söndürme (Hardware Debounce)

Butona basılmadığı zaman kondansatör (C1), R1 pull-up direnci üzerinden şarj olur. Mikrodenetleyicinin butonun bağlı olduğu pin girişinde (MCU ucu) kondansatör üzerindeki gerilim değeri görülecektir. Bu değer Mantıksal “1” seviyesindedir .
Butona basıldığında kondansatörün iki ucu kısa devre olarak GND’ye bağlanır ve kondansatör hemen deşarj olur. Giriş gerilimi kondansatör üzerindeki gerilim değeri olduğundan giriş anında 0V değerine düşer. Butonu bıraktığımızda kondansatör şarj olmaya başlayacaktır. Kondansatörün üzerindeki gerilim Mikrodenetleyiciyi mantıksal “0” seviyesinde tutacak gerilim değerine ulaşana kadar giriş pini mantıksal “0” seviyesinde kalır. Bu gerilim değerine ulaşıncaya kadar geçen sürede ark oluşsa da arkın mantıksal “1” seviyeleri kondansatörün şarjını fazla etkilemeyeceğinden mikrodenetleyici pini bu sinyali mantıksal “0” olarak algılar. Böylece mikrodenetleyicinin pini ark durumundan fazla etkilenmemiş olur.

Arkları büyük oranda önledik ancak yine de bazı ufak salınımlar oluşabilir. Bu salınımlar kondansatörün ve direncin toplam empedansından dolayı meydana gelir ve deşarj işlemi hemen olmaz. Her elemanın ve her bağlantı noktasının bir endüktansı vardır. Bu endüktans ve kondansatör bir araya gelince salınım hareketi başlar ve  – (negatif) yönde gerilim oluşur. Bu negatif gerilim değeri  mikrodenetleyicinin pinleri için minimum değeri aşarsa  mikrodenetleyiciye zarar verebilir. Bu nedenle  salınımların engellenmesi gerekir. Bunun için en iyi yöntem direnç eklemektir. Buna göre yeni devremiz aşağıdaki gibi olacaktır.

Son Haliyle Donanımsal Ark Söndürme (Hardware Debounce)
100 ohm’luk güvenlik direnci eklenmiş haliyle Donanımsal Ark Söndürme (Hardware Debounce)

Bunun yanında daha kararlı çıkış değerleri elde etmek için 74HC14 Schmitt Trigger buffer kullanabiliriz. Buna göre devreyi yeniden düzenleyecek olursak aşağıdaki gibi bir sonuç elde ederiz.

Schimtt Trigger Buffer Eklenmiş Donanımsal Ark Söndürme
Schimtt Trigger Buffer Eklenmiş Donanımsal Ark Söndürme

Aynı zamanda bir evirici olan 74HC14 entegresinin buradaki görevi mantıksal seviye geçişlerini daha hızlı hale getirmek ve istenmeyen sinyalleri engellemektir. Pull-up bağlantıda normalde butona basınca giriş pinine mantıksal “0” işareti gönderir ancak 74HC14 bu manıksal “0” işaretini tersleyerek mantıksal “1” yapar. Bu nedenle programda buton denetiminin mikrodenetleyiciye gelen mantıksal “1” değerine göre yeniden düzenlenmesi gerekir.

Yalnız unutulmamalıdır ki donanımsal olarak eklenecek her parça ek maliyet anlamına gelir. Geliştireceğiniz devrede çok hassas denetimler olmayacaksa donanımsal bileşenler kullanmak yerine yazılımsal olarak da ark söndürülebilir.

Yazılım Tabanlı Ark Söndürme

Bu konuyu ben pull-up bağlantı üzerinden anlatacağım. Pull-down bağlantıya da rahatlıkla uyarlayabilirsiniz. Yazılımsal ark söndürme için farklı çözümler mevcut ve duruma özel çözümler de geliştirilebilir. Ancak genel olarak kullanılan üç farklı yöntemden bahsedebiliriz.

1. Yöntem:

En basit yöntem bu yöntemdir diyebilirim. Bu yöntemde buton kontrolünden sonra bir süre gecikme sağlamaktır. Ancak gecikme, algılamanın hızlı olması gerektiği durumlarda kullanışlı olmayacaktır.

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;

    while (1) {
        if (PORTDbits.RD0 == 0) { // Eğer PORTD'nin 0. biti LOW ise
            __delay_ms(50);
            PORTBbits.RB0 = 1; // LED'i yak
        }else {
            PORTBbits.RB0 = 0; // LED'i söndür 
        }
    }
}

Ayrıca gecikmeden sonra butonu bir kez daha kontrol edebiliriz.

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;

    while (1) {
        if (PORTDbits.RD0 == 0) { // Eğer PORTD'nin 0. biti LOW ise
            __delay_ms(50);
            if (PORTDbits.RD0 == 0){
               PORTBbits.RB0 = 1; // LED'i yak
            }
        } else {
            PORTBbits.RB0 = 0; // LED'i söndür
        }
    }
}

2. Yöntem:

Butona basıldığı algılandığı anda bir önceki çevrimde butona basılmamış olması kontrol edilir. Eğer bir önceki çevrimde butona basıldı bilgisi varsa bu ark olarak algılanır ve dikkate alınmaz.

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;
    unsigned char butonabasildi = 0;
    while (1) {
        if (PORTDbits.RD0 == 0) { // Eğer PORTD'nin 0. biti LOW ise
            if (butonabasildi == 0){
               PORTBbits.RB0 = 1; // LED'i yak
               butonabasildi = 1;
            }
        } else {
            PORTBbits.RB0 = 0; // LED'i söndür
            butonabasildi = 0;
        }
    }
}

3. Yöntem:

Bu yöntemde butona basıldığı ve basılmadığı durumları tutmak için iki ayrı sayaç değişken tanımlanır. Butona basıldığı zaman basıldı sayacının değeri bir artırılır. Butona basılı olmadığı zaman ise basılmadı sayacının değeri artırılır. Her iki sayacın da belli bir eşik değere ulaşıp ulaşmadığı kontrol edilir. Eğer sayaçlardan birisi örneğin 300 değerini aşmışsa butona basıldığı veya basılmadığı anlaşılır. Sayaçlardan herhangi biri artarken diğer durum oluşursa o durumun sayacı sıfırlanır. Böylece ark durumları arasındaki hızlı geçişler algılanır. Bu yöntem kullanılabilecek en güvenli yöntemlerden biridir.

#include <xc.h>

#define _XTAL_FREQ 4000000

#pragma config FOSC = HS, IESO = OFF
#pragma config PWRT = ON, BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF

unsigned int basili = 0;
unsigned int basilidegil = 0;
unsigned char basildi = 0;

void main(void) {
    TRISD0 = 1; // PORTD'nin 0. biti giriş olsun
    TRISB0 = 0; // PORTB'nin 0. biti çıkış olsun

    PORTBbits.RB0 = 0;
    while (1) {
        if (PORTDbits.RD0 == 0) { // Eğer PORTD'nin 0. biti LOW ise
            basili++;
            basilidegil = 0;
            if (basili > 300) {
                if (basildi == 0) {
                    PORTBbits.RB0 = 1; // LED'i yak
                    __delay_ms(100);
                    basildi = 1;
                    basili = 0;
                }
            }
        } else {
            basili = 0;
            basilidegil++;
            if (basilidegil > 300) {
                basildi = 0;
                basilidegil = 0;
                PORTBbits.RB0 = 0; // LED'i söndür
            }
        }
    }
}

Söylediğim gibi yazılımsal ark söndürme konusunda çok farklı yöntemler de geliştirilebilir. Burada en çok bilinen üç yönteme değindik. Donanımsal çözümün maliyeti yüksektir ve hassas projelerde tercih edilmelidir. Yazılımsal çözüm ise daha düşük maliyetlidir ancak donanımsal çözümle yakaladığınız hassasiyeti elde edemeyebilirsiniz.

haruncetin.com.tr

 

6 Yorum

  1. faydalı bir yazı emeğinize sağlık

  2. Hatice Ular Hatice Ular

    Merhabalar size bir sorum olacak cevaplarsanız sevinirim
    Esembly dilinde buton kontrolünde LED yakarken butona basılıp basilmadıgıňı tırıs â yı giriş yapmak için atadigimiz
    Sayının bitlerinin 0 veya 1 olmasından anlayabilirmiyiz

    • Merhaba, TRIS yazmaçları PIC mikrodenetleyicilerinde yalnızca portları giriş/çıkış olarak ayarlamayı sağlar. Bu nedenle bir portun pinine gelen sinyali bu yazmaçlardan alamazsınız. Bu amaçla PORTA, PORTB, vs. yazmaçlarını kullanabilirsiniz.

      Yazıda PORTD için örneklendirdiğim gibi if (PORTDbits.RD0 == 0) veya while(PORTDbits.RD0 == 0) şeklinde bir kullanımla PORTD‘nin sıfırıncı bitine bağlı butona basılıp basılmadığını kontrol edebilirsiniz. Bu amaçla kesme de kullanabilirsiniz. Ancak TRIS yazmacını bu amaçla kullanamazsınız.

  3. mehmet mehmet

    Merhaba
    Muhtemelen basit bir çözümü vardır ama bulamadım yardımcı olur musunuz. Üç buton olduğunu düşünelim, birinci butona basıldığında bir döngüye girmesini, ikinci butona basıldığında ikinci bir döngüye girmesini, üçüncü butona basıldığında da tüm döngülerden çıkmasını programda nasıl sağlarız.
    Teşekkürler

    • Merhaba, geç gelen bir cevap oldu ama bahsettiğiniz problem eğer ikinci butona basıldığında başlayacak döngü birinci döngü ile birlikte devam edecekse, çok görevli çalışma (multi-tasking) konusu. Bilindiği üzere bir mikrodenetleyicide birden fazla döngüyü aynı anda çalıştırmak demek birden fazla işin aynı anda yapılıyor olması demek. Doğası gereği mikrodenetleyiciler aynı aynda yalnızca tek bir işi icra edebilir, etmesi de gerekir. Ama bazen birden fazla işi de yapması icap edebiliyor. Burada çok görevli (multi-tasking) çalışma teknikleri devreye giriyor. Bunun amaçla kullanılabilecek en kestirme yöntem freertos’un imkanlarını kullanmak. Fakat o kadar yüklü bir kütüphaneye gerek yok derseniz kendiniz de basit multi-tasking çalışma düzeneği kurabilirsiniz.
      Konu detaylı olduğundan tüm hatlarıyla bir yorum satırında çözmemiz makul değil ancak sizin probleminiz özelinde şöyle bir çözüm önerebilirim. Tüm multi-tasking altyapısını hazırladığınızı varsayarak globalde bir değişken tutup örneğin byte dongulerCalisiyor=0; gibi. Ardından butonların durumunu kontrol etmek için bir task ve iki tane de döngüler için task olacak. Döngülerin olduğu taskların içinde öncelikle dongulerCalisiyor=1; şeklinde bir atama olacak ardından döngüler şu şekilde kurulacak: while(dongulerCalisiyor == 1){/* dongu icinde ne yapiacaksa burada olacak */}. Butonların durumunu kontrol eden taskın içinde birinci butona basıldığında birinci döngünün içinde olduğu taskı çalıştıracak, ikinci butona basıldığında ikinci döngünün içinde olduğu taskı çalıştıracak. Üçüncü butona basıldığında ise dongulerCalisiyor = 0; kodu olacak. Bu kod her iki döngünün de while koşulunu false yapacak ve böylece bu butona basıldığında her iki döngüden de çıkılmış olacak.

Murat için bir yanıt yazın Yanıtı iptal et

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir