Sériová linka je nejstarší způsob propojení zařízení mezi sebou, která je stále velmi užitečná, protože poskytuje docela rychlý komunikační kanál, který může být použit na delší vzdálenosti než jiná připojení, jako USB. Dnes se sériová linka používá převážně k připojení malých počítadel a jednoduchých zařízení. Může být také použita jako domácí dekódovač signálů, viz níže.
Sériový protokol
Sériový protokol je velmi jednoduchý. Je to proto, že byl vynalezen v dobách, kdy se používaly převážně elektromechanické součátsky, relé a motory a podobně. Byl vynalezen, aby umožňoval propojení dálnopisů (ang. teletype) a proto najdeme zkratku TTY, když se sériovou linkou někam připojujeme. Elektrické zařízení spojované sériovou linkou se nazývá universální asynchronní přijímač/vysílač (ang. Universal Asynchronous Receiver/Transmiter) a proto se používá také termín UART.
Původní standarty protokolu jsou V24 a RS232. Původní sériová komunikace probíhala mezi +24V a -24V, později mezi +-12V. Dnes se používá TTL logika s úrovněmi 0V a 5V nebo 0V a 3.3V. Bez ohledu na napěťové úrovně je protokol stále stejný.
Podívejme se nyní na samotný protokol. Klidový stav linky je vysoká úroveň a vysoká úroveň značí hodnotu 0 (nízká úroveň pak hodnotu 1). Tomuto se říká invertovaná logika. Když chce zařízení vysílat, nejprve stáhne linku na nízkou úroveň a tím generuje startovací bit. Čas trvání startovacího bitu nastavuje rychlost komunikace, všechny ostatní bity mají stejnou délku trvání. Po startovacím bitu následuje obvykle 8 datových bitů (někdy 7 bitů) a někdy paritní bit a nakonec jeden nebo dva stop bity.
Původně bylo účelem startovacího bitu umožnit roztočit motory a umožnit přijímači provést korekci časování. Stop bit byl k tomu, aby se motor dostal do klidu. To bylo v době, kdy byl protokol používán při velmi nízkých rychlostech 300 baudů, tj. 300 bitů za sekundu.
Dnes je protokol prakticky stejný, ale není potřeba více stop bitů a komunikace je často tak spolehlivá, že se obejdeme i bez paritních bitů. Přenosové rychlosti jsou také vyšší, typicky 9600 až 115200 baudů. Obvykle je vyšší rychlost vždy dvojnásobkem předchozí. Nízké rychlosti mají význam při hodně dlouhém vedení nebo u pomalých zařízení.
| rychlost v baudech | rychlost v bitech za sekundu |
|---|---|
2400 |
2400 |
4800 |
4800 |
9600 |
9600 |
19200 |
19200 |
38400 |
38400 |
57600 |
57600 |
115200 |
115200 |
Abychom přesně určili jaký konkrétní protokol používáme, uvidíte často zkrácený zápis třeba 9600 8n1. To znamená přenosovou rychlost 9600 baudů, 8 data bitů, žádný paritní bit a jeden stop bit. Protokol posílá nejdříve nejméně významný bit (little endian).
První nízká úroveň je start bit, potom osm teček ukazuje ideální časovou pozici pro přijímač. Základní dékódovací algoritmus při příjmu sériových dat je detekovat začátek start bitu, potom určovat hodnotu datových bitů uprostřed časového úseku. Poznamenejme, že závěrečná vysoká úroveň napravo je stop bit.
Co to je baud? Rychlost v baudech odpovídá přenosové rychlosti v bitech za sekundu. To znamená, že při rychlosti 300 baudů je start bit 1/300s široký. To znamená, že při 9600 baudech je přenosový bit 1/9600s široký, což je \(104 \mu s\) a při 115200 badech je bit široký 1/115200s, což je \(8.6 \mu s\). Rychlost přenosu neodpovídá přesně rychlosti posílání, protože musíme z kalkulace rychlosti přenosu dat odečíst start a stop bity, případně paritní bit.
Co to je parita? Parita není nic jiného, než zabezpečení dat při přenosu. Jsou dva druhy parity, sudá (even) a lichá (odd).
-
Sudá parita (even parity) znamená, že pokud máme ve vysílaném bitovém toku lichý počet jedniček, tak paritní bit bude 1. Pokud máme v vysílaném bitovém toku sudý počet jedniček, paritní bit bude 0. To znamená, že mezi start bitem a stop bitem je celkový počet jedniček sudý.
-
Lichá parita (odd parity) znamená, že pokud máme ve vysílaném bitovém toku lichý počet jedniček, tak paritní bit bude 0. Pokud máme v vysílaném bitovém toku sudý počet jedniček, paritní bit bude 1. To znamená, že mezi start bitem a stop bitem je celkový počet jedniček lichý.
Pokud se při přenosu znaku změní nějaký bit, tak je přijímací strana schopna poznat chybu tak, že si spočítá počet jedniček celého přenosu znaku a porovná to s nastavením komunikace.
| Obě zařízení musí mít nastavené stejnou rychlost přenosu v baudech, stejný počet datových bitů, stejnou paritu a stejný počet stop bitů. Jinak to nebude fungovat. |
Hardware UART
Jednoduché zařízení pro sériovou linku má vysílací pin (transmit TX) a přijímací pin (receive RX) a zem. To znamená, že uplné sériové zařízení potřebuje tři dráty k obousměrné komunikaci. Obvykle TX drátek z jednoho zařízení se připojí na RX pin druhého zařízení a obráceně. Jediný problém bývá v tom, že někteří výrobci označují piny ne podle funkce na daném zařízení, ale podle toho, jak mají být zapojeny na druhém zařízení (prostě mají prohozené TX a RX). Pokud máme pochybnosti, který pin je který, dá se to zjistit pomocí osciloskopu, logickým analyzátorem nebo multimetrem. Jeden drátek odpojíme, necháme zařízení vysílat a pokud vidíme pulsy, tak je to TX pin.
Úplné sériové zařízení má ještě několik řídicích pinů (vodičů). Byly určeny k tomu, aby pomáhaly starým dálnopisům fungovat a již se moc nepužívají. Například RTS — Request To Send je linka, která se používá k žádosti o možnost vysílat z jednoho zařízení na druhé, CTS — Clear To Send je linka, která říká: můžeš nebo nemůžeš vysílat. Obvykle CTS nastavuje samotný hardware automaticky, když je přijímací buffer plný nebo prázdný.
RTS a CTS se dají použít k hardwarovému řízení toku dat. Existuje i softwarový standard, který používá znaky XON a XOFF k označení začátku a konce vysílání. Tohle asi dnes nebudete moc potřebovat, jedině pokud byste chtěli implementovat plné sériové rozharní a potom si na to budete muset napsat svůj program.
Pico ná dva UARTy, které mohou být používany společně s USB zařízením. Oba mají buffer na 32 8bitových znaků a umožnují používat jak bufferovaný tak nebufferovaný přenos a také mohou používat harwarové řízení toku pomocí RTS a CTS. Každý z nich může být připojen ke skupině 4 pinů podle následující tabulky:
| piny pro UART0 | ||||
|---|---|---|---|---|
TX |
GP0 |
GP12 |
GP16 |
GP28 ? |
RX |
GP1 |
GP13 |
GP17 |
GP29 ? |
CTS |
GP2 |
GP14 |
GP18 |
|
RTS |
GP3 |
GP15 |
GP19 |
|
| piny pro UART1 | ||||
|---|---|---|---|---|
TX |
GP4 |
GP8 |
GP20 |
GP24 ? |
RX |
GP5 |
GP9 |
GP21 |
GP25 ? |
CTS |
GP6 |
GP10 |
GP22 |
GP26 ? |
RTS |
GP7 |
GP11 |
GP23 |
GP27 ? |
Piny v posledním sloupci bych raději nepoužíval, protože se na RaspberryPi Pico překrývají s jinými funkcemi. Např. na GP25 je připojena vestavěná LED.
Lepší je se orientovat podle obrázku:
Abychom mohli používat GPIO linku jako UART, je potřeba zavolat funkci:
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
Pokud chcete připojit Pico RX a TX piny na sériovou linku k počítači, tak je tu malý problém. Sériové porty na PC pracují na jiných napěťových úrovních než Pico. RS232 používá +13V a -13V, což je nekompatibilní s Picem (+3.3V a 0V) i třeba Arduinem (+5V a 0V). Řeší se to obvykle převodníkem úrovní z TTL na RS232 s obvodem MAX3232 nebo převodníkem z TTL na USB.

UART USB

Vodiče jsou zapojeny takto:
-
červený (+5V) — Pico VBUS (pin 40), budeme Pico zároveň napájet kabelem
-
černý (zem) — Pico GND (třeba pin 38)
-
bílý (TX) — Pico GP0 (pin 1)
-
zelený (RX) — Pico GP1 (pin 2)
Na straně PC pak můžeme komunikovat pohodlně pomocí třeba sudo minicom -b 115200 -c on -D /dev/ttyUSB0
Nastavení UARTu
Jsou dva základní přístupy k nastavení sériového zařízení, buď přímo nebo pomocí knihovny stdio.
V mnoha případech potřebujete vědět jak fungují oba přístupy, začneme s funkcemi SDK, které nastavují UART.
Všechny funkce potřebují vědět adresu UARTu, který nastavujeme.
Máme k dispozici dvě konstanty uart0 a uart1 a funkci, která převádí hardwarovou adresu na index 0 nebo 1.
static uint uart_get_index( uart_inst_t *uart )
Jednoduché nastavení je pomocí funkce:
uint uart_init( uart_inst_t *uart, uint baudrate )
Ukončení práce UARTu se provádí funkcí:
void uart_deinit( uart_inst_t *uart )
Funkce:
static bool uart_is_enabled( uart_inst_t *uart )
se používá k zjištění zda je příslušný UART nastaven.
Můžeme také samostatně nastavit rychlost:
uint uart_set_baudrate( uart_inst_t *uart, uint baudrate )
Kompletní nastavení UARTu, kde nastavujeme počet datových bitů, počet stop bitů a paritu je:
uart_set_format( uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity )
data_bits mohou mít hodnotu 5, 6, 7 nebo 8, stop_bits mohou být 1 nebo 2 a parity může být nastavena na jednu z těchot hodnot:
-
UART_PARITY_NONE— nepoužíváme paritní bit -
UART_PARITY_EVEN— sudá parita -
UART_PARITY_ODD— lichá parita
Někdy mohou být užitečné funkce pro nastavení RTS a CTS pinů, pokud je musíte použít ke komunikaci s hardwarem, který RTS a CTS potřebuje na hardwarové řízení toku.
uart_set_hw_flow( uart_inst_t *uart, boot cts, boot rts )
Je-li parametr cts nastaven na true potom jenom když je CTS linka na vysoké úrovni, tak se povoluje UARTu vysílat data, jinak se nepovoluje vysílat.
Je-li parametr rts nastaven na true potom RTS linka bude na vysoké úrovni, když jsou k dispozici nějaká data k poslání. RTS na nízké úrovni znamená, že žádná data k poslání zatím nejsou k dispozici.
UART má na straně TX i RX vyrovnávací paměť (buffer) o velikosti 32 8bitových znaků. To je obvykle dostatečné na věci, které potřebujeme. Pokud potřebujeme okamžitě reagovat na jednotlivé znaky a bufferování vypneme, riskujeme, že můžeme některé znaky ztratit. Při vypnutí bufferu je v paměti jenom jeden znak a jakmile přijde další, tak se přepíše.
Funkce pro zapnutí buferování:
uart_set_fifo_enabled( uart_inst_t *uart, bool enabled )
Je-li parametr enabled true, je zapnutý buffer, pro enabled false je vypnutý buffer.
Nakonec můžeme ještě nastavit přerušení, které můžeme potřebovat jak pro příjem, tak i pro vysílání:
uart_set_irq_enables( uart_inst_t *uart, bool rx_has_data, bool tx_has_data )
Obluhu přerušení nastavíme funkcí:
irq_set_exclusive_handler( uartIRQ, handler); (1)
irq_set_anabled( uartIRQ, true ); (2)
| 1 | Určení funkce obsluhy přerušení. Parametr handler je ukazatel na funkci obsluhy. Parametr uartIRQ může být UART0_IRQ nebo UART1_IRQ. |
| 2 | Zapnutí přerušení na UARTu. Parametr uartIRQ může být UART0_IRQ nebo UART1_IRQ. |
Přenos dat
Poté co nastavíme UAER můžeme posílat nebo přijímat data pomocí funkcí z SDK.
uart_write_blocking( uart_inst_t *uart, const uint8_t *src, size_t len ) (1)
uart_read_blocking( uart_inst_t *uart, uint8_t *dst, size_t len ) (2)
| 1 | Funkce pro posílání dat. Pošle len bajtů z pole src přes UART uart |
| 2 | Funkce pro příjem dat. Přijme len bajtů z UARTU uart a zapíše je do pole dst. |
Obě dvě funkce mají data v poli bajtů. Ukazatel na začátek pole je src nebo dst. Je na programátorovi, aby zajistil dostatečnou velikost pole dst.
Budeme-li posílat větší množství dat, může se stát, že se vysílací buffer UARTu zaplní a potom se funkce zablokuje, t.j. zastaví se do té doby, než se vysílací buffer vyprázdní a bude možné posílat další bajty.
V případě funkce uart_read_blocking parametr len určuje, kolik bajtů má funkce přečíst. Pokud nebude UARTem přijato dostatek bajtů, funkce se opět zablokuje, t.j. bude čekat na další data. Návrat z funkce proběhne až tehdy, pokud bude přečteno len dat.
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
int main()
{
stdio_init_all();
uart_init( uart1, 9600 );
gpio_set_function( 4, GPIO_FUNC_UART );
gpio_set_function( 5, GPIO_FUNC_UART );
uart_set_format( uart1, 8, 1, UART_PARITY_EVEN);
uint8_t SendData[] = "Ahoj studenti";
uint8_t RecData[20];
uart_write_blocking( uart1, SendData, 14 );
uart_read_blocking( uart1, RecData, 14 );
RecData[14] = '\0';
printf("%s", RecData);
}
Čtení a zápis znaků a řetězců
stdio
Pico má verzi Céčkové stdio knihovny a ta může být použita k posílání a příjem dat přes UART nebo přes USB port.
Má také některá nestandardní rozšíření k jednodušší práci s hardware.
Hlavní výhodou, kterou máme pokud použijeme stdio, je možnost použití funkce printf().
Práce s malými buffery
Příklad použití — komunikace dvou RaspberryPi Pico
Potřeboval jsem zkalibrovat teploměr s velmi nepřesným čidlem (RaspberryPi Pico s vestavěným termistorem) pomocí velmi přesného teploměru RaspberryPi Pico s čidlem BMP280. Zde je ukázka komunikace kalibrovaného teploměru s etalonovým teploměrem.
// nastavení UART
uart_init(uart1,115200);
gpio_set_function(8,GPIO_FUNC_UART); // TX
gpio_set_function(9,GPIO_FUNC_UART); // RX
uart_set_format(uart1, 8, 1, UART_PARITY_EVEN);
// ...
uint32_t raw=0;
float temp=0;
char txdata[] = "T";
char rxdata[32];
for(i=0;i<64;i++) {
raw = cteni_teploty(); // čtení naměřené teploty
nap[i] = (float) raw;
// posílám žádost o získání teploty z etalonového teploměru
uart_write_blocking(uart1,txdata,strlen(txdata));
while( ! uart_is_readable(uart1) ) { // čekáme na odpověď
gpio_put(25,1);
sleep_ms(1);
gpio_put(25,0);
sleep_ms(1);
}
#if TESTOVANI
printf("Čtu etalon\n");
#endif
// odpověď je připravená, nevíme jak bude dlouhá
for( int j = 0; j<31 && uart_is_readable(uart1); j++) {
uart_read_blocking(uart1,rxdata+j,1);
rxdata[j+1] = '\0';
if( rxdata[j] == '\n') {
break;
}
}
// v rxdata je teplota z etalonu jako řetězec znaků
// převedeme z řetězce znaků na float
temp = strtof(rxdata,NULL);
// nastavení UART
uart_init(uart1,115200);
gpio_set_function(8, GPIO_FUNC_UART); // TX
gpio_set_function(9, GPIO_FUNC_UART); // RX
uart_set_format(uart1, 8, 1, UART_PARITY_EVEN);
// ...
if( kalibrace ) {
while( 1 ) {
#if TESTOVANI
printf("Čekám na žádost o měření.\n");
#endif
while(! uart_is_readable(uart1) ) {
// čekání na port a blikání LEDkou
gpio_put(25,1);
sleep_ms(1);
gpio_put(25,0);
sleep_ms(1);
}
// dostal jsem pokyn, změřím teplotu
// čtení teploty z přesného čidla a konverze
bmp280_read_raw(&raw_temperature, &raw_pressure);
int32_t temperature = bmp280_convert_temp(raw_temperature, ¶ms);
int32_t pressure = bmp280_convert_pressure(raw_pressure, raw_temperature, ¶ms);
// přečtu 1 znak žádosti aby nezavazel v RX bufferu a zahodím ho
uart_read_blocking(uart1, rcvdata, 1);
// převod float hodnoty na řetězec znaků
sprintf(txdata,"%f\n",temperature / 100.f);
// poslání řetězce znaků na kalibrovaný teploměr
uart_write_blocking(uart1, txdata, strlen(txdata));
#if TESTOVANI
printf("Poslal jsem: Teplota = %.2f %cC\n", temperature / 100.f, 0xb0);
#endif
}
}