13 Nisan 2013 Cumartesi

Bir başka PIC #2


Geçtiğimiz hafta müsvedde kağıtlarımın arasında "PNP kağıdıyla baskı devre yapımı" başlıklı yazıda yaptığım devrenin şemasını buldum ve oturup yaptığım devrede arızanın nerede olduğuna bakmaya başladım. Buna en son değinirim. Önce asıl yazacağım konuyu ele alayım.

Yazı esas olarak Temmuz 2012'deki PIC yazımın devamı gibi olacak. Temmuz ayında kendi programlayıcımı yaptıktan sonraki aşamalara kısaca değineceğim.

Temmuzda yaptığım programlayıcı seri arabirimi kullanıyordu. Maalesef artık masaüstü makinalarda bile seri arabirim kalmadı. Seri arabirimi, yönlendirici (router), switch ve disk denetleyici ayarlarken hala kullanıyorum ama acil bir durumda bile kendi dizüstü bilgisayarıma USB-Seri çevirici takıp onu kullanmam gerekiyor. Bu çevirici için ayrıca bir sürücü gerekli. Ve sürücü programlayıcı için bilgisayar tarafında kullanan programla (IC-Prog gibi) uyumlu çalışmadığından, seri PIC programlayıcıyı kullanabilmek için Windows 98'i olan ayrı bir makina bulmak gerekiyordu. Bu da hiç pratik değil.

Yazıcıoğlunda gezerken BioPIC USB Programlayıcı'yı daha önce görmüştüm. 


USB kullandığından programlaması pratik. Üstelik programlayıcının üzerindeki USB iletişimi sağlayan bir PIC18F2550. Artısı seri programlayıcıya göre pratik olması, dış güç kaynağına gerek olmaması ve onlarca PIC serisi denetleyiciyi desteklemesi. Eksisi diyemeyeceğim ancak çeviriciler gibi bilgisayara bir sürücü kurmak gerekiyor. Satın aldıktan sonra CD'de Usburn yazılımıyla geliyor.

Programlayıcıyı bilgisayara takınca ışıklar kısa bir yanıp sönecek. Usburn'u çalıştırınca "Detect Programmer" yazıyorsa sürücülerde bir sorun vardır. "Identify PIC programmer" düğmesi çıkmalı. Buna tıklayınca artık .hex dosyaları okumak ve yazmak olanaklı. Internette bulunabilecek örnek bir .hex dosyayla denemeler yapılabilir. Daha önce denediğim arabirim programlarına göre üstün bir özelliği disassembler ve hex dump'ı da göstermesi. Programlayıcı bazen bilgisayara takıldığında ışıkları yanmadığında yada .hex dosya yazılıp doğrulaması bittikten hemen sonra sürekli ışığı yanıyorsa artık bilgisayar onu görmüyor. USB'yi çıkarıp tekrar takınca sorun düzeliyor.

Elbette ki bir derleyici olmadan sadece başkalarının .hex dosyalarını yazmak bir yerden sonra anlamsız. Benim amacım yaktığım PIC'deki kodu sıfırdan yazmaktı. Bu arada PIC assembly'si oldukça basit. Kendim anlatacak kadar değil ancak idare edecek kadar biliyordum, onu da kullanmaya kullanmaya çoğunu unuttum. O nedenle burada anlatamam ancak piyasada bunu anlatan kitaplar var. Sadece komut seti ve iç yapısı microchip firmasının sayfasındaki "datasheet"inden bulunabilir. O güne kadar PIC doğru düzgün Assembly kodu yazmamıştım. Gidip microchip'in sayfasından MPLAB IDE'yi indirdim. Bu bir IDE ancak içinde hazır bazı derleyici modüllerle geliyor ve bunları seçebiliyorsunuz. Üstelik ICE denen "In Circuit Emulation" özelliğiyle programlayıcıyı tanıyorsa ve destekleniyorsa IDE'den doğrudan programlayıcıya hex kodu aktarmak ve emülasyonunu yapmak olanaklı. Ben bu kadar gelişmiş özelliklerini ne biliyorum ne de kullanabiliyorum. Zaten bildiğim kadarıyla BioPIC'in böyle bir desteği de yok. 

Bu programı kullanılabilir hale getirmek biraz karışık olduğundan ayrıntılı anlatma ihtiyacı duyuyorum. MPLAB'ı kurup IDE'yi açtım. IDE'de doğrudan dosya seçmek yerine önce bir proje oluşturmak gerekiyor. Project -> New diyip proje için bir ad bir de dizin seçiyorum. Karışıklık olmasın diye bütün kodları bu dizinde tutacağım ama bu zorunlu değil. Tamam'a tıkladıktan sonra proje ve çıktı pencereleri çıkması lazım. Çıkmıyorsa View'dan gösterilmesi sağlanabilir. Proje penceresinde 'Source Files'a sağ tıklayıp 'Add Files' kaynak kodu seçip eklemek gerekiyor. Ekledikten sonra proje penceresinde dosya görünecek. Kaynak kod penceresi çıkmıyorsa dosyaya çift tıklayınca çıkacaktır. 

Burada Make yada Build demeden önce yapılması gereken önemli işler var. Birisi Project menüsü altında 'Select Language Toolsuite'den "Microchip MPASM Toolsuite" seçili olmalı ve elbetteki yüklü olmalı. Bu araç MPLAB IDE ile geliyor diye hatırlıyorum ama yüklü değilse yüklemek gerekir. Yüklüyse derleyici, linkleyici gibi ıvır zıvırların yollarının doğru bir biçimde belirtilmesi lazım, yani 'Toolsuite Contents' altında bunların karşısında kırmızı bir çarpı bulunmamalı. Resimde, benim bilgisayarımda MPASM kitinin varsayılan kurulumda nerede bulunduğu görülüyor. Bu ayarlar sanıyorum Project -> Set Language Tool Locations altında da bulunabilir. Uygun derleyiciyi seçtikten sonra Ctrl + F10 ile yada Project -> Build All diyerek kodu derliyorum. Kodun tamamını vermek gereksiz olacak ancak kodun başlığı şu şekilde:

;*************************************************************
    title    "zamanlayici"
;*************************************************************
    list    p=16F84A
    #include    <p16F84A.inc>
    __CONFIG    _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
    ERRORLEVEL -302

SaH        equ        0x10
SaL        equ        0x11
;*************************************************************
        org        0x0000
        goto    BASLA
...


Buradaki #include<p16F84A.inc> ifadesi önemli. Kullanılan sabitlerin birçoğu bu dosya eklendikten sonra tanımlanıyor. Diğer önemli bir ifadeyse __CONFIG makrosu. Bu, programlama sırasında denetleyicinin ayar yazmacına yazılacak olan 'word'u belirtiyor. Buradaki örnekte CP (Code Protection) kapalı, WDT (Watchdog Timer) kapalı, PWRTE (Power-Up Timer) açık ve kristal osilatör seçili. Kodda ve donanımda herşey doğru olsa bile buradaki bitlerden herhangi biri yanlışsa devre hiç çalışmayabilir. Bütün bu tanımlar ve daha bir çoğu (STATUS, PORTA, PORTB, TRISA, TRISB, ...) 16F84A'ya özgü olacak şekilde p16F84a.inc dosyasında bulunur. 

Eğer kodda bir hata yoksa sorunsuz derlenmesi ve .hex dosyanın oluşması gerekiyor. Eğer oluştuysa bundan sonrası .hex dosyayı Usburn'da açıp ROM'a yazdırmak oluyor. Elbette geliştirme aşamasında her seferinde kodda ufak hataları düzeltmek, sonrasında ROM'a yeni kodu yazıp mikrodenetleyiciyi programlayıcıdan sökmek ve devre kartına takıp devre kartında test etmek ve her hatada bunu tekrarlamak bir yerden sonra işkenceye dönüşebilir. ICE'nin avantajı burada devreye giriyor.

Herşey buraya kadar iyiydi. Kodun başındaki ayarları yapıp belleği ayarladım. Kodun altmış küsuruncu satırında bir kere daha hatırladım ki 16F84A komut setinde bölme komutu yok. Benim hem saniyeler için 60'a bölme ve kalan (modulo) hem de basamaklar için 10'a bölme ve kalan koduna ihtiyacım vardı ve açıkçası bölme kodunu ayrı ayrı yazmak istemiyordum. Tek bir bölme fonksiyonuysa beni daha da uğraştıracak gibi görünüyordu. Üstelik yığın (stack) yoktu, fonksiyona argüman göndermek ayrı bir sıkıntıydı. Bu nedenle kodu PIC C'de yazmaya karar verdim çünkü PIC C gibi bir araç varken Assembly'de uğraşmak kılıç kalkanla tanklara saldırmak gibi olacaktı.

Yine hatırladığım kadarıyla MPLAB'le birlikte bir C derleyicisi gelmiyor ama varolan C derleyicilerine destek veriyor. Zaten 'Select Language Toolsuite' altında bir çok C derleyicisi için ayar var. Yalnız varolan C derleyicileri paralı veya bedava ama kısıtlı özellikleri var. Ben iki tane C derleyicisi tavsiye edeceğim. Birisi MPLAB'ın XC8 derleyicisi, diğeri de HI-TECH C derleyicisi. Derleyicileri kurmadan önce hangi mikrodenetleyiciler için olduklarına dikkat edilmeli. Ben bu derleyiciler için bir gecemi ayırdığımdan yarı uykulu halde 16 bit, 32 bit ne varsa kurup çok sonradan aslında 8 bitlik derleyiciye ihtiyacım olduğunu anlamıştım. Hi-Tech C için yazılan C kodlarında Assembly'de eklenen dosya gibi htc.h adında bir başlık dosyası var. Nasıl ki neredeyse bütün C kodlarının başında #include<stdio.h> varsa Hi-Tech C'de de #include<htc.h> bulunuyor. __CONFIG makrosu yine var ama kullanımı Assembly'dekinden biraz daha farklı. Bir de #define _XTAL_FREQ 4000000 demek gerekiyor ki, zamanlamayla ilgili komutlar (__delay_ms() gibi) doğru çalışabilirsin. Elbetteki o sayı yerine devredeki kristal osilatör frekansı neyse o yazılmalı. Ben çoğunlukla Hi-Tech C'yi tercih ediyorum. Burada yine C kodlamayı yada PIC C'nin gündelik hayatta kullandığımız C'den farkını anlatamayacağım ancak bunun için 320volt.com'daki bir yazıyı önereceğim. Önbilgiye sahip okuycular yazının sonunda ek olarak verdiğim örnek kodu da inceleyebilirler. Devre kartının ayrıntısını vermeyeceğimi söylemiştim ancak kodun tamamını kendim yazdığımdan bunu yayınlayacağım.

Herhangi bir C derleyicisi kurup 'Set Language Tool Locations' altında gerekli çalıştırılabilir dosyaları gösterdikten sonra yapılacaklar aslında Assembly kodu derlerken yapılanlarla aynı. Elbette ki farklı derleyicilerin çıktıları farklı olacak. Ben aşağıdaki çıktıya dikkat çekmek istiyorum. 


Bu çıktı devresini kurduğum sayaç kodunun derleyici çıktısı. Gerçekten de derleyici sorununu halledip internetteki örneklere bakarak PIC C'yi çözdüm, kodu yazmaya başladım ve bir kaç saat sonra kodu bitirdim. O gece sabah ezanından sonrasına kadar çalıştım, sorunun çoğunu halletmiştim. İş yerine o gün gelemeyeceğimi e-postayla bildirip yattım. 4-5sa uykudan sonra kodun geri kalanını da bitirdim, denemeleri gerçekleştirdim ve herşey hazırdı.

Çıktıya geri dönersek; anlaşılacağı üzere derleme tamamlanmış ve şöyle birşey demiş: "The HI-TECH C PRO compiler output for this code could be 362 words smaller.". Yani derleyicinin paralı sürümü optimize kod üretiyor ve 906 word'de 362 word'luk bir küçülme aslında hiç de azımsanamayacak bir oran.

Önceki yazıda devreye oldukça hakim olduğumu belirtmiştim. Sayıcıyla ilgili herşeyi bitirip tekrar devreyi incelemeye geri dönünce aslında devreyi sadece sayıcı olarak kullanmanın haksızlık olacağını gördüm. Devre 3 tane giriş (klavye), bir tane gösterici dekoderi (ekran kartı) üzerinden sürülen 4 basamak 8 segment gösterici çıktısı (monitor), bir tane de röle çıktısı (port) olarak düşünüldüğünde aslında küçük bir bilgisayar olarak görülebilir. O halde devre kartı bir toplama bilgisayarı olarak yeniden programlanabilirdi. Şöyle ki iki düğme 00..99 arası sayıları arttıracak, bu sayılar göstericinin sağ iki ve sol iki basamağında gösterilecek ve üçüncü düğmeye basıldığında bu sayıların toplamı göstericide gösterilecek. Günün geri kalanında bu kodu da yazıp bitirdim ki aslında bu kodu yazmak sayıcının kodunu yazmaktan daha kolaydı. Göstericiyle ilgili kodlar zaten hazırdı. Bunun kodunu da yazının sonunda ek olarak vereceğim. Elbette ki basit bir sayıcı devresi, yeni birşeyler geliştirmek için çok da uygun değil. Bunun yerine daha genel amaçlı, geliştirmeye çok daha fazla açık PIC geliştirme kartları da elektronikçilerde bulunuyor. Örneğin aşağıdaki gibi:

Toplayıcıyı da bitirdikten sonra sayıcının kodunu biraz daha geliştireyim, örneğin dakika ve saniye ayarlama modundayken dakika ve saniyeler yanıp sönsün, fırın saati gibi görünsün istedimse de yukarıdaki çıktıda da görüldüğü üzere program ROM'unda 118 word kalmıştı. Dolayısıyla ikinci sürüm kodun bir kısmını tamamladım ama ROM'a sığmadı. Oysa PRO sürümünde optimizasyonlar açık olsaydı en az 362 word daha olacaktı ve çok rahatlıkla gelişmiş sürümü de bitirebilecektim. Bundan sonra da bu devreyle ilgili geliştirmeyi sonlandırdım.

Yazının en başında geçen ay yaptığım çift taraflı baskı devrede arızanın nerede olduğuna bakmaya başladığımı yazmıştım. Bir yandan bu böyle olmaz diyerek UT603 Sığa - Bobin Ölçme cihazını da sipariş ettim, yeni oyuncağım da kargoda. Devre kartında bir tanesi soğuk lehim kaynaklı ve 3 tanesi tasarım hatası kaynaklı arıza buldum. Tasarımda iki tane veri yolunu unutmuştum. Lehimleri tazeleyip iki tane veri yolunu dışarıdan tel lehimleyerek hallettim. Son olarak röle atmıyordu. Röleyi süren transistörü de söktüm ancak onu sökünce sadece kapasite modunda çalışan devre yeniden çalışmamaya başladı. Transistör bozuk değilmiş ancak sökerken büyük olasılıkla bozdum. En son durumda en azından kendisini açılışta kalibre etmeye çalışıyordu:


Ekler:


/*    16F84A entegreli zamanlayici devresi icin PIC C kod dokumu
    19.06.2012   */

#include 
#define _XTAL_FREQ 4000000

__CONFIG(CP_OFF & WDTE_OFF & PWRTE_ON & FOSC_XT);

int counter = 0;
int onesec = 0;        // bir saniye flag
char sayimda = 0;      // gerisayim flag
char cursec = 0;
char curmin = 0;
char digit1, digit2, digit3, digit4;

void guncelle();

void interrupt isr()    {       // kesme hizmet programi
    if(T0IF == 1)    {          // zamanlayici kesmesi mi?
        counter++;
        if(counter >= 1953)  {  // 4Mhz icin 1953 kesme 1sn olur.
            onesec = 1;
            counter = 0;
        }
        if(onesec == 1)    {
            onesec = 0;         // reset flag
            if(cursec == 0)  {  // saniye tasmasi
                if(curmin > 0)    {
                    cursec = 59;
                    curmin--;
                }
                else    {
                    sayimda = 0; // sayimi durdur
                    T0IE = 0;    // kesme'yi kapat
                }
            }
            else    {
                cursec--;
            }
            guncelle();          // basamaklari guncelle
        }
        T0IF = 0;
    }
}

void guncelle()    {
    digit4 = curmin / 10;        // en soldaki basamak
    digit3 = curmin % 10;
    digit2 = cursec / 10;
    digit1 = cursec % 10;        // en sagdaki basamak
}

void goster()    {
    char temp = PORTA & 0x10;
    /* PortA'nin 4. bitinde role bagli. 0-1-2-3'de 7 segment

       LED'leri adresliyor. Role'nin durumunu degistirmeden

       LED'leri taramak icin temp'de onceki PortA degerini

       yedekle. */

    PORTB = digit1;
    PORTA = temp | 0x01;
    __delay_ms(2);
    PORTA = temp;
   
    PORTB = digit2;
    PORTA = temp | 0x02;
    __delay_ms(2);
    PORTA = temp;

    if(counter < 976)   PORTB = digit3;
    else                PORTB = digit3 | 0x10;
    /* PortB'nin 4. bitine 7 segment'in noktalari bagli. Eger

       saniyenin ilk yarisiysa tam ortadaki 3. noktayi yakma ama

       ikinci yarisiysa yak. boylece saniyede bir kere yanip 

       sonecek. */ 


    PORTA = temp | 0x04;
    __delay_ms(2);
    PORTA = temp;

    PORTB = digit4;
    PORTA = temp | 0x08;
    __delay_ms(2);
    PORTA = temp;
}

void klavye()    {
    if(RB7 == 0)    {    // ucuncu dugmeye bagli.
        if(sayimda == 0)    {
            sayimda = 1;
            T0IE = 1;    // sayimda degilse kesmeyi ve sayimdayi 

                         // baslat
        }
        else    {
            sayimda = 0;
            T0IE = 0;    // sayimdaysa kesmeyi ve sayimdayi kapat
        }
        while(RB7 == 0)    goster();
        // basili tutuldugu sure icerisinde sadece ekrani tazele
    }
   
    if((RB6 == 0) && (sayimda == 0))    {
        // ikinci dugmeye bagli. sadece sayimda degilse aktif
        cursec++;          // saniyeyi arttir
        if(cursec == 60)   cursec = 0;        // saniye tasmasi
        guncelle();        // artmis saniyeyi guncelle
        while(RB6 == 0)    goster();
        // basili tutuldugu sure icerisinde sadece ekrani tazele
    }

    if((RB5 == 0) && (sayimda == 0))    {
        // birinci dugmeye bagli. sadece sayimda degilse aktif
        curmin++;          // dakikayi arttir
        if(curmin == 100)  curmin = 0;        // dakika tasmasi
        guncelle();        // artmis dakikayi guncelle
        while(RB5 == 0)    goster();
        // basili tutuldugu sure icerisinde sadece ekrani tazele
    }
}

void role()    {
    if((cursec == 0) && (curmin == 0))  RA4 = 1;
    // saniye ve dakika sifir oldugunda roleyi ac
    else                                RA4 = 0;
    // degilse roleyi kapat.
}

void main()    {

    TMR0 = 0;
    GIE = 1;           // kesmeleri aktiflestir
    T0IE = 0;          // zamanlayici kesmesini kapat
    OPTION_REG = 0;    /* PortB pull-up enable. Timer0 clock

                          source = CLKOUT. Prescaler to Timer0. 

                          Prescaler 1:2  */
    RBIF = 0;          // PortB kesmesini kapat

    PORTA = 0x10;      // Role bacagi haricindeki bacaklar sifir.
    PORTB = 0;
    TRISA = 0;         // Butun bacaklar cikti
    TRISB = 0xE0;      // ilk 3 bacak dugmelere bagli girdi
                       // geri kalan bacaklar cikti.

    guncelle();        // basamak degerlerini guncelle
    while(1)    {
        goster();      // degerleri goster
        klavye();      // dugmeleri oku
        role();
    }

}



/*    16F84A zamanlayici devre icin toplayici   
    19.06.2012    */

#include 
#define _XTAL_FREQ 4000000

__CONFIG(CP_OFF & WDTE_OFF & PWRTE_ON & FOSC_XT);

int sayi1 = 16;
int sayi2 =  9;
int toplam;
char basamak1, basamak2, basamak3, basamak4;

void interrupt isr()    {    // bos kesme hizmet programi
    #asm
        NOP
    #endasm
}

void guncelle()    {
    basamak1 = sayi1 % 10;        // en sagdaki basamak
    basamak2 = sayi1 / 10;
    basamak3 = sayi2 % 10;
    basamak4 = sayi2 / 10;        // en soldaki basamak
}

void goster()    {
    // basamaklari PortB'den gonder. PortA 7 segment'in basamaklarini tarar.
    PORTB = basamak1;
    PORTA = 0x01;
    __delay_ms(2);
   
    PORTB = basamak2;
    PORTA = 0x02;
    __delay_ms(2);

    PORTB = basamak3;
    PORTA = 0x04;
    __delay_ms(2);

    PORTB = basamak4;
    PORTA = 0x08;
    __delay_ms(2);

}

void klavye()    {
    if(RB7 == 0)    {
        toplam = sayi1 + sayi2;
        // ucuncu dugmeye basilinca iki sayiyi topla
        basamak4 = 0;
        basamak3 = toplam / 100;
        basamak2 = (toplam / 10) % 10;
        basamak1 = toplam % 10;
        // bu toplama icin kendi guncelleme rutini gerekli.
        while(RB7 == 0)    goster();
        // ucuncu dugme basili oldugu surece toplami ekranda goster
        guncelle();
        // sonra yine eski sayilarla ekrani guncelle
    }
   
    if(RB6 == 0)    {
        // ikinci dugme basiliysa birinci sayiyi arttir [0, 100]
        sayi1++;
        if(sayi1 == 100)    sayi1 = 0;
        guncelle();
        while(RB6 == 0)    goster();
    }

    if(RB5 == 0)    {
        // birinci dugme basiliysa ikinci sayiyi arttir [0, 100]
        sayi2++;
        if(sayi2 == 100)    sayi2 = 0;
        guncelle();
        while(RB5 == 0)    goster();
    }
}

void main()    {
   
    TMR0 = 0;        // Timer0 sifirlama
    GIE = 0;         // Kesmeleri durdur
    T0IE = 0;        // Zamanlayici kesmesini durur
    OPTION_REG = 0;   
    RBIF = 0;        // PortB kesmelerini durdur

    PORTA = 0x10;    // Roleye dokunma diger bacaklari sifirla
    PORTB = 0;
    TRISA = 0;       // PortA cikis.
    TRISB = 0xE0;    // PortB ilk 3 pin giris, digerleri cikis.

    guncelle();
    while(1)    {
        goster();
        klavye();
    }
}

Hiç yorum yok:

Yorum Gönder