Pi Piper
Pi PiperはRaspberry PiでGPIOをRubyから制御できるGemです。
https://github.com/jwhitehorn/pi_piper
導入
導入はGemをインストールするだけといたって簡単です。
$ sudo gem install
Digital Input/Output
今回はデジタル入出力について記述します。
PiPiper::Pinクラスを用いて制御します。
ここではRaspberry Pi B+をベースに記述してます。
出力
Ledを点灯させるなどの場合に出力を使用します。
require 'pi_piper'
include PiPiper
pin = Pin.new pin:23, direction: :out
loop do
pin.on
sleep 0.5
pin.off
sleep 0.5
end
Pin.new pin:23, direction: :out
でオブジェクトを生成しています。
オプションのpin
で渡している23
はGPIOのピン番号でGPIO 23
を意味し、direction
はI/Oの方向で、出力の場合は:out
を指定します。
この例では1秒周期でLedが点滅します。
$ sudo ruby led.rb
入力
スイッチやセンサーの状態を取り込む場合に入力を使用します。
require 'pi_piper'
include PiPiper
pin = Pin.new pin:20, direction: :in, pull: :down
loop do
pin.read
puts "#{pin.last_value} => #{pin.value}" if pin.changed?
sleep 0.5
end
実行結果で、状態が変わるごとに表示されます。
$ sudo ruby ./sw.rb
0 => 1
1 => 0
0 => 1
0.5秒ごとに状態を更新し、変化があれば表示します。
Pinオブジェクト生成
Pinオブジェクトの生成ではpull
オプションが増えています。
実はRaspberri PiのGPIOにはプルダウン/プルアップ抵抗が内臓されていて、わざわざ外付けのプルダウン/プルアップ抵抗を付けなくてもいいのです。
指定できるのは
| 値 | 内容 |
|---|---|---|
|:down|プルダウン|
|:up|プルアップ|
|:float|なし|
|:off|:floatに同じ。デフォルト|
Pinオブジェクト生成時のオプションでこの他にinvert
があります。
true
の場合に値が反転して取り込まれます。入力が回路的に負論理の場合に、プログラム上では正論理で扱いたい場合に便利です。
出力でも反映されると便利な気がしますが、今の所入力のみのようです。
値の更新
pin.read
を呼び出すことで入力の状態が取り込まれます。
現在の状態はpin.value
で取得でき、pin.last_value
はその前にpin.read
を行った時の値が取得できます。
pin.changed?
で変化したかどうかが判別できます。
pin.value
の代わりにpin.on?
、pin.off?
も利用できます。ONかOFFか直接判断したい場合に利用できます。
通常はトリガーやイベントとして取り扱いたい場合が多いと思いますので、この様なポーリングでの使い方はあまりしないと思いますが、原理として押さえておきたい所です。
トリガー
上記の入力処理では0.5秒置きにpin.changed?
で入力の状態が変化したかどうか調べて、変化があったら表示するという、ポーリングといわれる方法を使って読み取りました。
トリガーを使うとPin#wait_for_change
を使用して状態が変化するまで待つことができます。
Pinオブジェクト生成時にtrigger
オプションを渡す事でトリガーを利用できます。
triggerオプションとして指定できるのは下の3つになります。
| 値 | 内容 |
|---|---|---|
|:rising|立ち上がり
OFFからONになった時|
|:falling |立ち下がり
ONからOFFになった時|
|:both|立ち上がりと立ち下がりの両方|
下の例ではPinオブジェクト生成時にtrigger
オプションで:rising
を指定してます。
pin.wait_for_change
を実行すると、立ち上がり(つまりスイッチを押した時)までブロックされた状態になります。
無駄な待ちや分岐が不要になります。
require 'pi_piper'
include PiPiper
pin = Pin.new pin:20, direction: :in, pull: :down, trigger: :rising
loop do
pin.wait_for_change
puts "rising"
end
スイッチを押す度にrisingが表示されます。
$ sudo ruby trigger.rb
rising
rising
rising
:bothを指定した場合は立ち上がりと立ち下がりのどちらか分からないので、pin.read
とpin.on?
を使って調べる事になります。
require 'pi_piper'
include PiPiper
pin = Pin.new pin:20, direction: :in, pull: :up, trigger: :both
loop do
pin.wait_for_change
pin.read
if pin.on?
puts "rising"
else
puts "falling"
end
end
:bothではrisingとfallingは交互に現れるはずですが、チャタリングのように高速に状態が変わる場合はputsなどの処理をしているときに状態が変わってしまい取りこぼしてしまう事があります。
そのため、rising, risingやfalling, fallingと同じ状態が続いてしまうときがあります。
sudo ruby trigger.rb
rising
falling
rising
falling
falling
rising
チャタリングによる影響は:bothに限ったことではなく他でも発生する事で、ちゃんとやろうとすると対策が必要になってきますが、ここでの説明は割愛します。
抵抗とコンデンサでハード的にフィルタリングするのが最善策です。
GPIOに時定数設定などあったらいいのにですね。
並列処理
triggerを使用すると処理としてはスッキリしますがブロックされるので、同時に他の事をしたいという時に困ってしまいます。
そういう時はThreadを使って処理します。
スイッチが押されるとLEDの点滅パターンが変わる処理はThreadを使ってこんな風になります。
スイッチが押された事を伝えるのにQueueを使ってます。
require 'pi_piper'
include PiPiper
switch_pin = Pin.new pin:20, direction: :in, pull: :up, trigger: :rising
led_pin = Pin.new pin:23, direction: :out
@queue = Queue.new
Thread.new do
loop do
switch_pin.wait_for_change
@queue.push :rising
puts "rising"
end
end
patterns = [[0.5, 0.5], [0.2, 0.2, 0.2, 0.4]]
pattern = 0
step = 0
loop do
if step % 2 == 0
led_pin.on
else
led_pin.off
end
sleep patterns[pattern][step]
# goto next step
step = (step + 1) % patterns[pattern].size
unless @queue.empty?
pattern = 1 - pattern
step = 0
@queue.pop
end
end
イベント
後で書く