Komunikacja UART na STM32. Transmisja do PC | STM32 na Rejestrach #6

msalamon.pl 1 tydzień temu
Zdjęcie: Komunikacja UART na STM32


Komunikacja UART na STM32. Transmisja do PC | STM32 na Rejestrach #6

Poznaliśmy już podstawowe operacje na GPIO, oraz sposoby na opóźnianie poszczególnych zadań. Dzisiaj wyjdziemy poza mikrokontroler. Skomunikujemy się z naszym komputerem poprzez interfejs UART.

W tym wpisie zajmiemy się transferem, czyli wysłaniem danych z STM32 gdzieś na zewnątrz. Tym zewnętrznym urządzeniem będzie nasz komputer. Odbierzemy dane na terminalu, który to emuluje port szeregowy, którym w zasadzie jest interfejs UART.

Chcesz nauczyć się programowania STM32 na rejestrach w pełni? Zapraszam do mojego pełnego kursu na ten temat: https://stm32narejestrach.pl

Seria STM32 na Rejestrach na YouTube

Wpisy te powstają równolegle do serii na moim YouTube o tej samej tematyce. jeżeli wolisz wersję video to zapraszam Cię właśnie tam. Artykuły te są skrótem z tego, co pokazuję na YouTube.

Interfejs UART

Interfejs UART jest jednym z najprostszych sposobów na komunikację między urządzeniami. choćby jego nazwa oznacza coś prostego i uniwersalnego

  • U – Universal, czyli do wszelakich zastosowań
  • A – Asnchronous, asynchroniczny, czyli bez sygnały zegarowego utrzymującego rytm komunikacji
  • R – Receiver, odbiornik – inaczej Rx
  • T – Transmiter, nadajnik – inaczej Tx

Jest to interfejs dwukierunkowy. Każdy z kierunków na swój oddzielny pin. Komunikacja po UART odbywa się tylko między dwoma urządzeniami. Są oczywiście sposoby na powiększenie liczby urządzeń, ale w takiej „normalniej” formie jest to komunikacja 1:1. Linie Tx i Rx łączymy krzyżowo tzn. Nadajnik jednego urządzenia łączymy z odbiornikiem drugiego i odwrotnie.

Komunikacja jest asynchroniczna co oznacza, że nie ma sygnału taktującego transmisję. Obudwie strony muszą się “umówić” co do prędkości tansmisji i czasów trwania poszczególnych bitów. W UART wykorzystuje się często standardowe prędkości wyrażane w takiej śmiesznej jednostce – w bodach na sekundę. Są to m.in 9600, 19200, 38400 115200 baud.

Do tego oprócz danych dochodzą jeszcze tzw. bity startu, stopu oraz parzystości, który to jest prymitywną formą sprawdzania poprawności przesyłanych danych. Tutaj najpopularniejszy format tzw. ramki to 8n1 co oznacza 8 bitów danych, brak bitu parzystości i jeden bit stopu.

UART jest wykorzystywany w popularnym standardzie RS232. W zasadzie można powiedzieć, iż jest to to samo tylko RS232 to dodatkowy standard napięciowy. W komputerze musimy mieć taki sam interfejs. Od wielu lat niestety RS232 nie jest już dostępny….

Nie ma co się martwić, bo jest na to bardzo prosty sposób. Nazywa się on konwerter USB – UART. Podłączasz takiego małego pendrive’a do USB i masz już UART w formie wirtualnego portu COM. Tyle. Takie konwertery znajdziesz w moim sklepie. Jest ich kilka rodzajów, więc znajdziesz coś dla siebie.

(baner z konwerterem)

My natomiast korzystamy z płytki Nucleo, a ona już ma taki konwerter w sobie. Dokładnie do w ST-Linku, który jest programatorem, debugerem i właśnie takim konwerterem. Wystarczy zaintalować sterowniki do ST-Linka, aby konwerter działał.

Gdzie on jest podłączony do STM32? Sprawdźmy to.

Podłączenie USB-UART ST-Linka z Nucleo do STM32

Najwygodniej będzie wziąć schemat płytki Nucleo. W nim szukamy fragmentu odpowiadającego za część programatora ST-Link. Szukamy pinów, które będą oznaczone jako TX i RX. Mamy coś takiego jak STLK_TX i STLK_RX i to są nasi kandydaci. Są tutaj tzw. etykiety, więc powinniśmy szukać takich samych etykiet po stronie mikrokontrolera, który programujemy. Możemy klikać po tych etykietach.

Trop STLK_TX przeniósł nas na ogólny schemat blokowy. Widzimy na nim, iż pin ten podłączony jest do etykiety VCP_RX. Zwróć uwagę, iż jest połączenie krzyżowe! Idziemy za tą etykietą.

I już prawie to mamy. Pin VCP_RX może byc podłączony do PA3 lub PB7. Widzimy tutaj zworki, które nazywają się SBxx. DNF oznacza, iż ta zworka nie jest obsadzona, więc nie ma połączenia. Drugie zworki są polutowane na płytce, a przynajmniej tak powinno być domyślnie. Zatem widzimy, iż UART idący do ST-Linka jest na pinach PA2 i PA3.

Zerknijmy w Datasheecie czy są tam opisane te piny jako UART.

Widzimy, iż jest tutaj USART2. To jest bardzo ważna informacja! W naszym C031 mamy dwa UARTy, a większe STM32 mają ich jeszcze więcej. Zawsze więc będziemy się odwoływali do UARTa razem z jego numerem.

Konfiguracja pinów UART na STM32

Piny odnalezione – trzeba je skonfigurować. Aby “wypuścić” interfejs UART na piny mikrokontrolera należy odpowiednio skonfigurować piny. Są to tzw. Alternate Functions. Trzeba podać w porcie GPIO informację, iż chcemy dany pin fizyczny podłączyć do funkcji alternatywnej. Przekierowujemy ten pin na interfejs UART.

Zaczniemy od gołego i skonfigurowanego projektu. Możemy skopiować nasz projekt początkowy lub którykolwiek i wyczyścić kod. Wiele do tej pory nie pisaliśmy, więc da radę poczyścić te projekty

Skoro konfigurujemy funkcję alternatywną w GPIO to najpierw trzeba włączyć odpowiedni zegar GPIO. Działamy na porcie A.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; }

Teraz musimy przekierować ten pin w rejestrach GPIO. Po pierwsze należy ustawić tryb pinu na AF, czyli Alternate Function. Robimy to w rejestrze GPIOx_MODER.

WAŻNE! W tej lekcji spróbujmy ustawić tylko te bity, które po resecie nie są w wymaganym przez nas stanie. Zobaczysz, iż kod może być dzięki temu krótszy. Pamiętaj też, iż jest to po resecie. Nie oznacza to, iż podczas rekonfiguracji podczas pracy mikrokontrolera będzie to samo.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); }

Teraz trzeba wybrać którą funkcję alternatywną ustawiamy. Służy do tego rejestr GPIOx_AFRH i GPIOx_AFRL. Tylko tutaj jest to opisane dosyć enigmatycznie. Każdy z pinów ma aż 4 bity, co daje 16 możliwości. Mamy do ustawienia PA2 i PA3, więc interesują nas AFSEL2 i ASFEL3. Tylko na które dokładnie funkcje alternatywne je ustawić? To znajdziesz w Datasheecie poniżej tabeli z rozpiską pinów.

Dla Pinu PA2 UART2_TX znajduje się pod AF1. Dla PA3 UART_RX mamy również na AF1. Musimy więc ustawić to w ten sposób.

Jest jeszcze jedna “przeszkoda”. W nagłówkach CMSIS nie ma rejestrów AFRH i AFRL. Jest tablica dwuelementowa AFR.

Literki H i L oznaczają rejestr górny i dolny. W CMSIS są połączone. AFR[0] to jest rejestr dolny (czyli L), a AFR[1] to górny (czyli H). Po resecie rejestr ten ma w całości wartość zero. Ustawimy tym razem tylko ten bit, który potrzebujemy. W ten sposób uzyskujemy poniższy kod.

Oddzielimy sobie konfugurację RX i TX, aby mieć to uporządkowane.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0; // Pin PA3 - RX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE3_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;

Czy musimy ustawić coś jeszcze? Zerknijmy co mówi Reference Manual na temat Alternate Functions.

Możemy tutaj wyczytać, iż dla AF konfigurujemy również pozostałe elementy GPIO. Bloki wejściowe są wykorzystywane te same, więc są tutaj również Pull-up, pull-down oraz wybór wyjścia jako Push-Pull lub Open Drain. Trzeba to skonfigurować. Jak? Zastanówmy się.

Najpierw pin RX, czyli odbiornik. Skoro odbiera, to pokrywa się dla niego część GPIO Input. Aby część Output mu tak na 100% nie przeszkadzała, dla pewności ją ustawimy jako Open Drain. Po resecie jest push-pull, więc musimy to zmienić. jeżeli chodzi o prędkość GPIO to tutaj wystarczy najniższe ustawienie, czyli domyślne po resecie. Rezygnujemy też z rezystorów PUPD. Nie są konieczne przy interfejsie UART. Ustawmy teraz to co wymaga ustawienia.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0; // Pin PA3 - RX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE3_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0; GPIOA->OTYPER |= GPIO_OTYPER_OT3; }

Teraz TX. Jest wyjściem, więc interesuje nas to, aby na tym wyjściu ustawiać poprawnie stany wysokie i niskie. Musimy tutaj ustawić (a w zasadzie to nie, bo już jest ustawione po resecie) Push-pull, aby to osiągnąć. PUPD również odpuszczamy bo nie mają sensu przy PP. Prędkość najniższa.

Kod pozostaje więc bez zmian!

Konfiguracja UART na STM32

Mamy ustawione piny, więc możemy ustawić sam UART. Jest to oddzielny blok więc w pierwszej kolejności trzeba mu podać taktowanie zegarem. Robimy to podobnie jak dla GPIO tylko we właściwym rejestrze RCC.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0; // Pin PA3 - RX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE3_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0; GPIOA->OTYPER |= GPIO_OTYPER_OT3; // Enable USART2 Clock RCC->APBENR1 |= RCC_APBENR1_USART2EN; }

Jak już mamy doprowadzony zegar do UART to możemy go odpowiednio ustawić. Pierwszą rzeczą którą ustawimy będzie prędkość transmisji. Jak wspominałem wcześniej urządzenia muszą się umówić na jakąś prędkość. Będziemy działać na prędkości 115200 baud. To jedna ze standardowych i taka nie za szybka, nie za wolna. Moja ulubiona

Służy do tego rejestr UARTx_BRR. należy wpisać tam odpowiednią wartość którą uzyskuje się z częstotliwości taktowania interfejsu UART oraz prędkości na jakiej chcemy nadawać. Wzór jest przedstawiony w Reference Manualu.

Jak widzisz mamy tutaj dwie możliwości. Zależą one od ustawionego oversamplingu. Po resecie jest ustawiony na 16 (zadanie dla Ciebie – potwierdź to w rejestrach i napisz w komentarzu który bit w którym rejestrze za to odpowiada!). Obowiązuje więc nas pierwszy wzór.

Skąd wziąć zegar, który idzie do UART? Możemy posłużyć się schematem z Reference Manual (strona 108).

Widzimy na nim, iż do UART1 prawdopodobnie biegnie zegar PCLK. Ale my mamy UART2, więc gdzie on jest? jeżeli nie ma wyróżnionego multipleksera dla danego peryferium to będzie ono taktowane z APB peripherals. W większych układach spotkasz dwie szyby APB, które mogą mieć różne taktowania. Wtedy w Datasheecie musisz sprawdzić pod który APB jest podłączony konkretny UART. Zerknijmy w nasz DS (strona 10).

Widać jak krowie na rowie, iż UART1 i 2 rozmawiają przez szynę APB, więc musimy znać częstotliwość PCLK.

Z lekcji nr 3 pamiętamy, iż na HCLK mamy 12 MHz, bo zegar wewnętrzny jest podzielony przez 4. Z HCLK tworzony jest PCLK, więc musimy sprawdzić jak jest ustawiony dzielnik pomiędzy HCLK, a PCLK. Z Reference Manuala wynika, iż jest to wartość 1.

Na UART idzie więc 12 MHz i to tej wartości użyjemy. Musimy ją wpisać w podstawowej jednostce. Dzielimy ją od razu przez wartość docelową, czyli 115200.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0; // Pin PA3 - RX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE3_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0; GPIOA->OTYPER |= GPIO_OTYPER_OT3; // Enable USART2 Clock RCC->APBENR1 |= RCC_APBENR1_USART2EN; // 115200 baudrate USART2->BRR = 12000000/115200; }

Wartość po dzieleniu to ok 104. Niestety nie jest równo i zostaje coś po przecinku. W tym miejscu muszę Ci wspomnieć o tym, iż istnieją tzw. częstotliwości przyjazne UART. Są to takie taktowania dla których ten przecinek właśnie jest minimalny, a na ogół zerowy. Wpływa to na ilość błędów w transmisjach. Na szczęście dla nas tutaj nie będzie aż tak dużo błędów, jednak lepiej jest korzystać właśnie z tych przyjaznych taktowań.

W dokumentacjach STM32 możesz znaleźć wzór na błąd transmisji. STM32 ma tak dużo dzielników, iż według mnie nie ma czym sie przejmować. Z łatwością uzyskasz błędy poniżej 1%, a to już prawie ideał

Wiesz, iż przez cały czas nie włączyliśmy UARTa? Mamy do niego jedynie doprowadzony zegar. Musimy włączyć:

  1. UART sam w sobie
  2. Transmiter osobno
  3. Receiver też osobno

To są 3 pojedyncze bity do włączenia w rejestrach już samego UARTa. Sprawdźmy które to w Reference Manualu. Możesz włączyć każde z osobna, lub wszystkie jednocześnie w jednej linii.

void UART2_Config(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Pin PA2 - TX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE2_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0; // Pin PA3 - RX AF Mode GPIOA->MODER &= ~(GPIO_MODER_MODE3_0); GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0; GPIOA->OTYPER |= GPIO_OTYPER_OT3; // Enable USART2 Clock RCC->APBENR1 |= RCC_APBENR1_USART2EN; // 115200 baudrate USART2->BRR = 12000000/115200; // USART2->CR1 |= USART_CR1_UE; // UART Enable // USART2->CR1 |= USART_CR1_TE; // Transmitter Enable` // USART2->CR1 |= USART_CR1_RE; // Receiver Enable USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; }

Musimy jeszcze zadbać o format ramki. Potrzebujemy ustawić 8n1. Służą do tego bity M0, M1, PCE w rejestrze CR1 oraz STOP w CR2. Sprawdźmy jak je ustawić. Najpierw CR1.

M0 i M1 to bity odpowiedzialne za długość danych. Co interesujące nie są obok siebie w rejestrze. Musimy je ustawić jako 0b00. Cały rejestr po resecie ma zera, więc nie wymaga to od nas akcji.

PCE oznacza kontrolę parzystości. 8n1, n oznacza, iż nie chcemy tej funkcji. Musi być ustawiony na 0, więc po resecie jest już ok.

Bity STOP w CR2 konfigurują długość bitów stopu w ramce UART. Chcemy jeden, więc powinny być ostawione na 0b00 co ma miejsce po resecie.

Wychodzi na to, iż format ramki jest domyślnie taki jak potrzebujemy. interesujące skąd się to wzięło wiedząc, iż to najpopularniejszy format ramki UART?

I to wszystko, czego będziemy potrzebowali w podstawowej i najprostszej konfiguracji. Oto kod, który konfiguruje nam UART2 do pracy.

Transmisja po UART na STM32

W pierwszej kolejności wyślemy coś do komputera. Mając skonfigurowany UART musimy jedynie:

  1. Włączyć transmitter. Zrobiliśmy to przy konfiguracji.
  2. Wrzucić bajt do rejestru transmitującego.
  3. Poczekać aż zakończy transmisję. Oznacza to bit TXFNF = 1, czyli rejestr nie zapełniony (może przyjąć dane).
  4. Powtarzamy 2-3 tyle razy ile mamy bajtów do wysłania.

W C031 mamy dwa rejestry na dane. Jeden odbiorczy (RDR), jeden nadawczy (TDR). Częściej jednak będziesz miał jeden wspólny (DR) w którym ukryte pod spodem są tak naprawdę dwa. Czytanie i pisanie tak czy siak odbywa się z użyciem innych rejestrów choćby jeżeli jest to ukryte dla programisty pod postacią jednych „drzwi”.

Spróbujmy zatem coś wysłać na UART. Wykorzystamy Timer programowy, aby wysyłać cyklicznie. Wrzucenie danych do bufora jest proste. Co innego sprawdzenie, czy zakończono wysyłanie.

// Software Timers variables uint32_t Timer_UART2; /* Loop forever */ while(1) { // UART2 transmit if((GetSystemTick() - Timer_UART2) > UART2_TIMER) { Timer_UART2 = GetSystemTick(); // Put data to transmit register USART2->TDR = 'A'; // // ..wait for end of the transfer // } }

Bit TXFNF znajduje się w rejestrze statusowym ISR. Zero oznacza, iż kolejka FIFO która prowadzi do rejestru transmisyjnego jest pełna i nie możemy nic wrzucić. Jedynka oznacza, iż można wrzucać.

Zaraz?! Jaka kolejka FIFO? UART w STM32C031 ma funkcję kolejkowania danych do wysyłki. Domyślnie kolejka FIFO jest wyłączona (zadanie dla Ciebie: sprawdź który to bit). Traktujemy zatem ten bit statusowy jako można/nie można wrzucać danych do TDR.

Czekamy aż tutaj pojawi się jeden. Sprawdzając więc ten bit w pętli while musimy odwrócić warunek logiczny.

// Wait till end of transmission while(!(USART2->ISR & USART_ISR_TXE_TXFNF)) { // Wait for end of transmition }

Dzięki temu dopóki TDR nie będzie wolny, to będziemy czekać. TDR zajęty oznacza tyle, iż transmisja po UART jeszcze się nie zakończyła.

Kod main wygląda więc następująco:

int main(void) { // Configure SysTick Timer SysTick_Config(12000000 / 1000); // UART2 Initialization UART2_Config(); // Timers uint32_t Timer_UART = GetSystemTick(); /* Loop forever */ while(1) { // UART Task if((GetSystemTick() - Timer_UART) > UART_TIMER) { // Reload Timer Timer_UART = GetSystemTick(); USART2->TDR = 'A'; // Put byte 'A' to send // Wait till end of transmission while(!(USART2->ISR & USART_ISR_TXE_TXFNF)) { // Wait for end of transmition } } } }

Sprawdźmy czy to działa. Korzystam na PC z RealTerm. Ustawiamy w nim odpowiedni COM na którym pojawił się ST-Link, prędkość 115200 i czekamy na to, co przyjdzie z Nucleo.

Przychodzi nam regularnie literka A.

Jak wysłać na przykład cały napis? Tego dowiesz się w kolejnym wpisie. Ten już wystarczająco mocno się rozciągnął. W przyszłym artykule pokażę Ci jak wysłać dłuższe napisy, a także jak odebrać coś z komputera i zareagować na to, co odbieramy w taki bardzo prosty sposób.

Podsumowanie

Komunikacja UART to absolutna podstawa w świecie embedded. Pozwala na wymianę danych między STM32 a komputerem, co otwiera przed nami nowe możliwości – od debugowania po sterowanie urządzeniami zewnętrznymi.

W tym wpisie skonfigurowaliśmy USART2 na STM32, przekierowaliśmy jego piny do odpowiednich funkcji alternatywnych i ustawiliśmy adekwatne parametry transmisji. Dzięki temu możemy już wysyłać dane do komputera i odbierać je w terminalu szeregowego portu COM. Wykorzystaliśmy konwerter USB-UART, ale w przypadku płytek Nucleo mamy już wbudowany interfejs w ST-Link.

Wysłaliśmy pierwszy pojedynczy znak. W kolejnych wpisach zajmiemy się wysyłaniem całych napisów oraz odbieraniem danych z komputera. Dzięki temu będziemy mogli w pełni wykorzystać UART w praktycznych zastosowaniach.

Daj znać w komentarzu, czy ten wpis był dla Ciebie przydatny! Masz pomysł na kolejne tematy w serii STM32 na Rejestrach? Podziel się swoją sugestią!

Zapraszam Cię również do mojego sklepu, gdzie znajdziesz NUCLEO-C031C6, z którego korzystamy w tej serii, konwertery USB-UART oraz inne akcesoria do STM32:
https://sklep.msalamon.pl

Kod projektu znajdziesz na GitHubie:
https://github.com/lamik/stm32narejestrach_6

Idź do oryginalnego materiału