Haskell
Node.js
RaspberryPi

Raspberry Pi の Haskell GPIO ライブラリ

More than 1 year has passed since last update.

Raspberry Pi でボタンによる LED の点灯制御ができるライブラリの検証です。

今回は以下のライブラリを検証しました。

HPi は bcm2835 のラッパー、wiringPi は Wiring Pi のラッパーです。その他に Hackage には system-gpio という仮想ファイルを読み書きするライブラリもありますが、これは試すまでもないので除外しました。

結論から言うと、HPi も wiringPi も、どちらも GPIO の割り込みには対応しておらず、ボタンの状態をポーリングしなければなりません。wiringPi はオリジナルの C ライブラリには wiringPiISR という割り込みコールバック関数があるので期待していたのですが、Haskell のライブラリには実装されていませんでした。

なので今回のサンプルはどちらもポーリングです。ちなみに Node.js だとポーリングではなくコールバックで書けます。このエントリの最後におまけとしてサンプルを提示しておきます。

回路

今回は LED ひとつとボタンひとつの回路です。ボタンはタクトスイッチではなくマイクロスイッチを使用しているので論理が逆になっています。

回路図は上記のとおりです。

ブレッドボードに配線した様子が以下です。

HPi のサンプル

HPi は物理ピンでコーディングするスタイルです。

hpi-sample.hs
import Control.Concurrent
import Control.Monad
import System.RaspberryPi.GPIO

led = Pin08
button = Pin07

main = withGPIO $ do
  setPinFunction led Output
  setPinFunction button Input
  forever $ do
    threadDelay 10000
    state <- readPin button
    writePin led $ not state

withGPIO 関数はおそらく setPinFunction の後始末をしてくれるのだと思いますが、今回のサンプルは SIGINT で強制終了するので有効に機能しないと思います。

wiringPi のサンプル

wiringPi は GPIO でコーディングします。私はこちらの方が好みです。

wiringPi は暗黙的に wiringPiSetupGpio が呼び出されるようで、明示的に呼び出す必要がありません。C の Wiring Pi 同様、実行にはルート権限が必要です。なお、ルート権限が不要な wiringPiSetupSys 相当の関数は提供されていません。

wiringpi-sample.hs
import Control.Concurrent
import Control.Monad
import System.Hardware.WiringPi

led = Gpio 14
button = Gpio 4

main = do
  pinMode led OUTPUT
  pinMode button INPUT
  forever $ do
    threadDelay 10000
    state <- digitalRead button
    digitalWrite led $ if state == HIGH then LOW else HIGH

内容はほとんど HPi と同じです。digitalRead と digitalWrite は独自型を読み書きするので、Bool で読み書きする HPi に比べて面倒です。

まとめ

  • ボタンぐらいならポーリングでも良いが、PWM 等には使えない
  • 消費電力が増えるので、バッテリー駆動の機器には向かない
  • ポーリングはダサい

Node.js のサンプル(おまけ)

npm で gpio を検索すると 400 以上ヒットします。上から順に簡単そうなライブラリを探して見つけたのが onoff です。おそらく内部的には仮想ファイルの読み書きで実現しているため、I2C や SPI は扱えませんが、扱う範囲が GPIO だけなら良い選択肢だと思います。

onoff-sample.js
const Gpio = require('onoff').Gpio;
const led = new Gpio(14, 'out');
const button = new Gpio(4, 'in', 'both');

button.watch((err, value) => {
  if (err) {
    throw err;
  }
  led.writeSync(value ? 0 : 1);
});

process.on('SIGINT', () => {
  led.unexport();
  button.unexport();
  process.exit();
});

Node.js は process.on() で割り込みが検知できるので、SIGINT が来たら後始末して終了するようにしています。