Dairesel bellek (ring buffer) konusuna giriş yaptığımız bir önceki
bölümde paylaştığım tabloyu tekrar paylaşıyorum. Bu tablo üstünde anlatmaya devam
edeceğim.
Yazma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Veri
|
a
|
b
|
c
|
||||||
Okuma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Daha önce sıra olarak belirttiğim bölümleri adres olarak
adlandıracağım. Yeni bir veri geldiğinde yazma adresi bir artarak sonraki
adrese kayıt yapar. Veri gelmeye devam ettikçe adres artarak devam
eder. Son olarak “i” karakteri aldık ve ayırdığımız belleğin sınırına geldik bu
durumda adres başa alınacak ve yazmaya devam edilecektir. Ana program içinde
okuma yapıldıkça okuma sırasını belirleyen adres de artacaktır.
Yazma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Veri
|
a
|
b
|
c
|
d
|
e
|
f
|
g
|
h
|
i
|
Okuma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Veri gelmeye devam etti, adresi başa aldık yazmaya ve okumaya devam
ettik. Birçok programda bu adres verilerinden yazma önde devam ettiği için “head”
“baş” okuma arkadan geldiği için “tail” “kuyruk” olarak adlandırılıyor.
Yazma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Veri
|
j
|
k
|
c
|
d
|
e
|
f
|
g
|
h
|
i
|
Okuma adresi
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
Bu döngü halinde devam edecektir. C diliyle bunu programa nasıl aktaracağız
konusunu açıklayayım. Önceki bölümden hatırlayacağınız kesme rutini komutları
aşağıdaki gibidir.
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;
}
}
|
Bu komutları kısaca tekrar edeceğim. Bir bellek ve bu belleğin
elemanlarını yazıp okuyabilmek için sıra/adres verisini tutan bir değişken
tanımladık. Veri geldiğinde adresi artırdık ve belleğe yazdık. Bellek sınırına
ulaşınca da sıfırlayarak başa döndük. Bu başa dönme sıfırlama işlemini farklı
şekilde yapacağım. Daha öncede bahsetmiştim onluk sistemde “10”katları şeklinde
devam ettikçe sona “0” ekliyorduk. 10, 100, 1000… şeklinde ikilik sistemde de “2”
nin katlarına “0” ekliyoruz.
2=10 (eşitliğin sağı ikilik)
4=100
8=1000
16=10000
Şeklinde devam ediyor ve nasıl onluk sistemde “10” katlarının “1”
eksiği 9, 99, 999, 9999… şeklinde devam ediyorsa ikilik sistemde de;
2-1=1
4-1=11
8-1=111
16-1=1111
Olarak devam eder. Bitsel “ve” “&” işleminde operatörün iki
tarafında da “1” ise sonuç “1” olur. “1011&0111” işleminin sonucu “0011”
dir. Bellek tanımlarken “2” nin katları şeklinde tanımlarsak. “#define UART_Rx_Boyut 16”
bellek adres sınır “16” olacaktır ve bu sınıra gelince sıfırlamak gerekir. Bunun
için “&” operatörünü kullanıyoruz. Bunu yapmak için bellek sınırının bir
eksiğini bit mask olarak kullanıyoruz. Bunun tanımlaması “#define UART_Rx_Mask (UART_Rx_Boyut-1)” şeklinde olacaktır. Bu
tanımlara göre;
UART_Rx_Boyut=00010000 //16
UART_Rx_Mask =00001111 // 15 olacaktır.
Adres 12=00001100
olsa 00001100&00001111=00001100 yine “12” olacaktır. Adres 16=00010000 olsa
00010000&00001111=0 olacaktır gördüğünüz gibi maskeleme yaparak sınıra
gelince adresi sıfırlamış olduk. Aşağıda yeni adlandırılmış değişken ve kesme
içi komutu görebilirsiniz.
#define UART_Rx_Boyut 16
#define UART_Rx_Mask (UART_Rx_Boyut-1)”
volatile uint8_t rx_bas=0;
volatile uint8_t rx_ring[UART_Rx_Boyut];
ISR (USART_RX_vect){
rx_bas=(rx_bas+1) & UART_Rx_Mask;
rx_ring[rx_bas]= UDR0;
}
|
“rx_ring[rx_bas]” veriyi yazdığımız bellek ve adresi belirleyen kodumuz bu şekilde
“rx_bas” kesme ile “1” artırılıyor ve “&” işlemi
yapılıyor, işlem sonucuna göre veri belleğe yazılıyor. Okuma işlemi içinde yapılanlar
aynı şekildedir. “kuyruk” değişkeni okuma yaptıkça artırılır ve tekrar başa
döndürülür.
volatile uint8_t rx_son=0;
uint8_t uart_oku(){
rx_son=(rx_son+1) & UART_Rx_Mask;
return rx_ring[rx_son];
}
|
“uart_oku()” fonksiyonu çağrılınca okuma adresini tutan “rx_son” “1” artırılır. Eğer baş ve son/kuyruk eşitse veri yok demektir. Değilse
“rx_ring[rx_son]” adresteki veriyi döndürür. “(rx_son==rx_bas)” eşitlik sorgusunu kullanabilirsiniz. Arduino
da meşhur Serial.available() fonksiyonu aynı sorgulamayı yapar. Aşağıda bu
şekilde bir fonksiyon paylaşıyorum “uart_varsa()”J
uint8_t uart_varsa(){
if (rx_son==rx_bas){
return 0;
}
return 1;
}
|
Bu bellek düzenlemesi ve kesme sayesinde veriyi kayıpsız alıp
okuyabildik. Bellek sınırının düşük tutulması taşmalara neden olabilir. Bu
durumu önlemek ve uart biriminin hata bayraklarının kontrol etmek için çeşitli
sorgular yapabilirsiniz ben bu kısımları kullanmıyorum.
Veriyi almak için kesmeleri kullandık göndermek için yine kesme
kullanabiliriz. Göndereceğimiz veriyi de almak için kullandığımız dairesel
bellek gibi bellekte tutabiliriz. İlk olarak gönderim yaparken USART_UDRE
kesmesini kullanıyoruz. Bu kesme UDRE0 boş olduğunda devreye giriyordu bu
yüzden bu kesmeyi açık tutamayız çünkü veri almıyor ya da göndermiyorsak UDRE0
boş demektir ve sürekli kesme yaşanacaktır. Veriyi gönderirken kesmeyi açıp gönderim
sonrası kapatmalıyız.
void uart_gonder(uint8_t uData){
while(!(UCSR0A & (1<<UDRE0)));
UDR0=uData;
}
|
İlk bölümde paylaştığım uart veri gönderme fonksiyonu yukarıdaki
gibidir. Kesme kullanarak gönderim yaparken artık bu fonksiyonda kesmeyi açıp
kapatan komut olmalıdır. Bunu kolay yoldan yapmak için makro tanımlıyorum.
“#define UART_Bos_On UCSR0B|=(1<<UDRIE0)” ve “#define UART_Bos_Off UCSR0B&=~(1<<UDRIE0)”
tanımlamasını yapıyorum. Bu tanımlarda rx kesmesinde olduğu gibi udre0 kesmesi
için ilgili bit “1” ve “0” yapılır. Gönderilecek veriyi gelen kısmında olduğu
gibi giden ve gidecek gibi isimlendirdiğimizi farz edelim. Bu durumda giden “kuyruk”
gidecek “baş” olacaktır. Gelen kısmında
olduğu gibi yine artarak devam edecek sınıra gelince başa dönecektir. Tanımlamalar
ve fonksiyon bu şekle gelir.
#define UART_Tx_Boyut 16
#define UART_Tx_Mask (UART_Tx_Boyut-1)
#define UART_Bos_On UCSR0B|=(1<<UDRIE0)
#define UART_Bos_Off UCSR0B&=~(1<<UDRIE0)
volatile uint8_t tx_ring[UART_Tx_Boyut];
volatile uint8_t tx_bas=0,tx_son=0;
void uart_gonder(uint8_t uData){
tx_bas=(tx_bas+1)&UART_Tx_Mask;
while (tx_bas==tx_son);
tx_ring[tx_bas]=uData;
UART_Bos_On;
}
|
Gördüğünüz gibi fonksiyonun sonunda kesmeyi açıyoruz UDRE0 boş
olduğundan hemen kesme devreye girecektir. Kesme içi komutlar aşağıdaki gibi
olacaktır.
ISR (USART_UDRE_vect){
tx_son=(tx_son+1)&UART_Tx_Mask;
UDR0=tx_ring[tx_son];
if (tx_son==tx_bas){
UART_Bos_Off;
}
}
|
Kesme içini incelediğimizde gidecek olan veri adresini tutan “kuyruk”
“tx_son” artırılmış bellekteki veri UDR0 registerine yazılmış. Baş ve
kuyruk (tx_son==tx_bas) eşit olana kadar bu devam
ettirilmiş. Eşitlik sağlandığında yani bellekte gidecek veri kalmadığında kesme
kapatılmış. Alırken olduğu gibi kayıpsız olarak veriyi göndermiş olduk. Aşağıda
paylaştığım programı Arduino idesinde karta yükleyebilir serial ekran da deneme
yapabilirsiniz.
/*
* uart_kesme_ring.c
*
* Created: 19.03.2019 23:16:31
* Author : haluk
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define UART_Rx_Boyut 128
#define UART_Tx_Boyut 128
#define UART_Rx_Mask (UART_Rx_Boyut-1)
#define UART_Tx_Mask (UART_Tx_Boyut-1)
#define UART_Bos_On UCSR0B|=(1<<UDRIE0)
#define UART_Bos_Off UCSR0B&=~(1<<UDRIE0)
volatile uint8_t rx_bas=0,rx_son=0,tx_bas=0,tx_son=0;
volatile uint8_t rx_ring[UART_Rx_Boyut];
volatile uint8_t tx_ring[UART_Tx_Boyut];
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();
}
uint8_t uart_oku(){
rx_son=(rx_son+1) & UART_Rx_Mask;
return rx_ring[rx_son];
}
uint8_t uart_varsa(){
if (rx_son==rx_bas){
return 0;
}
return 1;
}
void uart_gonder(uint8_t uData){
tx_bas=(tx_bas+1)&UART_Tx_Mask;
while (tx_bas==tx_son);
tx_ring[tx_bas]=uData;
UART_Bos_On;
}
void uart_dizi_gonder(char*str){
uint8_t i=0;
while(str[i]) {
uart_gonder (str[i]);
i++;
}
}
ISR (USART_RX_vect){
rx_bas=(rx_bas+1) & UART_Rx_Mask;
rx_ring[rx_bas]= UDR0;
}
ISR (USART_UDRE_vect){
tx_son=(tx_son+1)&UART_Tx_Mask;
UDR0=tx_ring[tx_son];
if (tx_son==tx_bas)
UART_Bos_Off;
}
int main(void){
uart_basla(19200);
DDRB|=(1<<PORTB5);
sei();
while (1) {
PORTB|=(1<<PORTB5);
_delay_ms(1000);
PORTB&=~(1<<PORTB5);
_delay_ms(1000);
if (uart_varsa()){
uart_gonder(uart_oku());
}
}
}
|
Hiç yorum yok:
Yorum Gönder