FK~

Moje odkazy

Ostatní odkazy

EFF: svoboda blogování
Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF

GPIO v Raspberry Pi jako soubory

vydáno: 12. 11. 2016 20:16, aktualizováno: 13. 11. 2016 23:26

Univerzální vstupně/výstupní piny (GPIO) můžeme ovládat pomocí zvláštních knihoven v různých programovacích jazycích (Java, Ruby, Python, C atd.). Tyto knihovny ale nejsou nutné – ukážeme si jednodušší postup.

Raspberry Pi – GPIO piny

V souladu s unixovou (resp. Plan 9) filosofií „všechno je soubor“ budeme s GPIO pracovat jako se soubory – čtení hodnoty pinu (např. dveřního čidla nebo tlačítka) znamená tedy čtení souboru. A nastavení hodnoty pinu (např. rozsvícení/zhasnutí LEDky) je zase obyčejný zápis do souboru.

Glenda + GNU + Tux

Všechno je soubor – i GPIO

Předně je potřeba říct, že se nejedná o soubory fyzicky uložené na pevném disku – při práci s GPIO tak nedochází ke čtením a zápisům na váš HDD/SSD disk. Souborový systém totiž může zpřístupňovat virtuální soubory a adresáře – pro uživatele je to transparentní – pracuje s nimi stejně, bez ohledu na to, jestli jsou data na pevném disku, vypočítávají se za běhu (např. spojením dvou jiných souborových systémů nebo třeba de/šifrováním) nebo se získávají ze sítě (NFS, SSHFS, WebDAV atd.) nebo interakcí se speciálním HW jako je GPIO.

Soubory, které nás zajímají, se nacházejí v adresáři /sys/class/gpio/ a poskytuje nám je syntetický souborový systém sysfs:

$ df /sys/class/gpio/
Filesystem      Size  Used Avail Use% Mounted on
sysfs              0     0     0    - /sys

Než začneme se čtením a zápisem, připomeňme si, co GPIO znamená.

  • GP = general purpose – jde tedy o obecně použitelné rozhraní bez předem přiřazeného významu jednotlivých pinů – tím se liší např. od sériových portů (/dev/ttyS*) a částečně se podobá paralelnímu portu (LPT), který se sice používal hlavně pro připojení tiskáren, ale v zásadě jde o univerzální rozhraní – máme tam osm datových pinů, na kterých si můžeme nastavovat 1 a 0 podle svého + vstupní piny a dokonce i vstupně/výstupní.
  • IO = input/output – tentýž pin jde použít jak pro vstup, tak pro výstup (ale ne oboje najednou)

Před použitím musíme tedy GPIO rozhraní nastavit a říct systému, v jakém směru chceme který pin používat. A to uděláme – jak jinak než – zápisem do souborů:

PIN=8

# otevřeme pin pro čtení:
echo $PIN > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio$PIN/direction # in=čtení out=zápis

# čteme hodnotu:
cat /sys/class/gpio/gpio$PIN/value

# ukončíme práci:
echo $PIN > /sys/class/gpio/unexport

Hodnoty čteme a zapisujeme v ASCII jako 0 a 1. Jde tedy o jakýsi textový „protokol“ se kterým můžeme snadno pracovat z příkazové řádky a nemusíme kódovat nějaké bajty.

GPIO.sh

Pro pohodlnější práci jsem si vytvořil sadu shellových funkcí GPIO.sh. Není pak potřeba pořád psát cestu /sys/class/gpio/…, export pinu a nastavení směru se udělá jedním příkazem a taky to kontroluje zadané hodnoty a zobrazuje nápovědu.

Ukázka použití:

PIN=8

# importujeme funkce:
. ./gpio.sh

# otevřeme pin pro čtení:
gpio_open $PIN in

# načteme hodnotu do proměnné:
hodnota=`gpio_read $PIN`

# zavřeme pin:
gpio_close $PIN

# otevřeme pin pro zápis:
gpio_open $PIN out

# nastavíme na něm logickou jedničku:
gpio_write $PIN 1
# tzn. když k němu bude připojená LEDka, tak se rozsvítí

# zavřeme pin:
gpio_close $PIN

GPIO.sh – uklázka použití a nápovědy

Reagujeme na události – inotify

Jestliže události vznikají mimo náš systém (např. stisk tlačítka připojeného ke GPIO pinu), bylo by dost hloupé muset se periodicky ve smyčce dotazovat na hodnotu a porovnávat ji s předchozí, zda se změnila. Místo toho chceme dostávat upozornění na události, které se staly (ve chvíli, kdy se stanou, ne když se na ně zeptáme).

Přesně k tomu slouží v případě změn souborů subsystém Linuxu zvaný inotify. A protože s GPIO pracujeme jako se soubory, je zcela logické použít inotify pro odběr notifikací o změnách hodnot jednotlivých pinů.

# přes inotify budeme sledovat změny souboru
# a hodnotu vypíšeme na standardní výstup (vypisuje 0 nebo 1)
echo both > /sys/class/gpio/gpio$PIN/edge;
while inotifywait -qq /sys/class/gpio/gpio$PIN/value; do
	gpio_read $PIN;
done

Příklady použití

Použití si ukážeme na příkladu dveřního čidla připojeného k pinu 17. Na Raspberry Pi si vytvoříme skript /root/skripty/dveře.sh:

#!/bin/bash

# importujeme si gpio_* funkce:
DIR="`dirname $0`";
source "$DIR/gpio.sh";

# otevřeme si pin 17 pro čtení:
PIN=17;
gpio_open $PIN in;

# připravíme si funkci pro zavření pinu:
__exit() {
	gpio_close $PIN;
	exit 0;
}

# zavoláme ji při ukončení procesu resp. stisku Ctrl+C:
trap '__exit' INT

# přes inotify budeme sledovat změny souboru
# a hodnotu vypíšeme na standardní výstup (vypisuje 0 nebo 1):
echo both > /sys/class/gpio/gpio$PIN/edge;
while inotifywait -qq /sys/class/gpio/gpio$PIN/value; do
	gpio_read $PIN;
done

# Sem to běžně nedojde a tento kód se už nevykoná – výše je cyklus
# a při jeho přerušení pomocí Ctr+C se pokračuje funkcí __exit(),
# kde voláme exit 0.
gpio_close $PIN;

# Při spouštění přes ssh je tento skript nutné volat s volbou -t:
# ssh -t root@rpi.example.com 'stty -opost; skripty/dveře.sh'
# protože jinak by proces neměl přiřazen řídící terminál
# a nedošlo by k ukončení potomků (inotifywait) - ti by zůstávali
# běžet i po ukončení spojení a rodičovského procesu.

Po spuštění tento skript sleduje události na GPIO a vypisuje na standardní výstup hodnoty 0 a 1 (na každý řádek jednu hodnotu). Takový proud událostí můžeme snadno využít v unixových rourách a propojit s jinými příkazy. A protože GPIO máme na Raspberry Pi, ale na události chceme reagovat na našem PC, jednoduše si tento proud nasměrujeme k sobě pomocí SSH.

Desktopová upozornění (notifikace)

Při otevření nebo zavření dveří si necháme zobrazit upozornění na ploše:

ssh -t root@rpi.example.com 'stty -opost; skripty/dveře.sh' \
	| grep --line-buffered '^[01]$' \
	| sed --unbuffered -e 's/^0$/zavřeno/g' -e 's/^1$/otevřeno/g' \
	| xargs -l1 -exec bash -c 'echo_datum "$@"' "{}" \
	| xargs -l1 -d\\n notify-send

Grepem si filtrujeme pouze hodnoty 0 a 1 (kdyby tam bylo např. nějaké varování, tak ho tímto přeskočíme). A sedem si překládáme nuly a jedničky na něco srozumitelného.

Volby --line-buffered--unbuffered, které běžně nepoužíváme, jsou zde velmi důležité – příkazy grepsed by jinak čekaly na zaplnění zásobníku a vypsaly by až více řádků najednou (což obvykle nevadí a je to naopak výkonnostní optimalizace, ale tady potřebujeme reagovat na události hned).

Rovněž je důležitá volba -t u SSH, viz komentář v kódu výše. S tím souvisí i stty -opost, které mj. zabrání tomu, aby se nám na výstup přidával znak CR před konce řádků LF, což by nám překáželo v dalším zpracování. Pokud máte jednodušší řešení, jak zajistit ukončení všech (pod)procesů při ukončení spojení a zároveň se zbavit CR, budu rád za komentáře.

V dalším kroku (první xargs v rouře) si rozšíříme informaci o datum a čas. Napíšeme si na to jednoduchou funkci, která vypisuje svoje parametry (podobně jako echo) a předřazuje před ně datum a čas:

echo_datum() { echo "`date --rfc-3339=seconds`: ${@}"; }
export -f echo_datum # funkci exportujeme, abychom ji „viděli“ v xargs

To se může hodit mj. pro logování – např. přes tee zkopírovat proud dat i do souboru. Ukázka použití:

$ echo_datum aaa bbb ccc
2016-11-12 13:37:08+01:00: aaa bbb ccc

Příkaz xargs čte standardní vstup a volá zadaný příkaz (nofity-send) s parametry, které načetl ze vstupu. Volba -l1 slouží k tomu, aby xargs předal příkazu nofity-send vždy jen jeden parametr. A volba -d\\n k tomu, aby se za oddělovač považoval znak konce řádku \n – jinak by se nám totiž data rozpadla i podle mezer a vzniklo by víc notifikací, než chceme.

A zde máme konečný výsledek – upozornění na otevření/zavření dveří:

desktopové notifikace – upozornění na otevření/zavření dveří

BTW: na unixových rourách se mi líbí, že člověk může odmazávat od konce jednotlivé části a snadno zjišťovat, co se mezi jednotlivými fázemi předává.

Ovládání hudby dveřním čidlem

Začátek necháme stejný, jen místo zobrazení notifikace pošleme příkaz hudebnímu přehrávači, aby začal hrát nebo naopak přestal (toggle):

ssh -t root@rpi.example.com 'stty -opost; skripty/dveře.sh' \
	| grep --line-buffered '^[1]$' \
	| sed --unbuffered -e 's/^1$/toggle/g' \
	| xargs -l1 -d\\n xmms2

Pomocí grepu jsme si vyfiltrovali pouze událost otevření dveří a navázali na ni ovládání přehrávače XMMS2. Pokud odejdeme z místnosti (tzn. otevřeme a zavřeme dveře), přehrávání se pozastaví a po našem příchodu (resp. při otevření dveří) se zase spustí tam, kde předtím skončilo.

Na XMMS2 se mi líbí jeho architektura – není to monolitický program, ale je rozdělený na démona (proces běžící na pozadí, pod daným uživatelem, není to systémový démon) a uživatelské rozhraní (UI). Démon přehrává hudbu, má v paměti seznam skladeb a další věci. UI mu pouze posílá příkazy (např. další, předchozí, přehraj, přidej skladbu) a existuje více jeho implementací – pro GTK, Qt, pro příkazovou řádku… Navíc UI není pro přehrávání vůbec nutné – tento program můžete klidně ukončit a hudba bude hrát dál (protože démon na pozadí pořád běží) a následně se můžete připojit jiným UI nebo být klidně připojen z několika UI současně:

XMMS2 – hudební přehrávač

Všechny instance UI jsou navzájem synchronizované přes démona a když např. přehazujete pořadí skladeb, projeví se to hned ve všech oknech. Pro spojení démona a UI se standardně používá unixový soket, ale můžete použít i TCP a ovládat program po síti.

I jiné přehrávače obvykle nabízejí nějaké API, díky kterému je můžete ovládat čidlem připojeným ke GPIO. Typicky se dnes používá D-Bus:

# VLC:
qdbus \
	org.mpris.MediaPlayer2.vlc \
	/org/mpris/MediaPlayer2 \
	org.mpris.MediaPlayer2.Player.PlayPause

# Clementine:
qdbus \
	org.mpris.MediaPlayer2.clementine \
	/org/mpris/MediaPlayer2 \
	org.mpris.MediaPlayer2.Player.PlayPause

Dodatek: Dědictví systému Plan 9

Líbil se vám ten králík-kosmonaut na druhém obrázku? To je Glenda, symbol operačního systému Plan 9. Tento systém dotáhl asi nejdál koncept „všechno je soubor“. V jiných směrech se Plan 9 může zdát dost divný (asi jako stejnojmenný film) a v praxi se moc nerozšířil, ale něco po něm přeci jen zbylo. Jeho protokol 9P se dnes používá v qemu-kvm virtualizaci pro sdílení souborů mezi hostem a hostitelem.

Závěr

Ukázali jsme si, jak pracovat s GPIO a reagovat na události pomocí klasických unixových (resp. GNU) nástrojů, rour a skriptů – bez dodatečných knihoven a programování. Pro mnoho úkolů je tento systém vhodný a i když nějaký programovací jazyk používáte, nemusíte hned shánět knihovnu – máte souborové rozhraní.

Pokud ale máte vysoké nároky na rychlost a chcete přes GPIO např. emulovat nějaký protokol nebo měřit data s vyšší frekvencí, bude přeci jen potřeba použít specializovanou knihovnu (viz test v odkazech níže).

Odkazy a zdroje:

Témata: [GNU/Linux] [svobodný hardware] [hardware] [elektřina]

Komentáře čtenářů


RM, 14. 12. 2016 17:47 [odpovědět]

Pěkné! Ten datum za xargs by šel vložit přímo v substituci sed s///e, pokud bude GNU.

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a trollům

Náhled komentáře