Önceki bölümde UART kullanımını anlatmaya çalıştım. Bölümün
sonunda paylaştığım kodun karmaşık bir program içinde işe yaramayacağını
belirttim. Bunun nedenini aşağıdaki kodlarda görebilirsiniz.
while (1) {
PORTB|=(1<<PORTB5);
_delay_ms(1000);
PORTB&=~(1<<PORTB5);
_delay_ms(1000);
uart_gonder(uart_oku());
}
|
While (loop) içine 1 saniye aralıklarla ledin yanıp sönmesini
sağlayan bir ilave yaptım. “delay” kullanıldığında mikro denetleyici hiçbir
işlem yapmıyor. Mecbur kalmadıkça benzer işlemleri zamanlayıcı ile yapmak doğru
olacaktır. Bu notu da aktarmış olayım. Bu basit ilave ve 2 saniyelik bekleme, gelen
verinin tam olarak alınamamasına neden olacaktır. Delay içinde ya da başka bir
fonksiyon içinde komutlar çalışırken gelen verinin alınamaması normaldir. Bu
durumda yapılması gereken kesmeleri kullanmaktır. Bazen bir şeye daldığınızda
size seslenen insanları duymazsınız ya da söylenenleri anlamazsınız. Kişinin
gelip dokunmasıyla ancak daldığınız konudan sıyrılıp cevap verebilirsiniz.
Mikro denetleyici de aynı şekilde ana programı döngü halinde satır satır
işlerken ve hatta bu satırlardan bazıları durmasını söylerken dışarıdan gelen
çağrıları algılamayacaktır. Bunun yapmak için parmak ucuyla dürtmek
(kesme/interrupt) gerekir.
Kesme (interrupt) konusunu araştırdığınızda verilen
örnekler de bu şekildedir. Komutlar işlenmeye devam ederken
ayarlanmış/tanımlanmış kesme gerçekleştiğinde denetleyici kesme rutini içindeki
satırları işler, sonrasında ana programda kaldığı satıra geri döner. Kesmeler
kitap okurken zilin çalması kapıyı açmamız ve tekrar kitap okumaya kaldığımız
yerden devam etmemiz gibi çalışır. Uart biriminde ayarlayıp kullanabileceğimiz
3 adet kesme bulunuyor.
USART_RX kesmesi veri alımı tamamlandığında, USART_TX veri
gönderimi tamamlandığında ve USART_UDRE kesmesi veri kaydedici register UDR0
boş ise devreye girer. Bu kesmelerin nasıl ayarlanacağı konusunu açıklayayım.
AVR de kesme ayarları ilgili register bitleri “1” yapılarak ayarlanır. Uart ya
da başka bir konuda datasheet içinde hangi kesmenin hangi register ile
yapılacağı belirtilmiştir. Önceki bölümde Uart kontrol ve durum registerlerini
anlatırken UCSR0B registerini de tanımladım. Bu registerin 7,6 ve 5. Bitleri
yukarıda saydığım kesmeleri aktif eder. USART_RX için RXCIE0, USART_TX için
TXCIE0 ve son olarak USART_UDRE kesmesi için UDRIE0 bitlerini “1” yapmak
gerekir. Örnek olarak “RX” kesmesi için “UCSR0B|=(1<<RXCIE0);”yazmamız gerekir. RX kesmesinin eklenmiş haliyle “uart_basla” fonksiyonu aşağıdaki gibi olacaktır.
Kesme ayarlaması yapıldı ama kesmelerin açılması gerekiyor. Bunun
için eklememiz gereken “#include <avr/interrupt.h>” isimli bir kütüphane var. SREG isimli durum registerinin 7. Biti “1” olursa kesmeler çalışır. Bunu elle
ya da “sei();” (tüm kesmeleri açar) yazarak yapabiliriz. (“cli();” tüm kesmeleri kapatır)
void uart_basla(uint32_t baud){
uint16_t baudRate=0;
baudRate=(F_CPU/baud/16)-1;
if (baud>=112500){
baudRate=(F_CPU/baud/8)-1;
UCSR0A|=(1<<U2X0);
}
UBRR0H=(baudRate>>8);
UBRR0L=baudRate;
UCSR0B|=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);
sei();
}
|
Atık kesmeler ayarlandı ve açıldı. Şimdi kesme oluştuğunda
denetleyicinin ne yapacağını bildirmemiz gerekiyor. Kesme rutini AVR de aşağıdaki
şekilde yapılıyor.
ISR (Kesme_ismi_vect){
Yapılacak işler;
}
|
Kesmelerle çalışırken dikkat edilmesi gereken bazı konular var.
Kesme içinde başka bir fonksiyonun çağrılması doğru değil. Mümkün olduğunca az
işlem yapılmalı olabildiğince hızlı ana programa dönülmeli. Kesme içinde tüm
kesmeler kapatılır bu nedenle kesme gerektiren komutlar çalışmaz. Örnek olarak
zamanlama kesmesi kullanan bir “delay” komutu kullanırsanız amacınıza
ulaşamazsınız. Kesme içinde kesme ayarlanabilir. Kesme içinde kullanılacak olan
global değişkenlerin “volatile” olarak tanımlanması gerekir. Kesme içinde
tekrar yazılan değişkeni ana programda okumak istersek kesmeleri kapatmamız
gerekir aksi halde daha biz okuyamadan kesme devreye girerek okumaya
çalıştığımız değeri değiştirebilir.
Tüm bu işlemler sonunda kesme açıkken ana program ne olursa olsun
RX bacağına veri geldiği anda tüm işlemleri bırakır gelen veriyi alır. USART_RX
kesme rutini içine yazacağımız komutlar “uart_oku()” fonksiyonu gibi UDR0 registerindeki veriyi okuyacaktır. Aşağıda
kesme içinde kullanılan değişken “volatile” olarak tanımlanmalıdır.
ISR (USART_RX_vect){
gelen=UDR0;
}
|
Kesme kullanımı ile veri alımı konusunda bir kaybımız olmaz. Gelen
veriyi bir değişken içine yazdık. Ama bu yazdığımız komutlar tam olarak yeterli
olmayacaktır. Veri biz okudukça geliyorsa bir sorun olmaz ama sürekli geliyor
ve ana programda biz bunu aynı hızda okuyamıyorsak yeni veriler öncekilerin
üstüne yazılır ve biz doğru okuma yapamayız. Gelen veri sürekli kaydırılarak
yazılmalı, bu sayede biz okuyana kadar veri doğru şekilde saklanmış olur. Bu
kaydırma işlemi adresi belli olarak yapılmalı ki yazılan yeri bilelim ve
okuyabilelim. Bunu dizi tanımlayarak ve gelen her veriyi dizinin bir elemanına kaydederek
yapabiliriz. Aşağıda bir örneğini paylaşıyorum. uart_gelen isimli
bir dizi ve gelen verinin sırasını gösteren uart_sira değişkeni
tanımlandı. Her veri gelişinde kesme devreye girecek sıra numarası bir artacak
ve diziye eklenecek. Veriyi okurken dizinin elemanları “uart_gelen[uart_sira]”
şeklinde okuyacağız.
volatile uint8_t uart_sira=0;
volatile uint8_t uart_gelen[64];
ISR (USART_RX_vect){
uart_sira++;
uart_gelen[uart_sira]=UDR0;
if (uart_sira>=64){
uart_sira=0;
}
}
|
“İf
“ şartı ile sıra sınırlandırıldı böyle yapmamış olsaydık dizinin eleman
sınırları dışına taşacak ve gelen veriyi okuyamayacaktık. Bu sınırlama ile
sürekli başa dönmüş olduk. Veri yazarken bu sorun olmayacaktır. Belirlediğimiz limite
gelince başa alacak fakat okuma yaparken uart_sira artacak
ve biz en son yazılan verinin sırasını biliyor olacağız. Örnek olarak “a”,”b”
ve “c” karakterleri uart ile alındığında uart_gelen []={“a”,”b”,”c”,\0}
şeklinde diziye eklenecektir. Gelen bu veri aralıksız olarak gelecektir. Bizim ana
programımızda mevcut olan beklemeler nedeniyle gecikmeli olarak okuma yapmamız
sonucu uart_sira ilerleyecek ve “uart_gelen[0]”
ve “uart_gelen[2]”
okuyabileceğiz. “uart_gelen[1]”
yani “b” karakterini kaçıracağız. Daha büyük
bir veri paketinde daha büyük kayıplar yaşanır. Gördüğünüz gibi verilerde yazma
sırası “uart_sira” ile okuma yapamayız. Bunun çözümü
okumak için de bir sıra numarası belirlemektir. Böylece ilk kayıttan itibaren
hiç atlama yapmadan alınan veriyi okuyabiliriz. Buna ilk giren ilk çıkar “first
in first out” “FIFO” deniyor. Birçok farklı disiplin bu sistemi kullanıyor. Aşağıdaki
tabloda 3 adet veri kaydı yapılmış yazma sırası “3” olmuş henüz okuma
yapılmamış okuma sırası “1” veri gelmeye devam ederse yazma sırası artacaktır. Okuma
yaptığımız zaman kaldığımız yerden devam edeceğimiz için atlama yapmadan tüm
veriyi okuyabiliriz.
yazma
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
veri
|
a
|
b
|
c
|
||||||
okuma
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Bir
özet geçersek veriyi bir belleğe yazıyoruz, bu belleğin sınırlarını tayin
ediyor ve yazdığımız her veriyi bir sırayla ilerletiyoruz. Sona gelince tekrar
başa alıyoruz aslında bir sonu olmayan döngü oluşturuyoruz. Yazılan veriyi
okumak içinde bir sıra belirliyoruz aynı şekilde okumaya devam ettikçe sırayı
ilerletiyoruz. Bu yaptığımız döngüye “ring buffer, circular buffer” deniyor. Türkçe
olarak dairesel ya da halka bellek diyebiliriz. Dairesel bellek yapısı oldukça
kullanışlı ve kolay bir yöntemdir.
Çok
uzun yazarak devam etmek istemiyorum o yüzden burada kesiyorum sonraki bölümde
dairesel belleğin programa nasıl ekleneceğini açıklayarak devam edeceğim.
Hiç yorum yok:
Yorum Gönder