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