Reklama

Začínáme s mikrokontroléry ATxmega – USART 1. díl (odesílání a příjem)

Úvod

Návod na použití jednotky USART má formu komentovaných příkladů. V každém příkladu vysvětlím funkci nebo část periferie, která je k jeho demonstraci nutná. Budu předpokládat, že příklady čtete všechny od začátku, takže se budu snažit nic nevysvětlovat dvakrát. Připadá mi to jako ten nejvhodnější kompromis mezi tím co si myslím, že se chcete dozvědět a tím co sám umím (a že toho zatím není mnoho). Doufám, že to nebude na škodu. Když přece jen vzniknou nějaké nejasnosti, tak si budete muset jejich rozuzlení najít v datasheetu. Což někdy nemusí být zrovna nejsnazší. Dokumentace totiž nedosahuje kvalit jako u mikrokontrolérů řad ATmega a ATtiny. Není to nutné, ale výhodou pro vás bude, pokud se budete orientovat v:

  • Konfiguraci I/O pinů mikrokontrolérů ATxmega
  • Konfiguraci taktovací frekvence mikrokontrolérů ATxmega
  • Principu DMA přenosu

Pokud si potřebujete USART obecně trochu zopakovat, můžete to zkusit tady.

Možnosti USART

Pokud přecházíte z mikrokontrolérů ATmega, tak vás nečeká při seznamování s USARTem moc práce. Principiálně je totiž podobný. Zvládá asynchronní přenos s datovou rychlostí až 4 Mb/s a synchronní přenos s 16 Mb/s. Formát zprávy lze měnit v rozsahu 5 – 9 bitů, 1 – 2 stop bity a lze nastavit paritu. Většinou si ale vystačíte s klasickým formátem 8N1 (osm datových bitů, jeden stop bit, žádná parita). Posunu k lepšímu doznal “Baudrate” generátor, který je nyní zlomkový a je schopen vytvořit de facto libovolnou datovou rychlost bez ohledu na takt mikrokontroléru. Takže už nemusíte kvůli vyšším datovým rychlostem volit takt na podivných frekvencích jako 9,216 MHz nebo 1,8432 MHz. USART umí kromě běžných režimů pracovat také jako “one-wire”, tedy komunikovat po jedné lince, dále umí komunikovat pomocí SPI a zvládá i IrDA. Mimo to lze využívat MCPM režim a s pomocí XCL modulu lze tvořit Manchaster kódování. Přirozeně většinu výše zmíněných funkcí jsem nikdy nevyužil a u mnohých z nich si ani nedovedu představit, jak bych je využít mohl:D Ale, pokud se k některé z nich během své praxe dostanu, určitě návod doplním.

Seznam příkladů

  • Odesílání s Printf (ATxmega E) (A)
  • Příjem řetězců s přerušením + printf/scanf (ATxmega E) (B)
  • Odesílání s DMA (ATxmega E) (C) – 2. díl
  • Odesílání s DMA (ATxmega AU) (D) – 2. díl
  • SPI režim odesílání 16bit s “pollingem” (ATxmega E) (E) – 3. díl
Odesílání s Printf (Atxmega E) (A)

Prosté odesílání bude asi to nejtypičtější, co budete po USARTu chtít. Pokud vám to aplikace dovolí, tak skoro jistě budete chtít využít komfortu funkcí printf. Tak pojďme na to. Z datasheetu k ATxmega32E5 jste se dočetli, že máte dvě USART jednotky. Pojmenované USARTC0 a USARTD0. USARTC0 má k dispozici vývody na portu C a druhý USART analogicky na portu D. V druhém datasheetu v sekci “Alternate Functions” se dočtete, kde příslušné piny jsou. Piny příslušející USARTC0 mají označení XCK0 (hodiny pro práci v synchronním módu), RXC0 a TXC0. A mohou se nacházet buďto na trojici pinů PC1,PC2 a PC3 a nebo na pinech PC5,PC6 a PC7. Která z těchto skupin bude platná, rozhoduje obsah REMAP registru portu C. Bitem USART0 můžete volit, kterou trojici budete používat. S USARTem na portu D je to analogické. Já ve svém příkladu používám USARTD0 a jako Tx mi slouží PD7. Tento pin by přirozeně měl být nastaven jako výstup.

Vypořádali jsme se s porty a teď je nutné vyřešit otázku přenosové rychlosti (Baudrate). Její hodnota se odvíjí od taktu mikrokontroléru (F_CPU) a od dvou hodnot BSEL a BSCALE. Všechny potřebné matematické vztahy najdete v tabulce 21-1. Pokud budete volit hodnoty ručně, tak se snažte víc pracovat s BSEL (dvanáct bitů) a méně s BSCALE (znaménkové čtyři bity). Vzhledem k omezenému množství kombinací (přibližně 65536) není problém napsat si program, který hrubou silou spočte všechny kombinace a vybere tu, která nejlépe vyhovuje vámi zvolené přenosové rychlosti. Podobnou aplikaci někdo napsal a můžete ji nalézt zde. Pokud budete Atmel provozovat na kmitočtu 32 MHz, můžete využít tabulku 21-6 v datasheetu, kde jsou hodnoty BSEL a BSCALE pro typické komunikační rychlosti. Opět si povšimněte, že má dva sloupečky, jeden pro běžný provoz a druhý pro režim “double speed”. Režim přepnete bitem CLK2X. Při práci s BSCALE nezapomeňte, že jde o čtyř bitové znaménkové číslo. Vše najdete v registrech BAUDCTRLA a BAUDCTRLB. My budeme používat přenosovou rychlost 115200 b/s.

Poslední co zbývá, je konfigurace formátu zprávy a pracovního režimu. Formát zprávy volíme klasicky 8N1, tedy osm datových bitů, jeden stop bit a žádnou paritu. Nastavení se provádí v registru CTRLC. Nakonec už stačí jen v registru CTRLB zapnout vysílač (bitem TXEN). V tom okamžiku USART přebírá kontrolu nad pinem Tx (PD7). Samotné odesílání dat už probíhá klasicky. Stačí zapsat do registru DATA. USART na vysílací straně obsahuje dvojitý “buffer”. Takže jakmile USART zahájí vysílání, tak už je datový registr prázdný a může být zaplněn další hodnotou. O tom, zda je datový registr volný vás informuje stavový bit DREIF. Pokud jej nebudete respektovat a zapíšete do datového registru i přes to, že je plný, tak se jeho obsah nepřepíše. Všimněte si proto, že odesílací funkce nejprve počká, až se datový registr vyprázdní a teprve potom do něj naloží nový znak.

Všechny ostatní funkce, které se starají o natavení odesílací funkce uart_putchar() na standardní výstup jsou pro mě spíše kouzelné formule, takže vám jejich podstatu neobjasním. Připomenu ještě, že v knihovně stdio.h pro AVR naleznete kromě printf i její varianty pro práci s řetězci v paměti programu (Flash). V příštích příkladech je určitě budeme používat. Abyste je mohli používat, tak je vhodné vložit (includovat) ještě knihovnu pgmspace.h. Málem bych zapomněl na funkci clock_init(). Ta slouží k nastavení hodin mikrokontroléru. Využívá interního 32 MHz oscilátoru. Který není nejpřesnější, ale naštěstí je jeho chyba typicky tak malá, že při komunikaci neprojevuje.

 

001: //A) USART – odesílání s Printf (s podporou PGM), Tx at PD7
002: #define F_CPU 32000000
003: // baudrate 115200
004: #define BSEL 131// Parametry baudrate
005: #define BSCALE –3
006:
007: #include <avr/io.h>
008: #include <avr/pgmspace.h>
009: #include <stdio.h>
010:
011: // odesílání znaku
012: static int uart_putchar(char c, FILE *stream);
013: void clock_init(void);
014: void usart_init(void);
015: // kouzlo (stdio)
016: static FILE mystdout = FDEV_SETUP_STREAM (uart_putchar, NULL, _FDEV_SETUP_WRITE);
017: volatile int x=0;// něco zajímavého k odesílání
018:
019: int main(void){
020: stdout = &mystdout;// mapujeme standartní výstup na USART
021: clock_init();// interní oscilátor 32MHz
022: usart_init();
023:
024: while (1){
025: printf("%i\r\n",x);// pošle hodnotu x do PC
026: x++;
027: }
028: }
029:
030: static int uart_putchar (char c, FILE *stream){
031: // čekej než bude Data Registr Empty
032: while ( !( USARTD0.STATUS & USART_DREIF_bm));
033: USARTD0.DATA = c;// pošli znak
034: return 0;
035: }
036:
037: void usart_init(void){
038: PORTD.DIR = PIN7_bm;// Tx (PD7) je výstup
039: PORTD.OUTSET = PIN7_bm;// Tx do log.1 – není nutné
040: PORTD.REMAP = PORT_USART0_bm;// Remapujeme Tx na pin PD7
041: USARTD0.BAUDCTRLA = (char)BSEL;// dolních 8 bitů 12bitové hodnoty BSEL
042: // Horní 4 bity hodnoty BSEL + hodnota BSCALEBSCALE
043: USARTD0.BAUDCTRLB = (BSCALE << USART_BSCALE_gp) | ((char)(BSEL>>8) & 0x0f);
044: // 1 stopbit, žádná parita, 8 datových bitů,
045: // asynchronní režim
046: USARTD0.CTRLC = USART_CHSIZE_8BIT_gc;
047: USARTD0.CTRLB = USART_TXEN_bm;// Povolit vysílač (Tx)
048: }
049:
050: void clock_init(void){
051: OSC.CTRL |=OSC_RC32MEN_bm;// spustit interní 32MHz oscilátor
052: // počkat než se rozběhne
053: while (!(OSC.STATUS & OSC_RC32MRDY_bm)){};
054: CCP=CCP_IOREG_gc;// odemčít zápis do registru CLK.CTRL
055: // vybrat 32MHz jako systémový clock
056: CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
057: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 

Příjem řetězců s přerušením + printf/scanf (Atxmega E) (B)

Mnoho aplikací vyžaduje nejen odesílání ale i příjem zpráv. Pokud mikrokontrolér komunikuje s uživatelem pomocí terminálu je žádoucí, aby komunikace probíhala na bázi řetězců. Jinak řečeno, aby byly příkazy “čitelné”. Taková forma předávání informací má své pro a proti. Výhodou textové komunikace je možnost posílat “potvrzovací” znaky, jako je například stisk klávesy Enter. Zprávy pak mohou být různě dlouhé a přijímači (mikrokontroléru) je vždy jasné, které z příchozích znaků tvoří zprávu. A přesně takový příklad si teď předvedeme. Zprávu bude vždy tvořit sekvence znaků ukončená znakem ‘\n’ (0x0A) nebo ‘\r’ (0x0D). Zpráva se ukládá do paměti a je omezena délkou šestnácti znaků. Tu si přirozeně můžete zvolit různou. Díky tomu napíše uživatel v terminálu zprávu a potvrdí ji Enterem. Mám trochu zmatek v tom, který znak je odeslán při stisku klávesy Enter. Mám za to, že to závisí na terminálovém programu a operačním systému. Proto ve svém programu oba znaky považuji za ukončovací. A teď k samotnému programu.

Konfiguraci přenosové rychlosti i nastavení pinů jsem komentoval v předchozím příkladě. Protože zde ale používáme přijímač, tak musíme nakonfigurovat i pin Rx (PD6) a to jako vstup. A protože příjem provádíme pomocí přerušení, tak bychom si o něm měli stručně povědět. Přerušení v mikrokontrolérech ATxmega má tři priority – High, Medium a Low. Každé periferii, které zapnete přerušení zároveň i vyberete prioritu. Přerušení se vždy nastavuje dvojicí bitů. Ta nám dává čtyři možnosti – vypnuto anebo jednu ze tří výše zmíněných priorit. USART má k dispozici tři zdroje přerušení (ve skutečnosti jich je spíš pět, ale nebudeme to komplikovat):

  • Data Register Empty – vyvolá se jakmile se uvolní datový buffer a je možné do něj zapsat další znak.
  • Transmit Complete – vyvolá se až když skončí celý přenos a USART už nemá k dispozici žádná další data.
  • Receive Complete – informuje vás o tom, že USART přijal data. Přesněji řečeno, že má ve svém přijímacím bufferu data, která jste si ještě nevyzvedli.

My budeme logicky používat poslední z nich. Padla zmínka o tom, že přijímač má nějaký buffer. USART vždy přijatá data uloží do bufferu. Ten je schopen uchovat dva byty. Váš program má díky tomu dost času si data vyzvednout. Vyzvednutí není nic jiného než čtení z DATA registru. Data vyčítá v takovém pořadí, v jakém byla do bufferu ukládána. Je potřeba poznamenat, že spolu s daty buffer obsahuje i stavové bity (registr STATUS). Pokud vás tedy zajímají, musíte je přečíst vždy před čtením z registru DATA. Stavové bity vás informují o stavu USARTu. Najdete v nich všechny tři informace, které mohou vyvolat přerušení a ještě některé další jako třeba Frame Error, který naznačuje chybu komunikace. Nebo také Buffer Overflow, která vás informuje o tom, že jste si včas nevyzvedli přijatá data a USART musel nově příchozí byte zahodit. Stavové bity přirozeně můžete (a někdy i musíte) mazat ručně (zápisem hodnoty log. 1). Bit RXCIF se maže automaticky čtením z DATA registru a analogicky bit DREIF se maže zápisem do DATA registru. Vraťme se ale k přerušením. Nastavit je můžete v registru CTRLA. To také v programu provedeme a nastavíme příjmu prioritu medium. Každá úroveň (level) přerušení lze pak globálně povolovat nebo zakazovat (v registru PMIC.CTRL). Takže náš program přirozeně musí medium level přerušení povolit a pak ještě povolit všechna přerušení globálně pomocí funkce sei().

Přijímací rutina sbírá znaky do pole docasne[] tak dlouho, než přijde ukončovací znak, nebo zpráva dosáhne limitní délky. Na její konec přidá ukončovací znak 0, aby se s polem dalo pracovat jako s řetězcem. Pak ji zkopíruje do pole zprava[], aby si tak uvolnila pole docasne[] k přijmu další zprávy. A skrze globální proměnnou nova_zprava informuje hlavní vlákno programu, že došlo k přijmu celé zprávy. Hlavní vlákno nemá na práci nic jiného než čekat na novou zprávu. Jakmile dorazí, tak se pokusí ji pomocí funkcí scanf() dekódovat. Zprávu očekává ve tvaru “c=13245\n”. Všechny funkce scanf/printf jsou modifikované pro práci s řetězci z paměti Flash. Proto jsou zapsány s příponou “_P” a řidící řetězec je uzavřen do makra PSTR(). Tento přístup není nutný, ale šetří paměť. Zvláště u rozsáhlejších řetězců by se vyplatil. Vše ostatní je shodné s předchozím příkladem a nestojí za komentář.

 

001: // B) USART Příjem řetězců s přerušením + managment
002: // zpráv pomocí printf/scanf s podporou PGM
003: #define F_CPU 32000000
004: #include <avr/io.h>
005: #include <avr/pgmspace.h> // podpora pro práci s Flash pamětí
006: #include <avr/interrupt.h>
007: #include <stdio.h>// Printf a ekvivalenty
008: #include <string.h>// Pro práci s řetězci (např strncpy)
009:
010: #define BSEL 131// parametry baudrate 115200
011: #define BSCALE –3
012: #define DELKA_ZPRAVY 16// maximalní délka zprávy
013:
014: void clock_init(void);// inicializace clocku
015: void usart_init(void);// inicializace USART modulu
016: // funkce odesílání znaků
017: static int uart_putchar(char c, FILE *stream);
018: // kouzlo
019: static FILE mystdout = FDEV_SETUP_STREAM (uart_putchar, NULL, _FDEV_SETUP_WRITE);
020:
021: // informuje o dokončení příjmu nové zprávy
022: volatile char nova_zprava = 0;
023: // tady bude uložena celá přijatá zpráva
024: volatile char zprava[DELKA_ZPRAVY];
025: uint32_t x;
026:
027: int main(void){
028: stdout = &mystdout;// namapování Printf na USART
029: clock_init();// interních 32MHz
030: usart_init();
031: PMIC.CTRL = PMIC_MEDLVLEN_bm;// povolit všechna medium level přerušení
032: sei();// globální povolení přerušení
033:
034: while (1){
035: if(nova_zprava){// pokud přišla nová zpráva
036: // pokud zpráva odpovídá formátu
037: if(sscanf_P(zprava,PSTR("c=%lu"),&x)){
038: // pro ověření odešleme načtenou hodnotu
039: printf_P(PSTR("Nacteno %lu\n\r"),x);
040: // a něco navíc
041: printf_P(PSTR("Dvojnasobek %lu\n\r"),x*2);
042: }
043: // jinak vynadáme uživateli
044: else{printf_P(PSTR("Error\n\r"));
045: }
046: nova_zprava = 0;// zpráva je zpracována, budeme čekat na novou
047: }
048: }
049: }
050:
051: ISR(USARTD0_RXC_vect){
052: char prijaty_znak;
053: // dočasné místo pro přicházející zprávu
054: static char docasne[DELKA_ZPRAVY];
055: static uint8_t i=0;// počítadlo přijatých znaků
056: prijaty_znak = USARTD0.DATA;// vytáhneme znak co nejdřív z přijímacího bufferu
057: // pokud nepřišel ukončovací znak a ještě není znaků moc
058: if((prijaty_znak != ‘\n’) && (prijaty_znak!=‘\r’) && (i<DELKA_ZPRAVY-2)){
059: docasne[i] = prijaty_znak;// uložíme nově příchozí znak
060: i++;
061: }
062: else{// pokud je konec zprávy
063: docasne[i]= ‘\0′;// uložíme na konec znak ‘\0′ – ukončení řetězce
064: // předáme přijatou zprávu hlavní smyčce (v poli "zprava")
065: strncpy(zprava,docasne,DELKA_ZPRAVY);
066: nova_zprava = 1;// dáme vědět hlavní smyčce, že má novou zprávu
067: i = 0;// připravíme se na příjem nové zprávy
068: }
069: }
070:
071: static int uart_putchar (char c, FILE *stream){
072: // počkej dokud není odesílací registr prázdný
073: while ( !( USARTD0.STATUS & USART_DREIF_bm));
074: USARTD0.DATA = c;// pošli znak
075: return 0;
076: }
077:
078: void usart_init(void){
079: PORTD.DIRSET = PIN7_bm;// PD7 Tx výstup
080: PORTD.DIRCLR = PIN6_bm;// PD6 Rx vstup
081: PORTD.OUTSET = PIN7_bm;// log.1 na Tx // není nutné
082: PORTD.REMAP = PORT_USART0_bm;// Remapujeme Tx na PD7 a Rx na PD6
083: USARTD0.BAUDCTRLA = (char)BSEL;// dolních 8 bitů z 12bit hodnoty z BSEL
084: USARTD0.BAUDCTRLB = (BSCALE << USART_BSCALE_gp) | ((char)(BSEL>>8) & 0x0f);
085: // 1 stopbit, no parity, 8 data bits, asynchronni mod
086: USARTD0.CTRLC = USART_CHSIZE_8BIT_gc;
087: // povolit vysílač i přijímač
088: USARTD0.CTRLB = USART_TXEN_bm | USART_RXEN_bm;
089: // povolit přerušení od přijímače s prioritou medium
090: USARTD0.CTRLA = USART_RXCINTLVL_MED_gc;
091: }
092:
093: void clock_init(void){
094: OSC.CTRL |= OSC_RC32MEN_bm;// spustit interní 32MHZ oscilátor
095: // počkat než se rozběhne
096: while (!(OSC.STATUS & OSC_RC32MRDY_bm)){};
097: CCP = CCP_IOREG_gc;// odemčít zápis do CLK_CTRL
098: // vybrat 32MHz jako systémový clock
099: CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
100: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 

V příštím díle si ukážeme práci USARTU pomocí DMA.

 
Autor: Michal Dudka
 

Jiné příspěvky v kategorii:

 
Začínáme s mikrokontroléry ATxmega – USART 2. díl (využití DMA)
Začínáme s mikrokontroléry ATxmega – USART 3. díl (Práce v SPI režimu)
 
Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.