Mikrokontrolery AVR część 10 – transmisja szeregowa UART

 Po bardzo długiej przerwie postanowiłem wznowić pracę nad kursem programowania mikrokontrolerów AVR. Zmotywowało mnie to, że obserwując statystyki to jest to najpopularniejszy cykl na mojej stronie, więc warto go dalej rozwijać.

Następne części będą poświęcone transmisją danych, a dzisiaj opiszę chyba najpopularniejszą transmisję UART.

UART (Universal Asynchronous serial Receiver and Transmitter)  to metoda asynchronicznej transmisji dwu przewodowej, gdzie jedna linia służy do nadawania, a druga do odbierania. Mikrokontrolery AVR obsługują także transmisje synchroniczną (USART) ale ją opiszę w oddzielnym artykule.

Podstawowe parametry transmisji szeregowej to:

  • szybkość przesyłu, określana jako baud
  • ilość przesyłanych bitów w jednej ramce (databits)
  • ilość bitów stopu (stop bits)
  • ilość bitów parzystości (parity)

Pojawiło się tutaj kilka specyficznych określeń, takich jak ramka bity stopu, czy parzystości, zaraz wszystko wytłumaczę a zacznę od ramki.

Ramka to zbiór bitów który jest cyklicznie wysyłany, jest to jednostka danych większa od bita.

Bity stopu służą do oddzielenia danych w następujących po sobie ramkach, to taka cyfrowa spacja między słowami ułatwiająca odczytanie tekstu (danych).

Bity parzystości służą do kontroli czy transmisja zachodzi bez błędów, a dokładnie to określa czy mamy parzystą lub nie parzystą ilość 0 lub 1.

Baud to szybkość z jaką dane będą przesyłane i jest wyrażana w bitach na sekundę (b/s)

Poniższy rysunek przedstawia budowę ramki:

ramka

Na samym początku ramki mamy bit startu, który zawsze ma wartość 0 (low), następnie znajdują bity danych, których może być 5,6,7,8 lub 9. Ilość jest zależna od tego ile chce programista. Po bitach danych znajduje się opcjonalny bit parzystości, a następnie bit lub bity stopu. Taki układ jest wysyłany bit po bicie zaczynając od bity startu.

Ważne jest aby urządzenia komunikujące się buły zaprogramowane na dokładnie taką samą transmisje, taką samą szybkość ilość bitów danych, bitów parzystości oraz stopu.

W warstwie sprzętowej transmisji UART znajdują się dwie linie:

  • TxD – transmisja
  • RxD – retransmisja (odbiór)

Aby połączyć dwa urządzenia należy połączyć wspólnie ich masę, a następnie linie TxD z linią RxD drugiego urządzenia (TxD -> RxD), i to samo wykonać z linią RxD (RxD -> TxD). Najlepiej przedstawia to rysunek:

polaczenie

Bardzo ważne jest to jaki standard napięciowy obowiązuje przy połączeniu układów. Od strony mikrokontrolera AVR mam 0V dla logicznego zera i 5V dla logicznej jedynki (poziomy TTL), więc dwa mikrokontrolery AVR możemy łączyć bezpośrednio tak tak na powyższym rysunku. Połączenie takie możemy wykorzystać także w celu komunikacji na przykład z modułami Bluetooth.

Komplikuje się sytuacja jak chcemy się komunikować z komputerem przy pomocy magistrali RS232, ponieważ na niej obowiązują napięcia +12V dla logicznego zera i -12V dla logicznej 1. W tym celu musimy zastosować układ który będzie nam konwertował te napięcia, tak aby układy mogły się dogadać i nie popalić. Najpopularniejszym układem tego typu jest MAX232, oraz jego różne zamienniki (zdecydowanie tańsze). Na schemacie poniżej przedstawiam jak podłączyć mikrokontroler z komputerem używając układu MAX232.

rs232

Do połączenia mikrokontrolera z komputerem możemy także wykorzystać USB, w tym wypadku należy zastosować układ FT232, który jest jednym z najpopularniejszych układów tego typu. W ostatnim czasie pojawia się coraz częściej układ PL2303, prawdopodobnie ze względu na niską cenę.

Aby wykorzystać USB najlepiej posłużyć się tym schematem:

Konfiguracja UART w Atmega8

Firma Atmel na szczęście bardzo dokładnie opisała w dokumentacji konfiguracje transmisji uart, łącznie z gotowymi programami w c i asemblerze, więc po prostu omówię to co oni już nam dostarczyli.

Pierwszym elementem będzie obliczenie co należy wpisać aby uzyskać odpowiednią prędkość transmisji, a służy do tego prosty wzór.

wzow_baud

Rejestr UBRR ma za zadanie przechowywanie liczby, która określa szybkość transmisji. Do wyboru mamy dwa tryby, normalny i podwójnej szybkości, a do wyboru trybu służy nam bit U2X z rejestru UCSRA, który jest jednym z podstawowych rejestrów służących do konfiguracji transmisji UART. Oczywiście jak można się domyśleć wartość wpisywana do UBRR jest zależna od taktowania naszego procesora.

Poniżej przedstawiam najprostszy kod pozwalający uruchomić transmisje i jest to dokładnie ten który znajduje się w nocie katalogowej.

#define FOSC 100000                //czestotliwosc zegara
#define BAUD 9600                   //szybkosc transmisji
#define MYUBRR FOSC/16/BAUD-1       //obliczenie UBRR


void main( void )
{
    USART_Init ( MYUBRR );         //wywolanie inicjalizacji UART
}

void USART_Init( unsigned int ubrr)
{
    /* ustawienie baud */
    UBRRH = (unsigned char)(ubrr>>8);
    UBRRL = (unsigned char)ubrr;

    /* odblokowanie transmisji i retransmisji */
    UCSRB = (1<<RXEN)|(1<<TXEN);

    /* Ustawienie parametrów ramki: 8data, 2stop bit */
    UCSRC = (1<<URSEL)|(1<<USBS)|(3<<UCSZ0);
}

Aby wyliczyć wartość UBRR wykorzystujemy dyrektywy define, w ten sposób zostanie to obliczone i podstawione już przez kompilator, a nie kontroler. W ten sposób oszczędzamy pamięć mikrokontrolera. Aby zapisać tą wyliczoną wartość należy rozłożyć to na dwie operację, ze względu że rejestr UBRR to w rzeczywistości dwa rejestry: UBRRL oraz UBRRH. Wpisanie odbywa się przez przesunięcie wyliczonej wartości w prawo i rzutowanie na zmienną typu unsigned char (8 bitów), a następnie wpisanie do UBRRH. Aby wpisać dane do rejestru UBRRL wystarczy wykonać rzutowanie na zmienną 8 bitową, co spowoduje odcięcie wyższych bitów i pozwoli na wpisanie do rejestru.

Następnym zadaniem programu jest odblokowanie transmisji i retransmisji. Aby to wykonać należy ustawić bity RXEN i TXEN z rejestru UCSRB. Na koniec zostało nam wybranie jakie chcemy parametry ramki, służą do tego bity rejestru UCSRC. W przypadku tego programu jest to: 8 bitów danych, 2 bity stopu i brak bitów parzystości.

Rejestr UCSRC

Jest to rejestr zawierający bity którymi ustawiamy między innymi parametry ramki i na tych bitach się skoncentruje.

UCSRC

Do ustawienie bitów parzystości służą bity UPM0 oraz UPM1.

UPM

Do wyboru ilości bitów spotu służy bit USBS.

USBS

Aby ustawić ilość bitów danych musimy skorzystać z bitów: UCSZ2UCSZ1UCSZ0.

UCSZ

Transmisja (nadawanie)

Rejestr z którego bity zostaną nadane to UDR, ale nie wystarczy tylko wpisać do niego nadawaną zmienną, może wystąpić sytuacja, że jeszcze wszystko nie została wysłane i jak byśmy wpisali dane, to nadpiszemy poprzednie bity, co będzie skutkowało utratą nadawanych danych. W tym celu jest prosty mechanizm w formie bitu który mówi czy już wszystko zostało nadane. Ten bit to UDRE z rejestru UCSRA. Poniżej przedstawiam program, także zaczerpnięty z noty katalogowej.

void USART_Transmit( unsigned char data )
{
    while ( !( UCSRA & (1<<UDRE)) );    //oczekiwanie na opróżnienie bufora nadawania
    UDR = data;                         //wpisanie danych do bufora
}

Jak widać nadawanie jest całkiem proste, czekamy aż się zwolni bufor, a jak się zwolni to wpisujemy nowe dane.

Retransmisja (odbiór)

Odbiór danych jest tak samo prosty jak nadawanie, tutaj także używamy rejestru UDR jako bufora. Podstawową sprawą jest to, że musimy sprawdzić, czy cokolwiek jest do odebrania, a mówi nam o tym bit RXC z rejestru UCSRA. Poniżej przedstawiam program, oczywiście też zaczerpnięty z noty, ponieważ producent mikrokontrolerów wszystko nam dostarczył.

unsigned char USART_Receive( void )
{
    while ( !(UCSRA & (1<<RXC)) );     //oczekiwanie na dane od odebrania
    return UDR;                        //zwrócenie zawartości bufora z odebranymi danymi
}

Jest to bardzo proste, czekamy, aż będzie coś do odebrania, a kiedy już jest to odczytujemy nasze dane z rejestru UDR.

Prosty przykład

Aby lepiej zaprezentować działanie napisałem przykład. Stworzyłem także prostą bibliotekę, aby łatwiej było poeksperymentować z transmisją UART. U mnie mikrokontroler to Atmega8 podpięta przez FT232 do komputera.

IMG_0126_1

Projekt składa się z trzech plików main.c, uart.c i uart.h.

main.c

#include <avr/io.h>
#include "uart.h"

#define FOSC 16000000
#define BAUD 9600

void main( void )
{
    SerialInit(FOSC, BAUD, 8, 2, 0);
    SerialPrint("Test komunikacji USART\n");

    while(1)
    {
        SerialPrintChar(SerialRead());
    }
}

uart.h

void SerialInit(unsigned long int fosc, unsigned int baud, short int bits, short int stopBits, short int patity);
void SerialPrintChar(unsigned char data);
void SerialPrint(unsigned char *s);
unsigned char SerialRead(void);

uart.c

#include <avr/io.h>
#include "uart.h"

void SerialInit(unsigned long int fosc, unsigned int baud, short int bits, short int stopBits, short int parity)
{
     unsigned int ubrr = fosc/16/baud-1;

     UBRRH = (unsigned char)(ubrr>>8);
     UBRRL = (unsigned char)ubrr;

     UCSRB = (1<<RXEN)|(1<<TXEN);

     switch(parity)
     {
          case 1:
               UCSRC = (1<<URSEL)|(1<<UPM1);
          break;
          case 2:
               UCSRC = (1<<URSEL)|(1<<UPM1)|(1<<UPM0);
          break;
     }

     switch(stopBits)
     {
          case 2:
               UCSRC = (1<<URSEL)|(1<<USBS);
          break;
     }

     switch(bits)
     {
          case 6:
               UCSRC = (1<<URSEL)|(1<<UCSZ0);
          break;
          case 7:
               UCSRC = (1<<URSEL)|(1<<UCSZ1);
          break;
          case 8:
               UCSRC = (1<<URSEL)|(3<<UCSZ0);
          break;
     }
}
void SerialPrintChar(unsigned char data)
{
     while (!(UCSRA&(1<<UDRE)));

     UDR = data;
}

void SerialPrint(unsigned char *s)
{
     while(*s)
     {
          SerialPrintChar(*s);
          s++;
     }
}

unsigned char SerialRead(void)
{
     while (!(UCSRA&(1<<RXC)));
     return UDR;
}

Biblioteka umożliwia konfigurację transmisji (za wyjątkiem 9 bitów danych), nadawanie znaku i ciągu znaków, oraz odbiór znaków. Zadaniem programu jest nadanie Ciągu znaków, a następnie zwracanie każdego ze znaków który odbierze.

W przyszłości planuje mocno rozbudować tą bibliotekę, ale już w takie postaci jej możliwości są całkiem duże. Polecam pobawić się i poeksperymentować z ustawieniami, aby lepiej zrozumieć i poczuć jak to wszystko działa.

W następnych częściach, które postaram się napisać niedługo opiszę inne metody komunikacji takie jak I2C, SPI, 1-wire. Zapraszam do komentowania i czytania innych moich artykułów.

Komentarze do „Mikrokontrolery AVR część 10 – transmisja szeregowa UART

  1. Cześć, mam pytanie co do zdjęcia układu. Czym jest ten zielony przewód wyprowadzony z portu USB?

  2. Ok, nie rozumiem tylko dlaczego te przewody są wyprowadzane z obudowy usb? Konwerter służy nam do tego, żeby łatwo podpiąć mikrokontroler do komputera. Nie wystarczy jednie podpiąć z jednej strony USB a z drugiej piny Tx, Rx, Vcc i mase?

    PS. Fajny kurs, dużo wiedzy w pigułce bez zbędnych wywodów. Oby tak dalej 🙂

  3. Jak dasz const char w procedurze SerialPrint, nie będziesz miał warningu.

    Czemu nie powstają już kolejne części?

    • Niestety w tym momencie mam problem z czasem, ale kolejne części będą powstawały na pewno. Podejrzewam, że przyjdzie kilka długich zimowych wieczorów, to wszystko uda się zrobić.

  4. Cześć, próbuję wykorzystać twój przykładowy program żeby skomunikować dwie atmegi. Transmisję przetestowałem w połączeniu z komputerem i działa ok, ale jak odbieram to samo na atmega i wyświetlam na LCD to pokazuje się dużo dziwnych znaków zamiast liczby która nadaję, czym może to być spowodowane?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.