Propojujeme Raspberry Pi a Arduino

Tento článek nebude úplně ryze o Raspberry Pi, ale o tom, jak ho propojit s nějakým dalším zařízením. Komunikace bude realizovaná pomocí sériového portu emulovaného přes USB.

Krátce po zakoupení RPi jsem se začal zajímat, jak využít jeho GPIO piny k získávaní dat z různých čidel, senzorů a podobných udělátek. Brzy jsem zjistil, že situace není tak růžová, jak bych si přál. RPi postrádá A/D převodníky, PWM a navíc je drahé na to, abych si mohl dovolit ho nějakou svou neuvážeností spálit. Co se týče experimentování s elektronikou, na to je staré známé Arduino prostě vhodnější. Čínský klon z Ebay stojí pár dolarů (¼ ceny oproti originálu!), free shipping je samozřejmost, jen ta cesta domů mu trvala okolo měsíce.

Abych se přiznal, tak původní motivace nechat obsluhovat senzory Arduino byla ještě jiná, než výše zmíněné nevýhody Rpi. A to sice, že pokud chcete včas zareagovat na událost vyvolanou čidlem, musí synchronní polling provádět sám procesor, což je na multiprocesovém systému velmi nevhodné. Takový program by spotřebovával značnou část procesorového času naprosto zbytečně. Navíc nemůžete zaručit, že ve chvíli výskytu signálu bude mít tento program procesor vůbec přidělen (frekvence přepínání procesů bývá 100 Hz, tento případ je tedy spíše teorií). Mnohem lepší je přenechat obsluhu senzoru Arduinu, které navíc může zaručit reakci v reálném čase, a našemu RPi už jen bude posílat zprávu o výskytu této události. To bude data číst pomocí blokujícího systémového volání, výskyt události jednoduše obslouží a pak se do dalšího výskytu zablokuje. Pojďme tedy na to.

USB sběrnice může s pomocí hubů obsluhovat až 127 zařízení, s tím by si měl vystačit snad každý. Délka kabelu může být standardně 5 metrů, v případě použití optického kabelu i delší. Po připojení Arduina k PC se vytvoří emulovaná sériová linka. Tu lze identifikovat pomocí symbolického odkazu v /dev/serial.

pi@raspberrypi-test ~ $ ls -l /dev/serial/by-id/*
lrwxrwxrwx 1 root root 13 Jan 28 17:15 /dev/serial/by-id/usb-Arduino__www.arduino.cc__Arduino_Mega_2560_7523733353635140C190-if00 -> ../../ttyACM0

Naše sériové rozhraní má soubor umístěný v /dev/ttyACM0. Abychom s ním mohli komunikovat i bez práv roota, je nutné být ve skupině dialout.

pi@raspberrypi-test ~ $ sudo usermod -a -G dialout pi

Nezapomeňte, že změna se projeví, až když se znovu přihlásíte.

Do Arduina nahrajeme jednoduchý kód, který přečte znak ze sériového portu, malá písmena převede na velká a odešle zpět. Komunikace tedy bude probíhat oběma směry.

int character;

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

void loop() {
if(Serial.available()) {
character = Serial.read();

if(character <= 'a' && character >= 'z') {
character += 'A' - 'a';
}

Serial.print(character);
}
}

Popisem kódu se zabývat nebudu, důležité je jen, že budeme používat baudrate 9600 bit/s.

Nyní napíšeme krátký skript, který poběží na RPi. Pochopitelně můžete použít jakákoli váš oblíbený programovací jazyk. Já dávám přednost Pythonu. Pro pohodlnou práci se sériovým portem nejdříve nainstalujeme modul PySerial.

pi@raspberrypi-test ~ $ wget http://pypi.python.org/packages/source/p/pyserial/pyserial-2.6.tar.gz
pi@raspberrypi-test ~ $ tar -zxf pyserial-2.6.tar.gz
pi@raspberrypi-test ~ $ cd pyserial-2.6/
pi@raspberrypi-test ~ $ python setup.py build && sudo python setup.py install

Instalace je opravdu primitivní. Ještě jednodušší je jeho použití. Vlastně jen vytvoříme vhodný objekt a potom už nás mohou zajímat jen jeho metody read() a write().

Pokud chceme současně číst a zapisovat data, jsou nasnadě tři možná řešení.

  1. Přesně definovat pořadí komunikace – synchronní výměna dat. Jednoduše řečeno program dopředu ví, kdy se budou data odesílat a kdy přijímat. Jakákoli nekonzistentnost je špatně. Například při čtení dat se program zastaví a pokračuje až když data opravdu příjme. Arduino na druhou stranu ale může odeslat data, až když mu něco přiteče po sériové lince. Tomuto stavu se říká deadlock a je silně nežádoucí.
  2. Pro čtení a zápis vytvořit samostatný proces/vlákno. Jedná se o velmi běžné řešení, vzniká ale problém jejich vzájemné synchronizace.
  3. Využití techniky multiplexingu, neboli asynchronní programování. Jinak řečeno, program zpracovává ta data, která má k dispozici. Pokud má možnost něco přečíst, čte. Pokud potřebuje něco zapisovat, zapisuje. V jeden okamžik se ale vykonává pouze jedna činnost. Výhodou je, že si vystačíme pouze s jedním vláknem, odpadá tedy nutnost jakékoli synchronizace. Naopak nevýhodou je asi největší náročnost na kód. Velmi lehce se může stát, že jedním zapomenutým blokujícím volám zablokujeme úplně všechno. My využijeme tohoto principu, avšak v té nejjednodušší podobě.

Základem všeho je volání select. Jedná se o jediné povolené blokující volání, vše ostatní by mělo být neblokující. Pracuje velmi jednoduše, jako parametry se mu předají tři seznamy souborových descriptorů (ukazatelů na otevřené soubory). První seznam obsahuje ty descriptory, u kterých nás zajímá, jestli z nich můžeme něco přečíst, druhý seznam obsahuje descriptory kam bychom chtěli něco zapsat a třetí seznam descriptory, u kterých chceme ošetřovat výskyt nějaké chyby. Volání vrátí také tři seznamy, ovšem jen s těmi descriptory, které jsou připraveny na požadované operace. Jedná se tedy vlastně o takový filtr. Například pokud chceme číst data z deseti různých descriptorů a select nám vrátí jen tři, znamená to že pouze tři descriptory skutečně nějaká nová data obsahují. Ty potom obsloužíme a vše se opakuje. Pokud nic není připraveno na žádnou operaci, select zastaví program. Odblokuje ho až ve chvíli, kdy alespoň jeden descriptor bude obsloužitelný.

Celý zdrojový kód skriptu vypadá takto:

 

#!/usr/bin/env python

import serial

import sys

import select

import os

import time

 

def main():

ser = serial.Serial(‚/dev/ttyACM0‘, 9600)

 

time.sleep(1) # cekani nez se seriova linka inicializuje

 

ser.flushOutput() # vyprazdneni bufferu

ser.flushInput()

ser.nonblocking() # prepnuti seriove linky do neblokujiciho modu

 

while True:

r, w, x = select.select([ser.fileno(), sys.stdin], [], [])

 

for read in r:

if read is sys.stdin:

ser.write(os.read(sys.stdin.fileno(), 1))

if read == ser.fileno():

sys.stdout.write(os.read(ser.fileno(), 1))

 

if __name__ == ‚__main__‘:

try:

main()

except KeyboardInterrupt:

exit(0)

Pozn: prosím dodržte správné odsazování, Python je na něj citlivý.

Proces čeká na vstup z stdin a sériového portu. Pokud nějaká z nich má připravena data ke zpracování, přečte jeden byte, provede příslušnou akci a znovu zkontroluje jestli jsou k dispozici nějaká další data.

Skript není úplně ideální, správný asynchronní program by se měl chovat tak, že z jednoho descriptoru zpracuje všechna dostupná data naráz. Ne tedy, že po každém přečteném byte bude volat select. Bohužel, nepodařilo se mi vstup z stdin přepnout do neblokujícího módu, program tedy blokovaně čekal na další data nebo EOF signál (konec souboru, v případě stdin stisk ctrl + d). Při nespecifikované maximální délce čtení jeden byte, by deadlock vznikl velmi lehce. To samé platí o řádcích 22 a 24. Pokud by požadavek pro zápis nebyl vyřízen okamžitě, program by byl zbytečně blokován, místo toho aby dělal něco užitečného. Berte to jako příklad, jak je snadné jedním zapomenutým blokujícím voláním zbořit celý program.

Tímto způsobem je tedy efektivně možné RPi ulehčit od zátěže. Samozřejmě například teplotní čidlo není problém připojit na GPIO. Kontrola teploty, řekněme jednou za pět sekund, v pohodě stačí a procesor téměř nic nepozná. Pokud ale chcete obsluhovat cokoli charakteru tlačítka, jedná se o velmi vhodné řešení. V případě digitalizace analogového vstupu nebo PWM snad i jedné možné řešení za rozumné peníze. Navíc vyhrazený čip v Arduinu dokáže zaručit reakci v reálném čase.

18 thoughts on “Propojujeme Raspberry Pi a Arduino

  1. Pokud bych neuvažoval napětí, které používá RPi na GPIO, tak teoreticky ano. Nezkoušel jsem to ale bylo by nějak potřeba vyřešit problém s buffery. Tedy RPi musí vědět, kdy že mu nějaká data přišla. To by v tom nejjednodušším případě znamenalo, že by procesor musel aktivně ten pin kontrolovat a hlídat příchozí start bit. Tedy opět ten samý problém, jak na začátku :) V případě USB, aktivní polling provádí sama sběrnice a nezatěžuje se tak procesor. Použití USB tu hraje tedy celkem klíčovou roli. Abych odpověděl na otázku. Nejsem si jist jestli to bez aktivní účasti hlavního procesoru jednoduše nejde, nebo jestli by šlo využít HW přerušení. V případě toho přerušení (pokud ho GPIO podporuje), znamenalo by to dost programování na úrovni jádra OS.

  2. A je to tady, RPI vs mikrokontrolery,tesim se na reakce odpurcu mikrokontroleru :-) Nicmene kombinace mikrokontroleru (obsluha seznozru, preruseni atd) a RPI (komunikace, zaznamy, web server) je vyborna kombinace a tak by to melo i byt. Mikrokontroler je proste vybaven na praci se senzory (nejen sbernicove) mnohem lepe a navic se jedna o Realtime a to vse v minimalni spotrebe bez zahrivani atd. Jinak doplnim, ze pokud to nekdo s mikrokontrolery mysli vazne tak at si sice poridi napr. Arduino, ale programuje rovnou v C, bude se pak hodit na jine typy atd. Takze s autorem clanku souhlasim a tahle cesta je ta spravna :-D

  3. Hezký článek, ale vychází ze špatné premisy (nutnost pollingu procesorem) – protože GPIO v RPi by mělo mít podporu pro edge detection. Tj. pro většinu běžných použití (tlačítka atd) není třeba pollovat GPIO pin, ale stačí se zavěsit na odpovídající událost.

    Více např:
    http://raspberrypi.stackexchange.com/questions/3440/how-do-i-wait-for-interrupts-in-different-languages
    http://www.kernel.org/doc/Documentation/gpio.txt
    http://quick2wire.com/2012/11/using-interrupt-driven-gpio-in-raspian/

    V každém případě připojení Arduina jako „ovladače pro HW“ smysl má – např. proto, že Arduino má 5V GPIO a analogové vstupy.
    Ale není třeba to odůvodňovat nutností pollovat GPIO procesorem.

    Mimochodem dtto platí i pro výstup – PWM výstup se dá dělat bez zatížení procesoru pomocí DMA.

  4. Jinak prevodnik USB UART (ttl rs232 vhodne pro prime spojeni s mikrokontrolerem) se da poridit na ebay od 2USD. Takze tento prevodnik se zasune do USB HUBu, z nej se napaji mikrokontroler + TTL UART vstup/vystup . Takze prevodnik, ktery samotny mikrokontroler napaji a zaroven umoznuje s nim komunikovat prijde na 50Kc vcetne postovneho ;-) Mam odzkousenou podporu pro Linux, vse OK.

    • Nic méně me tento prevodnik jede na prijem velmi dobře. Horsi je to odesilanim. Komunikuji z jednocipama kde jako prevdoni na 485 pouzivam SN75176B. Data odeslu ale nic se me zpet nevrati. Pritom pokud nahodim mastera tak data se vraci. Vidim problém v tom, ze nedokazu ovládat signal DE, takze nemuzu prevodniku rict ted odesílej a hned se prepni na prijem. Mam pocit ze proste linka je drzena a tim se komunikace zastavi.

  5. Zadne zarizeni nema RS232 sbernici primo, kvuli napetovym urovnim, takze jak mikrokontroler, tak RPI potrebuje pro komunikaci s RS232 zarizenim prevodnik TTLRS232 (jedna se jen o prevedeni napetove urovne). Ale dve zarizeni mohou komunikovat primo bez prevodniku na urovni TTL, akorat pozor musi byt piny stejne napetove urovne (5V nebo 3,3V – ma RPI). Jinak RS232 ma napetove urovne +-10V (minimalne +- 5, ale muze byt +-15, nebo dokonce +-25V !!!

  6. Toto nechápu, Raspberry PI ma na sobě přímo UART, převodník USB-RS232 není vůbec potřeba, stačí jen omezit napětí logické 1 např. odporem a zenerkou :)

  7. Narazil jsem u RPi na zajímavý zádrhel.
    Propojil jsem RPi s mikrokontrolérem přes USB-RS232 FT232RL. Když jsem použil komunikaci znakově orientovanou bylo vše ok. Jakmile jsem přešel na binárně orientovanou komunikaci nastal problém. Ve finále jsem na RPi napsal kratičký programek v PHP a pak i v C, který posílá jeden byte přes RS232 do mikrokontroléru ten tento byte vrácí zpět a RPi přijme a porovná jestli je shodný. Hodnota posílaného bytu se inkrementuje od 0 do 255.
    Zjistil jsem že RPi z nějakého důvodu NEZPRACUJE hodnoty 17d a 19d. Při vyčtení hodnoty z bufferu byla 0.
    Pokud převodník s mikrokontrolérem připojím k PC, tak v PHP i v C vše funguje jak má, vždy se vrací stejná hodnota.

  8. Dobrý den,
    Nemá náhodou někdo zkušenost nahrávat program z Raspberry PI do Arduina MEGA? Na Raspberry přistupuji přes VNC (vzdálená plocha), mám v něm nainstalovaný Arduino IDE. Pokud chci nahrát program, tak se zkompiluje, ale nahrátí provázejí errory (něco s časem). Předpokládám, že Raspberry je asi pomalý…
    Děkuji

  9. Existuje jednodušší řešení. Koupit tohle http://www.dx.com/p/iic-i2c-level-conversion-module-compatible-with-3-5v-system-sensor-blue-197198#.VKv0_NJhnjs
    na jednu stranu připojit Arduino, na druhou RPi. Na stranu arduina ty i2c doplňky co potřebují 5V, na stranu Rpi pak ty co pracují pod 3,3V. Na netu spousta návodů na to jak i2c komunikuje arduino a RPi. naráz běží všechny i2c prostředky a je jedno kam jsou zapojeny a to tak, že z GY-80 jsem na arduinu četl mangnetometr a z RPi pak četl gyro a k tomu ještě z arduina posílal informace do RPI.

Napsat komentář

Vaše emailová adresa nebude zveřejněna.