Arduino ve Timer Interrupts
Arduino timer interrupts mantığına girmeden önce;
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 interrrupts kullanmaktan bahsetmeden önce bu mimariye sahip tüm işlemciler benzer şekilde ayarlamalar yapabileceğinizi belirtmek isteriz.
Timer Interrupt 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 interrrupts 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 İnterrrupts 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.
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 Tımer 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;
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.
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.
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şlemi de uygulanabilir.
Timer Interrupt ile Uygulama Örnekleri
- LED Blink: Timer interrupt kullanarak LED’in belirli bir zaman diliminde yanıp sönmesini sağlayabilirsiniz. Bu sayede, ana döngü (loop) içinde sürekli olarak LED’in durumunu kontrol etmek yerine, zamanlayıcı kesmesi ile LED’i kontrol edebilirsiniz.
- Sensor Okuma: Belirli bir aralıkla sensör okuması yapmak için timer interrupt kullanılabilir. Bu sayede, sensör verilerini düzenli aralıklarla toplayabilir ve diğer işlemleri ana döngüde aksatmadan gerçekleştirebilirsiniz.
- Motor Kontrolü: Zamanlı motor kontrolleri için timer interrupt kullanımı, motorun hızını ve çalışma zamanını hassas bir şekilde ayarlamanıza olanak tanır.
Timer Interrupt’ın Avantajları
- Kesintisiz Çalışma: Timer interrupt kullanarak, programınızın çalışmasını durdurmadan zamanlı işlemler gerçekleştirebilirsiniz.
- Düşük Enerji Tüketimi: Mikrodenetleyicinin, timer interrupt sayesinde her zaman aktif olmasına gerek kalmadan, belirli aralıklarla görevleri yerine getirmesini sağlayarak enerji verimliliği elde edebilirsiniz.
- İleri Seviye Zamanlama: Timer interrupt ile mikrodenetleyiciye çok daha hassas zamanlama görevleri verebilirsiniz. Bu özellik, genellikle zamanlama doğruluğunun önemli olduğu projelerde kullanılır.
Timer Interrupt Kullanımında Dikkat Edilmesi Gerekenler
Arduino timer interrupt kullanırken dikkat edilmesi gereken birkaç önemli nokta vardır:
- Zamanlayıcılar Arası Çakışmalar: Farklı zamanlayıcıları aynı anda kullanırken, her zamanlayıcının kesme işlevinin birbirini etkilemediğinden emin olmalısınız. Eğer iki farklı timer interrupt birbirini engellerse, istenmeyen sonuçlar doğurabilir.
- ISR Kodunun Süresi: ISR fonksiyonlarının süresi çok kısa olmalıdır. Eğer ISR çok uzun sürerse, mikrodenetleyici diğer interrupt’ları kaybedebilir.
- Donanım ve Yazılım Uyumu: Timer interrupt ile donanımınızın uyumlu olması gerekmektedir. Bazı donanım özellikleri, belirli zamanlayıcıların kullanılmasını zorlaştırabilir.
Sonuç
Arduino timer interrupt, zamanlama işlemlerini etkin bir şekilde yönetmek ve projelerinizi daha verimli hale getirmek için harika bir yöntemdir. Timer interrupt ile donanımınıza hassas zamanlama işlemleri ekleyebilir, mikrodenetleyicinin çalışma verimliliğini artırabilirsiniz. Timer interrupt kullanırken doğru ayarları yapmak, yazılım ve donanım uyumunu göz önünde bulundurmak büyük önem taşır. Timer interrupt ile gerçekleştirebileceğiniz birçok yaratıcı ve pratik proje vardır.
Yorum yapma özelliği, forum tarafından gelen istek sebebiyle kapatılmıştır. Lütfen tartışmalar ve sorularınız için topluluk forumumuza katılın.