Port szeregowy – AVR tutorial

Port szeregowy – AVR tutorial

Witam!

Niedawno napisałem tutorial na temat korzystania z interface szeregowego przy użyciu arduino.

Postanowiłem jednak napisać jeszcze jeden. Tym razem jednak wykorzystanie zostanie język C.

Opiszę tutaj podstawy korzystania z asynchronicznego portu szeregowego na mikrokontrolerach AVR.

Nie będę tutaj jednak powtarzał jeszcze raz informacji na temat protokołów itp. więc polecam najpierw przeczytać mój poprzedni artykuł na ten temat :

https://majsterkowo.pl/przesylanie-danych-przez-asynchroniczny-port-szeregowy-arduino-tutorial/

Dodatkowo przydatne mogą być informacje na temat programowania AVR które umieściłem w 2 częściowym artykule 5 prostych projektów AVR w C

https://majsterkowo.pl/5-prostych-projektow-avr-w-c-czesc-1/

https://majsterkowo.pl/5-prostych-projektow-avr-w-c-czesc-2/

Po tym krótkim wstępie pora na właściwy tutorial (:

Na początek

Na początek potrzebny nam będzie układ na którym będziemy eksperymentować :

Jest to bardzo prosty układ. Za mikrokontroler służy popularna Atmega328p, taktowana z zewnętrznego oscylatora kwarcowego 16 MHz.

Jest ona zasilana bezpośrednio ze złącza programatora co dodatkowo upraszcza całość.

Jako urządzenie do którego będzie gadała atmega  zastosowałem konwerter USB-UART wbudowany w arduino. W tym celu należy podłączyć pin TX atmegi do pinu RX konwertera i pin TX konwertera do pinu RX atmegi.

Ale UWAGA! Pin RX konwertera jest podłączony do pinu TX Mikrokontrolera wbudowanego w arduino, tak samo pin TX konwertera jest podłączony do pinu RX mikrokontrolera. Tak więc aby użyć arduino jako konwerter USB – UART musimy wgrać program który nie będzie używał UART (najlepiej pusty ) i

poodłączyć przewody na odwrót : RX do pinu oznaczonego na płytce jako RX i TX do TX.

Po podłączeniu wszystkiego musimy ustawić fusebity.

Ustawienia fusebitów dla tego układu to :

Podałem od razu jako argumenty do AVRDUDE.

Gotowa komenda to :

Co do wgrywania i kompilowania zamieszczonych tu programów, to opisałem to w moim artykule https://majsterkowo.pl/5-prostych-projektow-avr-w-c-czesc-1/

Lista rejestrów

Oto lista rejestrów które służą do kontroli działania UART w mikrokontrolerze Atmega 328p.

W innych mikrokontrolerach AVR jest podobnie.

Jednak zawsze należy najpierw się upewnić w dokumentacji danego mikrokontrolera.

Szczegóły na temat przeznaczenia poszczególnych bitów rejestrów można znaleźć w dalszej części artykułu oraz w dokumentacji mikrokontrolera.

Oto lista:

UDR0 – Rejestr danych

UCSR0A – Rejestr kontrolny A

UCSR0B – Rejestr kontrolny B

UCSR0C – Rejestr kontrolny C

UBRR0H / UBRR0L – Rejestry do ustawiania prędkości przesyłu danych.

Ustawienia i inicjalizacja

Notatka : Niektóre mikrokontrolery mają więcej niż 1 sprzętowy UART, w takiej sytuacji zmieniają się nazwy rejestrów i bitów. 

W nazwach bitów i rejestrów dodałem małe “n”. Zastępuje ono numer UART. (Numeracja od 0). 

Pierwszą rzeczą którą trzeba zrobić przed przesłaniem lub odebraniem danych jest ustawienie parametrów przesyłu danych.

Podstawowym z nich jest prędkość przesyłu ( w bitach na sekundę).

Teorytycznie prędkość danych może być dowolna jednak w praktyce 99.9% urządzeń korzysta z jednej z tych wartości :

  • 300
  • 600
  • 1200
  • 2400
  • 4800
  • 9600
  • 14400
  • 19200
  • 28800
  • 38400
  • 57600
  • 76800
  • 115200
  • 230400
  • 25000
  • 500000
  • 1000000

Prędkość przesyłu danych jest ustawiana za pomocą rejestru UBRRn.

A konkretnie UBRRnH i UBRRnL ponieważ wartość ta ma dwa bajty (unsigned int).

Do UBRRnH zapisujemy 8 najbardziej znaczących bitów a do UBRRnL pozostałe 8 bitów zmiennej. W praktyce są to tylko 4 bity bo maksymalna prędkość nie jest na tyle duża aby zająć ostatnie 4 bity.

Jednak żeby było ciekawiej nie zapisujemy tam realnej prędkości ale wartość obliczoną ze wzoru :

(F_cpu/16xBAUD)-1

Przy czym F_cpu to prędkość zegara mikrokontrolera a BAUD to żądana prędkość przesyłu danych.

Następnie musimy tą wartość zapisać w tych dwóch rejestrach.

Przykładowy kod który to robi:

Oczywiście wartość “ubrr” trzeba najpierw policzyć, aby uniknąć robienia tego ręcznie można ją zdefiniować w ten sposób :

Jej obliczanie można również przenieść do funkcji.

Ale to nie wszystko!

W rejestrze UCSRnA znajduje się bit U2Xn którego ustawienie na 1 powoduje podwojenie prędkości przesyłu danych.

Po jego ustawieniu, wartość dla rejestrów UBRRn można policzyć ze wzoru :

(F_cpu/8xBAUD)-1

Następną rzeczą którą trzeba zrobić jest ustawienie interface-u do pracy  w trybie asynchronicznym. (Tak istnieje możliwość pracy w trybie synchronicznym)

Robi się to za pomocą bitów UMSELn0 i UMSELn1 w rejestrze UCSRnC. Aby USART odpalił w trybie asynchronicznym trzeba je ustawić na zero.

To znaczy, że nie należy nic robić bo domyślna wartość tych bitów to 0.

Teraz trzeba  ustawić pozostałe rzeczy :

  • Bit parzystości
  • Ilość bitów danych
  • Ilość bitów stopu

Za bity parzystości odpowiadają bity UPMn0 I UPMn1 w rejestrze UCSRnC.

Możliwe konfiguracje to

UPMn1 | UPMn0

  • 00  – Bit parzystości nieobecny
  • 01 – Zarezerwowane ustawienie
  • 10 – Bit parzystości
  • 11 – Bit nieparzystości

Następnym krokiem jest ustawienie ilości bitów danych.

Ustawia je się za pomocą bitów UCSZn2/1/0 w rejestrze UCSRnC

Możliwe konfiguracje to :

UCSZn2 | UCSZn1 | UCSZn0

  • 000 – 5-bitów
  • 001 – 6-bitów
  • 010 – 7-bitów
  • 011 – 8-bitów
  • 100 – Zarezerwowane ustawnie
  • 101 – Zarezerwowane ustawnie
  • 110 – Zarezerwowane ustawnie
  • 111 – 9-bit

Ostatnią rzeczą którą należy zrobić to ustawienie ilości bitów stopu.

Za ustawienia bitów stopu odpowiada bit USBSn z rejestru UCSRnC.

Wartość 0 oznacza, że jest jeden bit stopu, wartość 1 oznacza 2 bity stopu.

Po ustawieniu wszystkiego musimy jeszcze włączyć Interface szeregowy.

Co ciekawe nadajnik i odbiornik są włączane osobno.

Za włączanie i wyłączanie obu tych elementów odpowiadają bity :

  • RXENn – W przypadku odbiornika
  • TXENn – W przypadku nadajnika

Oba te bity znajdują się w rejestrze UCSRnB.

No dobrze pora więc na pierwszy przykład : Prosty program który zainicjalizuje nadajnik portu szeregowego i ustawi go na takie ustawienia :

  • 9600 bitów na sekundę.
  • 1 bit parzystości.
  • 1 bit stopu.
  • 8 bitów danych.

W tym przypadku napisana przez mnie funkcja umożliwia wybór prędkości przesyłu danych oraz tego które z modułów UART mają zostać uruchomione.

Wysyłanie danych

Kiedy już wszystko zainicjalizowaliśmy i ustawiliśmy, pora na wysyłanie jednego bajta danych.

Procedura wysyłania danych jest bardzo prosta i wygląda następująco :

Najpierw musimy sprawdzić stan poprzedniej transmisji.

Zgodnie z dokumentacją, nie można ładować danych do bufora zanim poprzednia transmisja się nie zakończy.

W tym celu należy sprawdzić czy flaga UDREn ma wartość ma wartość 1. Jeśli ma to wtedy bufor jest gotowy by przyjąć nowe dane.

Do tego wystarczy taka linijka kodu :

Bufor nadawania i bufor odbierania dzielą ten sam rejestr UDRn.

Z tym, że odebrane z niego dane pochodzą z bufora danych odebranych a dane do niego zapisane trafiają do bufora danych wysyłanych.

W drugim kroku należy po prostu zapisać żądany bajt do bufora (znajdującego się pod rejestrem UDRn) i tyle.

Nadajnik wykryje, że bufor nie jest pusty i natychmiast prześle zawarte w nim dane.

Funkcja wysyłająca dane wygląda tak :

Teraz mamy już wszystko niezbędne do wysłania pierwszego bajta danych!

Oto prosty program wysyłający 10 :

Screenshoot z analizatora stanów logicznych potwierdza, że 10 została odebrana poprawnie :

Pora na coś większego :

Nieśmiertelne Hello World! (:

Efekt :

Aby trochę to uprościć można jeszcze ewentualnie dodać funkcję wysyłająca stringi :

Finalny kod wysyłający dane :

Następnie postanowiłem podłączyć pin TX atmegi do pinu TX płytki arduino przez co zadziała ona jako konwerter USB-UART i będę mógł odbierać/wysyłać dane przez monitor portu szeregowego.

Jednak najpierw musiałem usunąć bit parzystości ponieważ w arduino IDE nie ma opcji ustawienie takich rzeczy jak Bity parzystości czy liczba bitów stopu ): . Musiałem też dodać znak nowej linii na końcu wysyłanego stringa żeby w terminalu dane nie pokazywały się w postaci super długiego ciągu znaków.

Jednak po tej zmianie, w monitorze portu szeregowego zobaczyłem “Hello world!”:

Odbieranie danych

Skoro już wiemy jak wysyłać dane to teraz pora na ich odbieranie.

W pierwszym kroku należy sprawdzić czy do odebrania są dostępne jakieś dane.

Za to odpowiada flaga RXCn w rejestrze UCSRnA.

Kiedy w buforze pojawiają się jakieś dane to wtedy przyjmuje ona wartość 1.

Następnie musimy po prostu odczytać dane z rejestru UDRn.

Napisałem więc dwie prościutkie funkcje zwracające obie te wartości :

Stworzyłem również ten przykład :

Program odbiera bajty z portu szeregowego i jeśli ich wartość wynosi ‘a’ to wtedy zapala on diodę. Wartość ‘b’ gasi diodę.

Dodatkowo wysyłane jest potwierdzenie zgaszenia lub zapalenia diody.

Kontrola odebranych danych 

UART mikrokontrolera ma wbudowane mechanizmy kontroli danych.

Mechanizmy te wykrywają :

  • Błąd parzystości  – Bit parzystości / nieparzystości nie zgadza się z danymi.
  • Błąd ramki – Bit stopu został odebrany nieprawidłowo jako 0 a nie 1.
  • Przepełnienie bufora – Bufor danych odebranych się przepełnił to jest zawiera dwa bajty i został wykryty bit startu.

Każdy z tych błędów wiąże się z utratą przesyłanych danych.

Flagi tych 3 błędów znajdują się w rejestrze UCSnA.

Nazwy bitów flag to :

  • FEn – Błąd ramki.
  • DORn – Przepełnienie bufora.
  • UPEn – Błąd parzystości.

Kiedy nastąpi błąd, odpowiednia flaga zmienia swój stan na 1.

Flagi są czyszczone (zmiana stanu na 0) kiedy bufor jest odczytywany.

Wysyłanie i odbieranie pakietów z 9 bitami danych

Podane do tej pory metody będą działały dla każdej ilości bitów danych z wyjątkiem 9.

Problem z 9 bitami pojawia się bo jeden bajt ma 8 bitów. Więc nadmiarowy bit należy odczytywać i wysyłać  osobno.

Procedura wysyłania danych z 9 bitami wygląda tak :

  1. Podobnie jak w przypadku normalnej transmisji należy poczekać aż bit UDREn zmieni swój stan na niski.
  2. Następnie musimy zapisać nadmiarowy (najbardziej znaczący) bit do bitu TXB8n w rejestrze UCSRnB.
  3. Potem należy zapisać pozostałe 8 bitów do rejestru UDRn.

Procedura odczytu danych wygląda natomiast tak :

  1. Należy sprawdzić czy w buforze są nowe dane.
  2. Należy odczytać  nadmiarowy bit (najbardziej znaczący) z bitu RXB8n.
  3. Należy odczytać dane z rejestru UDRn.

Jednak w przypadku korzystania z 9 bitów danych należy pamiętać, że ogranicza to pojemność bufora do 1 bajtu ( zw względu na bity RXB8n i TBB8n).

Normalnie pojemność obu buforów  to zawrotne 2 bajty.

Przerwania 

Z UART związane są 3 przerwania o wektorach :

  • USART_RX
  • USART_UDRE
  • USART_TX

Pierwsze przerwanie wykonuje się kiedy zostają odebrane dane.

Przerwanie drugie wykonuje się kiedy rejestr danych UDRn jest pusty.

Trzecie przerwanie wykonuje się kiedy UART kończy wysyłać dane.

Aby włączyć dane przerwanie należy ustawić odpowiedni bit rejestru UCSRnB na 1.

  • USART_RX – Bit RXCIEn
  • USART_UDRE – Bit UDRIEn
  • USART_TX -Bit TXCIEn

Gotowy kod definiujący wektory przerwań :

Warto zaznaczyć, że w celu korzystania z przerwań należy dołączyć bibliotekę  :

Należy również włączyć globalnie przerwania za pomocą bitu I w rejestrze SREG.

Trzeba również pamiętać o tym, że dla większej ilości UART w mikrokontrolerze, różne UART będą miały różne wektory przerwań.

Zawsze należy zapoznać się z dokumentacją danego mikrokontrolera.

Kilka słów na koniec 

To by było na tyle jeśli chodzi o ten krótki tutorial.

Mam nadzieję, że okazał się pomocny (:

Komentarze, zarówno pozytywne jak i negatywne mile widziane.

Ocena: 5/5 (głosów: 1)

Podobne posty

Jeden komentarz

  • Dziękuję. Nie działa mi tylko jedna rzecz:

    Kiedy próbuję wysłać zmienną int np 9 to nie działa, a jeśli wyślę zmienną char ‘9’ to normalnie działa

    Odpowiedz

Odpowiedz

anuluj

Masz uwagi?