Reklama

SPI u AVR 2. Díl – Praktické příklady I.

Úvod

Protože se tutoriál věnuje tomu, jak ovládat mikrokontrolér AVR a nezaměřuje se na ovládání různých jiných integrovaných obvodů s SPI, tak bude většina příkladů určena pro komunikaci mikrokontrolér – mikrokontrolér (Master – Slave). Existuje pro to několik důvodů. V prvé řadě nechci volit nějaký konkrétní integrovaný obvod, na kterém bych komunikaci ukazoval. Byli byste totiž závislí na tom si ho opatřit. Dále bych musel návod “zamlžit” vysvětlením jak zmíněný obvod používat a jak s ním komunikovat. V tom všem by se práce s SPI rozhraním na mikrokontrolérech AVR mohla ztrácet. A kromě toho všeho byste přišli o ukázky jak používat mikrokontrolér v režimu Slave. V praxi budete nejčastěji používat mikrokontrolér jako Master a to budeme v příkladech používat také, takže nemusíte mít strach, že byste přišli zkrátka. v tomto článku si ukážeme následující příklady:

  • Jednosměrný přenos Master >> Slave (A)
  • Jednosměrný přenos Slave >> Master s přerušením (B)
  • Obousměrný přenos (C)
Jednosměrný přenos Master >> Slave (A)

Jednosměrný přenos je jednoduchý příklad, který určitě využijete při ovládání ADC převodníků, digitálních potenciometrů a obecně integrovaných obvodů, kterým ke své činnosti stačí přijímat data. Připojení v takovém případě stačí pomocí linky SCK, MOSI a CS. U jednosměrné komunikace Master >> Slave není linka MISO potřebná (Slave obvod nemá co vysílat). Některé Slave obvody ani žádnou výstupní linku mít nemusí. Aplikace bude vypadat následovně. Master bude obsluhovat tlačítko (na pinu PA0) a posílat pomocí SPI do Slave obvodu informaci o tom, zda je tlačítko stisknuto nebo uvolněno. Slave obvod bude svítit LED diodou (na pinu PB1) po dobu, kdy je tlačítko stisknuto. Komunikaci uděláme úspornou. Jakmile uživatel stiskne tlačítko, tak Master odešle zprávu o tom, že je stisknuto a odmlčí se do té doby, než uživatel tlačítko uvolní. V tom okamžiku o tom opět informuje Slave obvod. Slave obvod budeme aktivovat pinem PB3. Pin PB3 Masteru tedy musíme připojit na SS pin Slave.

Tady je na místě udělat malou zastávku nad konfigurací MISO, MOSI, SS a SCK pinů. Když pomocí bitu SPE povolíte SPI a jste v roli Master, tak mikrokontrolér nastaví pin MISO striktně jako vstup. Linky MOSI, SCK nechá nastavené tak, jak jste to udělali v registru DDRB. Je proto potřeba nastavit ručně MOSI a SCK jako výstup. Role Masteru má ještě jedno úskalí – pin SS (PB4). V teoretické části jsme zmínili, že pomocí SS pinu je možné Master přepnout do role Slave. To jde jen v případě, že je SS pin nastaven jako vstup. Jestliže tuto vymoženost nechceme používat, tak musíme na pinu SS udržovat log. 1 anebo ho nastavit jako výstup. A to i přes to, že bychom ho nechtěli používat vůbec. Buď jej tedy necháme jako vstup a zapneme na něm “pull-up” rezistor (tím si zachováme možnost nechat se přepnout do role Slave) a nebo SS pin nakonfigurujeme jako výstup a pak jej můžeme používat k libovolným účelům. V mém příkladě jsem využil první možnosti a zapnul “pull-up” rezistor. Piny PB5 (MOSI) a PB7 (SCK) musíme nastavit jako výstupy. V roli Slave je situace opačná. Tedy po spuštění SPI se piny MOSI, SCK a SS nastaví jako vstupy a pin MISO si ponechá konfiguraci podle DDRB. Tedy jen v situaci že je Slave obvod aktivován. Deaktivovaný Slave obvod má pin MISO vždy ve stavu vysoké impedance (prostě odpojený). Pokud má tedy Slave obvod odesílat nějaká data, tak je potřeba mu v registru DDRB nastavit pin MISO jako výstup. Těm pinům, které jsou aktivováním SPI nastaveny jako vstup můžeme ještě stále zapnout nebo vypnout “pull-up” rezistor, pokud to potřebujeme.

Samotný zdrojový kód pro Master je docela dobře pochopitelný. Nejprve nakonfigurujeme vstupy a výstupy. Tlačítko (PB0) jako vstup s “pull-up” rezistorem, SCK a MOSI jako výstupy. Pin PB3 používáme k aktivaci a deaktivaci Slave obvodu, takže jej nastavujeme také jako výstup. Na PB4 (SS) zapínáme “pull-up” abychom dostáli podmínce, že v roli Masteru musí být SS pin buď v log. 1 nebo nastaven jako výstup. Ihned po konfiguraci pinů nastavíme PB3 do log. 1, abychom Slave obvod deaktivovali (aktivovat ho budeme až těsně před komunikací). V registru SPCR nastavíme bity SPE čímž spustíme SPI, MSTR čímž se přepneme do role Mastera a SPR0 čímž nastavíme datovou rychlost na F_CPU/16 = 500 kbit/s. Ponecháním bitů CPOL a CPHA v nule nastavujeme komunikační režim 0. Od tohoto okamžiku je SPI aktivní. Jakmile bychom zapsali data do SPDR,tak by se začaly okamžitě vysílat. Program pak sleduje stisk a uvolnění tlačítka. Pokud k některé z těchto událostí dojde, odvysílá se jeden byte dat s hodnotou 1 nebo 2, podle toho ke které události došlo. Vysílání vypadá jednoduše. Nejprve aktivujeme Slave obvod (PB3 nastavíme do stavu log. 0) a poté zapíšeme data do registru SPDR. V tomto okamžiku se začnou data odesílat. Pak ve smyčce stále čteme stavový bit SPIF a čekáme na jeho nastavení. Jakmile je nastaven, tak víme, že přenos skončil. Deaktivujeme Slave obvod (PB3 do log. 1) a vyčteme z registru SPDR hodnotu, aby se stavový bit smazal. Pokud bychom vyčtení opomenuli, tak by tento bit zůstal nastaven a při příštím vysílání bychom nebyli schopni správně rozpoznat konec přenosu. S největší pravděpodobností bychom pak Slave obvod deaktivovali příliš brzy (ještě před koncem přenosu) a data by se nepřenesla. Takže na to nezapomínejte anebo používejte přerušení, které maže stavový bit za vás :)

 

001: // A jednoducý přenos master >> slave (kód pro master)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004:
005: #define TLAC (PINB & (1<<PINB0))
006: #define CS_L PORTB &= ~(1<<PORTB3)
007: #define CS_H PORTB |= (1<<PORTB3)
008:
009: char tlac = 0;
010: char spi_prenos(char data);
011:
012: int main(void){
013: // PB3 – výstup pro řízení SS pinu slave obvodu
014: // MOSI a SCK (PB5 a PB7) výstup
015: DDRB = (1<<DDB3) | (1<<DDB5) | (1<<DDB7);
016: // pull-up na tlačítko, pull-up na SS pin !
017: PORTB = (1<<PORTB4) | (1<<PORTB0);
018: // deaktivovat slave
019: CS_H;
020: // povolit SPI, nastavit Master, clock F_CPU/16
021: SPCR = (1<<SPE) | (1<<MSTR) | (1<<SPR0);
022:
023: while (1){
024: // detekce stisku tlačítka + odeslání informace
025: if(!TLAC && tlac == 0){tlac = 1; spi_prenos(1);}
026: // detekce uvolnění tlačítka + odeslání informace
027: if(TLAC && tlac == 1){tlac = 0; spi_prenos(2);}
028: }
029: }
030:
031: char spi_prenos(char data){
032: CS_L; // aktivuje slave obvod
033: SPDR = data; // předej data k odeslání
034: while(!(SPSR & (1<<SPIF)))// počkej na skončení přenosu
035: CS_H; // deaktivuj slave obvod
036: return SPDR; // smaže vlajku SPIF (hodnota SPDR nás nezajímá)
037: }
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.
 

Program pro Slave obvod je velice jednoduchý. Nastavíme si výstup pro LED diodu. Protože je komunikace jednosměrná a Slave obvod nemá co vysílat, tak bychom nemuseli nastavovat MISO (PB6) jako výstup. Ale pro zajímavost to uděláme. Protože do SPDR nezapisujeme, tak uvidíte, že Slave obvod bude vysílat data, která během minulého přenosu přijal. SS pin necháváme jako vstup, ale pro jistotu zapneme “pull-up” rezistor. Tím máme zajištěno, že i kdyby Master nepracoval nebo byl odpojen, tak bude Slave obvod deaktivován a nebude zasahovat do dění na sběrnici. V našem případě by to ale nemělo mít žádný vliv. Povolení SPI provedeme stejně jako u Masteru nastavením bitu SPE. Ponecháním bitů CPOL a CPHA v nule nastavujeme komunikační režim 0. V hlavní smyčce čekáme na nastavení bitu SPIF, který bude signalizovat příjem jednoho bytu. Jakmile přijde, tak vyčteme data z SPDR čímž se smaže stavový bit a systém je připraven pro přijetí dalších dat. Pak už jen podle přijatého byte rozsvítíme nebo zhasneme LED diodu. Jestliže odpojíte SS pin na Slave obvodu, tak ho nebude Master schopen aktivovat a Slave obvod nebude zprávy přijímat a zůstane odpojený od linky MISO. Zkuste si to.

 

001: // A jednoducý přenos master >> slave (kód pro slave)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004:
005: char tlac = 0, x = 0;
006: char spi_prenos(char data);
007:
008: int main(void){
009: // MISO (PB6) a LED (PB1) výstupy
010: DDRB = (1<<DDB6) | (1<<DDB1);
011: PORTB = (1<<PORTB4); // SS (PB4) pull-up (pro jistotu)
012: SPCR = (1<<SPE); // povolit SPI
013:
014: while (1){
015: while(!(SPSR & (1<<SPIF))){}// čekej až přijdou data
016: x = SPDR; // přečti je
017: // a rozsviť …
018: if(x == 1){PORTB |= (1<<PORTB1);}
019: else{PORTB &= ~(1<<PORTB1);} // nebo zhasni LED
020: }
021: }
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.
 

Na Obr. 1. vidíte regulérní komunikaci. Master vysílá kód 1, tedy informaci o tom, že jsem stiskl tlačítko. Na MISO výstupu Slave obvodu vidíme hodnotu 2. Ta totiž zbyla v posuvném registru od poslední zprávy, kterou Master do Slave poslal (což bylo uvolnění tlačítka s kódem 2). Bystré oko uvidí lehké zkosení vzestupných a sestupných hran. Ty jsou způsobeny oddělovacími rezistory, které i s minimální kapacitou sběrnice tvoří RC články, které drobně omezují rychlost přeběhu. Při vyšších rychlostech by měl jev větší vliv. Na Obr. 2. pak vidíte situaci, kdy je SS pin Slave obvodu odpojen. Master ho není schopen aktivovat a Slave obvod data nepřijímá a ani žádné nevysílá. Podle přeslechů na lince MISO je evidentní, že ji Slave nechal odpojenou a kapacitní vazbou se na ni částečně přenáší signál ze sousedních linek (MOSI a SCK).

 
Přenos hodnoty 1 do slave (komunikační režim 0)

Obr. 1: Přenos hodnoty 1 do slave (komunikační režim 0)

 
Obr. 2: Slave obvod neaktivován (CS v log. 1), MISO linka ve vysoké impadanci a podléhá vlivům okolního signálu

Obr. 2: Slave obvod neaktivován (CS v log. 1), MISO linka ve vysoké impadanci a podléhá vlivům okolního signálu

 

Jednosměrný přenos Slave >> Master s přerušením (B)

V tomto příkladu si předvedeme jednosměrnou komunikaci ve které bude Master přijímat data ze Slave obvodu. Slave obvod necháme pracovat s přerušením. V této sestavě Master nepotřebuje linku MOSI, protože nic nevysílá. To ale neznamená, že ji může volně použít k jiným účelům. Pokud nechceme aby ji SPI ovládal, tak ji musíme nechat nastavenou jako vstup. Slave obvod bude sledovat stav tlačítka a podle toho, zda je stisknuto nebo ne bude odesílat příslušný kód po SPI. Protože ale Slave obvod nemůže sám od sebe zahájit komunikaci, musí se ho Master pravidelně “doptávat”. Tady by bylo na místě dát Slave obvodu nějakou možnost informovat Master, že jsou pro něj k dispozici nová data. A to se také typicky dělá. Zařízení na SPI sběrnici sdílejí ještě jednu samostatnou linku (IRQ linku), ke které jsou připojeni otevřeným kolektorem. Každé zařízení na této lince smí vytvořit log. 0 a informovat tím Master, že má některý ze Slave obvodů nová data. Master pak typicky zahájí komunikaci s každým Slave obvodem, aby zjistil od koho ta informace pochází. Konfigurace ale může vypadat i tak, že každý Slave (nebo vybraná skupinka) má svou vlastní linku, kterou dává masterovi najevo přítomnost nových dat. K tomu se typicky využívá externí přerušení. Master díky takové konfiguraci zavčas reaguje na jakoukoli změnu a nemusí se stále doptávat Slave obvodů, zda nemají nová data. Jak jsme ale řekli, tak my takovou funkci nemáme a tak necháme Master každých 50 ms přečíst data ze Slave obvodu. Čtení z hlediska Masteru vypadá v podstatě stejně jako vysílání a proto funkce spi_prenos() nedoznala žádných změn. Opět přenos začíná aktivováním Slave obvodu (PB3 do stavu log. 0) a poté zápisem čehokoli do SPDR. Zapsaná hodnota nemá význam, protože MOSI linka je nastavena jako vstup a žádná data neodejdou. Zapsat do SPDR je ale nutné, protože tím se spustí přenos a Master začne generovat hodiny. Ty jsou nezbytně nutné, aby Slave obvod mohl svoje data odeslat. Po skončení komunikace (po nastavení bitu SPIF) je Slave obvod deaktivován (PB3 do stavu log. 1) a přijatá data jsou vyčtena z registru SPDR (čímž se také smaže bit SPIF). Master pak podle přijatých dat rozsvítí nebo zhasne led na PB1.

 

001: // B jednoducý přenos slave >> master (kód pro master)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/delay.h>
005:
006: #define TLAC (PINB & (1<<PINB0))
007: #define CS_L PORTB &=~(1<<PORTB3)
008: #define CS_H PORTB |=(1<<PORTB3)
009:
010: char x = 0;
011: char spi_prenos(char data);
012:
013: int main(void){
014: // PB3 – výstup pro řízení CS pinu slave obvodu, PB1 LEDka
015: // SCK (PB7) výstup
016: DDRB = (1<<DDB3) | (1<<DDB7) | (1<<DDB1);
017: // pull-up na tlačítko, pull-up na SS pin !
018: PORTB = (1<<PORTB4);
019: // deaktivovat slave
020: CS_H;
021: // povolit SPI, nastavit Master, clock F_CPU/4
022: SPCR = (1<<SPE) | (1<<MSTR);
023:
024: while (1){
025: _delay_ms(50); // každých 50ms…
026: x = spi_prenos(0); // přečti data ze slave
027: // rozsviť nebo…
028: if(x == 1){PORTB &= ~(1<<PORTB1);}
029: else{{PORTB |= (1<<PORTB1);}} // zhasni LED
030: }
031: }
032:
033: char spi_prenos(char data){
034: CS_L; // aktivuje slave obvod
035: SPDR = data; // předej data k odeslání
036: while(!(SPSR & (1<<SPIF))); // počkej na skončení přenosu
037: CS_H; // deaktivuj slave obvod
038: return SPDR; // vyčti přijatá data
039: // krom toho také smaže vlajku SPIF
040: }
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.
 

Program pro Slave je tradičně jednodušší. Zapneme “pull-up” rezistor na tlačítku (PB0) a na SS pin (PB4), i když zde být nemusí. MISO konfigurujeme jako výstup, protože ze Slave chceme číst data. Povolíme SPI a nastavením bitu SPIE povolíme přerušení od SPI (v tomto případě od příjmu dat). Přirozeně nezapomeneme povolit přerušení globálně pomocí funkce sei(). Předvyplníme SPDR nějakými daty (aby od nás Master něco přijal) a necháme Slave obvod ať se “fláká” v prázdné smyčce. Jakmile Master dokončí přenos dat, tak to vyvolá u Slave obvodu přerušení. V něm Slave obvod zjistí stav tlačítek a podle toho naplní SPDR hodnotou 1 nebo 2. Aby si Master v příštím přenosu mohl přečíst, jaký byl stav tlačítek. Je evidentní, že Master dostává vždy 50 ms starou hodnotu. Může vás napadnout, že bychom měli nechat Slave v hlavní smyčce stále snímat stav tlačítek a ihned podle toho nastavovat SPDR. Master by pak četl vždy aktuální stav tlačítek a ne 50 ms starý. To ale není tak jednoduché. V roli Slave obvodu nemáme moc prostředků jak poznat, že přenos probíhá. A pokud bychom to udělali špatně, tak by se nám mohlo stát, že bychom do SPDR zapsali během přenosu ! Což by mělo neblahé následky na přenášená data. Dozvěděli bychom se o tom od bitu WCOL, ale to už by bylo pozdě, protože by Master obdržel nesmysly. Trochu bezpečnější by bylo sledovat stav SS pinu, pokud je v log. 1, tak víme, že s námi Master nekomunikuje a že můžeme upravovat hodnotu v SPDR. Ale opět nemáme záruku, že Master nestihl zahájit přenos mezi okamžikem, kdy jsme stav SS pinu přečetli a kdy měníme SPDR (ono to totiž chvíli trvá). Nabízejí se dvě řešení. Nastavit v Masteru pevné zpoždění mezi aktivováním Slave obvodu a zahájením přenosu dat. Pak by Slave obvod měl časovou rezervu na zápis do SPDR a nebo dát Slave obvodu další linku, aby mohl informovat Master o přítomnosti nových dat. U snímání tlačítka ale 50 ms nehraje žádnou roli.

 

001: // B jednoducý přenos slave >> master (kód pro slave)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <avr/interrupt.h>
005:
006: #define TLAC (PINB & (1<<PINB0))
007:
008: int main(void){
009: // MISO (PB6) výstup
010: DDRB = (1<<DDB6);
011: // SS (PB4) pull-up, PB0 tlačítko
012: PORTB = (1<<PORTB4) | (1<<PORTB0);
013: if(TLAC){SPDR = 1;}else{SPDR = 2;} // nahrej nynější stav tlačítka
014: // povolit SPI
015: SPCR = (1<<SPE) | (1<<SPIE);
016: sei(); // globální povolení přerušení
017: while (1){} // flákej se
018: }
019:
020: ISR(SPI_STC_vect){
021: if(TLAC){SPDR = 1;}else{SPDR = 2;} // připrav pro přenos nynější stav tlačítka
022: }
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.
 

Na Obr. 3. vidíte záznam přenosu. Slave odesílá hodnotu 1, takže bylo tlačítko uvolněno. MOSI linka je odpojená (ve vysoké impedanci – je vstupem), takže se na ní parazitně vážou stavy okolních signálů. Rychlost komunikace je nastavena na 8 MHz / 4 tedy na 2 Mbit/s. Což je nejrychlejší možná konfigurace, pokud jsou Slave i Master taktovány na 8 MHz. Slave běžící na 8 MHz vyžaduje, aby byla datová rychlost nižší než 4 MHz. Master na 8 MHz ale může vytvořit přesně 4 MHz (nastaven bit SPI2X), nebo 2 MHz a nic mezi tím. Rovné 4 MHz použít nemůžeme, protože Slave obvod vyžaduje, aby to byla frekvence nižší.

 
Obr. 3: Přenos hodnoty 1 ze slave do master. Datová rychlost 2Mbit/s

Obr. 3: Přenos hodnoty 1 ze slave do master. Datová rychlost 2Mbit/s

 

Obousměrný přenos (C)

Ve třetím příkladě sloučíme obě předchozí varianty do jednoho. Slave i Master budou mít tlačítko (pin PA1) a LED diodu (pin PA0). Master bude se Slave obvodem komunikovat každých 50 ms. Během přenosu si Slave i Master navzájem pošlou informaci o stavu tlačítka a každý z nich rozsvítí nebo zhasne LED diodu. Tlačítko připojené ke Slave obvodu bude tedy ovládat LED diodu u mikrokontroléru Master a obráceně. Tak jak ve všech předchozích příkladech musí komunikaci řídit Master a Slave obvod ji sám zahájit nemůže. Když chce tedy Master přijmout nějaká data ze Slave, tak musí Slave obvodu vždy nějaká data poslat! Odeslaná data mohou a nemusejí dávat smysl. To už záleží na tom, jak je Slave obvod interpretuje. Prostě to jinak nejde, protože nějaká logická hodnota na lince MOSI vždy je, data tedy VŽDY tečou oběma směry. V našem příkladu dávají všechna data smysl. Master odesílá hodnotu 1, pokud je jeho tlačítko stisknuté a hodnotu 2, pokud není. Slave obvod v tom samém okamžiku přenáší hodnotu podle svého tlačítka (kterou ale zjistil ke konci posledního přenosu, tedy odesílá informaci 50 ms starou). Pokud bychom po Slave obvodu chtěli aktuální informaci tak bychom mu museli nějak sdělit, ke kterému okamžiku nás stav tlačítka zajímá. Uvažujeme-li pouze SPI sběrnici, tak máme jen tři události, které na straně Slave můžeme detekovat.

  • První hranu na SCK lince (A)
  • Sestupnou hranu na CS pinu (B)
  • Konec přenosu (bit SPFI) (C)

Všechny tyto události ale nastanou defakto až v průběhu nebo po skončení vysílání. Nemáme tedy jakýkoliv signál, který by s dostatečnou rezervou předcházel vysílání. To vlastně přijde naprosto nečekaně. Tím se ale Slave obvod dostává do svízelné situace. Jestliže se rozhodne stále dokola měnit stav registru SPDR, aby obsahoval co nejaktuálnější stav tlačítka, vystavuje se riziku. Obsah registru SPDR se nesmí měnit během přenosu a Slave prakticky nemá šanci rozpoznat začátek přenosu s dostatečným předstihem. Slave by sice mohl sledovat stav pinu CS, aby rozpoznal, zda probíhá vysílání, ale ani to by mu nepomohlo. Master by mohl zahájit vysílání tak rychle po nastavení pinu CS do nuly, že by Slave obvod nestihl zavčas zjistit, že začíná vysílání a v mylné představě, že se ještě nevysílá by mohl zapsat do SPDR v nevhodný okamžik. Pro tyto případy by tedy bylo nutné vylepšit program pro Master o nějaký minimální čas po který musí CS linka setrvat v log. 0 než začne vysílání. To by Slave obvodu mohlo dát dost času připravit aktuální data do registru SPDR. Existuje ale přirozeně více variant, jak tento problém obejít. Skoro vždy ale bude vyžadovat jistou ohleduplnost na straně Masteru. My si náš příklad necháme hloupý a smíříme se s tím, že stav tlačítka je 50 ms zastaralý. Zdrojový kód pro Slave doznal oproti příkladu: Jednosměrný přenos Slave >> Master s přerušením (B) jen minimálních změn. Tlačítko se přemístilo z pinu PB0 na pin PA0, přibyla LED dioda na pinu PA1 a v rutině přerušení přibyla podmínka zpracovávající hodnotu přijatá data.

 

001: // C obousměrný přenos (kód pro slave)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <avr/interrupt.h>
005:
006: #define TLAC (PINA & (1<<PINA1))
007:
008: int main(void){
009: DDRA = (1<<DDA0); // výstup LED
010: DDRA &= ~(1<<DDA1); // vstup tlačítko
011: PORTA |=(1<<PORTA1); // pull-up tlačítko
012: DDRB = (1<<DDB6); // MISO (PB6) výstup (ostatní vstup)
013: PORTB = (1<<PORTB4); // SS (PB4) pull-up
014: if(!TLAC){SPDR = 1;}else{SPDR = 2;} // nahrej nynější stav tlačítka
015: // povolit SPI
016: SPCR = (1<<SPE) | (1<<SPIE);
017: sei(); // globální povolení přerušení
018: while (1){} // flákej se
019: }
020:
021: ISR(SPI_STC_vect){
022: // přečti SPDR, zhasni nebo rozsviť LED
023: if(SPDR == 1){PORTA |= (1<<PORTA0);}else{PORTA &= ~(1<<PORTA0);}
024: // připrav pro přenos nynější stav tlačítka
025: if(TLAC){SPDR = 1;}else{SPDR = 2;}
026: }

Program pro Master také není nijak zvlášť komplikovaný. LED dioda je na pinu PA0 a tlačítko na pinu PA1. Pin PB4 držíme “pull-up” rezistorem ve stavu log. 1, čímž umožníme pracovat v režimu Master (jako ve všech předchozích příkladech). V hlavní smyčce pak každých 50 ms čteme stav tlačítka a posíláme o něm informaci Slave obvodu a také zároveň přijímáme informaci o stavu jeho tlačítka. Na jejím základě pak rozsvěcíme nebo zhasínáme LED diodu.

 

001: // C obousměrný přenos (kód pro master)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/delay.h>
005:
006: #define TLAC (PINA & (1<<PINA1))
007: #define CS_L PORTB &=~(1<<PORTB3)
008: #define CS_H PORTB |=(1<<PORTB3)
009:
010: char tx = 0,rx = 0;
011: char spi_prenos(char data);
012:
013: int main(void){
014: // PB3 – výstup pro řízení CS pinu slave obvodu
015: // SCK (PB7), MOSI (PB5) výstup
016: DDRB |= (1<<DDB3) | (1<<DDB5) |(1<<DDB7);
017: PORTB |= (1<<PORTB4); // pull-up na SS pin !
018: PORTA |=(1<<PORTA1); // pull up na tlačítko;
019: DDRA |= (1<<DDA0); // výstup na LED
020: CS_H; // deaktivovat slave
021: // povolit SPI, nastavit Master, clock F_CPU/16
022: SPCR = (1<<SPE) | (1<<MSTR) | (1<<SPR0);
023:
024: while (1){
025: _delay_ms(50); // každých 50ms…
026: if(!TLAC){tx = 1;}else{tx = 2;} // zjisti stav tlačítka
027: rx = spi_prenos(tx); // pošli jej do slave a přečti si jeho informaci
028: // rozsviť nebo…
029: if(rx == 1){PORTA &= ~(1<<PORTA0);}
030: else{PORTA |= (1<<PORTA0);} // zhasni LED, podle toho co slave poslal
031: }
032: }
033:
034: char spi_prenos(char data){
035: CS_L; // aktivuje slave obvod
036: SPDR = data; // předej data k odeslání
037: while(!(SPSR & (1<<SPIF))); // počkej na skončení přenosu
038: CS_H; // deaktivuj slave obvod
039: return SPDR; // vyčti přijatá data (a smaž vlajku SPIF)
040: }

 
Obr. 4: Zapojení dvou a více Atmelů na SPI sběrnici při použití ISP programátorů. Pokud jsou připojeny jen dva Atmely, stačí jedna trojice oddělovacích rezistorů.

Obr. 4: Zapojení dvou a více Atmelů na SPI sběrnici při použití ISP programátorů. Pokud jsou připojeny jen dva Atmely, stačí jedna trojice oddělovacích rezistorů.

 

Zde bych s příklady mikrokontrolér – mikrokontrolér skončil. Ne, že by nebylo co ukazovat. Přinejmenším by bylo možné předvést ukázku, že Slave může spát a probudit se, až s přijetím zprávy (což by bylo užitečné v příkladu Jednosměrný přenos Master >> Slave (A)). Nebo by se dalo předvést, jak se navzájem mohou adresovat dva Master obvody. Případně bychom si mohli předvést protokol, který v první části zprávy pošle Slave obvodu informaci o tom, co po něm Master chce a ve zbytku přenosu Slave obvod vybranou informaci odešle zpět do Masteru. Bohužel všechny tyto příklady obsahují nějaký malý háček, jehož analýza by byla relativně náročná. Určitě by to mohlo být zajímavé, ale ve frontě čekají daleko jednodušší a pro vaši praxi asi i přínosnější příklady a já bych vás od nich nerad zdržoval. Proto tyto problémy nechám nevyřešené. V příštím díle si ukážeme návod na ovládání digitálního potenciometru MCP4251.

 
 
Autor: Michal Dudka
 
 

Jiné příspěvky v kategorii:

 
SPI u AVR 1. Díl – Teorie
SPI u AVR 3. Díl – Praktické příklady II.

 
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.