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


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