Reklama

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

Úvod

V tomto díle zakončíme seriál zaměřený na použití periférie USART. Tentokrát si ukážeme práci v SPI režimu.

Níže lze vidět seznam všech příkladů v seriálu.

  • Odesílání s Printf (ATxmega E) (A) – 1. díl
  • Příjem řetězců s přerušením + printf/scanf (ATxmega E) (B) – 1. díl
  • 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)
Popis

USART na mikrokontrolérech ATxmega umí pracovat jako SPI Master. Přirozeně se hned můžete zeptat, jaký to má smysl, když pro SPI má ATxmega jinou periferii? Správnou odpověď neznám. Možná proto, abyste měli více SPI rozhraní. Důležité je ale vědět, že se SPI a SPI realizované pomocí USART v několika věcech liší. Běžné SPI obsahuje jeden posuvný registr pro příjem i vysílání, takže je většinou možné SPI zařízení řetězit za sebe (tzv. daisy chain). Existence pouze jediného datového registru, znemožňuje jeho “bufferování”. A v tom se právě SPI realizované USARTem liší od běžného SPI. SPI realizované USARTem není možné řetězit, ale má k dispozici vyrovnávací paměť “buffer” (o něm už byla řeč v předchozích příkladech). Vyrovnávací paměť zjednodušuje program, který nemusí tak rychle reagovat na odeslání dat a přenos je plynulejší. Osobně mám ale dojem, že to je čistě estetická stránka věci a moc si nedovedu představit situaci, kdy by výhoda vyrovnávacího paměti nějak výrazně zkvalitnila komunikaci. I přes to si v následujícím příkladu předvedeme jak USART v režimu SPI používat.

Před komunikací je přirozeně potřeba vhodně nakonfigurovat piny. Jako hodiny “clock” slouží výstup synchronní verze USARTu XCK (PD5), jako MOSI potom poslouží Tx pin (PD7) a jako MISO vývod Rx (PD6). Dále si připravíme výstup pro CS (PD4). První změny si všimnete v konfiguraci přenosové rychlosti “Baudrate”. Vzhledem k tomu že jde o synchronní režim, tak je její výpočet výrazně jednodušší. My se pokusíme z periferie vyždímat co nejvíc a volíme tedy BSEL = 0, čímž nastavujeme přenosovou rychlost na 16 Mb/s. SPI může pracovat ve čtyřech režimech (více zde). Jejich volbu provádíme nastavením bitu UCPHA v registru CTRLC a případným invertováním výstupu (XCK). Invertování výstupu se provádí bitem INVEN v příslušném konfiguračním registru příslušného pinu (v našem případě v registru PIN5CTRL). Způsob konfigurace je uveden v tabulce 21-2 v “datasheetu”. Pořadí bitů ve zprávě se pak nastavuje bitem UDORD (my jej necháme vynulovaný a budeme vysílat jako první MSb). Tyto bity ale nejsou v hlavičkovém souboru definovány, pro přehlednost jsem je dodefinoval přímo ve zdrojovém kódu. Příklad používá režim 0, pokud chcete použít režim 3 odkomentujte si příslušné řádky v inicializační funkci usart_init().

Trochu více pozornosti budeme věnovat samotné odesílací funkci uart_spi(). Ta totiž využívá vyrovnávací paměti jak na vysílací, tak na přijímací straně. Jakmile je funkce zavolána, nejprve zjistí, zda je v odesílacím “bufferu” volno a případně počká než se vyprázdní. Poté aktivuje Slave obvod pomocí pinu CS a nahraje do “bufferu” první data. Data se typicky okamžitě začnou vysílat a “buffer” se opět vyprázdní. Funkce ihned naloží i druhou polovinu dat (odesíláme šestnáct bitů a MSB jako první). Pak počká na dokončení celého přenosu a deaktivuje Slave obvod (CS do stavu log. 1). Teprve pak funkce přečte oba nově příchozí byty. To si může dovolit právě díky vyrovnávací paměti u přijímače (který je schopen pojmout právě tyto dva byty a ještě další začít přijímat). Ty pak funkce složí do šestnáctibitového čísla a vrátí ho. Nezapomene přirozeně po práci ještě smazat stavový bit TXCIF. Tento způsob použití drobně zkrátí chod celé funkce. Pokud by ale k USARTu přistupovala nějaká další funkce a “neuklidila” by po sobě přijímací “buffer” (tedy nevyzvedla z něj data), měli bychom trochu problém. Jistější by tedy bylo před samotným vysíláním zkontrolovat, zda v přijímacím “bufferu” nezůstala nějaká nevyzvednutá data. A pokud ano, tak “buffer” vyčistit (třeba přečtením těchto nevyzvednutých dat). Když se podíváte na záznam komunikace z osciloskopu (Obr. 1), tak můžete vidět, že už je to docela fofr. Za přibližně 1.5 µs je celé vysílaní vyřízené. A vzhledem k tomu, že valná většina SPI zařízení má maximální komunikační rychlosti vyšší jak 10 MHz, otevírají se vám zajímavé možnosti. Představte si třeba, že tento příklad upravíte, aby využíval DMA. V takovém případě budete schopni odesílat datový tok přibližující se k 2 MB/s. Zajímavé to může být třeba tehdy, když vám nestačí RAM na mikrokontroléru. To pak můžete sáhnout po levné externí SRAM s SPI rozhraním. Datová rychlost by měla stačit ukládat data z A/D převodníku běžícího na plné rychlosti (1 Msps). Nebo může posloužit naopak, při čtení dat ze “sériové” RAM do D/A převodníku. Toto řešení vás oproti klasické “paralelní” RAM nebude stát odhadem dvacet nebo i více pinů, ale pouhé čtyři. Je to natolik zajímavá představa, že ji možná v některém z budoucích návodů prakticky vyzkouším. Zdrojový kód, najdete pod obrázkem.

 
Obr. 1: USART v režimu SPI, 16bit přenos, MSb jako první, datová rychlost 16 Mb/s.

Obr. 1: USART v režimu SPI, 16bit přenos, MSb jako první, datová rychlost 16 Mb/s.

 

Okomentovaný zdrojový kód

 

// E) USART – SPI mód s pollingem (16bit)
001: // – pro ATxmega8E5/16E5/32E5
002: #define F_CPU 32000000
003: #include <avr/io.h>
004: #include <util/delay.h>
005:
006: void clock_init(void);// inicializace clocku
007: void usart_init(void);
008: uint16_t uart_spi(uint16_t datain);
009:
010: #define BSEL 0// přenosová rychlost (16Mb/s)
011: // definování bitů (chybí v iox32e5.h) – zlepšuje čitelnost
012: #ifndef USART_UCPHA_bm
013: #define USART_UCPHA_bm 0x02
014: #endif
015: #ifndef USART_DORD_bm
016: #define USART_DORD_bm 0x04
017: #endif
018:
019: volatile uint16_t z;
020:
021: int main(void){
022: clock_init();// interní 32MHz
023: usart_init();
024:
025: while (1){
026: z = uart_spi(0x0100);// posíláme 16bit data
027: _delay_ms(50);
028: }
029: }
030:
031: void usart_init(void){
032: // konfigurace I/O
033: // Tx do log.1, CS do log.1
034: PORTC.OUTSET = PIN7_bm | PIN4_bm;
035: // Tx (PD7),XCK (PD5), CS (PD4) výstupy
036: PORTC.DIRSET = PIN7_bm | PIN5_bm | PIN4_bm;
037: PORTC.DIRCLR = PIN6_bm;// Rx (PD6) vstup
038: PORTC.REMAP = PORT_USART0_bm;// Remapujeme Tx,Rx a XCK na pin PD7,PD6 a PD5
039: // konfiurace USART
040: USARTC0.BAUDCTRLA = (char)BSEL;// konfigurace přenosové rychlosti
041: // konfigurace přenosové rychlosti
042: USARTC0.BAUDCTRLB = ((char)(BSEL>>8) & 0x0f);
043: // mód 0
044: USARTC0.CTRLC = USART_CMODE_MSPI_gc;
045: // USARTD0.CTRLC = USART_CMODE_MSPI_gc | USART_UCPHA_bm;
046: // mód 3
047: // PORTD.PIN5CTRL |= PORT_INVEN_bm; // mód 3
048: // Povolit vysílač i přijímač
049: USARTC0.CTRLB = USART_TXEN_bm | USART_RXEN_bm;
050: }
051:
052: void clock_init(void){
053: OSC.CTRL |= OSC_RC32MEN_bm;// spustit interní 32MHz oscilátor
054: // počkat než se rozběhne
055: while (!(OSC.STATUS & OSC_RC32MRDY_bm)){};
056: CCP = CCP_IOREG_gc;// odemčít zápis do registru CLK.CTRL
057: // vybrat 32MHz jako systémový clock
058: CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
059: }
060:
061: uint16_t uart_spi(uint16_t datain){
062: uint16_t byte0,byte1;// pro přijatá data
063:
064: // počkej až bude možné naložit data
065: while(!( USARTC0.STATUS & USART_DREIF_bm));
066: PORTC.OUTCLR = PIN4_bm;// CS do log.0
067: // nalož buffer daty
068: USARTC0.DATA = (uint8_t)(datain>>8);

069: // počkej až bude možné naložit další data…
070: while(!( USARTC0.STATUS & USART_DREIF_bm));
071: USARTC0.DATA = (uint8_t)datain;
// …nalož je
072: // počkej až se všechno odešle
073: while (!(USARTC0.STATUS & USART_TXCIF_bm));
074: PORTC.OUTSET = PIN4_bm;// CS do log.1
075: byte0 = USARTC0.DATA;// vyzvedni první příchozí byte (typicky nenese informaci)
076: byte1 = USARTC0.DATA; // vyzvedni druhý příchozí byte
077: // Smaž vlajku TXCIF
078: USARTC0.STATUS = USART_TXCIF_bm;
079: // vrať obě hodnoty ve formě uint16_t
080: return (((uint16_t)byte0)<<8) | (uint16_t)byte1;
081: }
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
 

Jiné příspěvky v kategorii:

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

 
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.