Vaja 5: UART

Namen tokratne vaje je, da se spoznate s serijsko komunikacijo po protokolu UART.

Protokol UART

UART je kratica za Universal Asynchronous Receiver/Transmitter. Gre za relativno enostaven protokol za serijsko komunikacijo, ki je izjemno razširjen. V preteklosti se je uporabljal predvsem v navezavi s standardom RS-232, danes se uporablja predvsem za komunikacijo z moduli za žično (Ethernet) in brezžično komunikacijo (Bluetooth, WiFi, ZigBee, …), GPS senzorji in mnogimi drugimi napravami. Pogosto se uporablja tudi za t.i. logiranje/beleženje (logging), ki se pošilja na osebni računalnik ali naprave namenjenje shranjevanju “logov”. Obstaja tudi sinhrona različica protokola, vendar se bomo pri tokratni vaji osredotočili predvsem na asinhrono različico, ki je bistveno bolj razširjena.

Najenostavnejši in hkrati najpogostejši priklop dveh UART naprav je prikazan na Slika 5.1. Na napravah uporabimo pina TX (Transmit) in RX (Receive) ter GND. Pin TX je namenjen pošiljanju, RX pa sprejemanju, zato jih povežemo križno.

Slika 5.1: Preprosta povezava UART naprav

Dodatno lahko na napravah uporabimo še pina CTS (Clear to Send) in RTS (Request to Send). Omenjena pina sta namenjena temu, da se UART napravi uskladita kdaj sta pripravljeni za sprejem/pošiljanje podatkov. Če je naprava pripravljena na sprejem podatkov, pin RTS postavi na nizko logično vrednost. Na pinu CTS pa naprava preveri, če je naprava na drugi strani pripravljena za sprejem. Ko oba pina na povezanih napravah povežemo križno, kot je prikazano na Slika 5.2, dobimo povezani napravi, ki pošiljata zgolj, ko je naprava na drugi strani pripravljena. Preverjanje in nastavljanje RTS in CTS pinov se v UART napravi lahko izvaja brez zunanje programskega posredovanja. Hardware Flow Control se uporablja predvsem pri visokih prenosnih hitrostih, ko prenašamo velike količine podatkov ali ko je naprava, ki sprejema podatke počasna.

Slika 5.2: Povezava UART naprav z Hardware Flow Control

Asinhroni prenos

V mirujočem stanju mora biti prenosna linija v visokem logičnem stanju (logična enica), kar se običajno zagotovi s pull-up uporom. Pojavitev ničle, ko je linija v mirujočem stanju, označuje začetek prenosa. Omenjena ničla se zato imenuje start bit. Start bitu sledi 8 ali 9 podatkovnih bitov, neobvezni paritetni bit, ter stop bit(i). V primeru, da paritetnega bita ne potrebujemo stop bit sledi neposredno za podatkovnimi biti. Primer prenosa števila 0x61 (0110 0001) brez paritetnega bita prikazuje Slika 5.3.

Slika 5.3: Povezava UART naprav z Hardware Flow Control

Hitrost prenosa je pri UART prenosu izražena v baud-ih. Baud je merska enota, ki označuje število simbolov na sekundo. Tipične vrednosti za baudrate so iz zgodovinskih razlogov 9600, 19200, 38400, 57600, 115200, 230400, 460800 in 921600. UART napravi, ki nastopata v prenosu, morata imeti usklajene nastavitve za hitrost prenosa, pariteto ter število stop bitov. baudrate. V nasprotnem primeru prenos ne bo uspešen oziroma sprejemnik podatkov ne bo sprejel pravilno.

UART v STM32H7

Krmilnik STM32H750 ima 8 naprav, ki omogočajo komunikacijo po protokolu UART: USART1, USART2, USART3, UART4, UART5, USART6, UART7 in UART8. Naprave z oznako USART so sposobne asinhrone in sinhrone komunikacije, naprave z oznako UART pa le asinhrone.

Inicializacija GPIO pinov

Pini TX, RX, RTS in CTS vseh U(S)ART naprav so povezani na GPIO pine. Te pine moramo inicializirati podobno, kot smo to storili, ko smo jih inicializirali pri vaji 2. Glavna razlika bo, da jim tokrat kot način delovanja ne bomo določili vhod ali izhod, temveč način alternativne funkcije (angl. alternate function). Hkrati moramo določiti tudi ali bomo za zagotavljanje logičnih nivojev uporabljali push-pull (GPIO_MODE_AF_PP) ali open-drain (GPIO_MODE_AF_OD).

Način “alternate function” pomeni, da s pini ne bomo upravljali programsko (pisali oz. brali njihovega stanja neposredno s funkcijami HAL-a), ampak bo pine uporabljala druga naprava. T tem primeru bo to ena izmed naprav U(S)ART. Za vsakega izmed pinov lahko uporabimo eno izmed 16 možnih alternativnih funkcij. V spodnji tabeli je prikazana vezava pinov, ter številčna oznaka alternativna funkcije s katero jih izberemo (AFx). Za elemente Alternate v inicializacijski strukturi za GPIO pine uporabimo konstante v obliki GPIO_AFx_USARTy oz. GPIO_AFx_UARTy, kjer x zamenjamo s številko alternativne funkcije, y pa z oznako U(S)ART naprave. Če bi tako na primer želeli uporabiti pin PB8 za RX pin naprave UART4, bi Alternate nastavili na GPIO_AF8_UART4. Če bi želeli pin PC6 uporabiti kot TX pin naprave USART6, bi Alternate nastavili na GPIO_AF7_USART6.

U(S)ART naprava Pin RX (alt. funkcija) Pin TX (alt. funkcija)
USART1 PA10 (AF7), PB7 (AF7), PB15 (AF4) PA9 (AF7), PB6 (AF7), PB14 (AF4)
USART2 PA3 (AF7), PD6 (AF7) PA2 (AF7), PD5 (AF7)
USART3 PB11 (AF7), PC11 (AF7), PD9 (AF7) PB10 (AF7), PC10 (AF7), PB8 (AF7)
UART4 PA1 (AF8), PA11 (AF6), PB8 (AF8), PC11 (AF8) PA0 (AF8), PA12 (AF6), PB9 (AF8), PC10 (AF8)
UART5 PB5 (AF14), PB12 (AF14), PC11 (AF8) PB6 (AF14), PB13 (AF14), PC12 (AF8)
USART6 PC7 (AF7), PG9 (AF7) PC6 (AF7), PG14 (AF7)
UART7 PA8 (AF11), PB3 (AF11), PE7 (AF7), PF6 (AF7) PA15 (AF11), PB4 (AF11), PE8 (AF7), PF7 (AF7)
UART8 PE0 (AF8), PJ9 (AF8) PE1 (AF8), PJ8 (AF8)

Primer inicializacije pinov PB10 in PB11 za RX in TX pina naprave USART3:

__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitTypeDef init_structure;
init_structure.Pin = GPIO_PIN_10 | GPIO_PIN_11;
init_structure.Mode = GPIO_MODE_AF_PP;
init_structure.Pull = GPIO_NOPULL;
init_structure.Speed = GPIO_SPEED_FREQ_LOW;
init_structure.Alternate = GPIO_AF7_USART3;

HAL_GPIO_Init(GPIOB, &init_structure);

Inicializacija UART naprave

Kot običajno moramo na začetku najprej prižgati uro U(S)ART naprave. To storimo z makrojem __HAL_RCC_USARTx_CLK_ENABLE(), kjer je USARTx oznaka naprave. Bodite pozorni na razlike v imenih naprav (USART ali UART).

Nato je potrebno napravi določiti vse nastavitve prenosa. Te določimo s strukturo UART_HandleTypeDef. Za razliko od GPIO naprave ta struktura ne služi zgolj za inicializacijo, ampak tudi za kasnejše spremljanje in upravljanje prenosov. Zato to strukturo običajno definiramo kot globalno spremenljivko. Pri inicializaciji naprave sta pomembna predvsem dva elementa strutkure: Instance in Init. Kot vrednost Instance določimo oznako naprave, ki jo bomo uporabljali, torej eno izmed oznak: USART1, USART2, USART3, UART4, UART5, USART6, UART7 in UART8.

Element Init je struktura, v kateri določimo nastavitve prenosa, ki ga bo naprava izvajala. Spodaj so zapisani vsi pomembni elementi Init strukture in nabor konsstant/vrednosti:

  • Mode: namen za katerega bomo uporabili napravo. UART napravo namreč lahko uporabimo samo za sprejemanje (UART_MODE_RX), samo za pošiljanje (UART_MODE_TX) ali za oboje hkrati (UART_MODE_TX_RX).

  • BaudRate: hitrost prenosa v številu simbolov na sekundo. Kot že rečeno, se največkrat uporablja vrednosti 9600, 19200, 38400, 57600, 115200, 230400, 460800 in 921600.

  • WordLength: koliko podatkovnih bitov se prenese v enemu UART prenosu. Možnosti sta 8 bitov (UART_WORDLENGTH_8B) ali 9 bitov (UART_WORDLENGTH_9B).

  • Parity: pariteto pri prenosu. Ta je lahko liha (UART_PARITY_ODD), soda (UART_PARITY_EVEN) ali pa je ni (UART_PARITY_NONE).

  • StopBits: število stop bitov ob zaključku prenosa - UART_STOPBITS_1, UART_STOPBITS_2.

  • HwFlowCtl: nastavitev Hardware Flow Control-a. UART_HWCONTROL_NONE (ne uporabljamo HFC), UART_HWCONTROL_RTS (samo RTS pin), UART_HWCONTROL_CTS (samo CTS pin), UART_HWCONTROL_RTS_CTS (uporabljamo oba pina).

Ko določimo vse nastavitve prenosa inicializiramo napravo s klicem funkcije HAL_UART_Init(UART_HandleTypeDef*). Primer inicalizacije naprave USART3 je prikazan spodaj:


__HAL_RCC_USART3_CLK_ENABLE();

UART_HandleTypeDef uart = {0};
uart.Instance = USART3;
uart.Init.BaudRate = 115200;
uart.Init.WordLength = UART_WORDLENGTH_8B;
uart.Init.StopBits = UART_STOPBITS_1;
uart.Init.Parity = UART_PARITY_NONE;
uart.Init.Mode = UART_MODE_TX_RX;
uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&uart);

Pošiljanje in sprejemanje brez prekinitev

Za pošiljanje uporabimo funkcijo HAL_UART_Transmit(...). Prvi parameter je kazalec na inicializacijsko strukturo UART naprave. Sledi kazalec na polje znakov za pošiljanje. Z zadnjima dvema parametroma določimo število bajtov v prenosu ter maksimalni čas prenosa (angl. timeout) v milisekundah. V primeru, da je prenos zaključen znotraj maksimalnega dovoljenega časa, funkcija vrne HAL_OK, v nasprotnem primeru pa HAL_TIMEOUT. Primer uporabe omenjene funkcije je prikazan spodaj. Funkcija za sprejemanje HAL_UART_Receive() je podobna funkciji za pošiljanje, s to razliko da podamo kazalec na polje kamor naj se shranijo podatki. Obe funkciji sta blokirajoči, program torej nadaljuje šele, ko je pošiljanje ali sprejemanje zaključen, ali pa je prišlo do napake. Predvsem za sprejemanje znakov je blokirajoč način okoren za uporabo in se raje poslužujemo prekinitev.

uint8_t buffer[4]; 
// ali uint8_t buffer[] = "test";
HAL_UART_Receive(&uart, buffer, sizeof(buffer), HAL_MAX_DELAY);
HAL_UART_Transmit(&uart, buffer, sizeof(buffer), HAL_MAX_DELAY);

POZOR: sizeof() deluje le v primeru polj katerih velikost je znana ob prevajanju, ne pa ob dinamično alociranih poljih.

Zastavice

Stanje naprave UART spremljajo preko zastavic (bitov). Ti nam sporočajo ali je naprava pripravljena za prenos, ali je prispel nov znak, prišlo do napake, in tako dalje. Spodaj je zapisan seznam vseh pomembnejših zastavic s kratkimi opisi njihovega pomena. Stanje zastavice preverjamo s funkcijo __HAL_UART_GET_FLAG(), brišemo pa jih s funkcijo __HAL_UART_CLEAR_FLAG(). Zastavica RXNE (prejeli smo nove podatke) se pobriše samodejno, ko preberemo prispeli podatek, tako da nam te zastavice ni potrebno brisati programsko.

Oznaka zastavice Opis
UART_FLAG_RXNE Naprava UART je prejela nov znak.
UART_FLAG_TC Zadnje pošiljanje se je zaključilo.
UART_FLAG_TXE Naprava je pripravljena na naslednji prenos.
UART_FLAG_PE Napaka paritete.

Prekinitve UART naprav

Vse zastavice lahko uporabimo tudi za proženje prekinitev. Imena prekinitev se ujemajo z imeni zastavic, le da _FLAG_ zamenjamo z _IT_. Za vklop prekinitve uporabimo makro __HAL_UART_ENABLE_IT(), podobno kot ste to storili pri časovnikih`. Ne pozabite, da je še vedno potrebno vklopiti prekinitve za izbrano UART napravo na prekinitvenem krmilniku (NVIC) ter jim določiti prioriteto. Spodaj je podana tabela prekinitvenih oznak ter imen prekinitveno-servisnih programov za vse UART naprave.

Naprava Oznaka prekinitve Ime PSP-ja
USART1 USART1_IRQn USART1_IRQHandler
USART2 USART2_IRQn USART2_IRQHandler
USART3 USART3_IRQn USART3_IRQHandler
UART4 UART4_IRQn UART4_IRQHandler
UART5 UART5_IRQn UART5_IRQHandler
USART6 USART6_IRQn USART6_IRQHandler
UART7 UART7_IRQn UART7_IRQHandler
UART8 UART8_IRQn UART8_IRQHandler

Znotraj prekinitveno servisnega programa lahko najenostavneje preberemo prispeli podatek kar z branjem registra RDR (Receive Data Register), kot je prikazano na primeru spodaj. Enako bi sicer lahko storili s klicem funkcije HAL_UART_Receive(), pri čemer beremo le en podatek in je največji čas čakanja 0, vendar je neposredno branje registra preprostejše.

Stanje prekinitev preverjamo z __HAL_UART_GET_IT(), brišemo pa jih z __HAL_UART_CLEAR_IT(). Kot že rečeno, nam zastavice UART_FLAG_RXNE ni potrebno brisati, saj se avtomatsko pobriše ob branju registra RDR.

void USART3_IRQHandler() {
  if (__HAL_UART_GET_IT(&uart, UART_IT_RXNE)) {
    uint8_t nov_znak = USART3->RDR;
    // ...
  }
}

Priprava projekta

Za to, da lahko uporabimo UART funkcije moramo v projekt v STM32Cube dodati datoteke iz STM32 HAL knjižnice. Vse datoteke knjižnice se nahajajo na sledečih lokacijah:

  • Windows:
C:/Users/<username>/STM32Cube/Repository/STM32Cube_FW_H7_Vx.xx.x/Drivers/STM32H7xx_HAL_Driver
  • Unix: /home/username/STM32Cube/

Za uporabo UART funkcij kopirajte Src/stm32h7xx_hal_uart.c ter Src/stm32h7xx_hal_uart_ex.c iz knjižnice v projekt na lokacijo Drivers/STM32H7xx_HAL_Driver/Src.

Nato skopirajte še datoteki Inc/stm32h7xx_hal_uart.h ter Inc/stm32h7xx_hal_uart_ex.h iz knjižnice v projekt na lokacijo Drivers/STM32H7xx_HAL_Driver/Inc.

Ko dodate omenjene datoteke, v datoteki Inc/stm32h7xx_hal_conf.h, ki je dela projekta, odkomentirajte vrstico, ki definira HAL_UART_MODULE_ENABLED.

Virtual COM port na STM32H750 Discovery Kit

Na razvojni plošči je implementiran Virtual COM port, ki ga lahko uporabljamo preko naprave USART3 in pinov PB10 in PB11. Preko Virtual COM porta lahko tako komuniciramo z namiznimi računalniki preko istega kabla s katerim debuggiramo in programiramo mikrokrmilnik.

Nastavitve UART-a za delo z Virtual COM port: baudrate 115200, 8 podatkovnih bitov, brez paritete in z enim stop bitom.

Serijska komunikacija na Windows/Mac/Linux

Windows

Na sistemih Windows vam za branje in pošiljanje svetujemo Putty. Ko vključite razvojno ploščo, odprite Device Manager in preverite oznako COMx, ki jo je OS dodelil plošči (glej Slika 5.4).

Slika 5.4: Device Manager - COM naprave

Nastavite Putty kot je prikazano na Slika 5.5.

Slika 5.5: Putty - konfiguracija

Če želite, da se vam prikazujejo znaki sproti, ko jih pišete vklopite funkcionalnost Echo, kot je prikazano na Slika 5.6.

Slika 5.6: Putty - echo

MacOS

Priporočamo SerialTools ali TM Terminal, ki je dodatek za Eclipse. Razvojna plošča se registrira kot naprava z imenom dev/cu.usbmodem1103 ali kaj podobnega.

Linux

Priporočamo minicom, ki je orodje v ukazni vrstici ali CuteCoM, ki je orodje z grafičnim vmesnikom. Razvojna plošča se registrira kot naprava z imenom /dev/ttyUSB0 ali kaj podobnega.

Naloga 5.1

Napišite program, ki omogoča sprejemanje ukazov v obliki LED x ON oz LED x OFF, kjer je x številka med 1 in 3. Ko sprejmete ukaz LED 1 ON, vklopite prvo LED. Ko sprejmete LED 1 OFF LED ugasnite. Poleg tega pošljite trenutno stanje prižgani LED diod preko UART v obliki:

LED: ON, OFF, ON

Sprejemanje realizirajte s prekinitvami. Predpostavljajte da bo poslan ukaz vedno pravilen. Ukazi se zaključijo z \r, ki ga pošljete s pritiskom tipke Enter.