czech english

Komunikace

předávání informací mezi čipy

Komunikace je základem spolupráce každého společenství, ať už jsou to lidé, buňky nebo čipy. Je to tedy i nepostradatelná schopnost robotů a jejich modulů. Jak lze tedy komunikovat mezi jednotlivými čipy, s PC, případně s jednoduchými senzory a efektory? Podrobněji se podíváme na nejčastěji používanou seriovou komunikaci RS-232 a sběrnici I2C…

Úvod

I u toho nejjednoduššího robota budeme potřebovat komunikovat — důkazem nechť je řízení serva, kde robot posílá „instrukce” formou délky řídících pulzu. Jednodušší senzory mají většinou na výstupu analogovou hodnotu, kterou váš jednočip může zpracovávat A/D převodníkem, a není tedy problém se se senzorem „domluvit”. Složitější senzory však svá měření vysílají digitálně a je třeba tyto informace dekódovat. Jako poslední motivaci uveďme rozdělení složité úlohy řízení robota na více modulů/zařízení: zatímco přímý přístup k HW je nejlepší řešit pomocí nějakého jednočipu, k vymýšlení strategií a plánování akcí je lepší nějaký výkonější mozek, např. standardní PC. Opět narazíte na potřebu komunikovat, a proto vznikla i tato kapitola — jak nejsnáze komunikovat mezi PC a jednočipy (RS-232) a mezi čipy navzájem (I2C).
Jak se tedy jednotlivá zařízení dorozumívají? Představte si dva jednočipy, kde první má nastavený komunikační pin jako výstupní a druhý jednočip má komunikační pin nastaven jako vstupní. Logickou hodnotu, kterou nastaví na výstupu první jednočip, pak druhý jednočip naměří na svém vstupu. Samotná zpráva se pak předá buď postupnými změnami napětí na výstupním pinu prvního jednočipu, nebo v složitějších případech jako kombinace výstupů na více propojujících linkách.

RS-232

RS-232 konektor
RS-232 konektor
Asi málokdo ví, že RS-232C znamená Recommended Standard číslo 232, revize C. Jedná se o relativně dlouho přežívající způsob komunikace, který vznikl někdy v letech 1969 a na většině počítačů ho nalezneme v podobě devíti-pinového konektoru dodnes (na nových přenosných počítačích už tomu bohužel tak není, a tak si musíme pomoci USB-RS232 redukcí). RS-232 je snad nejjednodušší způsob, jak se domlvit s většinou jenočipů, a tak stojí za podrobnější rozbor.
Budeme se věnovat nejpoužívanější variantě, kdy komunikační kabel má pouze tři žíly: jednu pro společnou zem, jednu pro příjem (IN) a jednu pro vysílání (OUT). Informace je pak kódována pomocí různých napětí mezi zemí a IN pro příjem a obráceně mezi zemí a OUT pro vysílání.
RS-232 komunikuje pomocí rámců (frames). Pokud se nic neděje, tak je linka v klidovém (IDLE) stavu, pro který se používá kladné napětí. Každý rámec začíná start bitem (St), což je změna na záporné napětí na dobu danou rychlostí komunikace (např. pro 9600baud je to 1/9600s, tj. cca 104us). Následují datové bity, kdy logická jednička odpovídá zápornému napětí a logická nula kladnému. Vysílá se od nejméně důležitého bitu (LSB). Celý rámec je zakončen stop bitem (Sp), kdy je linka zase v klidovém, tedy kladném napětí. Po stop bitu může následovat pauza (IDLE) nebo hned start bit (St).
Frame
Frame
Rámce mohou po sobě hned následovat, takže pokud používáme přenosovou rychlost 9600 baud, tak za 1s můžeme poslat maximálně 9600/10=960 bajtů (číslo 10 odpovídá jednomu start bitu, 8 datovým bitům a jednomu stop bitu).
Existuje mnoho konfiguraci RS-232, z nichž asi nejpoužívanější je:
  • 8 datových bitů
  • žádný paritní bit
  • jeden stop bit
(Pozn. chybí rychlost přenosu, která závisí na použitém zařízení.)

Generování signálu na jednočipu

Je to zvláštní, ale skoro bych řekl, že používání RS-232 je někdy jednodušší na jednočipu než na PC s operačním systémem. Ano, můžete si situaci hodně zesložitit a pokusit se signál generovat a přijímat sami, ale nedělejte to . Kdybyste to přeci jenom chtěli zkusit, jak na to?
Jak bylo popsáno už u řízení serva, než si počítat strojové instrukce a doby trvání, je vhodnější zapojit časovač (timer) s přerušením (interrupt). Jelikož přenosové rychlosti a jim odpovídající časy ne vždy dobře pasují na rychlost hodin procesoru (pozn. někdy právě z tohoto důvodu se volí „podivné rychlosti” jako např. 1.8432Hz místo 1MHz), nelze se spolehnout na přetečení po 255, ale je třeba časovači nastavit jinou hranici (Clear Timer on Compare Match --- Auto Reload, což je doslova vynulování časovače při shodě neboli automatické předvyplnění).
Pokud chcete komunikovat na rychlosti 9600 baud a máte časovač, co běží na 1MHz, tak si nastavíte hodnotu pro compare match na 104 (1s/9600 * 1000000). Při každé obsluze přerušení pak generujete nový bit rámce, nebo koukáte na vstupní linku, jestli na ní nezačalo vysílání (start bit).
Přiznejme si, že pokud toto řešení bude fungovat, tak pravděpodobně ne příliš dlouho. Problém je v nepřesném nastavení časovače, a co je ještě horší, v nepřesném nastavení časovače druhé strany. Pokud budete nepřerušeně přijímat dostatečně dlouho, může se Vám stát, že se Vaše vzorkování mine s vysílaným signálem. Z tohoto důvodu se provozuje hned několik triků. Asi první z nich je co nejlépe trefit střed jednotlivých bitů. To lze např. tak, že u start bitu co nejpřesněji detekujeme přechod hrany z 0 na 1. Pak můžeme start bit ještě potvrdit (podívat se, že za čas 104/2 je hodnota stále 1) a od toho okamžiku spustit pravidelné vzorkování.
Druhá možnost je zvýšit vzorkovací frekvenci a na každy bit se tedy podívat několikrát. Pak lze lépe odhadnout začátek rámce a případně opravit chyby v časování druhé strany.
Pokud Vás poslední čtyři odstavce vyděsily, tak zachovejte klid — všechy tyto problémy za Vás řeší modul UART (někdy obecnější USART, tedy Universal Synchronous and Asynchronous serial Receiver and Transmitter, což „česky” znamená univerzální synchronní a asynchronní seriový přijímač a vysílač).
Stejně jako v naší pokusné implementaci je třeba tomuto modulu nastavit časovač. Ve většině dokumentací k čipům jsou vzorové tabulky, jaké přesné číslo máte zadat a, což je snad ještě důležitější, jak velké chyby se tím budete dopouštět. Tak například poznáte, že s čipem ATmega8 a hodinami 1MHz byste neměli komunikovat na rychlosti 38400 baud. V tomto případě buď zrychlíte hodiny nebo zpomalíte komunikaci…
U modulu UART dále musíte nastavit vhodné parametry, tj. že budete posílat osm datových bitů a žádnou paritu. Konečně se musíte rozhodnout, zda UART budete používat s přerušením nebo bez. Snad všechny novější čipy mají vyrovnávací paměť (alespoň jednoho bajtu) a mohou zavolat přerušení, pokud je místo k poslání dalšího bajtu nebo naopak, že byl úspěšně nový bajt přijat. Stejně tak Vás informují, zda nedošlo k nějaké chybě (např. Frame Error, kdy jste očekávali stop bit a místo toho byla na vstupu 0).
Na závěr bychom měli zmínit ještě jeden „detail”, a to, že nožičky jednočipu __nemůžete__ přímo spojit s Vaším počítačem. Komunikace probíhá na dost odlišných napětích, a tak byste s největší pravděpodobností čip zničili. Na vstupech a výstupech čipů je většinou TTL napětí, takže pokud chcete propojit dva čipy a komunikovat s nimi přímo, tak je vše v pořádku. Pro napojení k PC ale potřebujete konvertor napětí, což se nejčastěji řeší pomocí čipu MAX232 a několika kondenzátorů (jsou levnější klony, ale i dražší, které již mají kondenzátory v sobě a potřebujete pouze převodníkový čip).

Seriová komunikace pod Windows

Občas se nás ptáte, jak ovládat jednočip ze strany počítače, tak popišme i druhého partnera v komunikaci. Pokud používáte systém Windows případně WinCE pro malé přenosné PDA, tak by Vás mohly zajímat následující funkce:
Pro otevření komunikačního kanálu je třeba v C/C++ zavolat:
m_hComm = CreateFile( comName,
                       GENERIC_READ | GENERIC_WRITE, 
                       0, 
                       0, 
                       OPEN_EXISTING,
                       0,
                       0);
kde comName je např. "COM1". Po skončení je třeba zase handle hComm uvolnit pomocí
CloseHandle(m_hComm);
Pravděpodobně si nevystačíte s defaultním nastavením rychlosti, a tak je třeba použít funkce
DCB dcb;

  FillMemory(&dcb, sizeof(dcb), 0);
  dcb.DCBlength = sizeof(dcb);
  //38400,n,8,1
  GetCommState (m_hComm, &dcb);

  // Change the DCB structure settings.
  dcb.BaudRate = 38400;             // Current baud 
  dcb.ByteSize = 8;                 // Number of bits/bytes, 4-8 
  dcb.Parity = NOPARITY;            // 0-4=no,odd,even,mark,space 
  dcb.StopBits = ONESTOPBIT;        // 0,1,2 = 1, 1.5, 2 

  // set new state
  //
  if (!SetCommState(m_hComm, &dcb))
    PRINT("SetCommState Error\n");
Konečně je vhodné i nastavovat timeouty pro čtení, což lze pomocí funcí
COMMTIMEOUTS m_timeouts;
   GetCommTimeouts(m_hComm, &m_timeouts)
a
SetCommTimeouts(m_hComm, &m_timeouts)
Toto by Vám snad mohlo stačit — další informace pak už byste měli dohledat v helpu. Ještě poznámka, že pro úplně první testy je lepší používat již funkční program, jako například Hyperterminál, a teprve v druhém kroku se pokouše rozchodit komunikaci sami.

Seriová komunikace pod Linuxem

Pod Linuxem místo „COM1” hledejte /dev/ttyS0. Port otevřete pomocí
m_handle = open(pathName, O_RDWR | O_NOCTTY );
a uzavřete pomocí
close(m_handle);
Parametry komunikace, včetně timeoutů, se nastavují pomocí funkcí
tcgetattr(m_handle,&m_oldtio); /* save current serial port settings */

    // now clean the modem line and activate the settings for the port
    tcflush(m_handle, TCIFLUSH);
    tcsetattr(m_handle, TCSANOW, &m_newtio);
Samotné nastavení je „trošku” strašidelné, ale mám pocit, že jsme to před mnoha lety opsali (stejně jako pro Windows) z nějakého příkladu na webu…
bzero(&m_newtio, sizeof(m_newtio));

    // speed,
    // CLOCAL = ignore modem control lines,
    // CREAD = enable receiver
    m_newtio.c_cflag = 38400 | CS8 | CLOCAL | CREAD;

    m_newtio.c_iflag = IGNPAR;
    m_newtio.c_oflag = 0;

    /*
      initialize all control characters
      default values can be found in /usr/include/termios.h, and are given
      in the comments, but we don't need them here
    */
    m_newtio.c_cc[VINTR]    = 0;     /* Ctrl-c */
    m_newtio.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
    m_newtio.c_cc[VERASE]   = 0;     /* del */
    m_newtio.c_cc[VKILL]    = 0;     /* @ */
    m_newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
    m_newtio.c_cc[VTIME]    = m_timeOutMs/100;     /* inter-character timer (in 0.1s)*/
    m_newtio.c_cc[VMIN]     = 0;     /* non-blocking read */
    m_newtio.c_cc[VSWTC]    = 0;     /* '\0' */
    m_newtio.c_cc[VSTART]   = 0;     /* Ctrl-q */
    m_newtio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
    m_newtio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
    m_newtio.c_cc[VEOL]     = 0;     /* '\0' */
    m_newtio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
    m_newtio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
    m_newtio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
    m_newtio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
    m_newtio.c_cc[VEOL2]    = 0;     /* '\0' */

I2C bus

Zatím co RS-232C je stále jeden z nejpoužívanějších prostředků komunikace mezi PC a jednočipem, pro komunikaci mezi čipy samotnými se spíše používá I2C. Jedná se obousměrnou komunikaci pomocí dvou drátů, kterou pro svá zařízení navrhl Philips. Nechal si ji patentovat (patent číslo 9398 393 40011), a tak z důvodu patentových poplatků někteří výrobci nehovoří o I2C (Inter IC), nýbrž např. o TWI (Two Wire Serial Interface - dvoudrátové seriové rozhraní).
Proč I2C vzniklo a k čemu je dobré? Philips řešil problémy rozvodů řídicích kabelů pro jednotlivé moduly v zařízeních jako je televize nebo video a ukázalo se, že mnohem ekonomičtější je propojit všechny moduly pomocí jedné sběrnice a individualně s nimi komunikovat. Vy byste na podobný problém mohli narazit, pokud váš robot už má alespoň 10 senzorů…
Asi základní rozdíl od RS-232 je zavedení adresace už do samotného protokolu a proměnná rychlost komunikace. U RS-232 obě zařízení mohla mluvit současně — to není u I2C možné, a tak vždy pouze jedno zařízení mluví na datové lince (SDA) a rychlost mluvy je určena pulzy na druhé, časové lince (SCL). I2C pak řeší problémy pokud některý z učastníků nestíhá, případně pokud by chtělo mluvit příliš mnoho zařízení současně.
Stejně jako RS-232 má i I2C několik možných variant, z nichž se detailněji budeme věnovat pouze jediné. Jednotlivé varianty se liší způsobem adresace zařízení (7 vs. 11 bitová adresa), rychlostí komunikace (maximální frekvence 100kHz vs. 400kHz) a zda se jedná o komunikaci s jedním či více zařízeními typu master. My se zaměříme na jednodušší sedmibitovou adresaci s jedním zařízením jako master a maximálně 127 zařízeními typu slave.

Zapojení

Jak již bylo řečeno, I2C komunikuje pomocí dvou drátů. Ty se standardně označují jako SDA (data) a SCL (clock). Oba dráty jsou nezávisle napojeny přes dva pull-up odpory (viz diskusi pro správné zapojení tlačítek) k napájení Vcc (typicky 5V).
Jednotlivá zařízení buď ze sběrnice čtou aktuální stav (pokud všichni čtou, tak je na obou drátech 1, kterou zajišťují pull-up odpory) nebo zapisují s tím, že sběrnice funguje jako AND (pokud libovolná strana zapisuje 0, tak všichni ostatní na sběrnici uvidí 0).

Průběh komunikace

Komunikaci zahajuje __vždy__ master. Zahajuje jí pomocí tzv. START condition, kdy se změní SDA z 1 na 0, ale SCL zůstane 1. Od tohoto okamžiku dochází k posílání jednotlivých datových bitů. Všechna ostatní zařízení slave zatím sledují průběh na obou drátech. Platnost bitu zaručuje SCL=1, takže k veškerým změnám SDA musí být SCL=0. Výjimku v tomto tvoří START a STOP condition, kdy se mění SDA a SCL=1. Každý rámec (frame) má 8 datových bitů a jeden potvrzovací bit ACK.
První bajt vyslaný masterem je adresa (7 bitů) + R/__W__ bit, zda chce chce přijímat či vysílat. Pro deváty ACK bit master generuje puls na SCL a čte SDA. Pokud slave zařízení má tuto adresu, tak odpoví potvrzovacím bitem (v době mezi osmým a devátým bitem změní výstup SDA na 0).
Podle R/__W__ bitu, buď master pokračuje ve vysílání dalších datových rámců (=0), nebo pouze mění SCL a data přijímá (=1). Adresované zařízení na druhé straně data tedy přijímá nebo odesílá. Pokud slave přijímá, tak potvrzuje každý rámec. Přijímá-li master, tak generuje potvrzení on.
Způsob realizace výstupu 0 a 1 se liší. Zatímco generování výstupu 0 se provede nastavením pinu na výstupní a zapsáním výstupu 0, je generování výstupu 1 realizováno pomocí uvolnění linky, kdy pin přepneme jako vstupní. Požadovaných 5V na SCL resp. SDA nám následně zajistí pull-up odpory. Při generování SDA=1 nebo SCL=1 je třeba ještě ověřit, že ke změně opravdu došlo (čtením pinu, dokud nepřečte 1) a teprve pak pokračovat v komunikaci.
Přestože časování SCL řídí master, má slave šanci komunikaci zpomalit. Pokud nastaví na SCL nulu, tak master nemůže vysílat (data na SDA by byla neplatná) a tedy čeká. Co se týče časování, tak specifikovaný je pouze horní limit, tj. již dříve zmíněných 100kHz resp. 400kHz (pozn. doba pokročila, takže zatím co první specifikace ze začátku 80tých let hovořila o 100kbit/s, v roce 1992 došlo k rozšíření adresace a zvyšení rychlosti na 400kbit/s a v roce 1998 dokonce na 3.4Mbit/s).

Implementace v čipech

Zatímco RS-232 lze i na relativně pomalém/jednoduchém čipu implementovat „ručně” s I2C je situace problematičtější. Zařízení master může být libovolně pomalé, ale slave se musí přizpůsobit rychlému masteru (je pravda, že nemusí potvrzovat všechny přijaté bajty, nebo může držet SCL=0, ale tak se celá komunikace zpomalí). U mnoha čipů je tedy k dispozici I2C slave.
Čipy, jako naše oblíbená ATmega8, už implementují obě strany komunikace. To je obzvlášť pohodlné, protože tak řeší i případné složitější varianty (konkrétně problémy, pokud je více masterů). Další výhodou jsou interní pull-up odpory, které lze pro I2C komunikaci použít — lze tedy přímo propojit dva čipy a není k tomu potřeba nic jiného než dva dráty.
Ještě je třeba zmínit, že existuje mnoho druhů čipů, které není třeba programovat a už přímo řeší nějakou konkrétní úlohu. Je jich prý více jak 1000 typů, s tím že jenom Philips každoročně přidává 40-50 nových. O co se jedná? Nejběžnější jsou EEPROM paměti, hodiny reálného času, pomalé A/D a D/A konvertory… Příkladem je čip PCF8574, který můžete použít, když se Vám nedostává digitálních I/O pinů. Čip má tři nožičky pro určení spodní adresy zařízení, osm I/O pinů, napájení a I2C. Software mastera pak místo zapsání do portu pošle po I2C příkaz k zapsání na rozšiřujícím čipu. Obráceně místo čtení z portu master adresuje po I2C rozšiřující čip pro čtení a získá tak aktuální hodnoty.

Závěr

Ukázali jsme si dva nejpoužívanější způsoby komunikace pro malé mobilní roboty. Měli byste mít nyní jasnější představu jak RS-232 a I2C funguje, případně jestli je ve vašich robotech použitelná. A jak vypadá komunikace u __velkých__ robotů? O tom někdy příště, kdy bude řeč o CAN-BUSu…

Související odkazy

RS-232
základní informace o RS-232 (wikipedia)
I2C
základní informace o I2C (wikipedia)
1-Wire
jednoduchá, jednodrátová sběrnice (wikipedia)
SPI
podobná sběrnice jako I2C (wikipedia)
CAN
sběrnice vyvinutá pro automobilový průmysl (wikipedia)
Serial Data Communications
kniha na wikibooks s podobnou cílovou skupinou jako tento článek