Prvotně publikováno na raspi.cz 29.3.2013.
V nedávném článku „Propojujeme Raspberry Pi a Arduino“ si Buben postěžoval, že
- RPi postrádá PWM
- Nelze rozumně spolehlivě reagovat na změny na vstupních pinech, protože synchronní polling by bral moc času procesoru a byl by z důvodu multiprocesingu v linuxu nespolehlivý
… a že tedy je lepší předat obsluhu I/O Arduinu.
S výsledkem této úvahy souhlasím. Složitější I/O nemá obtěžovat CPU, mají ho dělat kanálové procesory – tak nás IBM učí už více než 50 let. A volba Arduina není špatná. Nicméně předpoklady, na základě kterých Buben toto tvrzení postavil, jsou nepravdivé.
Pro spoustu aplikací stačí počet I/O portů, které má RPi – a pro spoustu aplikací stačí ihardwarová podpora, kterou má RPi pro řešení obou výše uvedených problémů.
Hardwarové PWM výstupy
Přímo na expanzním portu snadno najdete pin GPIO1 (18), což je zároveň výstup hardwarového PWM, které má RPi v sobě. Ale uznávám, že jedno PWM je nanic. Každý smysluplný kus železa potřebuje alespoň tři serva = tři PWM kanály. Co s tím?
Samozřejmě je blbost aplikačně simulovat PWM tím, že budeme na GPIO pin sypat jedničky a nuly. To by skutečně stálo všechen procesorový výkon a navíc by to nebylo spolehlivé – přepínání tasků v linuxu by způsobilo nahodilé a nepříjemné výpadky v modulaci.
Ale co kdyby ty jedničky a nuly na výstup za nás sypal někdo jiný? Někdo, kdo to umí bez zátěže procesoru?
Ano, to je správná cesta. V paměti připravíme „obraz“ jednoho PWM pulzu (tj. třeba 500 nul a pak 500 jedniček = máme pulz s plněním 50%) a pak stačí říct řadiči DMA, ať tento kus paměti fixním tempem neustále dokola posílá na daný pin. A ejhle, funguje to.
Hotovou implementaci pro základní PWM najdete zde: https://github.com/sarfata/pi-blaster/
Detailnější popis je na stránce autora.
Aplikaci stačí nainstalovat a spustit (nebo nechat spouštět automaticky při bootu). Ovládání je pak jednoduché: Příkazem
echo "1=0.3" > /dev/pi-blaster
nastavíme pin 1 na PWM plnění 30%,
echo "1=1" > /dev/pi-blaster
dá plnění 100% atd. Zatížení procesoru je nulové a signál je hezky pravidelný, bez výpadků. Takto může být obsluhováno více GPIO pinů, defaultně jich pi-blaster řídí 8.
Pokud nechcete pomocí PWM řídit úroveň jasu LEDky, ale chcete ovládat serva, nepotřebujete „standardní“ PWM, ale trochu jiné. U serv je to tak, že frekvence pulzů by měla být 100 Hz; impulz o délce 1 msec je 0% výkonu, impulz o délce 2 msec je 100% výkonu. Kratší pulzy jsou chyba, delší taky. Chce se vám s tím ladit? Jistě ne. Takže potřebujeme hotové řešení.
Najdeme ho tady: https://github.com/richardghirst/PiBits/tree/master/ServoBlaster
Výše popsaný projekt pi-blaster vznikl jako rozšíření myšlenky ServoBlasteru. Pi-blaster má hezčí implementaci (ovládání přes soubor).
Hardwarová detekce změny stavu GPIO pinu – jak nepollovat I/O procesorem
Procesor, na kterém je RPi postaveno, samozřejmě umí na změnu stavu vstupního GPIO pinu navázat přerušení.
Tedy zbývá jen zjistit, zda je tato služba podporována v linuxu a dá se používat?
Ano, je tam a funguje.
Tedy ve své aplikaci můžete snadno říct „až se změní stav GPIO0, zavolej mojí funkci X()“.
Test jsem provedl v Javě, což je pro real-time programování výrazně nevhodný jazyk. Nicméně Javu mám jako svůj denní nástroj a přemýšlím v ní; navíc jsem už líný používat pointery a podobné věci, ze kterých se v céčku dá postavit operační systém, a rád se od nich nechám odstínit.
Pro integraci Javy s GPIO na RPi existuje hezká knihovna pi4j. Více o ní napíšu za chvíli, ale ten důležitý výsledek testu je: interrupt-driven obsluha GPIO na RPi funguje. Za klidového stavu (tj. když se nic neděje, na GPIO nejsou žádné změny) to nežere žádný strojový čas. A v té ošklivé pomalé Javě to zvládá obsloužit až zhruba 2000 změn stavu za sekundu. A když přijde osamocený milisekundový impulz, neztratí se, Java ho dostane.
Podpora pro RPi GPIO v Javě – pi4j
Knihovna pi4j je přesně to, co potřebujete, pokud si chcete hrát s I/O na RPi ve vyšším jazyce.
Co umí?
- Pro začátek samozřejmě obsluhu jednotlivých GPIO pinů. Nastavení směru, nastavení hodnoty. A eventy o změnách stavu.
- Taky je tam podpora pro I2C. Snadno můžete mluvit s I2C zařízeními.
- Nezapomnělo se ani na sériové porty (UART).
- SPI? No jasně.
Už tohle vše by bylo dobrým důvodem knihovnu používat, ale zde funkce teprve začínají. Autoři si totiž uvědomili, že když už mají dobře navržené abstraktní rozhraní pro GPIO, tak by s ním šlo obsluhovat víc věcí.
- Máte na I2C připojený I/O expandér MCP23008 / MCP23009, o kterém jsem dříve psal? Tak si prostě místo standardního objektu „pin“ vyžádáte tento objekt od providera MCP23008GpioProvider. Toť vše. Veškerá další obsluha tohoto „drátu“ je stejná – je jedno, jestli pracujete s pinem přímo na expanzním portu, nebo s pinem za expandérem MCP23009. Wow!
- Totéž samozřejmě platí i pro I2C I/O expandéry MCP23017 a PCF8574. A taky pro expandér MCP23S017 připojený přes SPI.
- Koupili jste si expanzní desku PiFace? Kód je připraven.
- Přímá podpora pro řízení krokových motorků. Stačí namapovat řídící vodiče a pak už jen můžete říkat „100 kroků plnou rychlostí doprava“.
- Komunikace se senzorem Wii Motion Plus (modul s gyroskopem rozšiřující standardní ovladač pro Nintendo Wii, připojuje se přes I2C).
- Obsluha LCD displejů.
Knihovna je hezky navržená a pro jednotlivé funkční bloky jsou tam hotové samply.
Jak rychle vlastně Java na RPi s I/O pracuje?
Udělal jsem takový jednoduchý test. Na jeden GPIO pin (výstupní) jsem připojil LED diodu, a zároveň jsem ho spojil na druhý pin – vstupní.
O změnách na vstupu jsem si nechal posílat eventy.
A pak jsem v jednoduché smyčce posílal na výstup jedničky a nuly.
Co jsem zjistil?
Maximální frekvence na výstupním drátu dosažitelná z mé aplikace byla zhruba 2 kHz. Nicméně kdybych si dal práci s nastavení JVM, mělo by to být výrazně lepší.
Pro délku jedničky/nuly 1 msec se už začaly některé eventy o změnách ztrácet. V průměru jsem dostal 986 eventů na 1000 změn. Vytížení CPU bylo tvrdých 100%. První eventy přišly až po cca 100 msec od zahájení vysílání – ale to je dáno přepínáním threadů v Javě; smyčka posílající 1/0 prostě nepustila procesor. Ale eventy se frontují, takže se povětšinou neztrácejí. (U osamoceného milisekundového pulzu se eventa neztratila nikdy; ztrácení je skutečně funkcí objemu změn.)
Pro impulzy o délce 5 msec už vytížení procesoru spadlo na 50% a eventů dorazilo 998 z tisíce.
10 msec pulzy už vytěžovaly procesor jen na 30% a eventy se neztratily žádné.
Pro delší pulzy vytížení procesoru klesalo k neměřitelnosti.
A samozřejmě: když jsem takhle posílal na výstup třeba 100 Hz signál, na svitu LED byly jasně vidět nepravidelnosti . Linux prostě není real-time systém a občas vám procesor sebere na tak dlouhou dobu, že je to vidět jako zřetelné mrknutí LEDky.