OK

Při poskytování služeb nám pomáhají soubory cookie. Používáním našich služeb vyjadřujete souhlas s naším používáním souborů cookie. Více informací

Úvodní stránka » Arduino » Arduino v příkladech - VII.díl - Komunikace a periférie

Arduino v příkladech - VII.díl - Komunikace a periférie

Že k Arduino připojíte prakticky vše, je prostý fakt. Natáhnout dva dráty a jedeme, tak přesně takto to nefunguje. I připojování periférií má jasně stanovená pravidla, která je nutné dodržovat a stejně to neznamená, že to bude fungovat jak má. Tady jsem prošlapal nejvíc slepých uliček, o které bych se s Vámi chtěl podělit.

Článek rozdělíme na dvě části. V první se podíváme na nejčastěji používané komunikační rozhraní, jak fungují a co si k nim můžeme dovolit. V druhé se zaměříme na další velkou bolístku, kterou je konverze úrovní. Slepé uličky nejsou nijak označeny. Jsou součástí textu.

Komunikační rozhraní

Každé Arduino disponuje několika komunikačními rozhraními na HW úrovni. V praxi to znamená, že tím jsou definovány piny, na kterých je lze použít. Naštěstí každé z nich lze SW simulovat na jiných pinech, ale to pak už není ono.

Jednotlivá Arduina se mohou lišit i počtem rozhraní jednotlivých typů. Např. Arduino Mega a Due má 4 rozhraní UART, většina ostatních pouze jedno, některé žádné. Rozhraní SPI a I2C mají všechna. Jednotlivá Arduina se mohou lišit i napětím, na kterém komunikace probíhá. Podrobný seznam najdete na https://www.arduino.cc/en/Products/Compare

A hned na začátek POZOR: nekupujte zařízení, u kterého si nejste jisti, že k němu seženete funkční knihovnu. Jinak budete mít krásnou sbírku zbytečností jako mám já. A toto platí hlavně o nákupech na ebay, aliexpres a pod. Vězte, že jak snadno se tam nakupuje, tak špatně se shánějí knihovny. Je normální, že fotografie neodpovídá realitě a popis tam dali, protože jim připadal podobný jako mají ti ostatní. Co už považuji za standard je, že pokud u stejného prodejce v rozmezí několika týdnů, nakoupím stejné zboží, má jiné parametry. Samozřejmě, výjimky potvrzují pravidlo.

A ještě jednou pozor POZOR: aby komunikace probíhala, musí být vždy propojeno GND vysílač, přijímač.

UART – více známý pod názvem Serial.

Nejvíce používané, spolehlivé, funkční, jednoduché na ovládání, odolné na elektromagnetické rušení a ze všech nejpomalejší.

Připojení

Komunikace probíhá po dvou vodičích RX (receive – příjem) a TX (transmission – vysílání). Na úplně stejném principu pracuje i USB kabel, ale podstatně rychleji. Na Arduino jsou to obvykle piny 0 a 1, u MEGA ještě 14-19. Pokud propojujete dvě Arduina připojte TX jednoho na RX druhého a opačně. Vysílací port na přijímací port. Zapojení bude tedy křížem. Toto platí pro většinu zařízení a není potřeba určovat které je MASTER a které SLAVE. Jedná se o GSM moduly, displeje a cokoliv dalšího co využívá sběrnici USART.

A tady POZOR – pravidlo křížového propojení neplatí vždy. Např. u modulů bezdrátové komunikace 433 MHz, nápis RX neoznačuje pin příjem, ale vysílací pin druhého zařízení. Tedy tady se propojuje RX-RX a TX-TX.

A ještě POZOR – pokud nahráváte program pomocí USB, je nejprve nutné odpojit zařízení RX0 a TX0. Jinak program nenahrajete.

Počet připojených modulů

Doporučuje se připojit pouze 1 modul na jedno rozhraní. Někdy to ani není jinak technicky možné, ale Arduin můžete připojit kolik chcete. Stačí správně napsat program MASTER volá SLAVE.

Komunikace

Zařízení spolu komunikují za pomoci 8 bitových hodnot + stop bit (pozor byte a bit) tedy hodnot 0-255. Současně mohou odesílat a přijímat, protože komunikace probíhá po každé lince samostatně.

Vysílač posílá data do přijímače, který si data v případě Arduino ukládá do vyrovnávací paměti, odkud je potřeba data odebírat. Výchozí nastavení paměti je 64 byte. Pokud je vyrovnávací paměť plná, další příchozí data se ztratí. Pokud budete mít problém s příjmem dat např. z GSM modulu, může být na vině právě nedostatečná velikost vyrovnávací paměti, která se dá upravit přímo v knihovně.

Ale POZOR – čerpáte data z paměti SRAM. Je potřeba vyvážit potřeby celého programu.

Rychlost komunikace

Komunikace není nijak synchronizována. Je nutné nastavit stejnou rychlost jak ve vysílači tak v přijímači. Jinak to nebude fungovat. Když je více portů (MEGA) lze u každého nastavit jinou rychlost. Knihovna Arduino podporuje rychlosti  300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600 a 115200 baudů.

U Arduina je to jednoduché, v programu nastavíte rychlost a vše je OK. Jiná zařízení mají automatickou detekci rychlosti a není potřeba to řešit, u jiných je zapotřebí zjistit jakou rychlost podporují a podle toho ji nastavit. Dá se to dočíst v datasheet, ale číňani jimi neplýtvají, tak někdy nezbývá než metoda pokus omyl.

Tedy když něco nefunguje, může to být i nastavenou rychlostí.

Vzdálenost komunikace

To se dá naprosto přesně kvantifikovat – několik desítek metrů.

Super, tak jak to je :

  • při napětí 12V Vám to bude fungovat spolehlivě na 100 m
  • při napětí 5V, bych se spoléhal na vzdálenost do 50 m
  • při napětí 3,3V bych se spoléhal na vzdálenost do 30 m

Takže žádné přesné hodnoty. Závisí totiž na velikosti rušení co cestě kudy kabel probíhá, kvalitě kabelu a jeho odporu a rychlosti komunikace. To jsou základní parametry.

Jak z toho ven. Prostě natáhněte kabel a zkuste to. Doporučit mohu UPT Cat 5e a vyšší. Když to bude zlobit, snižte rychlost, když už to nepůjde, použijte převodník a zvyšte napětí a na druhé straně ho opět snižte. A když ani to nepomůže, máte někde velké rušení a nezbude nic jiného než natáhnout nový kabel jinudy, nebo zvolit jiný způsob komunikace, v tomto případě bezdrátový.

Zatím nedelší co jsem použil je 120 m na 12V, rušení maximálně eliminováno, spolehlivě chodí 4800 baud. Použitý kabel UTP Cat6A.

Druhé nejdelší je 36 m na 5V, z toho 6 m je souběh se silovým vodičem v jedné plastové trubce, 4 křížení, spolehlivě chodí na 19200 baud. Použitý kabel SYKFY 2+2x0,5.

Podpora sběrnice

Opravdu stačí připojit dva dráty a nic jiného. Na delší vzdálenosti je možné zvýšit napětí pomocí převodníku a zlepšit tak spolehlivost komunikace.

Příkazy a knihovny

Také si myslíte, že víte, no jen čtěte.

Pro toho rozhraní není zapotřebí žádná knihovna, je součástí standardní instalace Arduino IDE. Pokud používáte knihovny například u GSM modulu, tak jen zjednodušuje správné zasílání příkazů, ukončení komunikace a případně provádí kontrolní součet dat (CRC).

Serial.begin(speed)

Pokud chcete používat toto rozhraní, musíte inicializovat knihovnu tímto příkazem, obvykle ve funkci setup. Speed je rychlost komunikace, viz výše. Existuje ještě LCD.begin atd., jedná se ale o volání přidané knihovny, ve skutečnosti je to pořád Serial. Ještě existuje funkce Serial.begin(speed, config), ale s tím se v praxi nejspíše nesetkáte.

Odesíláme data

Serial.print(value) a Serial.println(value)

Tato funkce odešle řetězec value podle tabulky ASCII – tedy jako text nebo příkaz v rozsahu od 0-127. Jedná se o proměnnou char array, ne o String. Myslete na to až budete do řetězce přidávat další řetězec. jednoduše to jde pouze u String a porovnávat char array také není úplně legrace.

Třída print a println se liší pouze v tom, že println na konci řetězce přidá znak návrat vozíku '\n'  a znak nového řádku '\r'  Zjednodušuje psaní kódu.

U třídy .print také můžete použít makro F(), které jsme si popsali v práci s paměti SRAM.

POZOR – budete li odesílat desetinné číslo příklad: float value = 1.23456789; Serial.print(value); odejde pouze 1.23 Pokud chcete poslat více desetinných míst musíte přidat formát dat Serial.print(value, 4); odejde 1.2345, tedy 4 desetinná místa. Existují ještě příkazy BIN, OCT, HEX, DEC. Zajímavá pro práci jsou jen HEX a DEC. Ostatní v praxi moc nevyužijete.

Serial.write(value)

Odesílá data jako hodnotu byte. Odesílat můžete přímo čísla Serial.write(123), nebo i řetězce Serial.write("Hodnota"), je to jedno na druhé straně dostanete hodnotu v byte, rozsah 0-255.

Tato funkce je vhodná když potřebujete poslat číslo a na druhé straně ho nechcete převádět z textu na číslo (nezapomeňme že "0" není rovna 0), nebo posíláte kontrolní data v hex Serial.write(0xff), čímž třeba můžete druhé straně oznámit, že končíte blok odesílaných dat. Některé periférie toto přímo vyžadují a přijímají takto příkazy.

Zajímavá je následující funkce. Pokud u Serial.print a Serial.write uděláte např. int val = Serial.print("Text"); funkce odešle data a do proměnné vám vrátí počet odeslaných byte. Že je to zbytečné. No můžete porovnat kolik dat dorazilo na druhou stranu.

Serial.flush()

Poslední funkce související s odesílám dat. Nemá žádný parametr. Tato funkce čeká, až odejdou všechny data ze Serial. Nepřišel jsem na co čeká a jak. Jestli zastaví celý program a pokračuje až po odeslání dat, nebo další odeslání dat (print, write) provede až po vyprázdnění zásobníků.

Asi je užitečná, jednou jsem řešil problém, s množstvím odesílaných dat, druhá strana je nestačila zpracovávat, ale ať jsem dělal co jsem dělal, tato funkce problém nevyřešila. Prostě jsem do programu vložil kód, na základě kterého mi druhá strana odpověděla, že data došla a jsou zpracována a bylo po problému.

Přijímáme data

Serial.available()

Nemá žádný parametr. Vrací počet byte umístěných ve vyrovnávací paměti, jinými slovy oznamuje že jsme přijali data. Často vidím zápis if (Serial.available() > 0) což je naprosto zbytečné. Když ve vyrovnávací paměti nic není vrací hodnotu 0 = false a jinak 1 a více = true. Tento zápis se dá velmi snadno nahradit if (Serial.available()). Je to to samé a ušetříte místo.

V praxi je hojně používána a často zbytečně. Existuje ještě funkce Serial.event která je elegantnější a nebo můžeme rovnou Serial.read. Hlavní výhodou funkce .available a proč jí používat je, že když víme, že z vysílače očekáváme minimálně 8 byte dat, tak lze zadat if (Serial.available() > 7) a program začne něco dělat až dostaneme alespoň minimální objem dat. Neztrácíme tak čas čekáním na další byte.

Také jsem z dokumentace pochopil, že tato funkce používá vektor přerušení, tedy jsou-li data, přeruší činnost a vrátí počet dat v paměti. Zkoušel jsem to a nic, prostě se zavolala až když na ní přišla řada.

serialEvent()

Nemá žádný parametr a není to samostatná funkce. Musí se zadat void serialEvent() {něco} Tato funkce hlásí zda jsou v paměti data a pokud ano začne zpracovávat příkazy uvnitř. Také nepracuje s vektorem přerušení a kontroluje vyrovnávací paměť až na ní přijde řada. Také nefunguje na všech Arduinech – např. Esplora, Leonardo, nebo Micro. V podstatě je to if (Serial.available()) bez funkce přerušení. Svoje místo ale má, jen jí používat.

Serial.read()

Konečně čteme data. Tato funkce přečte 1 byte na první pozici vyrovnávací paměti, pak data na této pozici vymaže a vyrovnávací paměť všechna data přemístí o pozici níže, takže na první pozici máte co bylo před Serial.read() na druhé pozici. Jde to docela pomalu, takže když za sebou několikrát umístíte Serial.read(), klidně se může stát, že vám vrátí prázdnou hodnotu.

Serial.read() vrací hodnotu v rozsahu 0-255, pokud je vyrovnávací paměť na první pozici prázdná vrátí hodnotu -1 a ne NULL jak by se dalo očekávat. I z tohoto důvodu nevrací velikost byte, ale int.

Tato funkce, v případě příkazů o velikosti 1 byte, může elegantně nahradit ifi, eventy a blable. Typický zápis čekám na příkaz "0" (to odesíláte ze serial monitoru když stisknete 0):

if (Serial.available() > 0) {
 char text = Serial.read();
 if (text == '0') {
  // proveď nějaký kód
  Serial.println("Hura");
 }
}

Se dá snadno nahradit:

if (Serial.read() == '0') {
  // proveď nějaký kód
  Serial.println("Hura");
 }

Jaký formát dat očekáváme zadáváme pomocí proměnné do které přenášíme data. char text = Serial.read() nám vrátí "text", byte value =  Serial.read()  nám vrátí číslo v rozsahu 0-255 (podle ASCII hodnotu ve formátu DEC).

Čtení řetězců se dá vyřešit následující funkcí, kterou voláme když jsou přijata data. Použijeme pouze zjednodušený příklad, který vrací text pro další zpracování a co napíšeme vytiskneme na serial port. Použití v praxi najdete v V. díle.

void setup() {
  Serial.begin(9600);
}
void loop() {
  if (Serial.available()) Serial.println(serial_read());
}
// funkce cteni
String serial_read() {
  boolean END = false;
  unsigned long casSpusteni;
  byte konecBehu = 100;
  char cteni;
  String text;
  // cyklus pro cteni dat ze Serial portu
  do { // cekani na spravnou odpoved
    if (Serial.available()) { // kdyz jsou k dispozici data, precti data
      casSpusteni = millis(); // nastav cas pro ukonceni
      cteni = Serial.read(); // precti data
      text += cteni; // dopln promennou o dalsi nacteny znak
      if ((byte)cteni == 0xff) END = true; // kdyz prijde ukoncovaci znak, nastav konec programu na true
    }
  }
  while (!END && !((unsigned long)(millis() - casSpusteni) >= konecBehu)); // ceka na spravnou hodnotu, nebo uplynuti casu
  return text; // vrat nacteny text
}

Přijmeme data a teď je potřeba je nějak zpracovat. A tady se vždycky pořádně vyvztekám. Ukážeme si pár příkladů jak na to. Důležité je neustále vědět zda pracuji s textem, nebo s čísly.

Pošleme textovou "1" a potřebujeme ji převést na číslicovou 1. Existuje jednoduchá finta: byte value = Serial.read() – '0'; tady pozor na uvozovky, nejsou dvojité. Tato funkce funguje pouze u čísel v rozsahu 0-9

Pošleme textové číslo "7205" a potřebujeme číslo int value = 7205. Tady už je to složitější. Existují i jiné způsoby, ale tento mi přijde nejednodušší.

char* cteni = "7205"; // hodnota cteni
String text = (String)cteni; // preved ctenou hodnotu na String
int cislo = text.toInt(); // preved ulozenou hodnotu na cislo

Přijdou data a potřebujeme zjistit zda se data rovnají, obvykle texty. Řešení:

if (strstr(dotaz, "OK") != NULL) {
  // proveď nějaký kód
}

Přijdou data a je zapotřebí je rozdělit. První přichází text, druhé číslo, třetí zase číslo, pátý opět text. Jednotlivé položky jsou odděleny dvojtečkou (:) Řešení:

void setup() {
  Serial.begin(9600);
  rozdel_text();
}
void loop() {
}
void rozdel_text() {
  String text_String = "Zacatek:123:1972:Konec"; // vytvorime data pro simulaci
  char text[text_String.length() + 1]; // vytvorime char array
  text_String.toCharArray(text, text_String.length() + 1); // prevedeme string na char
  char *text1 = strtok(text, ":"); // prvni text
  int val1 = atoi(strtok(NULL, ":")); // prvni cislo
  int val2 = atoi(strtok(NULL, ":")) - 1970; // muzeme i pocitat
  char *text2 = strtok(NULL, ":"); // druhy text
  // test
  Serial.println(text1);
  Serial.println(val1);
  Serial.println(val2 * 2);
  Serial.println(text2);
}

Rozdělování textu doporučuji jako samostatnou funkci s lokálními proměnnými. Co když nevíme, jestli to bude text. Uložte vše do char a pak jsou nástroje jak zjistit zda se jedná o číslo. Ale už teď jdeme nad rámec článku.

A teď už zrychlíme, přitom stále čteme

Serial.peek()

Tato funkce se podívá a vrátí data z prvního místa vyrovnávací paměti, ale nechá je tam. Nemá žádný parametr. Tedy funguje podobně jako Serial.read(), ale nevymaže a neposune data. Použít se dá v případě, že čekáte na nějaká data a dokud nepřijdou ta správná, můžete je snadno za pomoci Serial.read() zahazovat. Nebo můžete zahazovat znaky co nechcete, případně se dá využít na čekání příchozích dat stejně jak jsme si to ukazovali u read a available.

Serial.setTimeout()

Nastavuje maximální dobu čekání na dokončení příjmu dat v milisekundách. Má jediný parametr Serial.setTimeout (time). Time určuje čas. Tato funkce funguje pouze ve spojení s následujícími funkcemi. Pokud ji nepoužijete, výchozí nastavení tohoto paremetru pro uvedené funkce je 1000 milisekund.

Serial.readBytes()

Tato funkce má dva parametry. Serial.readBytes(buffer, length). Buffer je proměnná do které zapisujeme čtená data a length je maximální délka načtených dat. Serial.readBytes () čte data tak dlouho, dokud se nedosáhne počtu znaků (byte) určených parametrem length, nebo se nedosáhne času nastaveného Serial.setTimeout (). Buffer mohou být data typu char array, nebo byte array. Funguje tedy podobně jako do...while viz náš příklad u Serial.read(), má ale svoje nevýhody. Zabírá dost místa a pak se neakceptuje ukončovací znaky.

Ideální zápis vypadá takto:

void setup() {
  Serial.begin(9600);
}
void loop() {
  if (Serial.available()) precti_retezec();
}
// funkce pro readBytes
void precti_retezec() {
  int len = 20; // nastavi delku rezece na 20 znaků
  char text[len]; // vytvori promennou pro cteni
  Serial.readBytes(text, len); // cte data
  // test
  Serial.println(text);
}

POZOR proměnnou buffer si vždy vytvářejte jako lokální. Pokud bude globální a načtete třeba jen 8 znaků, zbývajících 12 zůstane původních. S tím se nedá pracovat bez neočekávaných výsledků.

Serial.readBytesUntil()

Tato funkce se od předchozí liší pouze tím, že má ještě parametr character, Serial.readBytesUntil(character, buffer, length), kdy je čtení ještě navíc ukončeno, pokud najde znak character, který je typu char.

Serial.parseInt()

Opět velmi podobná readBytes s následujícími rozdíly. Zahazuje všechny znaky dokud nenarazí na znak -, nebo číslo, případně pokud neuplyne setTimeout (). Když narazí na -, nebo číslo, čte znaky do té doby, než narazí na znak který není číslem. Pak ukončí čtení a vrátí získanou hodnotu jako int. Tato funkce se dá použít bez parametru, nebo s parametrem Serial.parseInt (skipChar). Tento paremetr určuje, které znaky má přeskočit a přesto číst dál. Dají se tak např. přeskočit tečky, které oddělují tisíce (10.203) toto číslo s parametrem skipChar = "." přečte jako 10203, bez parametru jako 10.

Serial.parseFloat()

Podobná funkce jako parseInt s tím rozdílem že vrací desetinné číslo a nemá žádný parametr. První tečku považuje jako desetinnou čárku, pak už ji považuje také jako znak pro ukončení.

Serial.readString()

Podobná jako readBytes s následujícími rozdíly. Nemá žádný parametr, ukládá data jako String a čte tak dlouho dokud neuplyne setTimeout(). To vypadá jako ideální funkce, ale mám z ní obavy. Také může číst tak dlouho dokud se nenaplní SRAM a program klekne.

Serial.readStringUntil(character)

Stejné jako readBytesUntil s rozdíly jako readString.

Na závěr serial je nutno říci tři věci:

  1. drtivá většina příkazů funguje korektně při komunikaci na SPI, I2C, dá se využít s ethernetovými moduly a práci s SD kartou
  2. pokud komunikujete mezi dvěma Arduiny po UART a I2C, existuje naprosto úžasná knihovna, která vše co je doposud napsáno, řeší za vás. Stahujte https://github.com/madsci1016/Arduino-EasyTransfer
  3. počet portů UART lze rozšířit za pomoci knihovny Software serial na libovolný digitální pin. Má mnoho omezení a nevýhod, protože se jedná pouze o softwarovou simulaci, ale když není zbytí a dobře se nastaví, funguje spolehlivě.

SPI

Velmi rychlé a může komunikovat oběma směry současně. Tím výhody tohoto připojení končí. Je často využíváno pro připojení shieldů přímo na Arduino za pomoci ICSP portu, který má většina Arduin přímo na desce. Často se používá ve spojení s displeji, ethernetovými moduly a SD kartami. I když úvod může vypadat nezajímavě, má toto rozhraní svoje místo na slunci, především kvůli jednoduchosti komunikace.

Připojení

Pro připojení je zapotřebí 4 vodiče, kdy musí být určen MASTER a SLAVE. Podle toho se řídí komunikace.

Popis jednotlivých portů:

  • MISO (Master In Slave Out) – na tomto portu SLAVE vysílá, MASTER přijímá
  • MOSI (Master Out Slave In) – na tomto portu MASTER vysílá, SLAVE přijímá
  • SCK (Serial Clock) – na tomto portu běží něco jako hodiny a určuje rychlost komunikace. Tento port řídí vždy pouze MASTER a tedy není potřeba SLAVE synchronizovat
  • SS (Slave Select) slouží k výběru zařízení, se kterým bude komunikovat MASTER. Pokud je ve stavu HIGH, nereaguje SLAVE na příkazy, pokud je LOW ví SLAVE, že data jsou pro něj.

V praxi se můžete setkat na perifích s jinými názvy. Tady budete muset hledat co znamenají.

Připojuje se MISO – MISO, MOSI-MOSI, SCK-SCK a příslušný pin SS MASTER na pin SS SLAVE

Piny mimo ICSP mohou být na každém Arduino jinde. Například:

deska MISO MOSI SCK SS
Uno 12 11 13 10
Mega 50 51 52 53

POZOR – pin SS je zapotřebí před použitím nastavit jako výstup, pinMode(x, OUTPUT)

Počet připojených modulů

Připojit můžete libovolný počet zařízení, omezeni jsme pouze počtem výstupních pinů, které můžete nastavit jako SS. Tedy ke každému modulu vedete z MISO, MOSI a SCK drát připojený stále na stejné piny MASTER a každé pak samostatně k SS. Výběrem SS určíte se kterou periférií chcete zrovna komunikovat.

POZOR – tady jsem naprápil tak, až jsem chtěl s tímto koníčkem skončit a vám se to určitě stane taky. Na Arduino jsem narazil Ethernetový modul s SD modulem a vše fungovalo na jedničku. Pak samostatně naprogramoval displej a opět vše fungovalo. Pak jsem to sestavil dohromady a displej nefungoval. Žádná chyba, tisíckrát zkontrolované, až jsem vzal do ruky měřák. V knihovně byla chyba a Ethernetový modul byl stále ve stavu LOW a do stavu HIGH se přepnul pouze v okamžiku, kdy jste chtěli komunikovat s SD kartou. Tedy nešlo přepnout na komunikaci s displejem. Funkční knihovnu pro tento modul jsem nesehnal a napsat si svojí pro Ethernet, je u SPI nad moje znalosti.

Řešení: simuloval jsem SPI pro displej na jiných pinech a vše chodí jak má. Pak je řešením zavolat funkci SPI.endTransaction (). Bohužel ne vždy to funguje.

Komunikace

Zařízení spolu komunikují obvykle za pomoci 8 bitových hodnot + 2 hodnoty určující rychlost a stop bit (popsáno pro laiky hodně zjednodušeně). Každý SLAVE může komunikovat s jinými parametry. Současně mohou odesílat a přijímat, protože komunikace probíhá po každé lince samostatně.

Komunikace probíhá tak, že MASTER přepne pin SS na LOW a začne vysílat, či přijímat data. Po ukončení komunikace přepne pin SS na HIGH a může komunikovat s dalším SLAVE podle stejné logiky. Existují 4 způsoby přenosu dat.

Mode Clock Polarity (CPOL) Clock Phase (CPHA)
SPI_MODE0 0 0
SPI_MODE1 0 1
SPI_MODE2 1 0
SPI_MODE3 1 1

Při psaní kódu je na toto brát zřetel a MODE závisí na nastavení SLAVE. Pokud Vám komunikace nefunguje, může to být nastavením MODE.

Rychlost komunikace

Komunikace je synchronizována hodinovým signálem na SCK. Maximální rychlost komunikace je uváděna na hodnotě 70 MHz. Tedy vysoko nad reálnou rychlostí procesorů našich Arduin.

Vzdálenost komunikace

V oficiálních zdrojích se uvádí jednotky metrů. Také velmi přesný údaj. Opět závisí na zarušení a použitém kabelu.

V praxi jsem připojoval SPI displej na 5,6 m kabelu. To co jsem si užil ani nebudu popisovat. Nakonec to jakž takž funguje (občas dotyk nevrací správné hodnoty). Musel jsem použít nestíněný a nekroucený kabel a u displeje linku přiživit pomocí převodníku úrovní.

Druhé nejdelší zapojení je 3,2 m a na plochém kabelu funguje spolehlivě.

Obecně mohu doporučit vysílat na 5V na plochém kabelu, důsledně se vyhýbat křížení a souběhu se silovými kabely, jakýkoliv vysílač bezdrátového signálu umístit co nejdále a držet se do vzdálenosti 3 m. Pak budete spokojeni.

Podpora sběrnice

Tady opět stačí připojit 4 dráty a opět nic jiného.

Příkazy a knihovny

Knihovna SPI je součástí standardní instalace Arduino IDE a není potřeba ji nahrávat #include... Programovat SPI za pomocí příkazů knihovny SPI vyžaduje velmi dobré znalosti datasheet SLAVE. Pro začátečníky a středně pokročilé mohu pouze doporučit používat knihovny příslušné k dané periférii a příkazy používat ze souboru keywords.txt, který bývá obvykle v adresáři knihovny.

I2C

Opět rychlé a funkční zařízení. Má hodně společných vlastností z SPI, stejně jako mnoho rozdílů. Implementace a komunikace je o něco složitější než SPI, ale to za Vás řeší knihovny. Toto připojení se používá u LCD displejů, teplotních čidel, hodin času, posuvných registrů a mnoha a mnoha zařízeních. Toto rozhraní je primárně určeno ke komunikaci mezi jednotlivými "pomalými" čipy na jedné desce. Jak ale ukazuje praxe, velmi snadno můžete jít mimo desku.

Připojení

Pro připojení jsou zapotřebí 2 vodiče, kdy stejně jako u SPI musí být určen MASTER a SLAVE. Piny jsou označeny SDA – komunikační linka a SCL – hodinový signál (to samé jako SCK u SPI). Piny se připojují SDA-SDA, SCK-SCK.

I tady piny mohou být na každém Arduino jinde. Například:

deska SDA SCL
Uno A4 A5
Mega 20 21

Počet připojených modulů

Počet připojených zařízení na jedné sběrnici je omezen na 120. Každé připojené zařízení musí mít unikátní adresu v rozsahu 8-127. Adresa 0-7 je rezervována pro jiné účely a nemůže být použita. Od toho se odvíjí komunikace. Na shieldech se to obvykle nastavuje propojovacími jumpery. Existuje i program, který vám umožní určit adresy jednotlivých zařízení na sběrnici.

#include <Wire.h>
byte error;
byte address;
int nDevices = 0;

void setup() {
  Wire.begin();
  Serial.begin(9600);

  Serial.println("Skenuji...");
  for (address = 0; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C zarizeni nalezeno na adrese 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      Serial.println("  !");
      nDevices++;
    }
    else if (error == 4) {
      Serial.print("Neznama chyba na adrese 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0) {
    Serial.println("Zadne zarizeni nenalezeno");
  }
}
void loop() {
}

Komunikace

Komunikace začíná tak, že MASTER vyšle START bit a pak adresu volaného zařízení. Všechny zařízení porovnají přijatou adresu s odeslanou a pokud se shoduje potvrdí přijetí bitem ACK. A pak začíná komunikace. Každé vysílání začíná bitem START, následuje bit R/W, který určuje zda MASTER bude číst nebo psát, čeká se na odezvu ACK a pak probíhá komunikace odešle se 1 byte a následuje opět 1 bit ACK. Přenos je nutné ukončit bitem STOP (opět pozor na byte a bite).

Teto postup má několik nevýhod a jednu podstatnou výhodu

Nevýhody:

  • MASTER může komunikovat pouze s jedním SLAVE
  • lze komunikovat pouze jedním směrem
  • složitost komunikace dost zpomaluje objem přenesených dat vzhledem ke své rychlosti
  • SLAVE si MASTER nezavolá, řeší se to dalším drátem, který hlásí že SLAVE chce komunikovat a MASTER si pak volá

Nevýhoda nemusí být limitující, pokud I2C správně používáte.

Výhoda je v tom, že na rozdíl od předchozích sběrnic vždy víte, zda odesílaná data v pořádku dorazila. I UART a SPI to musíte softwarově ošetřit a to se bohužel moc nedělá a také to vždy dost dobře není možné.

Rychlost komunikace

Komunikace je opět synchronizována hodinovým signálem na SCK a může dosahovat rychlosti až 400 kHz.

Vzdálenost komunikace

Existuje jediný parametr omezují vzdálenost připojení a to je maximální přípustnou kapacitou vedení 400 pF. Tedy při použití kvalitních a správných kabelů (např UPT Cat 5e) i několik desítek metrů. Osobně ale I2C používám na připojení periférií blízko k Arduinu, přímo na licně a ještě jsem se nesetkal s jakýmkoliv problémem.

Podpora sběrnice

POZOR – linku SDA a SCL je potřeba připojit za pomoci pull-uprezistou ke kladnému napětí. Doporučuje se napájení doplnit diodou, jako ochranu pro případné přepětí. A tady bývá zakopaný pes. Pokud koupíte shield, tak pull-up rezistory na desce obvykle bývají připojeny. Tedy připojím jeden modul, nemusím se o nic starat. Připojím druhý mám dva pull-up rezistory, připojím třetí a mám tři. Sami vidíte, že to asi není dobré. Tady je zapotřebí na dalších shieldech tyto rezistory odpojit.

Příkazy a knihovny

Pro využití této komunikace ne nutné v hlavičce programu nahrát knihovnu WIRE, který je součástí instalace Arduino IDE: #include Wire.h

Pak záleží zda pro komunikaci s periférií využijete příslušnou knihovnu viz náš příklad s měřením analogového napětí a připojení LCD displeje, nebo si chcete program napsat sami. Doporučuji využít knihovnu, hodně si zjednodušíte práce, protože jinak opět bude studovat datasheet.

Abychom tento článek někdy ukončili, na příklady vlastní komunikace po I2C se podíváme jindy.

Další rozhraní

Existuje mnoho a mnoho dalších rozhraní, RS232, TWI, M-BUS, Modbus, PROFIBUS a mnoho a mnoho dalších. Ve všech případech se jedná o softwarové řešení a ne o fyzické rozhraní. Arduino je možné naprogramovat pro komunikaci se všemi těmiho protokoly. V praxi se ale používají tak málo že je vynecháme.

Co vynechat nemůžeme je One-wire

One-wire

Jednoduché, odolné rušení a v mnoha ohledech unikátní řešení, které navrhla společnost Dallas Semiconductor pro komunikaci se svými zařízeními. Jedná se o levné zařízení, u nichž cena neubírá nic na kvalitě. Nabídka čipů tohoto výrobce je opravdu bohatá a těžko budete hledat periferii která v nabídce není. Nejčastěji se používají teplotní čidla, hodinové moduly, moduly ke měření napětí, spínací moduly, měření tlaku i vzdálenosti, přístupové systémy i-Button, hlídání napětí baterií a tak dále..... Kde se obvykle nepoužívá jsou displeje.

Připojení

Pro připojení stačí jeden drát DATA. Existují dva způsoby připojení – standardní GND, DATA, VIN, což je velmi spolehlivé hlavně v při použití více čidel na velké vzdálenosti. Druhé se nazývá parazitní GND, DATA, kde napájení čidla probíhá před DATA prostřednictvím pull-up rezistoru připojenému k kladnému pólu napájení.

Parazitní napájení bývá nevyzpytatelné a je důvodem pro mnoho předem neurčitých problémů.

POZOR – jestli vás čidla zlobí a dostáváte občas neočekávané hodnoty, je na vině právě nesprávně realizované parazitní napájení. V domácích podmínkách těžko dodržíte normy (a také je to dost drahé a tak v podstatě deklasujete cenu zařízení), proto doporučuji od počátku počítat se standardním napájením.

Např. i-Button ale jinak než parazitně nepřipojíte. Ničemu to nevadí, zrovna toto funguje vždy spolehlivě.

Pro připojení mohu doporučit kroucený kabel, nezáleží moc na tom jestli stíněný, ideální a levný je UPT kabel.

POZOR – na typologii sítě. Jestli má komunikace fungovat spolehlivě zapojte periférie za sebou do série. Tedy DATA od jednoho k druhému. Vyvarujte se jakýchkoli hvězd, stromů, odboček a čehokoliv jiného. Specifikace to sice umožňuje, ale asi to testovali v laboratoři. V běžném prostředí to bude zdroj nekončících problémů, které skončí tak, že celá instalace bude nepoužitelná.

Jak to vyřešit pokud potřebuje hvězdu – natáhněte SYKFY kabel 2+2x0,5 a jeden pár zapojte GND DATA tam a druhý pár VIN DATA zpět, v centru hvězdy propojte DATA zpět a DATA tam a je vyřešeno. Nemusíte se bát, sběrnice snese stovky metrů a když ne, na Arduinu určitě najdete další pin na kterém můžete spustit další sběrnici.

Čipy Dallas jsou tolerantní k napájení i datové komunikaci z rozsahu 3-5,5V. Ohromná výhoda, nemusíte řešit spoustu věcí na které se podíváme v příštím článku.

Počet připojených modulů

Počet připojených zařízení je omezen pouze pamětí vašeho Arduina. Tolik kolik tam nasypete adres, tolik modulů můžete připojit. Každé zařízení má unikátní 8 byte (64 bit) adresu, kterou zařízení voláte. Tedy přesněji:

  • 1 byte označuje rodinu, např. DS18B20 má 0x28
  • 2-7 byte obsahuje unikátní sériové číslo
  • 8 byte obsahuje CRC kód pro kontrolu předchozích údajů

Kombinace 2–7 byte zajistí unikátní číslo zařízení, i kterého Dallas garantuje jeho jedinečnost. Teoreticky by se Vám nemělo stát, že budete mít v síti dvě zařízení stejného čísla, což skutečně platí i v praxi. Adresa je dána z výroby a nelze změnit. Tato vlastnost je velkou výhodou oproti jiným typům peririférií.

Komunikace

Komunikace na One-wire je velmi podobná komunikaci I2C. Je tedy možná komunikace pouze MASTER a jedno SLAVE, SLAVE si neumí zavolat MASTER a protože komunikace probíhá po jednom vodiči, je v jednom okamžiku pouze jednosměrná.

Komunikace funguje tak, že MASTER zavolá SLAVE konkrétní adresy, dostane odpověď a pak zadá příkaz co se má udělat, SLAVE vrátí data. Komunikace probíhá v 8 bite sekvencích, které lze kontrolovat za pomoci CRC. Podrobnosti vynecháme, protože nejsou důležité.

Co je důležité, že příkazem 0xCC můžeme oslovit všechna zařízení na sběrnici najednou, pak jim příkazem říci co mají udělat, na což potřebují čas (čtení teploty dle nastavení může trvat až 750 millisekund) a pak pěkně od jednoho po druhém vyzvednout zpracovaná data. Ani tady není nezbytné volat konkrétní adresou, stačí volat v pořadí jak jsou řazeny. Výrazně to zrychlí komunikaci, ale tomuto bych se vyhnul. Dojde k poruše, nebo odpojení a hned máte data odjinud. Když čtu připravená data, volám konkrétní adresu.

Rychlost komunikace

Toto rozhraní může pracovat rychlostí až 15.4kbps, tedy velmi, velmi pomalu. Pokud pominu čtení dat z externí EEPROM, tak to není nic limitního, protože třeba pro čtení teploty, při adresování čidla, se přenese celkem 22 byte dat.

Vzdálenost komunikace

Oficiálně více než 100 m. Není nad test – připojil jsem jedno čidlo teploty na špulku 305 m UTP Cat 6 spustil program, každých pět vteřin měřil teplotu a po jednom dni měření ani chybička.

Ale upřímně, cena této špulky 10 x převyšuje řešení za pomoci bezdrátové komunikace a to nepočítám že se kabel musí taky nějak instalovat. Prostě připojit čidlo teploty za 35 kaček na kabelu za dva tisíce považuji za ekonomický nesmysl. Ale proti gustu žádný dešputát, nehledě na to, že kabel má tu výhodu, že Vám zkušený soused bastlíř nezaruší pásmo, na kterém běží bezdrátová komunikace.

Podpora sběrnice

POZOR – linku DATA, ať ve standardním módu, nebo jako parazitně napájenou musíte podpořit pull-uprezistorem připojenému ke kladnému pólu napájení a to co nejblíže Arduina. Hodnotu použijte v rozsahu 5,6 kΩ - 1 kΩ. Platí přímá úměra – čím delší kabel tím menší hodnota odporu rezistoru.

Samozřejmě závisí i na použitém kabelu, zarušení atd. Prostě začněte u největšího a pokud to bude zlobit, snižujte hodnotu. Nechoďte ale pod 1 kΩ. Nic moc rada, ale každá instalace je natolik rozdílná, že je asi nejlepší co Vám mohu dát.

Příkazy a knihovny

Pro využití této komunikace ne nutné v hlavičce programu nahrát knihovnu WIRE, který je součástí instalace Arduino IDE: #include Wire.h

Pak existují dvě možnosti:

  1. Buď nastudujete datasheet a použijete příkazy, na což se podíváme jindy
  2. Nebo nahrajete knihovnu, která vyřeší příkazy za Vás, nicméně počítejte s velkými časovými prodlevami, kterými se vknihovnách nešetří.

Osobně píšu funkce podle první možnosti, ale to je opravdu nad rámec toto článku.

Co ale určitě využijete, je program, který Vám vyhledá připojená čidla.

#include <OneWire.h>
byte count = 0;
void setup() {
  Serial.begin(9600);
  for (byte pin = 2; pin <= 13; pin++) findDevices(pin);
  Serial.println("Nalezeno " + (String)count + " zarizeni.");
}

void loop()
{}
void findDevices(byte pin) {
  OneWire onewire(pin);
  boolean flag = false;
  byte address[8];
  Serial.print("Kontrola pin " + (String)pin + ": ");

  while (onewire.search(address)) {
    count++;
    flag = true;
    Serial.println();
    Serial.print("Nalezana adresa: ");
    for (byte i = 0; i < 8; i++) {
      Serial.print("0x");
      if (address[i] < 16) Serial.print('0');
      Serial.print(address[i], HEX);
      if (i < 7) Serial.print(", ");
    }
    if (OneWire::crc8(address, 7) != address[7]) {
      Serial.println("CRC neni spravna");
    }
  }
  if (!flag) Serial.print(" ONE WIRE nenalezeno");
  Serial.println();
  onewire.reset_search();
}

Není to moc popsáno. Není potřeba, prostě připojte čidlo na libovolný digitální pin a spusťte program.

Až se dostaneme k displejům, ukážeme si jak přiřadit za běhu programu čidlo, aniž bychom museli cokoliv nastavovat předem.

Příště se podíváme na vyrovnávání úravní a pak se konečně pustíme do těch displejů.

JB

jaroslav.bohac@arduinotech.cz

Přidat komentář

Zvýrazněné položky jsou povinné.

Přehled komentářů

  1. Chybička (Simonik, 27.5.2016 10:22:27) Odpovědět

    char cteni = "7205"; // hodnota cteni
    Že by chybička?

    JB: Děkuji za upozornění, správně má být char* cteni = "7205"; // hodnota cteni, v článku opraveno.

  2. I2C (Dan, 14.6.2016 17:07:58) Odpovědět

    Existuje prosím nějaká knihovna pro Arduino, abych pro komunikaci s I2C zařízením (např. RTC) mohl využít i jiné piny, než pro atmegu328p pevně dané A4 a A5? Nebo to z principu, či z praktického hlediska, nejde? Jde o to, že bych rád ke svému projektu připojil RTC, ale mám k dispozici dva jiné piny, než pro I2C pevně určené. Děkuji za radu.

    Dobrý den,
    Mrkněte zde https://github.com/todbot/SoftI2CMaster, otestujte a dejte vědět, zda je to to pravé ořechové.

    S pozdravem,
    PF

  3. Funkce Serial.flush (LuBoss, 14.10.2016 12:33:51) Odpovědět | Zobrazit odpovědi

    Funkce Serial.flush() má použití např. při odesílání dat pomocí RS-485 u které je převodník přepínám nějakým pinem procesoru mezi stavy vysílání/příjem. Protože má seriový kanál buffer dat, tak je jejich odeslání pro procesor zdánlivě rychlé a jeví se jako ukončené i když jsou data ještě v odesílacím bufferu a fyzicky nebyla poslána na linku. Pokud v tomto okamžiku, tedy po příkazu Serial.print() ihned následuje přepnutí RS485 z režimu zápis na čtení, tak nastane problém, protože je ještě pár dat v seriovém bufferu, ale linka je "násilně" přepnuta do režimu čtení a tím je odeslání dat ukončeno. To je příklad jedné ze situací, kdy potřebujete nutně znát, kdy byla data ze seriové linky opravdu fyzicky odeslána a pak teprve provedete další akci.

    1. Re: Funkce Serial.flush (JB, 24.10.2016 22:58:13) Odpovědět

      Děkuji za doplnění.


TOP produkty

Arduino MEGA2560

Arduino MEGA2560
424 Kč s DPH

NodeMCU s ESP8266

NodeMCU s ESP8266
350 Kč s DPH

Kontakt

Ing. Petr Foltýn
Kunčice pod Ondřejníkem 814, 73913
TOPlist