Vaja 6: 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 7.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.
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 7.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.
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 7.3.
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. V 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
= {0};
GPIO_InitTypeDef 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;
init_structure
(GPIOB, &init_structure); HAL_GPIO_Init
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
= {0};
UART_HandleTypeDef 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;
uart(&uart); HAL_UART_Init
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";
(&uart, buffer, sizeof(buffer), HAL_MAX_DELAY);
HAL_UART_Receive(&uart, buffer, sizeof(buffer), HAL_MAX_DELAY); HAL_UART_Transmit
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()
. 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 7.4).
Nastavite Putty kot je prikazano na Slika 7.5.
Če želite, da se vam prikazujejo znaki sproti, ko jih pišete vklopite funkcionalnost Echo
, kot je prikazano na Slika 7.6.
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.