Reklama

USART u AVR 2. Díl – Praktické ukázky I

Úvod

V předchozím díle o jednotce USART mikrokontrolérů AVR (můžete ho najít ZDE) jsme si řekli základní teoretické poznatky. V tomto článku navážeme na teorii praktickými ukázkami použití jednotky USART.
Z výčtu vzorových zdrojových kódů, který lze nalézt v předchozím díle, si ukážeme následující:

  • USART – Jednoduché odesílání
  • USART – Jednoduchý příjem
  • USART – Implementace printf
  • USART – Příjem znakových pokynů s přerušením
  • USART – Příjem textových pokynů s přerušením
  • USART – Příjem s argumenty
  • USART – Odesílání s přerušením
USART – Jednoduché odesílání

Nejtriviálnější, co si můžeme ukázat, je odeslání jednoho bajtu po stisku tlačítka. Tlačítko si připojíme na pin PA4 standardně proti zemi. PA4 nakonfigurujeme jako vstup s interním “pull-up” rezistorem. Problémy se zakmitáváním tlačítka vyřešíme pomocí funkce _delay_ms(). Nejprve je nutné nastavit přenosovou rychlost “baud rate”. Jedna z nejrozšířenějších datových rychlostí je 9600 Bd, což je vcelku pomalá komunikace. Mikrokontrolér budeme taktovat interním RC oscilátorem na 8 MHz (takže pozor, kdo ještě neměnil programovací propojky “fuses”, přehoďte si zdroj clocku z 1 MHz na 8 MHz). V tabulce v datasheetu nebo na webu (viz předchozí díl) si pak najdeme, že UBRR registr musíme naplnit hodnotou 51 (nemáme zapnutý “double speed”). V UCSRB si nastavíme bit TXEN, čímž zapneme vysílač. Pin Tx bude od této chvíle přidělen jednotce USART. Pak musíme nakonfigurovat délku zprávy, počet stop bitů a paritu. To se provádí v registru UCSRC. Využijeme toho, že po restartu je celý registr naplněn nulami. Bity, které mají být nulové, proto explicitně nulovat nebudeme. Nastavíme pouze ty bity, které potřebujeme mít ve stavu log. 1. Tady je hned první úskalí. Při zápisu do registru UCSRC je vždy nutné nastavit bit URSEL. Berte to jako fakt a neřešte zatím proč. Chceme odesílat zprávu dlouhou 8 bitů, a proto tedy musíme nastavit bity UCSZ1 a UCSZ0. Paritu nepoužíváme, takže bity UPM0 a UPM1 necháváme ve stavu log. 0. Pracujeme v asynchronním režimu, takže bit UMSEL také necháváme ve stavu log. 0. Ve zprávě chceme mít jen jeden stop bit, proto USBS necháme ve stavu log. 0. Bit UCPOL nás v asynchronním režimu nezajímá. A to je v podstatě vše. Odeslání zprávy pak probíhá tak, že do registru UDR zapíšeme odesílaná data. Musíme si však pohlídat, zda je volný. Jeho stav nám signalizuje bit UDRE v registru UCSRA. Stav log. 1. tohoto bitu říká, že UDR je prázdný a je možné do něj zapsat data. Funkce pro odeslání dat, tedy nejprve slušně počká, než bude registr UDR prázdný a teprve pak do něj zapíše data. Tady je na místě poznamenat, že UDR slouží jen jako jakási přestupní stanice. Vysílání se ve skutečnosti provádí z Transmit Shift registru. Jakmile zapíšete data do UDR, čekají tam tak dlouho, než je Transmit shift registr volný. Nemůžete se tedy spolehnout na to, že když je UDR volný, že jsou data už odeslána (jejich odesílání může pořád probíhat). Výhoda takového uspořádání je v tom, že vysílání pak nemusí mít prostoje. Zatímco se vysílá z Transmit Shift registru, vy už máte UDR volný a můžete ho plnit novými daty. Ale o tom více v dalších příkladech.

 

001: // USART – jednoduché odesílání
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/delay.h>
005:
006: // makro na kontrolu stavu tlačítka
007: #define STISKNUTO (!(PINA & (1<<PINA4)))
008: char stav_tlacitka = 1;
009:
010: void odesli( unsigned char data );
011:
012: int main(void){
013: UBRRH = 0;
014: UBRRL = 51;// podle tabulky pro clock 8MHz a baud rate 9600Bd/s
015: UCSRB = (1<<TXEN);// zapnout vysílač
016: // 1 stop bit,žádná parita, 8bit zpráva
017: UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
018: DDRA &= ~(1<<DDA4);// vstup pro tlačítko
019: PORTA |= (1<<PORTA4);// pull-up na tlačítko
020:
021: while(1){
022: if(STISKNUTO && stav_tlacitka){
023: stav_tlacitka = 0;// tlačítko bylo stisknuto
024: odesli(‘a’);// pošli zprávu
025: }
026: // tlačítko bylo uvolněno
027: if(!STISKNUTO){stav_tlacitka = 1;}
028: _delay_ms(100);// ošetření zákmitů tlačítka
029: }
030: }
031:
032: void odesli( unsigned char data ){
033: while (!( UCSRA & (1<<UDRE)));// počkej než bude UDR prázdný
034: UDR = data;// odešleme data
035: }
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.
 

 

Obr. 1:- Přenos jednoho bajtu v konfiguraci 8 bit + 1 stop bit + žádná parita na 9600 Bd

Obr. 1: Přenos jednoho bajtu v konfiguraci 8 bit + 1 stop bit + žádná parita na 9600 Bd

Na Obr. 1. vidíte výsledek pokusu. Po stisku tlačítka se odešle písmeno ‘a’. V ASCII tabulce můžete najít, že malé ‘a’ má dekadicky hodnotu 97, což binárně odpovídá “0b01100001″. Vysílá se nejprve bit s nejnižší váhou (LSB). Takže na osciloskopu bychom zleva měli vidět sekvenci “0100001101”. První je start bit (log. 0.), pak následuje vysílaný bajt (od konce) a nakonec jeden stop bit (log. 1.). Při datové rychlosti 9600 Bd by jeden bit měl trvat 1/9600 = 104 us. Celá zpráva (1+8+1) bitů by měla trvat 1.04 ms. Připojíte-li si UART převodník do PC, mělo by vám po každém stisku tlačítka přibýt jedno písmeno ‘a’ v terminálu. Tento příklad asi v praxi moc nevyužijete, ale poslouží jako základ pro odesílání komplikovanějších zpráv.

USART – Jednoduchý příjem

Další naprosto jednoduchý příklad je příjem jednoho znaku. Ten už najde i nějaké využití tam, kde se mikrokontrolér může soustředit pouze na příjem a stačí mu jednopísmenné pokyny. My budeme rozsvěcet a zhasínat LED. Konfigurace přenosové rychlosti je stejná jako v předchozím příkladě. Namísto vysílače zapínáme přijímač a to nastavením bitu RXEN do stavu log. 1. Opět konfigurujeme délku zprávy na osm bitů, jeden stop bit a žádnou paritu. V hlavní smyčce program stále čeká na nastavení bitu RXC (“Receive Complete”), která značí, že byl dokončen příjem zprávy (bajtu). Ihned potom přečteme hodnotu z registru UDR. Registr má dvě různé role podle toho, zda do něj zapisujete nebo čtete. Nelze si z něj tedy přečíst to, co jste tam zapsali. Čtením z UDR ve skutečnosti vyčítáme tzv. “Receive Data Buffer Register” (RXB). Ten obsahuje dvojúrovňovou FIFO paměť. Jinak řečeno, můžou v něm být uloženy dva byty. Příchozí zpráva se bit po bitu načítá do tzv. “Receive Shift Register”. Technicky vzato tedy mohou přijít celkem tři byty aniž byste jakýkoli ztratili. Tato konfigurace vám dává možnost přijímat jeden byte za druhým bez prodlev potřebných na vyčtení. Zatímco jeden přijatý byte zpracováváte, může probíhat příjem dalšího byte (ale o tom až později). Přečtením UDR se navíc automaticky vynuluje i bit RXC (a nejen on), takže se nemusíte starat o jeho mazání. Obecně je ale nutné vyzvednout přijatý byte co nejdříve, protože zatím nemůžete nijak přesvědčit vysílač, aby s vysíláním počkal. Pokud vám přijdou tři byty (máme FIFO + “Shift Register”), vy si je nevyzvednete a začne přicházet další byte tak o nejstarší nevyzvednutý byte přijdete. O této nešťastné situaci vás bude informovat bit DOR v registru UCSRA.

 

001: // UART – jednoduché přijímání jednoho znaku
002: #include <avr/io.h>
003: #define F_CPU 8000000
004:
005: char prijato;
006:
007: int main(void){
008: UBRRH = 0;
009: UBRRL = 51;// podle tabulky pro clock 8MHz a baud rate 9600Bd/s
010: UCSRB = (1<<RXEN);// zapnout přijímač
011: // 1 stop bit,žádná parita, 8bit zpráva
012: UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
013: DDRA |= (1<<DDA1);// LED
014:
015: while(1){
016: while (!(UCSRA & (1<<RXC)));// čekej na příchozí byte
017: prijato = UDR;// načti příchozí byte
018: // roszviť LED
019: if(prijato == ‘a’){PORTA |= (1<<PORTA1);}
020: // zhasni LED
021: if(prijato == ‘q’) {PORTA &= ~(1<<PORTA1);}
022: }
023: }
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.
 
USART – Implementace printf

Od posílání jednotlivých znaků vede ještě dlouhá cesta k alespoň trochu inteligentní komunikaci. Zvlášť pokud výstup z mikrokontroléru má číst člověk. Tu cestu už za vás programátoři prošlápli a dali vám k dispozici funkci printf a její ekvivalenty. Vzhledem k tomu, že se učí jako první věc ve všech kurzech jazyka C, nejspíš se s ní už znáte. Její použití v mikrokontrolérech má ale svoje úskalí. Je totiž dosti velká a tudíž i pomalá. Je dokonce tak velká, že její plná verze by se vám do paměti u mnoha AVR vůbec nevešla. Standardní knihovny pro AVR ale obsahují “ořezanou” verzi. I ta však zabere v paměti něco okolo 1.5 kB (a to bez podpory čísel s plovoucí desetinnou tečkou). Existuje spousta variací na funkci printf jako jsou sprintf, snprintf atd. Nepotřebují nic nastavovat a můžete je po “includování” knihovny stdio.h používat hned. Ale funkci printf musíte nastavit “standardní výstup”, kterým totiž může být kde co. Alfanumerický displej, USART, SPI, I2C nebo obecně jakákoli funkce. My si standardní výstup vytvoříme drobnou modifikací odesílací funkce z příkladu USART – Jednoduché odesílání. A pak pomocí kouzelné formule překladači vysvětlíme, že právě na ni má standardní výstup přesměrovat. Dívejte se na to jako na kouzelné formule, protože já tomu nerozumím:) Když nahlédnete do seznamu funkcí v AVR knihovně stdio.h, všimnete si, že tam některé z nich existují s příponou _P (printf_P, sprintf_P). Ty slouží k tomu, aby mohl být formátovací řetězec čten z paměti FLASH (tedy z paměti programu). To se hodí k úspoře RAM. Zavoláte-li funkci printf(“ahoj”) překladač stejně musí řetězec “ahoj” uložit někde ve FLASH paměti. Pak ho zkopíruje do RAM (to už ho máte dvakrát) a teprve pak předá funkci printf. Tenhle krok je zbytečný a proto existují funkce pro práci s řetězci přímo z paměti FLASH. K tomu, abyste mohli obsluhovat paměť FLASH ale potřebujete knihovnu avr/pgmspace.h. Ta mimo jiné obsahuje makro “PSTR()”, které zjistí ukazatel na řetězec v paměti FLASH. Všechny řetězce z FLASH paměti tedy budete funkcím předávat skrze toto makro. Tak jak je patrné v našem příkladu a jak bude patrné i v dalších příkladech. Náš zdrojový kód po přeložení zabírá 1.77 kB. Daň za použití funkce printf je ale jen jednorázová, takže dál už nás nebude příliš omezovat. Nicméně i tak vám v některých situacích může vadit dlouhá doba zpracovávání funkce printf. Pak vám nezbude nic jiného než si potřebné funkce napsat sami (nebo googlit).

 

001: //USART – implementace Printf a řetězce v paměti Flash
002: #define F_CPU 8000000
003: #include <avr/io.h>
004: #include <util/delay.h>
005: #include <stdio.h>// printf()
006: #include <avr/pgmspace.h>// chceme pracovat s řetězci ve flash paměti
007:
008: // odesila znaky na UART
009: int usart_putchar(char var, FILE *stream);
010:
011: unsigned int pocet = 0;
012: // kouzelná formule
013: static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
014:
015: int main(void){
016: // kouzelná formule
017: stdout = &mystdout;// nastavení standardního výstupu na naši
018: // funkci usart_putchar()
019: UBRRL = 51;// baud rate 9600 s clockem 8MHz
020: UCSRB = (1<<TXEN);// zapnout vysílač
021: // 8bit přenos, URSEL vybira zapis do UCSRC
022: UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1);
023:
024: while(1){
025: printf_P(PSTR("Uz ti to opakuju po %u\n\r"),pocet);
026: _delay_ms(1000);
027: pocet++;
028: }
029: }
030:
031: int usart_putchar(char var, FILE *stream) {
032: while (!(UCSRA & (1<<UDRE)));// čekej než se dokončí případná předchozí vysílání
033: UDR = var;// odešli data
034: return 0;
035: }
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.
 

Obr. 2: Přenos delší zprávy

Obr. 2: Přenos delší zprávy
USART – Příjem znakových pokynů s přerušením

V tomto příkladu si ukážeme jak nastavit USART na příjem dat pomocí přerušení. Aby funkce nebyla složitá, tak budeme přijímat vždy pouze jeden znak. Po přijetí zprávy si rozsvítíme nebo zhasneme příslušnou LED diodu na pinech PA1 a PA2. Přijetí zprávy bude program potvrzovat odesláním řetězce “OK” zpět na váš terminál. Konfigurace USARTU vychází z předchozích příkladů. Protože chceme provozovat obousměrný přenos, tak spustíme přijímač i vysílač nastavením log. 1. u bitů RXEN a TXEN. Zprávu opět nakonfigurujeme jako 8bitovou bez parity a s jedním stop bitem. V registru UCSRB si nastavením log. 1. do bitu RXCIE povolíme přerušení od přijímače. V rutině přerušení pak provádíme jednoduchou operaci. Vyčteme co nejrychleji data z UDR, abychom tak uvolnili místo pro případný další přijímaný znak a dáme hlavní smyčce vědět, že jsou k dispozici nová data. V hlavní smyčce pak přijatý znak dekódujeme. Pokud by šlo o jednoduchý úkon, mohli byste jej provádět přímo v rutině přerušení. Ale protože naše funkce obsahuje i vysílání (a to je časově značně náročný úkon) není dobré ji do rutiny přerušení vkládat. Uvědomte si také, že nemáme vytvořen žádný mechanismus, který by řešil výjimečné situace. Například příchod nové zprávy aniž bychom tu starou stihli zpracovat a podobně. Proto je toto řešení potřeba brát s rezervou a uvažovat zda vám tyto limity nevadí. Kdo chce, může si vyzkoušet vyšší datové rychlosti. Zapsáním 0 do UBRR nastavíme rychlost 500 kBd a nastavením bitu U2X v registru UCSRA nastavíte 1 MBd. V obou případech komunikace běží. Vyšší datové toky oceníte u přenosu objemnějších dat.

 

001: // USART – příjem znakových pokynů přerušením
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <stdio.h>
005: #include <avr/pgmspace.h>
006: #include <avr/interrupt.h>
007:
008: int usart_putchar(char var, FILE *stream);
009:
010: static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
011: volatile char prijem = 0, prijaty_znak = 0;
012:
013: int main(void){
014: stdout = &mystdout;// nastavení printf na UART
015: // indikační LED
016: DDRA = (1<<DDA1)|(1<<DDA2);
017: UBRRL = 51;// baud rate 9600 s clockem 8MHz
018: // zapnout vysílač i přijímač
019: UCSRB = (1<<RXEN)|(1<<TXEN);
020: // upravit na čitelnější
021: UCSRC = (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);
022: UCSRB |= (1<&ltRXCIE);// povolit přerušení od přijímače
023: sei();// globální povolení přerušení
024:
025: while(1){
026: if(prijem){// pokud přišel nový znak
027: prijem = 0;
028: if(prijaty_znak == ‘1’){PORTA |= (1<<PORTA1);}
029: if(prijaty_znak == ‘2’){PORTA |= (1<<PORTA2);}
030: if(prijaty_znak == ‘0’){PORTA &= ~((1<<PORTA1)|(1<<PORTA2));}
031: printf_P(PSTR("OK\n\r"));// pošli odpověď
032: }
033: }
034: }
035:
036: ISR(USART_RXC_vect){
037: prijaty_znak = UDR;// Vyčíst přijatá data
038: prijem = 1;// dej hlavní smyčce info o příchodu nového znaku
039: }
040:
041: int usart_putchar(char var, FILE *stream) {
042: while (!(UCSRA & (1<<UDRE)));// čekej než se dokončí případná předchozí vysílání
043: UDR = var;// odešli data
044: return 0;
045: }
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.
 

Obr. 3: Příjem znaků s přerušením. Červený průběh Tx Atmelu, modrý průběh Rx Atmelu. Znaky LF ('\n') a CR ('\r') slouží v terminálu k přejití na nový řádek a návratu kurzoru na začátek řádku.

Obr. 3: Příjem znaků s přerušením. Červený průběh Tx Atmelu, modrý průběh Rx Atmelu. Znaky LF (‘\n’) a CR (‘\r’) slouží v terminálu k přejití na nový řádek a návratu kurzoru na začátek řádku.
USART – Příjem textových pokynů s přerušením

Někdy budete chtít svou aplikaci vybavit trošku inteligentnější komunikací s PC. Budete chtít, aby obsluha mohla zadávat příkazy v trochu srozumitelnější formě než jedním písmenem. Může je zadávat třeba řetězcem (“Start”, “Stop” a podobně). Kromě čitelnosti tu ale neexistuje žádná další výhoda nad příkazy ve formě jednoho znaku. Naopak za příjem takových zpráv musíme zaplatit jistou daň jejich složitějším dekódováním. Nakonec se ale ukáže, že to nebude tak špatný nápad (hlavně v dalším příkladě). USART necháme nastavený stejně jako v příkladě USART – příjem znakových pokynů s přerušením. Upravíme ale rutinu přerušení a přidáme funkci dekódující řetězce. Pro práci s řetězci máme k dispozici knihovnu string.h. Ta obsahuje spoustu užitečných funkcí. My z ní použijeme funkce strncpy() a strncmp(). Přirozeně počítám s tím, že si pamatujete, jak vypadá v C řetězec. Funkce strncpy() kopíruje obsah jednoho řetězce do druhého s omezením počtu znaků. Stane-li se tedy jakákoliv havárie a kopírovaný řetězec nebude obsahovat ukončovací znak ‘\0′, zastaví se funkce po “n” znacích. Funkce strncmp() porovnává dva řetězce (opět s omezením na “n” znaků) a vrací log. 0. pokud jsou řetězce shodné. Protože očekáváme zadávání zprávy ručně z terminálu, měli bychom si připomenout, že stiskem klávesy Enter se odešle znak ‘\n’ (nebo ‘\r’ podle terminálového programu). Naše funkce tedy bude takový znak očekávat a pochopí podle něj, že je zpráva kompletní. Délku zprávy omezíme na 15 znaků (bez znaku Enter). Rutina přerušení bude vykonávat následující činnost. Nejprve vyzvedne z registru UDR nově příchozí byte a ověří, zda neobsahuje některý z ukončovacích znaků. Pokud ne, uloží příchozí byte do paměti na příslušnou pozici. Postupně tak v poli docasne skladuje přijaté znaky. Jakmile přijde ukončovací znak, nebo jakmile je přijímaná zpráva příliš dlouhá, budeme ji považovat za celou a přistoupíme k dalšímu zpracování. Nakonec pole doplníme ukončovacím znakem pro řetězce (‘\0′), čímž z pole znaků uděláme řetězec. Pak pole docasne pomocí funkce strncpy() zkopírujeme do pole zprava. Tím si uvolníme pole docasne na příjem nové zprávy. Nakonec dáme hlavní smyčce vědět, že je potřeba zpracovat novou zprávu (pomocí proměnné nova_zprava). Od tohoto okamžiku můžeme přijímat novou zprávu a máme dost času aktuální zprávu zpracovat. Zpracování provádíme pomocí funkce strncmp() a porovnáváme příchozí zprávu s očekávanými možnostmi. Tady je opět na místě připomenout, že pro ušetření RAM bychom mohli použít funkcci strncmp_P a porovnávaný řetězec držet v paměti FLASH. Pokud zpráva neodpovídá žádné možnosti, dáme o tom vědět obsluze.

 

001: // UART – příjem textovych pokynů s přerušením
002: #define F_CPU 8000000
003: #include <avr/io.h>
004: #include <stdio.h>
005: #include <avr/pgmspace.h>
006: #include <avr/interrupt.h>
007: #include <string.h>// funkce pro praci s řetězci strncmp(), strncpy()
008: #define DELKA_ZPRAVY 16// maximalni delka zpravy
009:
010: // odesila znaky na UART
011: int usart_putchar(char var, FILE *stream);
012:
013: static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
014: volatile char nova_zprava = 0;// indikuje prichod nove zpravy
015: // tady bude uložena celá přijatá zpráva
015: volatile char zprava[DELKA_ZPRAVY];
016: // sem se postupně ukládají přicházející znaky
016: volatile char docasne[DELKA_ZPRAVY];
017:
018: int main(void){
019: stdout = &mystdout;// nastavení printf na UART
020: // indikační LED
021: DDRA = (1<<DDA1)|(1<<DDA2);
022: UBRRL = 0x33;// baud rate 9600 s clockem 8MHz
023: // zapnout vysílač i přijímač
024: UCSRB = (1<<RXEN)|(1<<TXEN);
025: // 8bit přenos, URSEL vybira zapis do UCSRC
026: UCSRC = (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);
027: UCSRB |= (1 << RXCIE);// povolit přerušení od přijímače
028: sei();// globální povolení přerušení
029:
030: while(1){
031: if(nova_zprava){// pokud přišla nová zpráva
032: // porovnáme obsah zpráv
033: if(strncmp(zprava,"ahoj",DELKA_ZPRAVY) == 0){
034: // pošli odpověď
035: printf_P(PSTR("Nazdar.\n\r"));
036: }
037: else if(strncmp(zprava,"rozsvit",DELKA_ZPRAVY) == 0){
038: PORTA |= (1<<PORTA1);
039: // pošli odpověď
040: printf_P(PSTR("S radosti.\n\r"));
041: }
042: else if(strncmp(zprava,"rozsvit vic",DELKA_ZPRAVY) == 0){
043: PORTA |= (1<<PORTA1)|(1<<PORTA2);
044: // pošli odpověď
045: printf_P(PSTR("Jak je libo.\n\r"));
046: }
047: else if(strncmp(zprava,"zhasni",DELKA_ZPRAVY) == 0){
048: PORTA &= ~((1<<PORTA1)|(1<<PORTA2));
049: // pošli odpověď
050: printf_P(PSTR("Zhasnuto.\n\r"));
051: }
052: else{
053: // žádná známá zpráva
054: printf_P(PSTR("Nerozumim.\n\r"));
055: }
056: nova_zprava = 0;// zpráva je zpracována, budeme čekat na novou
057: }
058: }
059: }
060:
061: ISR(USART_RXC_vect){
062: char prijaty_znak;
063: static char i = 0;// pocitadlo znaku v prijimane zprave
064: prijaty_znak = UDR;// vytáhneme znak co nejdřív z přijímacího bufferu
065: // pokud nepřišel ukončovací znak a ještě není znaků moc
066: if((prijaty_znak != ‘\n’) && (prijaty_znak != ‘\r’) && (i<DELKA_ZPRAVY-2)){
067: docasne[i] = prijaty_znak;// uložíme nově příchozí znak
068: i++;
069: }
070: else{
071: // pokud je konec zprávy
072: docasne[i] = ‘\0′;// uložíme na konec znak ‘\0′ – konec řetězce
073: // zkopirujeme přijatý text do pole zprava
074: strncpy(zprava,docasne,DELKA_ZPRAVY);
075: nova_zprava = 1;// dáme vědět hlavní smyčce že má novou zprávu
076: i = 0;// připravíme se na příjem nové zprávy
077: }
078: }
079:
080: int usart_putchar(char var, FILE *stream) {
081: while (!(UCSRA & (1<<UDRE)));// čekej než se dokončí případná předchozí vysílání
082: UDR = var;// odešli data
083: return 0;
084: }
085:
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.
 

Obr. 4: Příjem textových pokynů s přerušením, zvýrazněn čas potřebný pro zpracování přijaté zprávy v rutině přerušení

Obr. 4: Příjem textových pokynů s přerušením, zvýrazněn čas potřebný pro zpracování přijaté zprávy v rutině přerušení

Na obr. 4. máme na tmavě modrém průběhu znázorněnuo dobu průběhu rutiny přerušení. Nejdelší část rutiny, tedy příjem ukončovacího znaku spolu s kopírováním celé zprávy z pole “docasne” do pole “zprava” trvá 19 us. Během tohoto času smí přijít nanejvýše dva nové znaky (na přijímači je buffer), abychom je ještě měli šanci zpracovat. Vidíte, že při rychlostech 9600 Bd je doba zpracování zanedbatelně krátká ve srovnání s dobou potřebnou na přenesení jednoho znaku. Takže si s tím nemusíte lámat hlavu. Při rychlejších přenosech to ale může být problém. Ale pro výrazně rychlejší přenosy si stejně budete muset přestoupit na nějakou jinou platformu jako třeba ARM, která je vybavená DMA.

USART – Příjem s argumenty

Poslední věc, která nám chybí v naší výbavě, je možnost přijímat vstupní hodnoty. Často budete chtít, aby obsluha z terminálu mohla zadávat vstupy v podobě “hladina = -15″, “time = 73″, “mode = a”. Budete tedy chtít předchozí příklad vybavit nějakým chytrým dekódováním zprávy. Variant, jak něco takového vytvořit, je mnoho. Můžete řetězec rozdělit pomocí funkce strtok a jeho části pak zpracovávat pomocí funkce itoa případně použít jiný přístup. My si ukážeme využití funkce sscanf. Celé nastavení USARTU i celá přijímací rutina je shodná jako v příkladě “Příjem textových pokynů s přerušením”. Na funkci sscanf se můžete koukat jako na opak funkce printf nebo přesněji na funkci sprintf. Dodáte jí formátovací řetězec, pak jí dodáte přijatý řetězec a ona se z něj pokusí vytáhnout hodnoty proměnných. Pokud se jí to povede, tak vám vrátí počet načtených položek. My použijeme její variantu s příponou “_P” abychom mohli mít řídící řetězec v paměti FLASH. Zpráva by měla být ve formátu “c=xxxx” + ukončovací znak. Za “xxxx” můžeme napsat libovolnou hodnotu typu “integer”. Tady vás opět odkážu na práci s funkcí printf obecně. Funkcemi printf a sscanf přenášíte veškerou práci se zpracováním textových pokynů na mikrokontrolér, proto počítejte s tím, že mu může vzít podstatné množství času. Pokud program pracuje výhradně s lidskou obsluhou u terminálu, tak není s čím si lámat hlavu. Lidské ruce i mozek jsou dost pomalé na to, aby zdržení vůbec nezaznamenaly. Při strojovém nahrávání větších bloků dat a při vyšších datových rychlostech už ale na dobu zpracování budete muset brát zřetel. V takovém případě je vhodnější nepoužívat pro komunikaci textovou formu a posílat data z PC programu do mikrokontroléru přímo binárně, aby se tak vyhnul jejich náročnému zpracování. Ale to je problematika na samostatný článek:) Protože je kód stejný jako v příkladě “Příjem textových pokynů s přerušením”, uveřejním jen hlavní smyčku, ve které probíhá zpracování zprávy.

 

001: // USART příjem hodnot – pomocí sscanf_P()
002: // zbytek kódu shodný s příkladem
003: // Příjem textových pokynů s přerušením
004: while(1){
005: if(nova_zprava){// pokud přišla nová zpráva
006: // pokud zprava odpovida formatu
007: if(sscanf_P(zprava,PSTR("c=%d"),&x)){
008: // vytvoření zprávy
009: printf_P(PSTR("Nacteno %d\n\r"),x);
010: }
011: // jinak vynadame uzivateli
012: else{printf_P(PSTR("Error\n\r"));
013: }
014: nova_zprava = 0;// zpráva je zpracována, budeme čekat na novou
015: }
016: }
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.
 
USART – Odesílání s přerušením

Někdy se vám může stát, že by vás odesílání příliš zdržovalo. Typicky při odesílání větších objemů dat, nebo při nízkých datových rychlostech. V takové situaci můžete (nebo snad i musíte) využít možnost odesílat pomocí přerušení. Filosofie je prostá. Odesílanou zprávu si vložíte do pole (nebo do řetězce), spustíte odesílání, po odeslání každého znaku jen rychle skočíte do rutiny přerušení, dáte odesílat další znak a zbytek času se můžete věnovat jiným úkolům. Naše funkce bude odesílat zprávu po stisku tlačítka na pinu PA4. Konfigurace bude opět tradičních, 8 datových bitů, 1 stop bit a žádná parita. Přenosová rychlost je 9600 Bd a zapneme jen vysílací modul (nastavením bitu TXEN). Pomocí proměnné provoz budeme signalizovat, že probíhá odesílání zprávy. I když by to náš program asi nepotřeboval, měli byste na to brát zřetel. Pokud byste totiž během posílání změnili odesílaná data, mohl by vám do PC dorazit pěkný zmatek. Funkcí sprintf si vytvoříme odesílaný řetězec v poli zprava. Proměnná i slouží jako index ukazující, který znak zprávy se má odesílat. Ten přirozeně před odesíláním vynulujeme, protože chceme zprávu odesílat od prvního znaku. Pomocí funkce strlen zjistíme délku vysílané zprávy abychom věděli kdy vysílání ukončit. Nastavením bitu UDRIE v registru UCSRB povolíme přerušení od prázdného UDR. Jakmile je registr UDR prázdný je vyvoláno přerušení a my do něj uložíme další znak zprávy. To děláme tak dlouho, než odešleme celou zprávu. Po jejím odeslání prostě přerušení vypneme a vše je hotovo. Je potřeba si ale uvědomit, že jakmile předáme poslední znak do UDR, ještě to neznamená, že je vše odesláno. V tom okamžiku se může z Transmit buffer registru stále ještě odesílat předchozí znak. USART tedy znak, který jsme teď uložili do UDR, ještě vůbec nemusel začít odesílat. Všechny znaky zprávy budou odeslány až v okamžiku, kdy bude nastavena vlajka TXC (v registru UCSRA). To nastane v okamžiku, kdy se Transmit shift registr vyprázdní a v UDR nečekají žádná další data.

 

001: //USART – odesílání s přerušením
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <stdio.h>
005: #include <avr/pgmspace.h>
006: #include <avr/interrupt.h>
007: #include <string.h>// funkce pro praci s řetězci strlen()
008: #define DELKA_ZPRAVY 16// maximalni delka zpravy
009: // tady bude uložena celá přijatá zpráva
010: volatile char zprava[DELKA_ZPRAVY];
011: char stisk=0, provoz=0;
012: unsigned int x=0;
013: volatile unsigned int delka_zpravy=0;
014: volatile unsigned int i;
015:
016: int main(void){
017: DDRA &= ~(1<<DDA4);
018: PORTA |= (1<<PORTA4);// pull up na tlačítko
019: UBRRL = 51;// baud rate 9600 s clockem 8MHz
020: UCSRB = (1<<TXEN);// zapnout vysílač i přijímač
021: // 8bit přenos, URSEL vybira zapis do UCSRC
022: UCSRC = (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);
023: sei();// globální povolení přerušení
024:
025: while(1){
026: // první stisk tlačítka
027: if(!(PINA & (1<<PINA4)) && stisk == 0 && provoz == 0){
028: provoz = 1;// dalsi odeslani nelze spustit drive nez toto skonci
029: stisk = 1;// tlačítko bylo poprvé stisknuto
030: x++;// proměnná kterou odesíláme – jen aby zpráva nebyla nudná
031: // tak mame ulozeno
032: snprintf_P(zprava,DELKA_ZPRAVY,PSTR("Odeslano: %u\n\r"),x);
033: i = 0;// index znaků v odesílané zprávě, začínáme
034: // odesílat od nultého znaku
035: // kolik znaků budu odesílat ?
036: delka_zpravy=strlen(zprava);
037: UCSRB |= (1<<UDRIE);// povolit přerušení od prázdného UDR – začínáme odesílat
038: }
039: if(PINA & (1<<PINA4)){
040: stisk = 0;// tlačítko bylo uvolněno
041: }
042: }
043: }
044:
045: ISR(USART_UDRE_vect ){
046: UDR = zprava[i];// odešli další znak zprávy
047: i++;
048: if (i > delka_zpravy – 1){ // pokud jsou odeslány všechny znaky
049: // vypni přerušení
050: UCSRB &= ~(1<<UDRIE);
051: provoz = 0;// odemci odeslani dalsi zpravy
052: }
053: }
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.
 

 
Autor: Michal Dudka
 

Následující a předchozí příspěvek v kategorii:

 
Předchozí: USART u AVR 1. Díl Teoretický popis

 
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.