Arduino Timer Kullanımı Kesmeler – Interrupts

Arduino Timer Kullanımı ve Ayarlanması

Merhaba arkadaşlar, bu makalemde arduino ve aynı zamanda avr 8 bit mimarisine sahip işlemcilerde timer kullanımından bahsetmek istiyorum. Arduino adı altında toplanan nerdeyse tüm hazır boardlarda kullanılan işlemciler atmega firmasına ait olan avr-8 bit veya 32 bit işlemcileridir. Bu işlemcilerde timer kullanmaktan bahsetmeden önce bu mimariye sahip tüm işlemciler benzer şekilde ayarlamalar yapabileceğinizi belirtmek isterim. Her işlemcide benim örnek vererek anlatacağım timerlar bulunmayabilir.Bunun için gerekli bilgiyi kullandığınız işlemcinin datasheet dosyasından öğrenebilirsiniz. ( Ornek : Atmega328 )

Timer Nedir ?

Timer belirli zaman aralıklarında yapmak istediğimiz olayların gerçekleşmesinde bize yardımcı olan yapıdır. İşlemciye o zaman dilimi geldiğinde ne yapması gerektiğini hatırlatır. Bu yapılacak işlem bir dizi kendi komutunuz yada timerın desteklediği spesifik bir özellik olabilir. Örneğin klasik bir örnek olan belirli zaman aralıklarında herhangi bir çıkışa bağlı led’i yakmak istediğinizde timer kullanmadığınızı varsayarsak bu işlemi gerçekleştirmek için geçen zamanı sürekli olarak kontrol edebilir yada delay gibi kaçılması gereken komutlarla o an işlemciyi bloke edebilirsiniz. Bunları yapmak yerine işlemcide var olan bu yapılar bize gereken zaman kontrolünü sağlamaktadırlar. Bunu yaparken işlemcide diğer paralel işlemlerin devam etmesi ise bize çoklu işlem yapabilme yeteneği kazandırıyor.

 

Timer Nasıl Çalışır ?

Timerlar sayaç yazmacı olarak bilinen ( counter register ) değişkenini periyodik olarak arttırırlar. Bu yazmaç belirli bir değere kadar ulaşabilir. Bu değeri belirleyen faktör ise kullandığımız timer’ın kaç bit olduğudur. Genellikle 8 bit veya 16 bit olarak karşımıza çıkarlar. Bu yazmaç maksimum değerine ulaştığı zaman (8 bit ise bu değer 255 çünkü 0’dan başlıyor)  sıfıra resetlenir ve taşmanın olduğuna dair bir flag bit’i set edilir. Bu biti bizim takip etmemize gerek kalmadan Interrupt Service Routine (ISR) bu işlemi bizim için gerçekleştirerek bizim kesme olduğundan haberdar olmamızı sağlar. Bu flag bit’i için gereken tüm kontrolleri ISR sağlamaktadır. Peki timer counter değişkenini periyodik olarak artırır dedik. Bu periyotu neye göre referans alıp arttırır ? Timer çalışması için bir clock sinyaline ihtiyaç duyar. Bu sinyal her oluştuğunda bu değişkeni bir arttırır. Bu sinyal harici olabileceği gibi dahili olarakta çoğu işlemcide mevcuttur.

Örneğin siz bir timer’a 16MHz clock sinyali sağlarsanız, bu timer için periyot :

T = 1 / f ( Frekans Hz olarak )

T= 1 / ( 16* 10^6 Hz )

T = 62.5 ns

Eğer 32Mhz olsaydı bu değer 31.25 ns olacaktır. ( 1 sn = 10^9 ns)

 

Yukarıda timerların genel çalışma mantığından bahsettik. 8-bit AVR işlemcilerinde birden çok timer mevcut. Her timer varsayılan olarak arduino için farklı işlemlerde kullanılmak üzere ayarlanmıştır. Timerlar üzerinden bilmeyerek yapabileceğimiz değişiklerle zaman bağımlı fonksiyonlarda beklenmedik sonuçlar ile karşılaşabiliriz. ( Motor, delay, millis vb.)

Varsayılan Timer Ayarları

Timer0 – 8-bit bir zamanlayıcıdır. Counter değişkeni maksimum 255 değerini alır. Arduino için gerekli temel fonksiyonlar( delay() ve millis() gibi ) bu timer’a bağlıdırlar. Bu timer üzerinde ne yaptığınızı bilmediğiniz sürece değişiklik yapmamak en iyisi olacaktır.

Timer1 – 16-bit bir zamanlayıcıdır. Counter değişkeni maksimum 65535 değerini alır. Servo kütüphanesi bu zamanlayıcıyı kullanmaktadır.

Timer2 – 8-bit bir zamanlayıcıdır. Counter değişkeni maksimum 255 değerini alır. tone() fonksiyonu bu zamanlayıcıyı kullanmaktadır.

Kullandığınız işlemcide bunların dışında farklı Timer3, Timer4, Timer5 gibi zamanlayıcılar bulunabilir. Gerekli bilgileri önceden bahsettiğim gibi onların pdfleri üzerinden ulaşabilirsiniz. Genel anlamda yapacağımız ayarlamaların hepsi birbiri için geçerli olacaktır.

Zamanlayıcı Yazmaçlarının Ayarlanması

Timerlar için gerekli ayarların saklanacağı yazmaçlar vardır. Bunlar üzerinde yukarıda bahsettiğimiz ayarlamaların nasıl yapılacağını birazdan örnekler üzerinde daha iyi anlayacaksınız. Bu yazmaçlardan iki tanesini timer ayarlarını set ederken kullanırız. Bunlar TCCRxA ve TCCRxB’dir. Herbir yazmaç 8 bit veri saklar.Burada x yerine ayarlamak istediğiniz timer rakamını yazabilirsiniz. Aşağıda örnek olarak Timer1 için bu yazmaçlar verilmiştir.

Timer interrupt’ın periyotunu set ederken gerekli olan clock sinyalini 0 1 2 numaralı bitlerin kombinasyonlarıyla sağlarız.

Örneğin Timer1’i 2MHz clock hızında çalıştırmak istersek. Yani her count arttırımı için geçen süre 0.5us olacak biçimde ayarlarsak  aşağıdaki gibi bir kod bloğu yazmamız gerekir. Ben ornekleri arduino ide’si üzerinden anlatıyorum.

#define PIN 13 // Board üzerinde var olan led. Arduino Uno R3

void setup()
{
  pinMode(PIN, OUTPUT);
  //  Timer1 Ayarlanması
  cli();         // tüm interruptlar dev dışı
  TCCR1A = 0;    // tüm TCCR1A yazmacını sıfırlar
  TCCR1B = 0;    // tüm TCCR1B yazmacını sıfırlar

  // timer1 için overflow interruptını açar
  TIMSK1 |= (1 << TOIE1); //Kullandığınız işlemcide bu timer yoksa farklı bir timer ilede aynı ayarları set edebilirsiniz

  // CS11 bitini atadık vede timer şuanda clock hızında çalışacaktır.
  TCCR1B |= (1 << CS11); //  böleni 8 olarak ayarladık. Yani 16/8 = 2 MHZ

  sei();//Tüm interruptları çalıştır.

}

void loop()
{

}

Overflow Interrupt

Burada yaptığımız ayarlamada timer’ın ne tür bir kesme kullanacağını belirtmedik. Bunun kontrolünü sağlayan Timer/Counter1 Interrupt Mask Register TIMSK1’dir. Bu yazmaca TOIE1 ataması yaparak timer’in taşma (overflow ) interruptı kullanmasını set ederiz. Biraz daha sonra farklı bir kesme türü olan CTC ( Clear Timer on Compare Match ) kesmesinden de bahsedeceğim. Şimdi kaldığımız yerden devam edecek olursak overflow oluştuğu zaman ISR(TIMER1_OVF_vect) çağrılır.Sonuç olarak :

#define PIN 13 // Board üzerinde var olan led. Arduino Uno R3

void setup()
{
  pinMode(PIN, OUTPUT);
  //  Timer1 Ayarlanması
  cli();         // tüm interruptlar dev dışı
  TCCR1A = 0;    // tüm TCCR1A yazmacını sıfırlar
  TCCR1B = 0;    // tüm TCCR1B yazmacını sıfırlar

  // timer1 için overflow interruptını açar
  TIMSK1 |= (1 << TOIE1); //Kullandığınız işlemcide bu timer yoksa farklı bir timer ilede aynı ayarları set edebilirsiniz

  // CS11 bitini atadık vede timer şuanda clock hızında çalışacaktır.
  TCCR1B |= (1 << CS11); //  böleni 8 olarak ayarladık. Yani 16/8 = 2 MHZ
  TIMSK1 | = (1 << TOIE1); // Overflow kesmesini açarız
  sei();//Tüm interruptları çalıştır.

}

void loop()
{

}
ISR(TIMER1_OVF_vect)
{

  digitalWrite(PIN, !digitalRead(PIN));

}

Buraya kadar olan bölümde kesme tanımlamasını oluşturduk. Kesme frekansını hepsaplayacak olursak:

  • Clock sinyalimizi 8 böleni kullanarak ( CS11 ) set ettik yani 2MHz clock sinyali elde ettik
  • Bir count değeri arttırmak için geçen süre 0.5us.
  • Counter değeri 0 – 65535’e çıkarken 65536 değer arttırmış olur.
  • T = 65536*0.5 =32.77ms olur
  • f=1/T = 30.51 Hz

Yani bizim ISR fonksiyonumuz saniyede yaklaşık olarak 30.51 kere çalıştırılacak.

 

Overflow kesmesinin bazı dezavantajlarından birtanesi istediğimiz frekans değerlerinin tam olarak ayarlanabilmesinin zor olması. Çünkü her seferinde counter’ın sona kadar arttırılıp kesmenin oluşması beklenmekte. Bunun yerine daha hassas frekans oynamaları yapabilmek için timer’in bizim belirlediğimiz bir değere ulaşmasından sonra kesme oluşmasını sağlayabileceğimiz mod olan CTC modundan bahsetmek istiyorum.

 

Clear Timer on Compare Match Kesmesi ( CTC )

Timer’ın counter değerinin overflow yapmasını beklemek yerine, bizim daha önceden set ettiğimiz bir count değerine eşit olunca oluşan bir kesmedir.

Burada bu değeri atayabileceğimiz register ACRxA’dır. Burada x seçeceğimiz timer’a göre farklılık göstermektedir. Aynı zamanda atayacağımız maksimum değer bu seçtiğimiz timer’ın kaç bit olduğuna göre değişir. Yine örnek olarak timer1 kullancak olursak atayacağımız değerler işaretsiz 65536’dan küçük değerler olmalıdır. Timer0 kullancak olursak bu değer 256’dan küçüktür. Bu modu kullanmanın diğer bir avantajı timer0 kullanırken default prescale (64 böleni) kullanırsak overflow kesmesinde herhangi bir değişiklik olmadığı için diğer temel fonksiyolar doğru çalışmaya devam edecektir. Yani timer0’ıda bu mod ile kesmelerde kullanabiliriz. Fakat 8 bit olmasından dolayı uyarlayabileceğimiz frekans aralıkları sınırlı olacaktır. Burada size istediğiniz frekansı elde edebileciğiniz bir formul vermek istiyorum. Bu formul ile istediğiniz frekansa ulaşmak için gereken adım sayısını kolayca hesaplayabilirsiniz.

Interrupts

Formulde yer alan frekans değeri elde etmek istediğimiz frekanstır. Bölen daha önce bahsettiğimiz işlecinin clock hızını ayarladığımız değerdir. 1 çıkarmamızın nedeni 0’dan başlıyor olmasıdır. Şimdi bir örnek ile 16KHz kesme frekansı oluşturalım.

#define PIN 13 // Board üzerinde var olan led. Arduino Uno R3

void setup()
{
  pinMode(PIN, OUTPUT);
  //  Timer1 Ayarlanması
  cli();         // tüm interruptlar dev dışı
  TCCR1A = 0;    // tüm TCCR1A yazmacını sıfırlar
  TCCR1B = 0;    // tüm TCCR1B yazmacını sıfırlar

  TCCR1A |= (1 << WGM21); // CTC modunu aktif hale getirir
  // CS11 bitini atadık vede timer şuanda clock hızında çalışacaktır.
  OCR2A = 124;// ((16*10^6)/(16000*8))-1 Bize 16KHz kesme frekansı sağlar
  TCCR1B |= (1 << CS11); //  böleni 8 olarak ayarladık. Yani 16/8 = 2 MHZ clock sinyali
  TIMSK1 |= ( 1 << OCIE1A); // Timer Compare kesmesini aktif hale getir.
  sei();//Tüm interruptları çalıştır.

}

void loop()
{

}
ISR(TIMER1_COMPA_vect) // 16 KHz frekansa sahip.
{

  digitalWrite(PIN, !digitalRead(PIN)); // Aç kapa 2 kesme tuttuğu için çıkışta ölçeceğiniz değer 8KHz olacaktır.

}

 

Kesmeler ile değişkenleri kullanırken dikkat edilmesi gereken önemli bir nokta ise volatile değimidir. Bu tanımlama ile değişkenlerde farklı kesmelerinin değişken üzerinde sıralı işlem yapmasını sağlayıp daha stabil güvenilir bir saklama yapmış oluruz. Çok yüksek frekans değerlerinde bu deyim yeterli olmayıp kesmeyi devre dışı bırakıp değişkeni okuyup tekrar açma işlemide uygulanabilir. Başka bir makalede görüşmek üzere hoşcakalın.

 

 

 

3 Comments

  1. çok yararlı bir anlatım olmuş teşekkürler.

  2. anlatımınız için teşekkürler, belki bilgi eksikliğimden kaynaklanıyor olabilir ancak son kod örneğinde sanırım OCR1A yerine OCR2A yazılmış. Arduino Uno’da OCR1A olarak değiştirdiğimde çalıştı.

Leave a Reply

Your email address will not be published. Required fields are marked *