Reklama

TWI u AVR 1. Díl – Teorie a první příklady

Úvod

Jak již bývá v těchto seriálech zvykem, tak se budeme s ovládáním periférie ATmegy seznamovat na příkladech. TWI modul ATmegy se ovládá relativně snadno.
Klíčová pro jeho zvládnutí je znalost I2C protokolu (popis lze nalézt ZDE anebo ZDE). Princip, jak uvidíte je velice prostý. Zvláště díky tomu, že budeme řešit jednodušší případ, kdy je na sběrnici jen jeden Master. Ve většině vašich aplikací bude mikrokontrolér hrát roli Mastera. Většina Slave budou různé integrované obvody, od D/A a A/D převodníků, přes čidla až po expandéry. Návod, který by prováděl ukázky na nějakém konkrétním I2C obvodu by byl ale málo obecný. K jeho testování byste potřebovali konkrétní obvod a navíc byste museli nastudovat práci tohoto obvodu. To by mohlo návod zbytečně “zamlžit”. Dále by přišli zkrátka ti, kteří chtějí stavět I2C síť z mikrokontrolérů. Proto budou příklady demonstrovat komunikaci ATmega -> ATmega (Master -> Slave). Zdrojové kódy tedy budou typicky dva. Jeden pro Master a jeden nebo více pro Slave. Ovládání TWI v Master mikrokontroléru bude přirozeně stejné, ať komunikujete s jiným mikrokontrolérem nebo jakýmkoli dalším zařízením, takže vám to práci nijak nezkomplikuje. V budoucnu by vám už mělo stačit pouze dostudovat dokumentaci k cílovému zařízení (akcelerometru, magnetometru nebo čemukoli). Kapitola nejspíš nebude patřit k nejjednodušším a příklady budou možná trochu rozsáhlejší, než jste zvyklí, ale věřím, že vám to nebude činit potíže. První příklad popíšu podrobně, další příklady už budu komentovat spíše rámcově, protože podrobný popis by byl zbytečně únavný.

 
Seznam příkladů v celém seriálu:

  • TWI – Přenos 1 byte Master >> Slave
  • TWI – Přenos více bytů Master >> Slave
  • TWI – přenos více bytů Slave >> Master (v příštím díle)
  • TWI – jednoduchá obousměrná komunikace (v příštím díle)
I2C na mikrokontrolérech ATmega

Atmel nazývá I2C protokol jako TWI (Two Wire Interface) – kvůli licenčním právům na název I2C. Obecně lze I2C komunikaci realizovat dvěma způsoby. Primitivnější z nich je softwarová implementace (nebo také tzv “bit-banging” I2C). Takový program sám zachází s piny mikrokontroléru a dodržuje příslušné časování a komunikuje podle pravidel I2C. Tento způsob je celkem častý. Běžně nedosahuje velkých datových rychlostí, ale umožňuje vám zvolit si SDA a SCL linku na libovolných pinech mikrokontroléru. Kromě toho lze touto cestou ovládat více než jen jednu sběrnici (nikdo vám nebrání mít tři různé SCL a SDA). Skoro výhradně se však tento postup využívá pro roli Mastera, což vám nejspíš bude stačit. U Slave zařízení se softwarová implementace moc nepoužívá. Složitější a v některých ohledech schopnější je hardwarová implementace (pokud se to tak dá nazvat). Většina mikrokontroléru rodiny AVR má nějaké hardwarové rozhraní, které I2C komunikaci podporuje. U některých ATtiny jde k těmto účelům využít USI (ale tato metoda se podobá spíše softwarové implementaci). Většina ostatních čipů má shodné TWI rozhraní, které řídíte pomocí pěti registrů. Díky němu lze používat rychlosti až 400kb/s, provozovat mikrokontrolér jako Master i jako Slave a dokonce lze Slave budit z režimu spánku. Dvojici pinů SDA a SCL najdete na ATmega16 na pinech PC1 a PC0 a je určena pevně.

TWI – obecná pravidla

Používání TWI modulu vypadá tak, že nejprve nastavíte základní parametry a modul zapnete. V roli Mastera je základním parametrem datová rychlost, v roli Slave je to jeho adresa. Pak už jen dáváte TWI modulu pokyny, sledujete jeho stav a čekáte na odezvu. V roli Mastera dáte například pokyn vygenerovat START sekvenci a až ji TWI modul vygeneruje, tak vám o tom dá vědět (přerušením nebo stavovým bitem). Váš program by na to měl zareagovat, zjistit si co TWI modul opravdu udělal a dát mu další pokyn (například odeslat adresu s příznakem zápisu / čtení). A tak stále dokola ve stylu pokyn -> čekání na reakci -> kontrola -> pokyn. Pokyny, které můžete použít najdete v tabulkách v datasheetu (později se dočtete, kde je najít). Ty shrnují všechny možnosti, které mohou nastat a všechny možné reakce, které můžete udělat, a jsou logické. Je třeba logické po START sekvenci odesílat adresu Slave zařízení a není logické po START sekvenci posílat data (není přece jasné komu jsou určena). Tabulky, které shrnují veškeré chování jsou čtyři a pokrývají tyto čtyři možné role TWI modulu:

  • Master Transmitter – situace kdy Master vysílá data, adresu nebo START či STOP sekvenci.
  • Master Receiver – situace kdy Master přijímá data
  • Slave Receiver – situace kdy Slave přijímá data nebo adresu nebo obecně poslouchá provoz na sběrnici
  • Slave Transmitter – situace kdy Slave posílá data

V každé ze čtyř rolí máte jiné možnosti. Master Transmiter může posílat Slave adresu nebo data, generovat START nebo STOP sekvenci. Master Receiver může dávat nebo nedávat potvrzení zprávy a generovat START nebo STOP sekvence. Slave Transmitter může pouze odesílat data. Slave Receiver může potvrzovat nebo nepotvrzovat zprávy. Prostě může dělat to co mu I2C protokol dovoluje.

TWI – registry

K chodu TWI většinou potřebujete pracovat se třemi registry. Zbylé dva (až tři) slouží k nastavení komunikační rychlosti a Slave adresy (pokud mikrokontrolér pracuje jako Slave). Datová rychlost se nastavuje v registru TWBR a je dána vztahem:

 

f = (F_CPU) / (16+2 * (TWBR) * 4^(TWPS))

 

F_CPU je takt procesoru, TWPS řídí předděličku hodin pro TWI modul a konfiguruje se pomocí bitů TWPS1 a TWPS0 v registru TWSR (viz Tab. 1).

Tab. 1: Předdělička pro TWI

TWPS1 TWPS0 Předdělička
0 0 1
0 1 4
1 0 16
1 1 64

 

Registr TWSR kromě předděličky obsahuje pět stavových bitů TWS7TWS3. Ty slouží k indikaci stavu, v němž se celý TWI modul nachází. Můžete podle nich poznat jakou operaci naposledy TWI modul provedl, v jaké roli se nachází a podle toho pak dát TWI správný pokyn.

Tab. 2: TWSR – TWI Status Register

Pozice bitu Označení
7 – (nejvyšší váha) TWS7
6 TWS6
5 TWS5
4 TWS4
3 TWS3
2 -
1 TWPS1
0 – (nejnižší váha) TWPS0

 

Řízení TWI modulu se provádí pomocí TWCR registru. Postup ovládání TWI rozhraní budeme provádět pomocí tabulek z datasheetu. Proto vás s jednotlivými bity seznámím jen ve stručnosti. Bitem TWEA nastavujete, zda chcete v dalším úkonu dávat nebo nedávat potvrzení zprávy. Bitem TWSTA nastavujete, zda v příštím úkonu chcete vysílat START sekvenci a analogicky bitem TWSTO řídíte vysílání STOP sekvence. Bit TWWC je stavový bit, který vás informuje, pokud uděláte chybu a přistupujete k registru TWDR, když nemáte. Bitem TWEN celý TWI modul zapínáte nebo vypínáte (a mimo jiné mu přidělujete nebo nepřidělujete piny SDA a SCL). TWINT vás informuje o tom že TWI modul dokončil nějakou činnost a čeká na vaši reakci. Smazáním stavového bitu TWINT (což se provádí zápisem log. 1) dáváte TWI modulu pokyn k další akci.

Tab. 3: TWCR – TWI Control Register

Pozice bitu Označení
7 – (nejvyšší váha) TWINT
6 TWEA
5 TWSTA
4 TWSTO
3 TWWC
2 TWEN
1 -
0 – (nejnižší váha) TWIE

 

Registr TWDR slouží k zápisu dat, která se mají na sběrnici vysílat nebo ke čtení přijatých dat. Použití registru TWDR se opět odvíjí od aktuálního stavu TWI modulu. Registr TWAR pak slouží ke specifikaci Slave adresy, pokud zařízení pracuje jako Slave.

TWI – kuchařka na psaní programů

Komunikace po I2C může být rozmanitá a je tedy možné, že si často budete upravovat programy na míru příslušnému Slave obvodu. Bude proto vhodnější, když dostanete do rukou jakousi kuchařku, jak programy psát. Ústřední roli v ní budou hrát tabulky, které najdete v datashetu pod názvy Status Codes for Master Transmitter Mode, Status Codes for Master Receiver Mode, Status Codes for Slave Receiver Mode a Status Codes for Slave Transmitter Mode (pro Atmega16A tabulky 20-2, 20-3, 20-4 a 20-5). Ať už budete TWI ovládat pomocí přerušení nebo hlídat jeho stav “pollingem”, postup bude stejný. Roli, v jaké se TWI nachází, rozpoznáte pomocí stavových kódů z výše zmíněných tabulek. Odvysílá-li mikrokontrolér START sekvenci, je v roli Master Transmitter. Odešle-li Slave adresu s příznakem čtení, tak se sám přepne do role Master Receiver a podobně. Při zadávání pokynu TWI modulu vždy nastavte bit TWINT a přirozeně udržujte bit TWEN nastavený. V roli Mastera musíte komunikaci začít vysíláním START sekvence. K tomuto účelu v TWCR nastavte bity TWINT a TWSTA. Jakmile dáte TWI nějaký pokyn, tak vždy vyčkejte, než jej dokončí (vyčkejte na nastavení stavového bitu TWINT). Potom vždy přečtěte stavové bity z registru TWSR a zkontrolujte jejich stav s tabulkou. Vyberte jednu z akcí, která smí následovat a do TWCR zapište hodnotu, kterou vyčtete z tabulky. Společně s tím smažte vlajku TWINT (zápisem log. 1). To stále opakujte, dokud nechcete komunikaci ukončit. V roli Slave obvodu je situace stejná s tím rozdílem, že komunikaci nezačínáte, ale pouze čekáte na nastavení vlajky TWINT a reagujete na to podle příslušné tabulky. Aby mohly být programy čitelnější a neobsahoval nic neříkající hodnoty stavových kódů jako třeba 0x08 nebo 0x10, máte k dispozici hlavičkový soubor util/twi.h. V něm jsou makra, která slovně vyjadřují každou položku tabulky. Názvy jsou samovysvětlující. MT znamená Master Transmitter, MR – Master Receiver, ST – Slave Transmitter a SR – Slave Receiver. Zkratka SLA znamená Slave Addres, +W a +R pak indikují, zda byla adresa odeslána s příznakem zápisu nebo čtení. Makro TW_STATUS vám usnadní kontrolu stavových bitů (ty se totiž nachází v registru společně s bity předděličky, které je nutno maskovat). Já si ve svých příkladech ještě několik užitečných maker dodělal.

Otevřete si datasheet, najděte si některou z tabulek a trochu si je okomentujeme. Probírat všechny buňky tabulek stavových kódů by nemělo smysl, protože jsou v nich nejspíš VŠECHNY možné situace. V levém sloupci je vždy stav TWSR (s maskovanými bity předděličky). Pokud je v TWSR například hodnota 0x20 (TW_MT_SLA_NACK), můžete se z druhého sloupečku dočíst, že byla odeslána adresa Slave obvodu s příznakem zápisu a Slave zprávu nepotvrdil. Vy jste v roli Master Transmitter (dozvíte se z názvu tabulky). Ve zbylých sloupečcích jsou čtyři možné reakce, které může TWI udělat. První z nich říká, že můžete do TWDR načíst data a pak zapsat příslušné hodnoty do TWCR (hodnoty jednotlivých bitů jsou v následujících sloupcích). V posledním sloupci se pak dočtete co TWI udělá. V tomto případě odvysílá data bez ohledu na to, že Slave svoji adresu nepotvrdil. Což skoro určitě bude znamenat, že data Slave ignoruje. Jiná možnost je do registru TWDR nic nezapisovat a příslušným zápisem do TWCR vygenerovat opakovaný START. Nebo vygenerovat STOP sekvenci anebo vygenerovat STOP sekvenci bezprostředně následovanou START sekvencí. Volba je pouze na vás. Pokud vygenerujete STOP sekvenci, tak TWI svoji činnost ukončí a už na nic nemusíte čekat. Pokud uděláte cokoli jiného, opět byste měli být připraveni na to, že dojde k nastavení stavového bitu TWINT a vy budete muset reagovat. Přečtete si stav TWSR a dostanete se do jiného políčka tabulky a budete mít na výběr jiné možnosti. A to je všechno :) Docela prosté, že? Předvedeme si to na příkladu.

TWI – Přenos 1 byte Master >> Slave

Nejtriviálnější příklad jaký lze ukázat je odeslání jednoho bytu z Masteru do Slave. V roli Slave bude mikrokontrolér ATmega a změnou definice MY_SLAVE_ADDRESS si jich můžete vytvořit kolik chcete. Teoreticky tento model můžete využít tam, kde potřebujete řídit složitější proces a je pro vás vhodné rozdělit ho na několik jednodušších. Například když vaše experimentální sestava obsahuje několik motorů a každý z nich je řízen samostatným mikrokontrolérem (Slave). Master pak dle požadavků obsluhy nebo podle potřeb měřícího procesu dává pokyny Slave zařízením (mikrokontrolérům), která jsou “jednoúčelová” a starají se každé o svůj motor. Díky tomu, že se odesílá pouze jeden byte, je komunikace v podstatě triviální. Master odešle adresu Slave obvodu s příznakem zápisu a ihned na to naváže odesláním datového bytu. Slave jen pasivně poslouchá a potvrzuje zprávy. Master si přirozeně musí kontrolovat, jestli mu Slave dává potvrzení a patřičně na to reagovat. Náš Master bude po stisku tlačítka na PA7 nebo na PA6 odesílat pokyn jednomu ze dvou Slave obvodů. Já budu mít pro testy na sběrnici připojen jen jeden (se 7 bitovou adresou 0x02). Vám ale nic nebrání připojit si bez úprav kódu dva.

001: // Přenos 1 byte Master >> Slave (kód pro master)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/delay.h>
005: #include <util/twi.h>
006:
007: // adresy různých slave zařízení na sběrnici
008: #define SLV1_ADDRESS 0x2
009: #define SLV2_ADDRESS 0x3
010:
011: #define TW_SEND_START TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN)
012: #define TW_SEND_STOP TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO)
013: #define TW_SEND_DATA TWCR = (1<<TWINT) | (1<<TWEN)
014: #define TW_WAIT while(!(TWCR & (1<<TWINT)))
015: #define TLAC1 (PINA & (1<<PINA7))
016: #define TLAC2 (PINA & (1<<PINA6))
017:
018: char i2c_posli_1B(char adresa, char data);
019: char tlac1 = 0, tlac2= 0;
020:
021: int main(void){
022: // žádná předdělička, F_SCL = 8MHz/(16+2*TWBR) = 100kHz.
023: TWBR = 32;
024: // zapnout TWI modul
025: TWCR = (1<<TWEN);
026: // dvě tlačítka
027: DDRA &= ~((1<<DDA7) | (1<<DDA6));
028: // pull-up pro tlačítka
029: PORTA = (1<<PORTA7) | (1<<PORTA6);
030: while(1){
031: // po stisku tlačítka 1
032: if(!TLAC1 && tlac1 == 0){
033: // zamči tlačítko (do uvolnění)
034: tlac1 = 1;
035: //pošli data=1 pro slave 1
036: i2c_posli_1B(SLV1_ADDRESS,1);
037: }
038: if(!TLAC2 && tlac2 == 0){
039: // zamči tlačítko (do uvolnění)
040: tlac2 = 1;
041: //pošli data=1 pro slave 2
042: i2c_posli_1B(SLV2_ADDRESS,1);
043: }
044: // tlačítka uvolněna
045: if(TLAC1 && TLAC2){
046: // odemči tlačítka
047: tlac1 = 0;
048: tlac2 = 0;
049: }
050: // ošetření zákmitů
051: _delay_ms(100);
052: }
053: }
054:
055: char i2c_posli_1B(char adresa, char data){
056: // zarovnání adresy na 8bit formát
057: adresa = adresa << 1;
058: // vygenerovat START sekvenci
059: TW_SEND_START;
060:
061: // počkat na odezvu TWI
062: TW_WAIT;
063: // pokud nebyl start vygenerován, máme error a končíme
064: if ((TW_STATUS) != TW_START){ return 1;}
065: // nahrát adresu slave s příznakem zápisu (SLA+W)
066: TWDR = adresa;
067: // odeslat adresu
068: TW_SEND_DATA;
069:
070: // počkat na odezvu TWI
071: TW_WAIT;
072: // Slave nám nedal potvrzení, případně nastal jiný
073: // problém ? končíme komunikaci STOP sekvencí
074: if ((TW_STATUS) != TW_MT_SLA_ACK){TW_SEND_STOP; return 2;}
075: // nahrát data která chceme poslat do slave
076: TWDR = data;
077: // odeslat data
078: TW_SEND_DATA;
079:
080: // počkat na odezvu TWI
081: TW_WAIT;
082: // Slave nám nedal potvrzení, případně nastal jiný
083: // problém ? končíme komunikaci STOP sekvencí
084: if ((TW_STATUS) != TW_MT_DATA_ACK){TW_SEND_STOP; return 2;}
085: // konec komunikace
086: TW_SEND_STOP;
087: return 0;
088: }
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.
 

Zápisem do registru TWBR nastavím datovou rychlost na 100kb/s. Nastavením bitu TWEN spustím TWI, které od tohoto okamžiku přebírá kontrolu nad SDA a SCL. V Master aplikaci si pak připravuji dvě tlačítka na PA6 a PA7. Po jejich stisku volám funkci i2c_posli_1B(), která obstarává komunikaci. Prvním argumentem funkce je adresa Slave obvodu, kterému chci poslat data, druhým argumentem je pak jeden byte dat. Ve funkci se nejprve vypořádám se Slave adresou, kterou si potřebuji zarovnat doleva (nejnižší bit potřebuji jako příznak zápisu / čtení). Funkce tedy vyžaduje adresu v sedmibitovém formátu. Pak dám TWI pokyn vygenerovat START sekvenci. Musím počkat, než se START sekvence vygeneruje. Čekání provádím smyčkou TW_WAIT. Když se na toto makro podíváte, uvidíte, že pouze čekám na nastavení bitu TWINT. Jakmile k tomu dojde, tak přečtu stav TWSR makrem TW_STATUS a zkontroluji, zda je stav takový jaký očekávám. Po odvysílání START sekvence očekávám, že byla sekvence odvysílána, tedy že stav bude TW_START (0x08). Viz tabulka Status Codes for Master Transmitter Mode. Pokud to tak je, mohu podle tabulky naplnit TWDR Slave adresou, které nechám nejnižší bit vynulovaný jako příznak zápisu. A zase dle tabulky zapíšu příslušnou hodnotu do TWCR a počkám než TWI dokončí svoji činnost. Jakmile skončí, tak opět čtu stav TWSR a zkontroluji, zda se stalo to, co očekávám. Tady se vám problém může typicky rozpadnout na dvě situace. Buď vám nějaký Slave adresu potvrdí anebo nepotvrdí. Typicky tedy dostanete z TWSR hodnotu TW_MT_SLA_ACK (0x18) když vám Slave svoji přítomnost potvrdí nebo TW_MT_SLA_NACK (0x20), když vám žádný Slave potvrzení nedá. Kromě první možnosti nemá žádná další komunikace smysl, takže ve všech ostatních případech nechám funkci odvysílat STOP a končím. Pokud Slave svoji adresu potvrdí, tak opět postupuji dle tabulky. Naplním TWDR daty a odešlu je (odpovídajícím zápisem do TWCR). Počkám na odezvu a zjistím si, zda Slave příjem dat potvrdil nebo ne. Pokud by je nepotvrdil, tak bych na to mohl nějak reagovat. Ale v tomto jednoduchém případě to neřeším a ať je Slave potvrdí nebo ne, prostě komunikaci STOP sekvencí ukončím. Nikdo vám ale nebrání informaci o nepotvrzení zprávy od Slave nějak zužitkovat. Program na straně Slave zařízení je jednodušší. O TWBR se jako Slave vůbec starat nemusím. Datovou rychlost stejně určuje Master. Do TWAR zapíšu svoji Slave adresu a nastavením bitu TWGCE povoluji reagovat i na tzv. “General call”. Můj Slave tedy bude poslouchat i na adresu 0x00. To typicky slouží k hromadnému rozeslání zprávy všem Slave na sběrnici. Můj master ale nic takového vysílat nebude :) Slave pak už jen čeká na nastavení stavového bitu TWINT a pomocí stavu TWSR selektuje, co se zrovna na sběrnici odehrálo (opět pomocí tabulek). Pokud TWI modul detekoval volání svojí adresy, dám TWI pokyn přijmout příchozí data a potvrdit zprávu. Pokud TWI modul přijal data, tak je zpracuji (přepnu stav LED) a nechám modul přijímat a potvrdit cokoli dalšího. Jestliže proběhla STOP sekvence, tak opět nechám modul čekat na další data a vše potvrzovat.

 

001: // Přenos 1 byte Master >> Slave (kód pro slave)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/twi.h>
005: #include <util/delay.h>
006:
007: // adresa tohoto zařízení, změňte ji pokud budete
008: // nahrávat kód do více slave
009: #define MY_SLAVE_ADDRESS 0x02
010: #define TW_WAIT while(!(TWCR & (1<<TWINT)))
011: char data = 0;
012:
013: int main(void){
014: // ledka na PA7
015: DDRA = (1<<DDA7);
016: // naše slave adresa, zarovnaná vlevo, General
017: // call povoleno
018: TWAR = (MY_SLAVE_ADDRESS<<1) | (1<<TWGCE);
019: // spouštíme TWI modul, příští zprávu potvrdíme
020: TWCR = (1<<TWEN) | (1<<TWEA);
021: while (1){
022: // počakat na odezvu TWI
023: TW_WAIT;
024: // čteme v jakém stavu se nachází TWI a podle
025: // toho reagujeme
026: switch(TW_STATUS){
027: // někdo nás adresoval s příznakem zápisu (SLA+W) – jsme
028: // v režimu Slave Receiver
029: case TW_SR_SLA_ACK:
030: // čekej na data a dávej potvrzení
031: TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
032: break;
033: // přišla nám data a potvrdili jsme je
034: case TW_SR_DATA_ACK:
035: // přečti co master poslal
036: data = TWDR;
037: // pokud přišla správná data přepni LED
038: if(data == 1){PORTA ^= (1<<PORTA7);}
039: // čekej na data a dávej potvrzení
040: TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
041: break;
042: // přišla STOP sekvence nebo opakovaný START
043: case TW_SR_STOP:
044: // to nás nezajímá, čekej na data a dávej potvrzení
045: TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
046: break;
047: default:
048: // pokud přijde cokoli nečekaného, čekej na data
049: // a dávej potvrzení
050: TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
051: }
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.
 

Obr. 1: Foto zapojení. Vpravo master s tlačítky, vlevo slave s LED.

Obr. 1: Foto zapojení. Vpravo master s tlačítky, vlevo slave s LED.
TWI – Přenos více bytů Master >> Slave

Stejně jako v prvním příkladě opět půjde o jednosměrnou komunikaci, kde Master odesílá data do Slave. Přenášet budeme tentokrát pole bytů. Abychom se však posunuli trochu dopředu, tak předvedu jak na straně Slave obsluhovat TWI pomocí přerušení. Podívejte se na kód pro Master. Inicializace zůstala stejná jako v předchozím příkladě (Přenos 1 byte Master >> Slave). Změnil jsem jen odesílací funkci. Jejím prvním argumentem je Slave adresa, druhým argumentem pak ukazatel na pole, které chceme odeslat a třetím argumentem je počet bytů, které chceme poslat. Přirozeně by počet odesílaných bytů neměl být větší jako pole, jinak dojde k přetečení pole a nebudeme mít kontrolu nad tím, jaká data od nás Slave obdrží. Pro jednoduchou kontrolu mám pole data[] naplněné čísly 0,1,2,3 a 4. Na straně Slave obvodu pak budeme kontrolovat, jestli přišla správná data. To je přirozeně vhodné jen pro ukázku, ale jinak by pole mělo obsahovat něco smysluplného :) Odesílací funkce vygeneruje Start sekvenci, pak odešle adresu Slave obvodu s příznakem zápisu. Potom ve for cyklu odesílá jednotlivé byty pole a po jejich odeslání generuje STOP. Samozřejmě po každém akci kontroluje stav TWI a v případě nesrovnalostí (například nepotvrzení zprávy od slave) zastaví komunikaci STOP sekvencí.

 

001: // Transfer více bytů master >> slave (kód pro master)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/delay.h>
005: #include <util/twi.h>
006:
007: #define SLV1_ADDRESS 0x2 // adresy různých slave zařízení na sběrnici
008: #define SLV2_ADDRESS 0x3
009: #define DELKA_ZPRAVY 5
010:
011: #define TW_SEND_START TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN)
012: #define TW_SEND_STOP TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO)
013: #define TW_SEND_DATA TWCR = (1<<TWINT) | (1<<TWEN)
014: #define TW_WAIT while(!(TWCR & (1<<TWINT)))
015: #define TLAC1 (PINA & (1<<PINA7))
016: #define TLAC2 (PINA & (1<<PINA6))
017:
018: char i2c_posli_pole(char adresa, char *pole, unsigned int pocet_bytu);
019: char tlac1 = 0, tlac2 = 0;
020: char data[DELKA_ZPRAVY]={0,1,2,3,4};
021:
022: int main(void){
023: TWBR=32; // žádná předdělička, F_SCL = 8MHz/(16+2*TWBR) = 100kHz.
024: // zapnout TWI modul
025: TWCR = (1<<TWEN);
026: // dvě tlačítka
027: DDRA &= ~((1<<DDA7) | (1<<DDA6));
028: // pull-up pro tlačítka
029: PORTA = (1<<PORTA7) | (1<<PORTA6);
030: while(1){
031: // po stisku tlačítka 1
032: if(!TLAC1 && tlac1 == 0){
033: // zamči tlačítko (do uvolnění)
034: tlac1 = 1;
035: // pošli pole "data" pro slave 1
036: i2c_posli_pole(SLV1_ADDRESS,data,DELKA_ZPRAVY);
037: }
038: if(!TLAC2 && tlac2 == 0){
039: // zamči tlačítko (do uvolnění)
040: tlac2 = 1;
041: // pošli pole "data" pro slave 2
042: i2c_posli_pole(SLV2_ADDRESS,data,DELKA_ZPRAVY);
043: }
044: // tlačítka uvolněna
045: if(TLAC1 && TLAC2){
046: // odemči tlačítka
047: tlac1 = 0;
048: tlac2 = 0;
049: }
050: // ošetření zákmitů
051: _delay_ms(100);
052: }
053: }
054:
055: char i2c_posli_pole(char adresa, char *pole, unsigned int pocet_bytu){
056: unsigned int i;
057: // zarovnání adresy na 8bit formát
058: adresa = adresa << 1;
059: // vygenerovat START sekvenci
060: TW_SEND_START;
061:
062: // počkat na odezvu TWI
063: TW_WAIT;
064: // pokud nebyl start vygenerován, máme error a končíme
065: if ((TW_STATUS) != TW_START){ return 1;}
066: // nahrát adresu slave s příznakem zápisu (SLA+W)
067: TWDR = adresa;
068: // odeslat adresu
069: TW_SEND_DATA;
070:
071: // počkat na odezvu TWI
072: TW_WAIT;
073: if ((TW_STATUS) != TW_MT_SLA_ACK){TW_SEND_STOP; return 2;}
074: // Slave nám nedal potvrzení, případně nastal jiný
075: // problém ? končíme komunikaci STOP sekvencí
076: for (i=0;i<pocet_bytu;i++){
077: // nahrát i–tý prvek pole
078: TWDR = pole[i];
079: // odeslat data
080: TW_SEND_DATA;
081: // počkat na odezvu TWI
082: TW_WAIT;
083: if ((TW_STATUS) != TW_MT_DATA_ACK){TW_SEND_STOP; return 2;}
084: // Slave nám nedal potvrzení, případně nastal jiný
085: // problém ? končíme komunikaci STOP sekvencí
086: }
087: // konec komunikace
088: TW_SEND_STOP;
089: return 0;
090: }
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 využívá přerušení (povoleného pomocí bitu TWIE). TWI modul je po inicializaci nastaven tak, aby potvrdil příjem (bit TWEA). Jakmile Master zavolá adresu Slave zařízení, tak se nastaví stavový bit TWINT a spolu s ní se zavolá přerušení od TWI. V rutině přerušení je pak stejně jako v předchozím příkladu (TWI – Přenos 1 byte Master >> Slave) switch, který zjišťuje pomocí stavu TWSR jaká operace na I2C sběrnici proběhla a příslušným způsobem na ni reaguje. Jestliže Slave přijal a potvrdil svoji adresu, tak se vynuluje počítadlo přijatých bytů a TWI se nastaví na příjem další zprávy, kterou má potvrdit. Pokud TWI modul přijal a potvrdil datovou zprávu, tak se uloží přijatá data do pole a počítadlo se inkrementuje. Přirozeně je při ukládání dat dobré hlídat meze pole. Master toho může nedopatřením poslat více, než očekáváme. Pak dáme TWI opět pokyn: čekat na další data a potvrdit je. Pokud přijde STOP sekvence, nebo se odehraje nějaká nečekaná událost, mohli bychom na to nějak zareagovat. Náš program tyto situace ale ignoruje a prostě jen čeká na jednu z prvních dvou situací. Použít přerušení u Slave obvodu je obecně docela užitečné. Master má situaci jednodušší, protože ví, kdy bude vysílat a jisté zdržení z komunikace ho nemusí příliš vytěžovat. Slave ale neví, kdy bude volán a měl by reagovat rychle, a proto by ho častý “polling” (pravidelná kontrola stavového bitu TWINT) dosti zatěžoval. Abychom měli důkaz, že Slave data správně přijal, tak se přepne stav LED na pinu PA7, pokud je poslední přijatý byte roven čtyřem (což je hodnota jakou Master posílá). Jen pro zpestření máte záznam komunikace na Obr. 2.

 

001: //Transfer více bytů master >> slave (kód pro slave)
002: #include <avr/io.h>
003: #define F_CPU 8000000
004: #include <util/twi.h>
005: #include <util/delay.h>
006: #include <avr/interrupt.h>
007:
008: #define DELKA_ZPRAVY 5
009: #define MY_SLAVE_ADDRESS 0x02// adresa tohoto zařízení, změňte ji pokud budete
010: // nahrávat kód do více slave
011: volatile char data[DELKA_ZPRAVY]={0};
012: volatile unsigned int pocet=0;
013:
014: int main(void){
015: // ledka na PA7
016: DDRA = (1<<DDA7);
017: // naše slave adresa, zarovnaná vlevo, General
018: // call povoleno
019: TWAR = (MY_SLAVE_ADDRESS<<1) | (1<<TWGCE);
020: // povolit TWI, povolit přerušení, potvrdit příští zprávu
021: TWCR = (1<<TWEN) | (1<<TWEA) | (1<<TWIE) | (1<<TWINT);
022: // globální povolení přerušení
023: sei();
024: while (1){}
025: }
026:
027: ISR(TWI_vect){
028: // čteme v jakém stavu se nachází TWI a podle
029: // toho reagujeme
030: switch(TW_STATUS){
031: // někdo nás adresoval s příznakem zápisu (SLA+W) – jsme
032: // v režimu Slave Receiver
033: case TW_SR_SLA_ACK:
034: // budeme prijimat prvni byte dat
035: pocet = 0;
036: // čekej na data a dávej potvrzení
037: TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA) | (1<<TWIE)
038: break;
039: // přišla nám data a potvrdili jsme je
040: case TW_SR_DATA_ACK:
041: // ulož co přišlo
042: data[pocet] = TWDR;
043: // inkrementuj pocitadlo dat a hlídej aby pole nepřeteklo
044: if(pocet<DELKA_ZPRAVY-1){pocet++;}
045: // pokud přišla správná data přepni LED
046: if(data[4] == 4){PORTA ^= (1<<PORTA7);}
047: // čekej na data a dávej potvrzení
048: TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA) | (1<<TWIE)
049: break;
050: // přišla STOP sekvence nebo opakovaný START ?
051: case TW_SR_STOP:
052: // to nás nezajímá, čekej na data a dávej potvrzení
053: TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE)
054: break;
055: default:
056: // cokoli dalšího nás nezajímá, čekej na data
057: // a dávej potvrzení
058: TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE)
059: }
060: }
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: Odeslání 5 bytů ze Slave do master.

Obr. 2: Odeslání 5 bytů ze Slave do master.

Určitě jste si všimli, že pole data nikde nemažu. To je tím, že přijatá data v podstatě nezpracovávám. Po přijetí celé zprávy by mělo následovat její zkopírování do jiného pole a vynulování pole data, aby se tak uvolnilo pro další příjem. Jinak by hrozilo, že rutina přerušení přepíše pole data během jeho zpracovávání!

 
Autor: Michal Dudka
 

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

 
Následující: TWI u AVR 2. 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.