Arduino ve Timer&Interrupts Kullanımı ve Timer Seçmek - devreyakan
Arduino ve Timer&Interrupts
Haziran 22, 2019 Ağustos 26, 2019 tarihinde gönderilmiş devreyakan tarafından
Timer interrupts mantığına girmeden önce;
1. Timer Nedir ?
2. Timer Nasıl Çalışır ?
3. Varsayılan Timer Ayarları
3.1. 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.
3.2. 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.
3.3. 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.
4. Overflow Interrupt
5. Clear Timer on Compare Match Kesmesi ( CTC )
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.
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.
Normal şartlar altında 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 veya çakışan kütüphanelerle kodlarımız tam anlamıyla çalışmayabilir. ( 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ı Timern, Timern+1, Timern+2 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.
//  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.
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;
TIMSK1 | = (1 << TOIE1); // Overflow kesmesini açarız
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.
Formülde yer alan frekans değeri elde etmek istediğimiz frekanstır. Bölen daha önce bahsettiğimiz işlemcinin saathı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.
TCCR1A |= (1 << WGM21); // CTC modunu aktif hale getirir
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.
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.
Arduino, clock, frekans, frequency, pwm, timer, zamanlayıcı