I/O v Javě, rychlé I/O, PWM modulace a tak dále

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čí i hardwarová 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.

 

9 thoughts on “I/O v Javě, rychlé I/O, PWM modulace a tak dále

  1. Potřeboval jsem z RasPi vytvořit generátor dvou obdélníkových signálů, které by vůči sobě byly posunuté o čtvrt periody.

    Obrázek:
    http://www.astromik.org/forum/signaly.gif

    Stvořil jsem to tím nejjednodušším způsobem – posíláním sekvence jedniček a nul na dva GPIO dráty.
    Mezi jednotlivé kroky jsem vkládal pauzy, které byly vypočtené z délky periody.

    Narazil jsem ale na problém o kterém v článku píšeš:
    Signály byly nestabilní – RasPi občas samo od sebe (náhodně) udělalo někde trochu delší pauzu, než jsem potřeboval.

    Chtěl bych se zeptat, jestli by byl nějaký z postupů, uvedených v tomto článku, vhodný pro řešení tohoto mého úkolu?

    • Principiálně by tvůj problém řešilo to PWM přes DMA (pi-blaster). Ale musel by sis pi-blaster opatchovat tak, aby generoval pulzy správně posunuté. Teď je má synchronní (na všech kanálech začínají ve stejný okamžik). Ale zdrojáky jsou k dispozici, nic ti nebrání.

      To, že si RPi samo náhodně vložilo delší pauzu je přesně výsledek multitaskingu, tomu nejde zabránit.

    • Díky.

      V Céčku se zatím vůbec nevyznám, takže v tom „pi-blasteru“ jsem marně hledal podprogram, ve kterém se tvoří v paměti obraz jednoho pulzu. Předpokládám, že to je někde v tom „make_pagemap(void)“, ale nikde tam nevidím, jak se zapisují do paměti jedničky a nuly.
      Spíš mi to připadá, že se tam pracuje s nějakým souborem.

      Vyzkouším ještě ten Python.

    • Tak jsem vyzkoušel ten „Pythoní“ způsob řízení PWM, ale vůbec jsem s tím nebyl spokojený.
      Sice je to stabilnější, než když jsem generoval signály pomocí přímého nastavování jedniček a nul na GPIO vývodech oddělených pauzami, ale pořád je vidět, že to trochu kolísá.
      Když na ty vývody připojím LEDky a nastavím frekvenci třeba 100Hz, tak je znát, že občas náhodně problikávají.
      Největší nevýhoda, kvůli které je to pro můj projekt absolutně nepoužitelné, je ta, že nelze nastavit stabilní fázový posun mezi dvěma signály.
      Postupoval jsem tak, že jsem spustil generátor na prvním GPIO, pak jsem chvíli počkal (pauza 1/4 periody) a pak jsem spustil druhý generátor na druhém GPIO.
      Problém je v tom, že se po čase (po několika sekundách) oba signály sesynchronizují.
      Tady je video:
      http://astromik.org/raspi/pwm-python.avi

      Rozhodl jsem se tedy řešit problém úplně jinak – s použitím externího obvodu pro generování PWM signálu.
      Použil jsem obvod PCA9685. Pomocí I2C komunikace se mu nastaví parametry, které udávají, kdy se má který signál přepnout na „1“ a kdy má padnout do „0“. Pak už je celý proces generování signálů naprosto nezávislý na RasPi.
      Signály jsou proto velice stabilní.

      Toto řešení má i své nevýhody.
      Největší nevýhoda je v tom, že se nedá plynule měnit výstupní frekvence. Při použití vnitřního oscilátoru je výstupní frekvence nastavitelná v 256 nelineárních krocích od 24Hz až do asi 6kHz.
      Já jsem ale potřeboval plynule nastavitelnou frekvenci od 1Hz do 300Hz.
      Naštěstí má obvod vstup pro externí oscilátor, takže by neměl být až takový problém řídit výstupní frekvenci tímto externím oscilátorem (zatím to ale nemám vyzkoušeno).

      Video s ukázkou stability signálů generovaných pomocí PCA9685 je tady:
      http://astromik.org/raspi/pwm-pca9685.avi

      (Ta dvě přehození signálů na videu, to je v pořádku. To jsem za chodu generátoru pomocí RasPi přes I2C změnil fázi z +25% na -25% a pak zpátky).

Napsat komentář: pebrou Zrušit odpověď na komentář

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