Tworząc system, w którym mikrokontroler będzie współpracował z innymi urządzeniami, należy w jakiś sposób przesyłać między nimi dane.
Bardzo częstym rozwiązaniem, które implementują konstruktorzy jest UART. W tym odcinku skupimy się na opisie tego interfejsu od strony teoretycznej oraz praktycznej z użyciem kreatora Cube oraz bibliotek HAL.
Uwaga! Ten kurs został zarchiwizowany. Sprawdź najnowszy kurs STM32 »
Komunikacja przez USART / UART
USART / UART (ang. Universal Synchronous and Asynchronous Receiver and Transmitter) służy do przesyłania danych pomiędzy urządzeniami. Znaleźć można go na wyposażeniu praktycznie każdego mikrokontrolera.
Bardzo często zamiennie używane są dwie nazwy nazwy: USART oraz UART, co może być mylące.
Litera S oznacza możliwość zrealizowania komunikacji w trybie synchronicznym, a więc takim, w którym bity przesyłane są w takt zegara podawanego na dedykowanej do tego linii sygnałowej. USART pozwala więc na komunikację zarówno synchroniczną jak i asynchroniczną.
My natomiast korzystać będziemy tylko z komunikacji asynchronicznej,
będziemy więc mówić o komunikacji przez UART.
UART pozwala na komunikację typu Full-Duplex. Oznacza to, że dane mogą być jednocześnie nadawane i odbierane. Do komunikacji w takim trybie niezbędne są (co najmniej) trzy linie. Dwie linie transmisyjne - Tx, Rx oraz wspólna masa (GND).
Jak działa UART?
Żeby zrozumieć co ustawiamy w dalszej części konfiguracyjnej, powinniśmy poznać najpierw podstawy działania tego interfejsu. UART ma dwa wyprowadzenia odpowiadające za transmisję i odbieranie danych (Tx oraz Rx).
Wysyłając dane na linii transmisji (Tx) zaczną pojawiać się odpowiednio stany niskie i wysokie. Jakie, kiedy i dlaczego? Aby odpowiedzieć na te pytania, trzeba poznać budowę ramki!
Ramka danych UART
Ramka danych jest odgórnie ustaloną sekwencją znaków, które muszą się pojawić w określonej kolejności podczas komunikacji. Obie strony transmisji (urządzenie nadawcze i odbiorcze) muszą znać parametry tej ramki, aby komunikacja przebiegła pomyślnie.
Ramka UART.
Stan bezczynności (Idle) - oznacza, że aktualnie nie przebiega żadna transmisja. Komunikowany jest przez wymuszenie stanu wysokiego na linii danych.
Bit startu (St) - rozpoczyna każdą ramkę danych. W tym wypadku jest to wymuszenie stanu niskiego na linii danych. Bit startu jest obowiązkowym elementem każdej ramki.
Bity danych (0-8) - bity danych reprezentują aktualne informacje, które chcemy przesłać. Standardowo przesyła się ramki złożone z 8 bitów danych. W teorii może być ich od 5 do 9.
Bit parzystości (P) - służy jako podstawowa, niskopoziomowa forma sprawdzenia poprawności przesłanych danych. Jest on opcjonalny i jego obecność jest jednym z konfigurowalnych parametrów transmisji. Bit parzystości może działać w dwóch trybach:
Parzysty - celem jest uzyskanie parzystej sumy bitów danych i bitu parzystości. Jeżeli liczba "jedynek" w bitach danych była nieparzysta, bit przyjmie wartość 1. Jeżeli liczba ta była parzysta, bit parzystości przyjmie wartość 0.
Nieparzysty - analogicznie do trybu parzystego. Bit parzystości zawsze przyjmie taką wartość, aby sumarycznie liczba bitów w stanie wysokim była liczbą nieparzystą.
Bit stopu (Sp1, Sp2) - bit oznaczający zakończenie całej ramki danych. Komunikowany jest jako wymuszenie stanu wysokiego na linii danych. Liczba bitów stopu jest konfigurowalna i może wynosić 1 lub 2.
Mam nadzieję że przedstawiona tu teoria nie znudziła was do reszty. Jestem pewny, że dzięki tej odrobinie informacji dalsza część artykułu będzie bardziej zrozumiała.
Sprzęt potrzebny do realizacji zadań
Aby przetestować UART w praktyce będziemy potrzebować konwertera USB-UART, który możecie znaleźć na wyposażeniu zestawu przygotowanego specjalnie na potrzeby tego kursu.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw elementów do przeprowadzenia wszystkich ćwiczeń z kursu STM32 F4 można nabyć u naszego dystrybutora! Zestaw zawiera m.in. płytkę Discovery, wyświetlacz OLED, joystick oraz enkoder.
Zauważ, że po włączeniu UARTA na podglądzie natychmiast pojawiły się wyprowadzenia TX oraz RX. Co, gdy nie podoba nam się ich umiejscowienie i chcielibyśmy wyprowadzić je gdzieś indziej?
Zmiana wyprowadzeń, czyli pin remapping w STM32 F4
Rodzina F4 umożliwia tzw. pin remapping, funkcja ta pozwala na wyprowadzenie peryferii w więcej niż jednym miejscu. Żeby sprawdzić gdzie jeszcze możemy wyprowadzić linie Tx oraz Rx, wystarczy wcisnąć i przytrzymać Ctrl, a następnie kliknąć lewym przyciskiem myszy na wyprowadzenie mikrokontrolera, które chcielibyśmy przenieść w inne miejsce.
Zmiana wyprowadzeń obsługujących transmisję.
Czerwonymprostokątem zaznaczony jest kliknięty z klawiszem Ctrl pin PA10. Na niebieskooznaczono wyprowadzenia PB3 i PB7, które zostały podświetlone podczas kliknięcia.
Takie podświetlenie oznacza, że na te wyprowadzenia może zostać
przeniesiona funkcjonalność aktualnie klikniętego pinu.
Spróbujmy w takim razie przenieść Rx oraz Tx na górną stronę mikrokontrolera. Dzięki poznanemu przed chwilą mechanizmowi wiemy, które wyprowadzenia udostępniają taką możliwość. Aby to zrobić, wystarczy kliknąć na któryś z dozwolonych pinów i wybrać odpowiednią funkcjonalność.
Efekt finalny remappingu.
Wybrany tryb zostanie automatycznie wyłączony na poprzednim pinie i przeniesiony w nowe miejsce. To samo możemy zrobić z pinem Tx.
Zmiana pinów dla komunikacji przez UART.
W efekcie dokonaliśmy remappingu pinów i przenieśliśmy UARTa na górną stronę mikrokontrolera.
Krok 3. Przechodzimy do panelu konfiguracyjnego UARTa.
Panel konfiguracji UART.
Baud Rate - prędkość transmisji. Wartość domyślna, a więc 115200 będzie w zupełności wystarczająca dla wszystkich naszych wymagań (o ile urządzenie po drugiej stronie transmisji obsługuje taką wartość).
Word Length - ilość bitów danych wraz z bitem parzystości przesyłanych w ramce. Zostawiamy domyślną wartość 8, ponieważ nie zamierzamy używać bitu parzystości.
Parity - obecność bitu parzystości. Nie będziemy próbować obsługiwać błędów zakomunikowanych przez niezgodność bitu parzystości (jak 99% miejsc na świecie gdzie zastosowano UART, z którymi się spotkacie).
Stop Bits - standardową wartością używaną w popularnych zastosowaniach jest 1 stop bitu, dlatego tu również zostawiamy wartość domyślną.
Data Direction - jeżeli z jakiegoś powodu potrzebujemy jedynie możliwości nadawania lub odbierania, w tym miejscu możemy to skonfigurować. My będziemy korzystać z obydwu form, dlatego pozostawiamy domyślne Receive and Transmit.
Over Sampling - w celu redukcji błędów transmisji wynikających chociażby z zakłóceń, stan każdego bitu pobierany jest kilku lub kilkunastokrotnie. W naszym wypadku może to być 8 lub 16 razy. Oczywiście większa wartość gwarantuje większą pewność poprawnej transmisji, natomiast mniejsza pozwala osiągnąć wyższe prędkości transmisji.
Dostępne prędkości są dla nas wystarczające, pozostawiamy więc ten parametr z domyślną wartością, czyli 16.
Jak widać, konfiguracja UARTa wymaga od nas jedynie jego włączenia, ponieważ wszystkie domyślne wartości to standardowo używane w większości przypadków parametry transmisji.
Konfiguracja przerwań dla UART
Z UARTa można korzystać w 3 trybach. Blokującym, na przerwaniach oraz z wykorzystaniem DMA. Ze względu na to, że tryby wykorzystujące przerwania i DMA są o wiele wygodniejsze i łatwiejsze w użytkowaniu niż tryb blokujący, na nich skupię się w tym artykule.
Krok 4. Uruchamiamy przerwania dla UARTA.
Uruchomienie przerwań dla UARTA.
Krok 5. Konfigurujemy odpowiednie wyjście GPIO, aby móc sygnalizować zdarzenia zapalaniem i gaszeniem wybranej diody LED. Jeżeli nie wiesz jak to zrobić, wróć do artykułu na temat GPIO.
Krok 6. Konfigurujemy licznik (timer) tak, aby generował przerwanie z częstotliwością 1Hz. Jeżeli nie wiesz jak zrealizować ten punkt, wróć do artykułu o licznikach.
Krok 5. Generujemy projekt pod nazwą 04_UART i importujemy go do IDE.
Uruchomienie UARTa w programie
Naszym pierwszym zadaniem będzie cykliczne przesyłanie wiadomości do komputera.
Krok 1. Załóżmy, że chcemy przesyłać do komputera wiadomości zawierające informację o swoim (kolejnym) numerze. Potrzebujemy więc tablicy przechowującej naszą wiadomość oraz zmienną będącą licznikiem.
size=sprintf(data,"Liczba wyslanych wiadomosci: %d.\n\r",cnt);// Stworzenie wiadomosci do wyslania oraz przypisanie ilosci wysylanych znakow do zmiennej size.
W linii 66 do zmiennej data wpisywana jest wiadomość "Liczba wyslanych wiadomosci: ", a następnie wpisana jest wartość zmiennej cnt. Funkcja sprintf zwraca ilość zapisanych znaków, stąd przypisanie do zmiennej size.
Składnia funkcji sprintf jest taka sama, jak używanej w języku C funkcji printf.
Co oznaczają znaki \n\r? Aby wiadomości wyświetlające się w terminalu były dla nas bardziej przejrzyste, musimy na nim wymusić zakończenie każdej wiadomości przejściem do nowej linii.
Znak \n oznacza LF - Line Feed i wymusza przejście kursora do nowej linii. Znak \r to CR - Carriage Return, czyli znak powrotu karetki. Dzięki niemu kursor po przejściu do nowej linii wraca również na jej początek.
Krok 3. Na końcu pozostało nam wysłać stworzoną wiadomość i zakomunikować rozpoczęcie transmisji zmianą stanu diody świecącej (LED).
Samodzielne odszukanie odpowiedniej funkcji do transmisji za pomocą UARTa jest bardzo proste. Wpisujemy standardowo HAL_UART, wciskamy ctrl+spacja i przeszukujemy wyświetlone podpowiedzi.
C
68
69
HAL_UART_Transmit_IT(&huart1, data, size);// Rozpoczecie nadawania danych z wykorzystaniem przerwan
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);// Zmiana stanu pinu na diodzie LED
Pierwszym parametrem funkcji HAL_UART_Transmit_IT adres struktury konfiguracyjnej. Jako drugi podajemy adres tablicy, która zawiera wiadomość do przesłania. Ostatnim parametrem jest ilość znaków, jaką należy przesłać
Krok 4. Cały powyższy kod umieszczamy w funkcji obsługi przerwania cyklicznego od timera.
C
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
size=sprintf(data,"Liczba wyslanych wiadomosci: %d.\n\r",cnt);// Stworzenie wiadomosci do wyslania oraz przypisanie ilosci wysylanych znakow do zmiennej size.
HAL_UART_Transmit_IT(&huart1, data, size);// Rozpoczecie nadawania danych z wykorzystaniem przerwan
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);// Zmiana stanu pinu na diodzie LED
}
/* USER CODE END PFP */
Krok 5. Pozostało nam już tylko uruchomić licznik tak, aby generował przerwania cykliczne.
C
95
96
97
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim11);
/* USER CODE END 2 */
Krok 6. Budujemy i wgrywamy program na płytkę. Po uruchomieniu, dioda powinna zmieniać swój stan (co sekundę).
Miganie wybranej diody - informacja o wysyłaniu danych (przykładowa animacja).
Podłączenie konwertera USB-UART
Krok 1. Łączymy linie Tx oraz Rx płytki z naszym konwerterem.
Połączenie zestawu Discovery z konwerterem UART.
Uwaga! Podłączając linie Tx/Rx zawsze należy je ze sobą zamienić. Wynika to z tego, że linia Transmit (Tx) mikrokontrolera nadaje dane, a linia Receive (Rx) konwertera będzie je odbierać. Oczywiście działa to analogicznie w drugą stronę.
Schemat połączenia linii Tx oraz Rx.
Wynika z tego, że przy obecnej konfiguracji podłączymy pin PB6 (Tx) mikrokontrolera z pinem Rx konwertera/modułu bluetooth. Linię PB7 (Rx) łączymy z wyprowadzeniem Tx konwertera.
Poza liniami transmisyjnymi trzeba połączyć ze sobą masy układów,
a więc wyprowadzenia GND.
Krok 2. Otwieramy menedżer urządzeń. Aby to zrobić klikamy prawym przyciskiem myszy na Mój komputer i wybieramy Zarządzanie komputerem. Otwieramy zakładkę:
Menedżer urządzeń > Porty (COM i LPT)
Podgląd dostępny portów COM.
Krok 3. Podłączamy konwerter do portu USB w komputerze. Sprawdzamy nazwę i numer portu COM, który się pojawił po podłączeniu sprzętu.
Pojawienie się nowego urządzenia.
W moim wypadku jest to Prolific USB-to-Serial Comm Port, któremu został przypisany numer portu COM7. Ta informacja przyda się zaraz przy konfiguracji oprogramowania.
Odbiór danych - niezbędne oprogramowanie
Aby sprawdzić co wysyła mikrokontroler, musimy zaopatrzyć się w oprogramowanie umożliwiające odbieranie i wysyłanie danych poprzez port szeregowy. Skorzystamy z programu RealTerm.
Krok 1.Pobieramy najnowszą wersję programu (w momencie pisania kursu jest to 2.0.0.70). Krok 2. Instalujemy program. Krok 3. Uruchamiamy RealTerma jako administrator.
Uruchomienie w trybie normalnym może generować problemy.
Uruchamiamy program jako administrator.
Krok 4. Przechodzimy do zakładki Port i zmieniamy parametry transmisji na takie, które ustawiliśmy konfigurując UART w mikrokontrolerze.
Rozwijana lista dostępnych wartości Baud Rate zawiera
większość standardowo używanych prędkości transmisji.
Zmieniamy wartość Baud Rate na 115200. Port zmieniamy na ten, który został przypisany naszemu konwerterowi (sprawdzaliśmy to wcześniej w menadżerze urządzeń). Reszta ustawień domyślnie jest zgodna z tym co ustawiliśmy w Cube. Na końcu klikamy przycisk Change żeby nasze ustawienia zostały wdrożone do otwartego kanału komunikacyjnego.
Uruchomienie transmisji.
W obszarze zaznaczonym na niebiesko przedstawione są obecne parametry transmisji. Skrót 8N1 oznacza 8 bitów danych, 0 bitów parzystości i 1 bit stopu.
Taki zapis jest bardzo popularny i często stosowany
przy opisie parametrów transmisji UART.
Jeżeli wszystkie przedstawione dotąd instrukcje wykonaliśmy poprawnie, wraz z kliknięciem przycisku Change w terminalu powinny się zacząć pojawiać odbierane wiadomości.
Zauważmy, że po każdej z nich znaki nowej linii (LF - Line Feed) oraz powrotu kursora na początek linii (CR - Carriage Return) są widoczne. Aby takie znaki nie były wyświetlane, wystarczy zmienić tryb wyświetlania danych na ANSI.
Podgląd wyświetlanych danych.
Dodatkowo na powyższym obrazku zaznaczone są trzy przydatne sekcje. W zielonym prostokącie znajdują się parametry terminala wyświetlającego odebrane dane. Domyślnie jest to 16 linii o szerokości 80 znaków.
W obszarze zaznaczonym na niebiesko znajduje się pole oznaczające odbieranie danych. Zauważmy, że co sekundę rozświetla się ono na bardzo krótko. Taki sam rodzaj komunikowania stosowany jest w konwerterach UART - na naszym jest to czerwona dioda LED opisana jako RXD.
Bardzo prostą lecz przydatną funkcjonalność dostarcza oznaczony na fioletowo przycisk Clear. Pozwala on na wyczyszczenie aktualnej zawartości terminala.
Odbieranie danych przez UART na STM32 F4
Potrafimy już przesyłać dane z mikrokontrolera i wyświetlać je na komputerze. Teraz spróbujemy odebrać dane wysłane z komputera.
Krok 1. Pierwszą rzeczą jaką zrobimy będzie zadeklarowanie zmiennej do przechowywania odebranych danych.
Krok 2. Teraz musimy uruchomić nasłuchiwanie na kanale UART. Służy do tego funkcja Receive.
C
107
108
109
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &Received, 1);
/* USER CODE END 2 */
Pierwszym parametrem funkcji jest standardowo wskaźnik na strukturę konfiguracyjną. Drugim jest wskaźnik na obszar pamięci, do którego będą wpisywane dane. Trzeci parametr, to oczekiwany rozmiar wiadomości, innymi słowy - ilość odebranych znaków, po których zostanie wywołane przerwanie.
W tym miejscu mamy dwie możliwości.
Jeżeli chcemy odbierać wiadomości, które składają się tylko z jednego znaku (np. proste komendy do Linefollowera typu 1-start i 0-stop) lub chcemy przetwarzać każdy znak odbieranej wiadomości po kolei, ilość odebranych danych należy ustawić na 1.
Jeśli odbieramy dłuższe ciągi znaków i wiemy dokładnie ile znaków jest wysyłanych po stronie nadawczej, wygodniej jest ustawić ilość odebranych znaków na tę właśnie wartość.
W dalszej części artykułu zaprezentowane zostaną przykłady obydwu zastosowań.
Krok 3. Po odebraniu konkretnej liczby znaków (sprecyzowanej w parametrze wywołania funkcji Receive) wywołane zostaje przerwanie, które możemy obsłużyć funkcją:
Poza funkcją RxCpltCallback (Receive Complete Callback), do dyspozycji mamy podobną funkcję TxCpltCallback, która zostanie wywołana po zakończeniu transmisji danych. Jeżeli dla jakiegoś zastosowania taka informacja byłaby przydatna, w ten sposób można ją otrzymać.
Odbieranie wiadomości o rozmiarze "1"
W tym przykładzie będziemy wysyłać do mikrokontrolera bardzo proste instrukcje sterujące. Znak 0 oznaczać będzie wiadomość STOP, a 1 oznaczać będzie START. Zadaniem programu będzie sygnalizacja obecnego stanu diodą LED oraz odesłanie odebranej instrukcji z powrotem.
Obsługa przerwania dla tego zadania będzie wyglądać następująco.
C
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Aby przetestować działanie powyższego mechanizmu, przechodzimy do zakładki Send w programie RealTerm. Wpisujemy wiadomość w pole zaznaczone na czerwono i wysyłamy przyciskiem Send ASCII.
Wysyłanie wiadomości z komputera.
W terminalu możemy zaobserwować działanie naszego programu. Dodatkowo dioda LED na płytce Discovery sygnalizuje w jakim stanie (START/STOP) znajduje się aktualnie nasz program.
Jaki problem wynika z takiego podejścia? Jeżeli do mikrokontrolera zostanie wysłana wiadomość składająca się z większej ilość znaków, przerwanie zostanie wywołane wielokrotnie w bardzo krótkim czasie, co spowoduje błędy. Żeby to sprawdzić, wystarczy wysłać z RealTerma wiadomość składającą się z kilku znaków.
Wysłanie zbyt długiej wiadomości.
Odbieranie wiadomości o rozmiarze większym od "1"
Aby zapobiec takiej sytuacji, wystarczy sprecyzować, że przerwanie ma się wywołać po odebraniu większej ilości znaków. W takiej sytuacji najpierw odbierana jest cała wiadomość, a dopiero po zakończeniu całego procesu zostaje wywołane przerwanie.
Problem jaki się tu pojawia, to znajomość konkretnej ilości znaków,
które zostaną przesłane w wiadomości.
Czemu? Załóżmy, że uruchamiamy nasłuchiwanie i oczekujemy wiadomości o długości 10.
Tablica odbieranych danych. Żadne dane nie zostały jeszcze odebrane (pusta tablica).
Wiadomość, która jest do nas wysłana ma tylko 7 znaków. W takiej sytuacji dane zostają odebrane i zapisane w tablicy, ale przerwanie informujące nas o zakończeniu transmisji nie zostanie wywołane.
Tablica odbieranych danych. Odebrano wiadomość o długości 7. Brak przerwania.
Przypuśćmy, że zostanie do nas wysłana kolejna wiadomość. Tym razem składająca się z 2 znaków. W buforze mamy 7 znaków, więc teraz będzie ich 9. Ponieważ oczekujemy odebrania 10 znaków, przerwanie nadal się nie wywoła, chociaż mamy już dwie odebrane wiadomości w buforze.
Tablica odbieranych danych. Odebrano drugą wiadomość o długości 2. Brak przerwania.
Kolejna wiadomość, która nadejdzie ma długość 4 znaków. Co się stanie w tym momencie? Po odebraniu pierwszego znaku nowej wiadomości w buforze będzie 10 znaków, a więc tyle ile oczekujemy. Zostaje wywołane przerwanie.
Tablica odbieranych danych. Odebrano pierwszy znak trzeciej wiadomości. Przerwanie wywołane.
Tym samym bufor jest zerowany. W odbieranej wiadomości pozostały jednak jeszcze 3 znaki, które zostają wpisane do pustego bufora.
Tablica odbieranych danych. Bufor został wyzerowany. Wpisano do niego pozostałe znaki trzeciej wiadomości. Brak przerwania.
Co z tego wynika?
Odebraliśmy 3 wiadomości, otrzymując powiadomienie w formie przerwania w środku ostatniej. Tym samym dostajemy bufor zawierający dwie pełne i kawałek trzeciej wiadomości. Jak to teraz obsłużyć? Cała sytuacja jest zupełnie bez sensu...
Na szczęście rozwiązanie tego problemu jest bardzo proste. Wystarczy zawsze wysyłać wiadomości o tej samej, wcześniej sprecyzowanej dla obydwu stron transmisji, długości.
Zmusza nas to jednak do tworzenia ramek wiadomości o takiej samej długości, co wydaje się bardzo problematyczne. Istnieje jednak bardzo prosty mechanizm pozwalający na ominięcie tego problemu, który opisałem poniżej.
Lepsza wersja komunikacji
Spróbujmy więc przetestować przedstawione rozwiązanie w praktyce! Pierwszą zmianą jaką musimy dokonać jest przekształcenie zmiennej przechowującej odebrany znak w tablicę.
Ostatnia modyfikacja czeka nas w funkcji main, gdzie trzeba zaktualizować parametry wywołania funkcji rozpoczynającej nasłuchiwanie do naszych aktualnych wymagań.
C
93
94
95
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, Received, 10);
/* USER CODE END 2 */
Teraz możemy przetestować działanie nowo stworzonego mechanizmu wysyłając wiadomość o długości 10 znaków z RealTerm.
Wysyłanie większej ilości znaków.
Wysyłanie wiadomości o stałej długości z mikrokontrolera
Zauważmy, że za każdym razem wysyłamy z mikrokontrolera dokładnie tyle znaków, z ilu składa się wiadomość. Możemy tak zrobić odczytując wartość zwracaną przez funkcję sprintf i podając ją następnie jako parametr funkcji transmitującej dane.
HAL_UART_Transmit_IT(&huart1, Data, size);// Rozpoczecie nadawania danych z wykorzystaniem przerwan
W celu wykonania pewnego testu, zmieńmy tryb wyświetlania danych z powrotem na ASCII.
Zmiana trybu wyświetlania danych.
Dzięki temu będziemy widzieć wszystkie odbierane przez RealTerm znaki. W tym wypadku, poza znakami standardowej wiadomości będą to tylko znaki CR i LF, wyświetlane w terminalu przy ustawieniu ASCII. Dzięki temu widzimy, że wysyłamy dokładnie tyle znaków, ile mają wszystkie znaki użyteczne wiadomości plus dodane znaki nowej linii.
Podgląd odebranych danych.
Ta informacja przyda nam się zaraz w porównaniu do innego przykładu. Jeżeli jednak chcielibyśmy komunikować się z innym mikrokontrolerem, który ma ustawione odbieranie na stałą wartość, za każdym razem musielibyśmy wysyłać wiadomości o tej właśnie stałej liczbie znaków.
Aby to zrobić, wystarczy ustawić trzeci parametr w funkcji HAL_UART_Transmit_IT na stałą szerokość wiadomości.
C
56
57
58
59
60
61
62
63
64
65
66
67
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Zmienna size staje się w takim razie zbędna. Co się dzieje teraz z częścią wiadomości, w której nie ma żadnych informacji? Możemy to zaobserwować oglądając odbierane dane w trybie ASCII.
Podgląd odebranych danych.
Wysłana wiadomość ma dokładnie 32 znaki (włącznie ze znakami LF oraz CR). Cała reszta zostaje zapełniona znakami zerowymi \0, które RealTerm wyświetla jako NULL. Widać, że jest ich dokładnie 8, a więc tyle, ile brakuje, aby cała wiadomość miała długość 40 znaków.
Teraz w urządzeniu odbiorczym, którym mógłby być inny mikrokontroler wystarczy ustawić odbieranie na długość 40 znaków. W ten sposób w urządzeniu odbiorczym przerwanie wywoła się zawsze w odpowiednim czasie, znaki zerowe na końcu nie będą w niczym przeszkadzać i tym samym transmisja zawsze będzie przebiegać poprawnie i efektywnie. W ten właśnie sposób można w praktyce rozwiązać problem wysyłania/odbierania długich wiadomości o różnej długości.
Obsługa UART z wykorzystaniem DMA
Spróbujmy teraz zaprząc do tego wszystkiego DMA. Dzięki temu odciążymy procesor w jeszcze większym stopniu. Jak zwykle z DMA, tak i teraz, cały proces jest bajecznie prosty.
Konfiguracja Cube
W zakładce DMA Settings panelu konfiguracyjnego UARTa dodajemy dwa kanały. Jeden dla transmisji, a drugi dla odbioru.
Ponowna konfiguracja w Cube.
DMA ma przesyłać pojedyncze bajty w trybie normalnym, więc pozostawiamy ustawienia domyślne.
W zakładce NVIC nie wyłączamy przerwań pochodzących od UARTa,
ponieważ dalej chcemy mieć informacje o momencie odebrania pełnej wiadomości.
Obsługa DMA w programie
Aby uruchomić DMA w komunikacji UART, jedyne co musimy zrobić, to zamienić wywołania funkcji do obsługi UARTa na te korzystające z DMA. Nasz ostatni program po tej drobnej modyfikacji będzie w takim razie wyglądał następująco.
C
60
61
62
63
64
65
66
67
68
69
70
71
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Rozpoczęcie nasłuchiwania w funkcji main też oczywiście musi zostać zamienione na funkcję wykorzystującą DMA.
C
95
96
97
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, Received, 10);// Rozpoczecie nasluchiwania na dane z wykorzystaniem DMA
/* USER CODE END 2 */
Problem - zagadka
Po przerzuceniu poprzedniego kodu na korzystanie z DMA, na końcu transmitowanej wiadomości dodawany był jeden dodatkowy znak. Jedyne rozwiązanie tego problemu jakie znalazłem, to zmiana deklaracji zmiennej Data na zmienną globalną lub zmienną statyczną (jak w przykładzie powyżej).
Jeśli ktoś z Was znajdzie rozwiązanie tego problemu, koniecznie dajcie znać w komentarzach!
Podsumowanie
W tym odcinku nauczyliśmy się wysyłać i odbierać wiadomości za pomocą interfejsu UART. Wiemy już jak go skonfigurować tak, aby wykorzystywał w tym celu przerwania oraz DMA. Omówiliśmy także kilka problemów związanych z implementacją, które pojawiają się w praktyce.
W następnym artykule zajmiemy się bardziej zaawansowanymi możliwościami, jakie oferują nam licznik (timery) mikrokontrolerów STM32. Jak zwykle, jeśli macie jakiekolwiek problemy, pytania czy wątpliwości związane z UARTem, piszcie śmiało w komentarzach!
Autor kursu: Bartek (Popeye) Kurosz Redakcja: Damian (Treker) Szymański
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...