logo_elektromys.eu

/ USART I |

/ Možnosti USARTu |

Na moderních AVR je USART mnohem dokonalejší. Zvládá širokou paletu funkcí jejichž kompletní popis by vydal na celý seriál. Jen pro přehled si bod po bodu projdeme zajímavé funkce. Časem se ukáže ke kterým z nich se dostaneme a které zůstanou neobjevené.

/ Příklad |

Protože nejsme začátečníci, nemusíme si zkoušet elementární úlohy a můžeme se věnovat hned těm užitečným. Téma prvního příkladu bude tradiční - vysílání dat do PC. K tomu budeme potřebovat nějaký peřvodník USB-UART. Kdo chce může použít běžně dostupné modulky (FDTI, PL2303, CP2102, CH340 atd.). Ti z vás kdo programují na Xnano modulech, můžete využít mEDBG (nemusíte tedy nic připojovat). Krom debugu totiž zastane i roli zmíněného převodníku USB-UART. Jen dávejte pozor na jeho chybičky při konfiguraci baudrate. Běží totiž na Atmega32U4 (16MHz), ten má USART shodný s většinou starších Atmelů a ty mají problémy s přesným nastavením vyšších baudrate. Nastavíte-li si v terminálu klasickou rychlost 115.2kb/s, tak váš mEDBG poběží s rychlostí 111kb/s (1MHz/9) - tedy s tak velkou chybou (3.5%), že vám terminál vypíše jen samé nesmysly. Na Xnano modulech tedy pracujte buď na nižších baudrate a nebo volte celočíselné zlomky 1MHz (tedy například 250kb/s).

Z tabulky 5-1 PORT Function Multiplexing v datasheetu můžete vyčíst že pin Tx je buď na PB2 (defaultně) a je možné ho přemapovat na PA1. Je na vás, kterou variantu využijete. V mém příkladu bude Tx remapován právě na PA1, neboť ten je na desce připojen do Rx mEDBG (prostě na Xnano se využívá pinů PA1 a PA2). Žádné další HW informace k následujícímu příkladu nejsou potřeba.

/ Baudrate |

Jak už jsem zmínil, "fractional baudrate generator" umožňuje volit baudrate pomocí zlomku a výrazně zlepšuje rozlišení. USART může používat dvě hodnoty oversamplingu 16x a 8x. Běžná hodnota je 16x, pro vyšší přenosové rychlosti můžete redukovat oversampling na 8x nastavením bitů RXMODE v registru CTRLB. Maximální přenosová rychlost v asynchronním režimu s 20MHz clockem je 1.25Mb/s (respektive 2.5Mb/s s oversampling 8x). Baudrate volíte pomocí 16bit hodnoty. Dolních 6bitů slouží jako zlomková část čísla s pevnou desetinnou tečkou. Což vás vlastně nemusí moc zajímat, protože baudrate i hodnotu registru BAUD si spočtete podle následujících vzorců.

Protože v datasheetu není tabulka s hodnotami BAUD pro různé baudrate a různé frekvence clocku, napsal jsem si pro výpočet jednoduché makro (pracující s "defaultním" oversamplingem 16x). Pokud se z nějakého důvodu rozhodnete používat vysoké přenosové rychlosti pro posílání dat do PC skrze FTDI a jiné moduly, mějte na paměti, že vyrovnávací paměť v těchto modulech není bezedná. Jinak řečeno při vyšších rychlostech a větším objemu dat se může stát že se FTDI zaplní paměť dřív než se PC rozhodne si přijatá data po USB přečíst (a paměť tak uvolnit). Krátké zprávy (pod 512B) posílané ne příliš často (pod 200Hz) by neměly činit žádné potíže ani s vysokým baudrate. Opět ale dejte pozor na to jaké hodnoty baudrate podporují vaše "modulky" (FTDI, PL2303...).

/ Printf na AVR |

Protože funkce printf() (z knihovny stdio.h) je na malé mikrokontroléry "velká", máme na AVR odlehčenou variantu (bez podpory float a podobně). Aby formátovací řetězec (tedy kusy neměnného textu) nezavazel v RAM, máme k dispozici upravenou funkci printf_P která čte formátovací řetězec z paměti flash. Ke snadnému použití konstant a řetězců ve flashi slouží makro PSTR (z knihovny progmem.h. Propojení výstupu funkce printf na UART vyžaduje pár "kouzelných" formulí o nichž se můžete něco víc dočíst v tutoriálu na UART pro starší AVR.

/ Inicializace UARTu |

Náš program využívá faktu, že po startu (restartu) jsou v registrech "defaultní" hodnoty. Ty jsou voleny tak aby odpovídaly nejběžnější konfiguraci UARTu. Tedy Asynchronnímu režimu, zprávě dlouhé 8 znaků, 1 stop bitu a vypnuté paritě. Díky tomu se o tuto konfiguraci většinou nemusíme starat. Přirozeně pokud budete chtít jinou konfiguraci, nebo UART rekonfigurujete z jiného nastavení budete muset obsah CTRLC změnit. Nastavení provádím ve funkci usart_init(). Nejprve nastavím piny. Jak jsme si již řekli, TX je v našem případě na pinu PA1. Nejprve tedy na PA1 pošlu log.1, poté jej nastavím jako výstup. Ve většině případů bych si to mohl odpustit a konfigurovat rovnou UART a těch pár mikrosekund kdy jsou jeho piny vstupy neřešit. Ale pro klid duše jsem to provedl podle návodu v datasheetu. Dál musím TX remapovat z PB2 na PA1 (kam je připojený RX mého mEDBG). Pak už nám stačí pouze zapsat BAUD hodnotu (v našem případě pro 38.4kb/s) a bitem TXEN zapnout vysílač. Od tohoto okamžiku je TX pin pod kontrolou UARTu a můžeme vysílat.

Odeslání znaku provádí funkce usart_putchar() blokujícím způsobem. Čeká dokud se ve vysílacím bufferu neuvolní místo - o čemž se dozví z vlajky DREIF (Data Register Empty Flag) v registru STATUS. Teprve pak zapíše jeden znak do registru TXDATAL (TXDATAH slouží pro vysílání v 9bit formátu). Drtivá většina aplikací pro 8bit mikrokontroléry si s tímto způsobem odesílání vystačí. Výjimečně mohou nastat situace, kdy by vám "blokování" během vysílání vadilo, pak můžete odesílat i s pomocí přerušení, ale o tom až v jiném díle. Dál asi není třeba nic dodat, takže následuje zdrojový kód.

/* tutorial TinyAVR 1-Series 
*  UART 1 - vysílání řetězců pomocí Printf 
*  (pozor na chyby baudrate mEDBG)
*  Ve fuses zvolen 20MHz oscilátor (lze volit ještě 16MHz)
*/

#define F_CPU 20000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <stdio.h> // kvůli Printf_P (formátovací řetězec je ve Flash)
#include <avr/pgmspace.h> // kvůli makru PSTR (v printf_P) 

#define BAUDRATE 38400UL // maximum baudrate F_CPU/16, (F_CPU/8 with CLK2X)

// Makro na výpočet hodnoty BAUD registeru (BAUDVAL) 
#if BAUDRATE < F_CPU/16 // pokud není baudrate větší než maximum
 #if (((10*4*F_CPU)/BAUDRATE) % 10) > 4 // zaokrouhlování (round)
  #define BAUDVAL (((4*F_CPU)/BAUDRATE)+1) // zaokrouhlíme nahoru (round up)
 #else
  #define BAUDVAL ((4*F_CPU)/BAUDRATE) // zaokrouhlíme dolů (floor)
 #endif
#else
 #warning "Baudrate is out of range ! (max baudrate without CLK2X is F_CPU/16 (1.25Mb/s if F_CPU=20MHz)"
 #define BAUDVAL 0
#endif

// fce k odeslání jednoho znaku po UARTu ve formátu nutném pro přesměrování printf na UART
int usart_putchar(char var, FILE *stream); 
void clock_20MHz(void);
void usart_init(void);

// kouzelná formule, pro přesměrování printf na UART
static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t pocet=0; // pomocná proměnná (aby bylo co posílat)

int main(void){
 clock_20MHz(); // taktujeme na 20MHz
 usart_init(); // konfigurace UARTu
 stdout = &mystdout; // přesměrujeme Printf na UART
 
 while (1){
  printf_P(PSTR("Uz to opakuju po %u\n\r"),pocet); // něco pošleme ...
  pocet++; // jen aby bylo co posílat...
  _delay_ms(1000); // ... každou sekundu
 }
}

// odešle jeden znak (formát pro "printf" implementaci)
int usart_putchar(char var, FILE *stream){
while(!(USART0.STATUS & USART_DREIF_bm)){}  // počkat než bude místo v odesílacím bufferu 
 USART0.TXDATAL = var; // naložit data k odeslání
 return 0 ;
}

void usart_init(void){
 // využijeme toho že po startu/restartu je UART nastaven ...
 // ... jako 8bit, 1stop bit, žádná parita, asynchronní režim
 // část konfigurace si tedy můžu odpustit
 PORTA.OUTSET = PIN1_bm;  // log.1 PA1 (Tx) - neutrální hodnota na UARTu
 PORTA.DIRSET = PIN1_bm; // PA1 (Tx) - je výstup
 PORTMUX.CTRLB = PORTMUX_USART0_bm; // Remapujeme Tx na PA1
 USART0.BAUD = BAUDVAL; // nastavit baudrate
 USART0.CTRLB |= USART_TXEN_bm; // povolit vysílač (od teď přebere UART kontrolu nad PA1)
}

// Nastaví clock 20MHz (z interního 20MHz bez děličky)
void clock_20MHz(void){
 // v případě potřeby zde dočasně vypněte přerušení
 CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru
 CLKCTRL.MCLKCTRLA =  CLKCTRL_CLKSEL_OSC20M_gc; // vypouští clock na PB5 (CLKOUT), vybírá 20MHz oscilátor
 CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru
 CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku)
}
Oscilogram našeho vysílání
Výpis z terminálu (Realterm)

| Závěr /

Všimněte si, že velikost programu kvůli printf() přesahuje 1.7kB (s optimalizací -O1) a na Attiny416 zabírá skoro polovinu paměti. Na demonstraci to stačí, ale jistě se najdou aplikace, které to bude limitovat. Přijde mi trochu škoda, že Atmel neosadil Xnano modul některou větší "tinou" jako třeba Attiny1617. Tím dnešní díl končí a já doufám, že vás nějak obohatil a že se setkáme u dalších tutoriálů.

| Odkazy /

Home
| V1.0 30.12.2018 /
| By Michal Dudka (m.dudka@seznam.cz) /