Timer (Zamanlayıcı) Kullanımı


Zamanlayıcı kullanmak için mikroişlemcilerde belirli birimler bulunur. PIC18F4585‘te bununla ilgili 3 birim bulunmakta. Timer0, Timer1 ve Timer2.
Bu bölümde amacımız bu zamanlayıcı birimlerini kullanarak belirli zaman aralıklarında kesme oluşmasını sağlamak. Oluşan kesmelerin kontrolünü yine ledlerle yapacağız.
Timer0 ve Timer1 hem zamanlayıcı hem de sayıcı olarak kullanılabilir. Birimin zamanlayıcı olarak kullanılabilmesi için değer artışının düzenli (periyodik) olması gerekir (Ör: 3 saniyede bir butona basmak). Sayıcı olarak kullanımda ise değer artışı düzenli olmak zorunda değildir (Ör: 10 dakika boyunca 35 defa herhangi bir zamanda butona basmak). Timer2 birimi ise sadece sayıcı olarak kullanılır. Bu birimleri sırayla inceleyelim.
Timer0 Birimi:
  • 8 bit Zamanlayıcı veya sayıcı olarak kullanılabilir
  • Okuma ve yazma yapabilir
  • 8 bitlik bölme oranı(prescaler) vardır. Yazılım tarafından programlanabilir
  • Dahili veya harici saat(clock) kaynağı seçilebilir
  • Timer0 taşma kesmesi (FFh’tan 00h’a geçilirken)
  • Clock kaynağının sinyal tetikleme kenarı seçilebilir (Yükselen ya da düşen kenar)
Bu birim ile biz 0-255 (00h – FFh) arası bir değer belirleriz ve Timer0 birimi bu seçtiğimiz değerden itibaren saymaya başlar. Değer 255′yi geçince tekrar 0′a döner. 255′ten 0′a geçişte bir taşma kesmesi(TMR0) oluşur.
Not: Eğer Timer0 harici kaynaktan beslenirse sayıcı olarak, dahili kaynaktan beslenirse    zamanlayıcı olarak kullanılır.
Bu bölümde kullanacağımız fonksiyonlar;
  • OpenTimer0(): Hiçbir parametre almaz. Timer0 modülünü kullanıma açar.
  • WriteTimer0(): 1 parametre alır, o da Timer0′ın saymaya başlayacağı 0-256 arası değerdir.
  • CloseTimer0(): Hiçbir parametre almaz. Timer0 modülünü kullanıma kapatır.
Programda amaç Timer0 birimiyle taşma kesmeleri oluşturmak ve kesme sayısı 10 olduğunda RB5 pinine bağlı ledi yakıp,  kesme sayısı 20 olduğunda RB5 pinine bağlı ledi söndürmek. Böylece Timer0 biriminin çalışmasını incelemiş olacağız. (Tek bir kesmenin oluşması çok hızlı gerçekleştiğinden dolayı gözlemleyebilmek için oluşan kesme sayılarını sayıyoruz). Program kodları aşağıdaki gibi olacak;

#include <p18f4585.h>
#include <stdio.h>
#include <timers.h>
#include <delays.h>
#pragma config OSC = HS, WDT = OFF, LVP = OFF
int i = 0;
void int_handler(void);
#pragma code high_vector=0x08
void high_interrupts (void) {
     _asm GOTO int_handler _endasm
}
#pragma code low_vector=0x18
void low_interrupts (void) {
     _asm GOTO int_handler _endasm
}
#pragma code
#pragma interrupt int_handler
void int_handler(void) {
     if(INTCONbits.TMR0IF == 1) {
          WriteTimer0(100);
          i++;
          if(i==10)
               PORTBbits.RB5 = 1;
          if(i==20) {
               PORTBbits.RB5 = 0;
               i = 0;
          }
          INTCONbits.TMR0IF = 0;
     }
}

void main(void) {
     TRISB = 0;
     PORTB = 0;
     CloseTimer1();
     CloseTimer2();
     OpenTimer0(TIMER_INT_ON & T0_SOURCE_INT & T0_PS_1_256 );
     WriteTimer0(100);
     INTCONbits.GIE = 1;
     INTCONbits.TMR0IE = 1;
     while(1);
}
Satır 10′da timer birimlerini kullanmak için “timers.h” kütüphanesi include ediliyor. Yoksa timer birimlerine ait fonksiyonlar çalışmaz.
Satır 17′de interrupt kodlarımızı yazacağımız int_handler() fonksiyonun prototipi belirtiliyor. Bu prototip goto komutu ile interrupt kodlarını yazdığımız bölüme atlamamız için gerekli.
Satır 19 ve 24′te mikrodenetleyicideki yüksek ve düşük öncelikli interrupt’ların vektör adresleri belirtiliyor. PIC18F4585′te high interrupt’lar için 0×08, low interrupt’lar için 0×18 vektör adresine  gidilir.
Satır 20 ve 25 ‘teki fonksiyonlar yüksek veya düşük öncelikli  bir interrupt geldiği zaman hangi fonksiyona gidileceğini belirtmek üzere oluşturulur. Normalde verdiğimiz vektör adreslerinin olduğu kısımlar çok az yer kaplayan kısımlardır. Interrupt kodlarını o kısma yazamayız ama bir interrupt geldiğinde  kodları  kendi  yazdığımız  interrupt  kodlarının olduğu başka bir fonksiyona yönlendirebiliriz. Bu yönlendirmeyi de 21 ve 26. satırlardaki _asm _endasm arasındaki goto komutuyla yaparız. Bu örnekte interrupt önceliği önemli olmadığından dolayı ben bütün interruptları aynı fonksiyona (int_handler) yönlendirdim. İstersek her iki öncelik için farklı fonksiyonlarda tanımlayabiliriz.
Satır 30′da interrupt kodlarımızla ilgili kısım başlıyor. 31. satırda int_handler() fonksiyonuna giriyoruz. Bu fonksiyon Timer0 birimi tarafından oluşturulan TMR0 taşma(overflow) interrupt’ının ne yapacağını belirleyen kodları içerecek.
int_handler() fonksiyonu bütün interrupt kodlarının bulunacağı bir fonksiyon. Bu örnekte başka interrupt kodları da kullanmak isteseydik nasıl kullanacaktık? Birden fazla interrupt’ın aktif olarak çalışacağı  -ki genelde programlar öyle olur- programlarda interupt kodlarını tutan fonksiyonda önce oluşan interrupt’ın hangi birim tarafından oluşturulduğuna bakılır, sonra işlemler ona göre yapılır. Bir kesme oluştuğunda kod int_handler fonksiyonuna yönlendirilecek. Daha sonra bu fonksiyonda 33. satırda olduğu gibi bu kesmenin hangi birim tarafından oluşturulduğu kontrol edilecek. Önceki bölümde INTCON register’ının TMR0 kesmesi için çeşitli bitler barındırdığını söylemiştik. INTCONbits.TMR0IF biti Timer0 birimi 255 değerinden 0 değerine geçerken set(1) olur. Bu bit(flag)‘i kontrol edersek TMR0 kesmesini gözlemleyebiliriz.
Not: Eğer interrupt kodlarımız çalışırken başka interruptların yüksek öncelikli olsalar bile o an çalışan interrupt kodlarının akışını kesmesini  istemiyorsak, kontrol interrupt kodlarına geçtiği anda global kesmelerdisableetmeliyiz. Böylece interrupt kodları çalışırken bir daha kesintiye uğramaz. Fakat kodlar çalıştıktan sonra global kesmeleri tekrar açmamız gerekir, yoksa program akışında bozulmalara yol açmış oluruz. 
Satır 35′te Timer0 birimine değer yazılıyor(bu değerden başlanarak 255′e kadar sayılacak). Bu sayma süresi (kesmenin oluşma süresi); komutun çalışma süresi, bölme oranı(prescaler) ve bizim girdiğimiz değere bağlıdır.  
Tkesme= Tkomut x (Bölme Oranı) x (256 – TMR0′a yazılan değer)
Tkomut = 1 / Fkomut                                  
Fkomut = Denetleyici Osilatör Frekansı / 4
Bölme oranı OpenTimer0() fonksiyonunda belirlenen bir değerdir. Bizim bilmemiz gereken, bölme oranı ne kadar yüksek olursa ve girdiğimiz değer 255ten ne kadar küçük olursa Timer0′ın interrupt oluşturma süresi o kadar uzun olur. Bu şekilde zamanlayıcıyı ayarlayabiliriz.
Biz bu fonksiyonu neden kesme içerisine yazdık? Normalde main() fonksiyonu içerisindede bu fonksiyonu yazarız(Satır 61). Fakat kesme oluştuğu anda Timer0′ın içeriği sıfırlanır ve kesme fonksiyonun geldiğimizde Timer0 birimini yeniden ayarlamamız gerekir. Yoksa 0′dan başlayıp 255′e kadar sayacak ama bizim istediğimiz şey bizim girdiğimiz değerden 255′e kadar sayması. Eğer değer olarak 100 vermişsek ve kesme oluşmuşsa (Kesme 255 – 0 geçişinde oluşur) 0-100 arasındaki sayım işlemini atlamamız ve Timer0 birimini yeniden 100′den itibaren sayacak şekilde ayarlamamız gerekir.
İnterrupt kodları 10 tane interrupt saydığında RB5 pinine bağlı ledi yakacak (Satır 40), 20 tane interrupt saydığında ise RB5 pinine bağlı ledi söndürüp sayac(i)’ı sıfırlayacak (Satır 43,44).
Satır 47′de yaptığımız işlem ise TMR0 interrupt kontrolü yaptığımız flag(TMR0IF)’i sıfırlamak. Bu flag set(1) olduktan sonra sıfırlanmazsa sürekli bu interrupt kodları çalışır. Program akışı bozulur.
Not: İnterrupt kodu yazılırken, interrupt kodunun işi bittiğinde o interrupt’a ait flag    reset(0) yapılmalıdır.
Satır 57 ve 58′de diğer timer birimleri CloseTimer() fonksiyonu ile kapatılıyor.
Not: Program yazarken birbirinin çalışmasını etkileyeceğini düşündüğünüz modülleri kapatın. Mikroişlemcinin pinleri birden fazla modül için ortak kullanılır ve tek bir modülü kullanırken ortak olan diğer modülleri kapatmak kodların düzenli çalışması açısından      önemlidir.
Satır 60′da OpenTimer0() fonksiyonu ile Timer0 birimi kullanıma açılıyor. Bu fonksiyonun    içerisindeki parametreleri inceleyecek olursak;
  • TIMER_INT_ON: Timer biriminin her 255-0 geçişinde kesme oluşturmasına izin verir.
  • TO_SOURCE_INT: Timer birimin dahili kaynak kullanır. (Zamanlayıcı modunda)
  • T0_PS_1_256: Prescaler (bölme oranı) 256′dır. (Bu değer 1,2,4,8,16,32,64,128,256 olmak üzere 9 değer alır)
Bu parametreleri ile birleştirdiğimiz için fonksiyon birden fazla parametreyle tek argümanlı olarak çalışır.
Satır 63′te kesmeyle ilgili işlem yapacağımız için global kesme‘leri, satır 64′te Timer0 birimine ait kesmeyle işlem yapacağımız için TMR0 kesmesini aktif hale getiriyoruz. Böylece artık program kesmelerle çalışmaya başlayabilir.
Programın yapacağı tek iş Timer0 biriminin kesme oluşturmasını beklemek olduğundan satır 66′da while(1)komutuyla programı sonsuz döngüde çalıştırıyoruz.
Timer1 Birimi:
  • 16 bit zamanlayıcı ve sayıcı olarak kullanılabilir
  • Okuma ve yazma yapabilir
  • Dahili veya harici saat(clock) kaynağı seçilebilir
  • Timer1 taşma kesmesi (FFFFh’tan 0000h’a geçilirken)
Timer1 birimi içerisinde iki adet 8 bitlik kaydedici bulunur. (TMR1H, TMR2H) Bunlar sayesinde 0 – 65535 arasında istenilen sayıdan başlayarak sayma yapabilir.  Timer0 gibi bunda da 65535 – 0 geçişinde TMR1 kesmesi oluşur.
Timer1 birimi mantığı Timer0 biriminin mantığıyla aynıdır. Timer0 birimi için yaptığımız işlemi Timer1 birimi için yaparken aşağıdaki kodları kullanırız
#include <p18f4585.h>
#include <stdio.h>
#include <timers.h>
#include <delays.h>
#pragma config OSC = HS, WDT = OFF, LVP = OFF
int i = 0;
void int_handler(void);
#pragma code high_vector=0x08
void high_interrupts (void) {
    _asm GOTO int_handler _endasm
}
#pragma code low_vector=0x18
void low_interrupts (void) {
    _asm GOTO int_handler _endasm
}
#pragma code
#pragma interrupt int_handler
void int_handler(void) {
    if(PIR1bits.TMR1IF == 1) {
        WriteTimer1(65000);
        i++;
        if(i==100)
            PORTBbits.RB5 = 1;
        if(i==200) {
            PORTBbits.RB5 = 0;
            i = 0;
        }

        PIR1bits.TMR1IF = 0;
    }
}
void main(void) {
    TRISB = 0;
    PORTB = 0;
    CloseTimer0();
    CloseTimer2();
    OpenTimer1(TIMER_INT_ON  & T1_SOURCE_INT & T1_PS_1_8);
    WriteTimer1(65000);
    RCONbits.IPEN = 1;
    INTCONbits.GIE = 1;
    PIE1bits.TMR1IE = 1;
    while(1);
}
TMR1 biriminin kontrol biti PIR1 register’ında, enable/disable biti ise PIE1 register’ında tutulur.
Satır 55 ve 56′ da Timer0 ve Timer2 birimleri kullanıma kapatılıyor.  Satır 58′ de  Timer1 birimi kullanıma açılıyor. Satır 59′da Timer1 birimine 65000 değeri yazılıyor. İsterseniz bu değeri değiştirebilirsiniz. Ben Timer1 biriminin çalışmasını gözlemleyebilmek için TMR1′e 65000 değerini yazdım, interrupt kodunu ise 100 kesme oluştuğunda RB5 pinine bağlı ledi yakacak, 200 kesme oluştuğunda ise RB5 pinine bağlı ledi söndürecek şekilde yazdım. Böylece program çalışırken timer1 birimine ait interrupt’ın çalıştığını gözlemlemiş oldum. TMR1′e 65000′den daha büyük değer verseydim ya da OpenTimer1() fonksiyonunda T1_PS_1_8 ile belirlediğim prescaler değerini daha küçük girseydim led daha yavaş yanıp sönecekti (OpenTimer1 fonksiyonu ile 1,2,4,8 olmak üzere 4 adet prescale değeri verilebilir).
Timer2 Birimi:
  • 8 bitlik zamanlayıcı olarak kullanılabilir
  • Fazladan postscaler değerine sahiptir
  • PWM birimi için sinyal üretmede kullanılır
Timer2 birimi diğerlerinden biraz daha farklı kullanılır. Timer0 birimi gibi sayma işlemi yapar  fakat bu sefer girilen değerden itibaren değil,  0′ dan başlayıp girilen değere kadar sayar. Bu sayma işlemi için başlanacak değeri PR2denen bir kaydediciden alır. Biz PR2′ye 0 – 255 arasında hangi değeri girersek Timer2 birimi de 0′dan o değere kadar sayar.
Ayrıca Timer2 birimi bir postscaler değerine sahiptir. Timer2 kesmesinin oluşma süresi bu    postscaler değerine de bağlıdır. Normalde Timer1′de sayma işlemi bir defa yapılır ve kesme oluşturulurdu. Timer2 biriminde kesme şartının oluşması yetmez. Kesme şartı postscaler     değeri kadar oluşmalıdır. Eğer postscale değeri 1 ise, Timer2 birimi 0′dan başlar ve PR2 içerisindeki değere eşit olunca kesme oluşur. Eğer postscaler değeri 5 ise Timer2 birimi PR2′ye 5 defa eşit olunca kesme oluşur (Her PR2 eşitliği oluştuğunda Timer2 içeriği sıfırlanır). Postscale değeri 1 ile 16 arasında olabilir.
Timer2 kesmesi oluşması için geçen süre aşağıdaki formülle hesaplanır;
Tkesme= Tkomut x (Timer2 bölme oranı) x (PR2 değeri + 1) x (Postscaler değeri)
Yazacağımız program tek sayıda Timer2 kesmesi oluştuğunda RB5 pinine bağlı ledi yakacak, çift sayıda Timer2 kesmesi oluştuğunda RB5 pinine bağlı ledi söndürecek. Yani ledin devamlı olarak yanıp sönmesini beklemekteyiz. OpenTimer2() fonksiyonu ile prescaler, postcaler ya da PR2 değerlerini değiştirdiğimizde ledin yanıp sönme frekansı da değişecek.
Program kodları aşağıdaki gibi olacak;
#include <p18f4585.h>
#include <stdio.h>
#include <math.h>
#include <timers.h>
#include <delays.h>
#pragma config OSC = HS, WDT = OFF, LVP = OFF
int i = 0;
void int_handler(void);
#pragma code high_vector=0x08
void high_interrupts (void) {
    _asm GOTO int_handler _endasm
}
#pragma code low_vector=0x18
void low_interrupts (void) {
    _asm GOTO int_handler _endasm
}
#pragma code
#pragma interrupt int_handler
void int_handler(void) {
    if(PIR1bits.TMR2IF == 1) {
        i++;
        if(i%2 == 0)
            PORTBbits.RB5 = 1;
        else
            PORTBbits.RB5 = 0;
        Delay10KTCYx(50);
        PIR1bits.TMR2IF = 0;
    }
}
void main(void) {
    TRISB = 0;
    PORTB = 0;
    CloseTimer0();
    CloseTimer1();
    OpenTimer2(TIMER_INT_ON  & T2_PS_1_16 & T2_POST_1_16);
    PR2 = 250;
    RCONbits.IPEN = 1;
    INTCONbits.GIE = 1;
    PIE1bits.TMR2IE = 1;
    while(1);
}

TMR2 biriminin kontrol biti PIR1 register’ında, enable/disable biti ise PIE1 register’ında tutulur.
Satır 37′de oluşan kesme sayısının çift mi tek mi olduğu kontrol ediliyor. Satır 42′de Delay fonksiyonu kullanılmış(Normalde interrupt kodları içerisinde böyle zaman alıcı delay gibi  fonksiyonlar ya da uzun kodlamalar kullanılmamalı, çünkü interrupt bir an önce bitmesi gereken bir program parçacığı. İşimizi hemen tamamlayıp çıkmamız gerekir. Bu nedenle yazacağımız kodlar kısa ve hızlı çalışacak kodlar olmalıdır). Timer2 birimi çok hızlı çalıştığından dolayı oluşan kesmelerin takibini yapabilmek için kesme kodlarının içerisinde delay fonksiyonu yerleştirdim. Yoksa RB5 pinine bağlı led sürekli yanıyormuş gibi görünüyordu.
Satır 54 ve 55′te Timer0 ve Timer1 modülü kullanıma kapatılıyor.
Satır 57′de Timer2 modülü kullanıma açılıyor. OpenTimer2() fonksiyonunun aldığı parametreleri inceleyecek olursak;
  • TIMER_INT_ON: Timer2 biriminin kesme oluşturmasına izin verir.
  • T2_PS_1_16: Timer2 biriminin prescaler değeri (1,4,16 olmak üzere 3 değer alır).
  • T2_POST_1_16: Timer2 biriminin postscaler değeri (1 -16 arasında değer alır).
Satır 58′de ise Timer2 biriminin 0′dan kaça kadar sayacağını belirtiriz. PR2 kaydedicisine girdiğimiz 0 – 255 arasındaki değere kadar sayma yapılır.
Satır 60′da kesme öncelik biti set ediliyor. Satır 61′de global kesmeler açılıyor. Satır 62′de Timer2 kesmeleri kullanıma açılıyor.
Formüle göre PR2′ye yazdığımız değeri, prescaler değerini ya da postscaler değerini artırırsak RB5 pinine bağlı ledin frekansı azalır. Değerleri değiştirip gözlemleyebilirsiniz


Yorumlar