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



Bu bölümde AVR de UART (Evrensel asenkron alıcı verici) birimini anlatmaya çalışacağım. Öğrendiğim ve kullandığım kısımlarını anlatacağım. Bir amatör olarak tamamını biliyorum diyemem. Şimdilik ihtiyaç duyduğum kadar kısmını öğrenebildim. Çok fazla kaynak bulunan protokolün detayına girmeden kullanımına yönelik uygulamalar paylaşacağım. Öğrenilmesi gereken registerleri açıklamaya çalışacağım. Verilerin dizi olarak alınması ve gönderilmesi, kesmelerin kullanımı ve RS485 protokolüne değineceğim. Her zaman olduğu gibi kullandığım mikro denetleyicinin datasheeti en önemli rehberimiz. Datasheette ki sırayla alıntılar yaparak başlayalım.



USART blok şemasında birimin registerleri ve ilişkileri görülmektedir. Zamanlama hesabının yapıldığı UBRR0H ve UBRR0L registerleri, verici-alıcı bölümünde verinin tutulduğu UDR0 registeri ve UCSR0A,UCSR0B ve UCSR0C isminde 3 adet kontrol ve durum registeri bulunmaktadır. Atmega328p de tek USART bulunmakta, farklı denetleyicilerde UCSR1A, UCSR2A şeklinde olabilir. Bu yüzden UCSRnA şeklinde şemaya yazılmış. Ben anlatımımı Atmega328p özelinde yapacağım bu yüzden kullanacağınız denetleyicinin datasheet kontrolünü mutlaka yapmalısınız. Şemada pin kontrolleri ve birimlere bağlı pinler de görülmektedir.




Yukarıda gördüğünüz tablo “Baud rate” hesaplama formülünü göstermektedir. Asenkron bir iletişim söz konusu olduğu için alıcı-verici arası veri alış-verişini kontrol eden saat sinyali söz konusu değildir. Bir bit verinin ne kadar sürede gönderildiği bilinirse alıcı gelen veriyi analiz eder ve doğru olarak alabilir. Bu gönderim süresini belirleyen "bir saniyede gönderilen bit sayısına" baud rate deniyor. 16mhz osilatör takılı olan bir denetleyiciyi 9600 baud rate ayarlamak istersek, formüldeki değerleri yerine koyalım;

UBBR0(H:L)=(16000000/16*9600)-1

UBBR0(H:L)=(16000000/153600)-1

UBBR0(H:L)=(104)-1 ve 
UBBR0(H:L)=103 ya da UBBR0(H:L)=0x67 buluruz. Bulunan değeri UBBR0(H:L) registerine yazmamız gerekir. Daha düşük bir baud rate seçmiş olsaydık daha büyük bir değer bulacaktık. Bulunan bu rakam 8 bitin üstünde olacaktır. Bu nedenle UBRR0;UBRR0H ve UBRR0L isminde iki adet registerden oluşan 12 bitlik bir registerdir.



Alıcı-verici arası doğru bilgi alış-verişi için önemli olan bir diğer konuda veri paketleri. UART protokolünde veri paketi aşağıdaki gibidir.



Alıcı ya da verici pinleri normalde “1” “HIGH” seviyededir. Veri gönderimi başlamadan önce “0” yapılır “Start biti”. Sonrasında kaç bit veri gönderilecek, eşlik biti var mı? Kaç stop biti olacak bilgisi iki taraf içinde aynı olmalıdır. Kontrol ve durum registerleri bu ayarları yapmamıza olanak vermektedir. 



Bu registerlerden ilki UCSR0A: 

RXC0: Veri alımı tamamlandı bayrak biti. Veri alımı tamamlandığında “1” olur. UDR0’dan veri okunuğunda “0” olur.

TXC0:  Veri gönderimi tamamlandı bayrak biti. UDR0 ‘a veri yazılan veri gönderildiğinde “1” olur.

UDRE0: Veri gönderme-alma belleği hazır-boş bayrak biti. UDR0 boş olduğunda “1” olur.

FE0: Çerçeve hatası ayarlı olandan farklı sırayla veri gelirse “1” olur.

DOR0: Veri üstüne yazma ya da taşma hatası. Bellek yeterli gelmediğinde “1” olur.

UPE0: Eşlik biti hatası olursa “1” olur.

U2XO: 2 kat hızlı iletişim ayarı için “1” yapılır. Kullanmadığım bir mod konu hakkında bilgim yok.

MPCM0: Çok işlemcili iletişim modu kullanmadığım bir mod konu hakkında bilgim yok.  


UCSR0B:

RXCIE0: Veri alımı tamamlandı kesmesini aktif etmek için “1” yapılır.

TXCIE0: Veri gönderimi tamamlandı kesmesini aktif etmek için “1” yapılır.

UDRIE0: Veri gönderme-alma belleği hazır-boş kesmesini aktif etmek için “1” yapılır.

REXEN0: Rx yani alıcı etkinleştirmek için “1” yapılır.

TXEN0: Tx yani verici etkinleştirmek için “1” yapılır.

UCSZ02: Veri paketi karakter boyutunu belirlemek için kullanılır. UCSR0C ile açıklayacağım.

RXB80: 9 bit veri alındığında 9. Bit buraya yazılır.

TXB80: 9 bit veri gönderilirken 9. Bit buraya yazılır.



UCSR0C:

UMSEL00 ve UMSEL01: USART mod seçimini belirleyen bitler. Asenkron için iki bit “0” olmalıdır.  


UPM00 ev UPM01: Eşlik biti seçimini belirleyen bitler. Tablodan istenen duruma göre seçim yapılır. Eşlik biti istenmiyorsa iki bit “0” yapılır.



USBS0: Stop bit sayısını belirler. “0” olursa bir, “1” olursa iki stop bit gönderilir.

UCSZ00 ve UCSZ01: UCSR0B de bulunan UCSZ02 ile beraber bu 3 bit alınan ya da gönderilen verinin boyutunu belirler. Tablodan seçimi yapabilirsiniz. 8 bit için UCZS00 ve UCSZ01 “1” yapılır, UCZS02 “0” yapılır.



UCPOLO: Senkron mod düşen ya da yükselen kenara göre saat düzenlemesi yapılmasını sağlayan bit. Konu hakkında bilgim yok.

Master SPI mod ile ilgili bitlere (UDORD0, UCPHA0) değinmeden geçeceğim.

Bu registerler dışında gelen ve gönderilen verinin yazıldığı UDR0 registeri bulunmaktadır.8 bite kadar olan paketlerde bu register kullanılır. 9 bit ayarlandığında daha önce değindiğim UCSR0B de bulunan RXB80 veTXB80 bitlerine veri yazılır.



Yüzeysel olarak değindiğimiz bu registerleri kullanarak UART iletişimi için gerekli ayarlamaları yapıyor ve iletişimi başlatıyoruz. Bunun için datasheette ya da internette kalıplaşmış birçok fonksiyon bulabilirsiniz. Benim kullandığım fonksiyonları paylaşacağım. Öncelikle UART iletişiminin iki taraf içinde olmazsa olmazı baud rate ve paket bilgisidir. Bir taraf 9600 ayarlıyken diğer taraf 4800 ayarlıysa gönderen taraf okuyanın bir bit beklediği sürede iki bit gönderecektir. Sonuçta iletişim gerçekleşmeyecektir. Aynı şekilde 8bit bir stop bekleyen alıcıya 7 bit, bir eşlik ve bir stop biti gönderilirse yine hatalı veri alınmış olacaktır. Bunun için bir başlatma fonksiyonu yazmamız gerekir ve burada kullanılan ayarlar iki tarafta da aynı olmalıdır.

Programın taşınabilir olması ve ayarların kolay değiştirilebilmesi için bazı değerleri ön tanımlı olarak yazabilirsiniz. Ben bunun yerine başlatma fonksiyonuna parametre olarak yazmayı tercih ediyorum.

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

void uart_basla(uint32_t baud){
  uint16_t baudRate=F_CPU/baud/16-1;
  UBRR0H=(baudRate>>8);
  UBRR0L=baudRate;
  UCSR0B|=(1<<RXEN0)|(1<<TXEN0);
  UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);
}

 “uart_basla” fonksiyonunda işaretsiz 32bit parametre bulunmakta bunun nedeni 16 bit ile 65535 sayısının üstüne çıkamıyor olmamız. Baud rate bu durumda en fazla 57600 olabilirdi.  32 bit kullanarak daha yüksek hızları parametre olarak girebildim. “#define F_CPU 16000000UL” denetleyicinin frekansını ön tanımladıktan sonra formülde parametre olarak gelen “baud” değeri ile hesaplama sonrası “baudRate” değerini buluyoruz. Bu değeri UBRR0(H:L) registerine yazmamız gerekiyor. 12 bit olan bu registere veriyi "H" ve "L" olarak iki bölümde yazıyoruz. Böylece hızımızı belirlemiş olduk. Veri paket boyutu, eşlik biti ve stop biti “UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);”gibi ayarlamaları yaparak ve alıcı-verici aktif ederek “UCSR0B|=(1<<RXEN0)|(1<<TXEN0);” fonksiyonu tamamlıyoruz. (eşlik biti yok, 8 bit ve 1 stop biti ayarlı).

UART birimi ayarlandı ve çalıştırıldı. Artık gelen veriyi okuyan fonksiyona sıra geldi.

uint8_t uart_oku(){
  while(!(UCSR0A & (1<<RXC0)));
  return UDR0;
}

Okuduğumuz veriyi döndüren bir fonksiyon yazıyoruz. Fonksiyonu çağırdığımızda UCSR0A kontrol registerinin RXC0 (7. Biti) kontrol ediliyor. Yukarıdaki tanımdan hatırlayacağınız gibi veri alındığında “1” olan bu biti while döngüsünde kontrol ediyoruz. Kodu biraz açalım, UCSR0A da bulunan tüm bitleri “0” olarak kabul edelim yani UCSR0A=0b 0000 0000 olsun. Veri gelmemişse 7. Bit de “0” olacaktır. Bu durumda “UCSR0A & (1<<RXC0)” işlemi sonucu (0b 0000 0000 &(0b 1000 0000)) =0 olacaktır. While şartında “!” değil operatörü bulunmakta dolayısıyla while şartı “1” olmakta ve döngü içinde kalmaktadır. Bunun anlamı veri gelene kadar RXC0 “1” olana kadar bekle demektir. RXC0 “1” olduğunda (0b 1000 0000 &(0b 1000 0000))=1 ve while şartı “0” olacak döngüden çıkılacaktır. Veri geldiğinde UDR0 veri registerine yazılacaktır. Fonksiyon sonunda “return UDR0;” ifadesiyle bu veriyi fonksiyon değeri olarak döndürerek okuma işlemini tamamlamış oluyoruz.

void uart_gonder(uint8_t uData){
  while(!(UCSR0A & (1<<UDRE0)));
  UDR0=uData;
}

Veri gönderim fonksiyonu da benzer şekilde çalışmaktadır. Fonksiyon çağrıldığında UCSR0A kontrol registerinin UDRE0 (5. Biti) kontrol ediliyor. Yukarıdaki benzer işlemler yapılarak UDRE0 biti “1” olana kadar bekleniyor. Hatırlayacağınız gibi bu bitin “1” olması durumu UDR0 registeri boşaldığında gerçekleşmektedir. Bu döngü ile yapılan da budur. UDR0 boş-hazır olunca UDR0 registerine parametre olarak gönderdiğimiz veriyi yazıyoruz. Ve UART birimi veriyi gönderiyor. Buraya kadar basit bir şekilde iletişim kurabiliriz. Aşağıda paylaşacağım kodu Arduino idesiyle Arduino Uno’ya yüklerseniz Serial.write(Serial.read()); koduyla aynı sonucu alırsınız  ama hem daha hızlı hem daha az yer kaplayacaktır.

Buraya kadar olan anlatım aslında datasheette mevcut olan bilgilerdir. Daha karmaşık bir program içinde bu şekilde kullanmanız doğru olmaz. Sonraki bölümde bu konuya açıklık getireceğim. 

/*
 * uart.c
 *
 * Created: 12.02.2019 22:05:41
 * Author : haluk
 */
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

void uart_basla(uint32_t baud){
  uint16_t baudRate=F_CPU/baud/16-1;
  UBRR0H=(baudRate>>8);
  UBRR0L=baudRate;
  UCSR0B|=(1<<RXEN0)|(1<<TXEN0);
  UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);
}
uint8_t uart_oku(void){
  while(!(UCSR0A & (1<<RXC0)));
  return UDR0;
}
void uart_gonder(uint8_t uData){
  while(!(UCSR0A & (1<<UDRE0)));
  UDR0=uData;
}
int main(void){
    uart_basla(19200); 
    while (1) {     
     uart_gonder(uart_oku());
   }
}


Hiç yorum yok:

Yorum Gönder