AVR UART Kullanımı Bölüm 2


Ö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.


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();
}
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)

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