GPIO入力をプログラムに取り入れる方法には幾つかの方法かある。 最も簡単な方法は、ある自国における入力値を調べることで、『ポーリング』 と呼ばれている。この方法は、まずいタイミングで値を読み込むと、入力をミ スする可能性がある。そこでポーリングはループ(繰り返し)の中で使われるの が一般的であるが、処理に負荷をかけることになる。別な方法は「割り込み」 (エッジの検出)を用いる方法である。ここでエッジとはHIGHからLOWへの状態遷移 (立ち下がりエッジ)と、LOWからHIGHへの状態遷移(立ち上がりエッジ)のことである。
何らかの回路に接続されていない入力ピンは「浮いた状態」と呼ばれる。 言い換えれば、そのようなピンから入力される値は、ボタンやスイッチを押す までどの回路にもつながっていなかったので「未定義」であったといえる。 おそらく電源からの干渉(ノイズ)を受けて何回も状態を変化することになる。
この状況を改善するには、プルアップかプルダウン抵抗を用いる。 これにより、入力のデフォルト値が設定される。ハードでプルアップ/プルダウン 抵抗をもたせ、ソフトでそれを使用することは可能である。 ハードでは 10K の抵抗を入力チャネルと 3.3 V ピン(プルアップ抵抗), もしくは 0 Vピン (プルダウン抵抗)をつなぐのが一般的である。 RPi.GPIOモジュールによりBroadcom SOCをこのような設定にさせることが ソフトで可能になっている:
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP) # または GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
(ここで channel はBOARD か BCMのどちらかで設定したピン番号付けによるピン番号)
ある瞬間での入力値のスナップショットを取ることができる:
if GPIO.input(channel): print('入力値はHIGH') else: print('入力値はLOW')
ループの中でポーリングすることで、ボタンが押されるまで待つことも可能:
while GPIO.input(channel) == GPIO.LOW: time.sleep(0.01) # 10 ms 待つことでCPUに他のことをする機会を与える
(ここではボタンを押すことにより入力がLOWからHIGHに状態変化すると仮定)
エッジとは状態がLOWからHIGH(立ち上がりエッジ)、もしくは HIGHからLOW(たち下げエッジ)に変化することである。値よりも入力状態の変化 に関心がある場合が多い。このような状態変化は「イベント」の一つである。
プログラムが忙しく動いていて ボタンが押されるのを見逃すことがないようにするには、 次の二通りの方法がある:
wait_for_edge() 関数はエッジが検出されるまでプログラムの実行を停止 させる仕様となっている。つまり、ボタンが押されるまで待つという先の例は 次のように書き換えられる:
GPIO.wait_for_edge(channel, GPIO.RISING)
エッジの検出はGPIO.RISING(立ち上がりエッジ)、GPIO.FALLING (立ち下がりエッジ) もしくは GPIO.BOTH (その両方)を指定して行われることに注意。 こうすることの利点はCPUをほとんど使わないため、他の仕事にCPUをあてられることである。
もしもボタンが押されるのを長く待ちたくない場合、 次のようにタイムアウト(timeout)を設定することができる:
# 立ち上がりエッジを最大 5秒まで待つ (タイムアウトはミリ秒単位) channel = GPIO.wait_for_edge(channel, GPIO_RISING, timeout=5000) if channel is None: print('タイムアウトした') else: print('次のチャネルでエッジ検出された', channel)
event_detected() 関数もループの中で用いられるが、ポーリングと異なり、 CPUが他の作業で忙しくしていても、入力状態の変化を見落とすことはない。 Pygame (ゲーム用のモジュール) や PyQt (GUIアプリ用モジュール) のように、 メインループがGUIイベントを監視し、イベントに対してタイムリーに反応を起こすような状況のときに役に立つものである。
GPIO.add_event_detect(channel, GPIO.RISING) # 立ち上がりエッジ検出をチャネルに加える do_something() if GPIO.event_detected(channel): print('ボタンが押された')
検出できるイベントに GPIO.RISING, GPIO.FALLING、そして GPIO.BOTH が指定できる。
RPi.GPIO はコールバック関数用に2番目のスレッドを走らせる。 つまり、メインプログラムと同時にコールバック関数も「同時に」実行された め、エッジに即座に反応して実行できるのである。例:
def my_callback(channel): print(これはエッジイベントのコールバック関数です!') print('%sチャネルでエッジ検出された'%channel) print('これはメインプログラムとは別のスレッドで実行される') GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback) # チャネルで立ち上がりエッジ検出を追加する ...以下にプログラムが続く...
複数のコールバック関数が必要な場合は:
def my_callback_one(channel): print('Callback one') def my_callback_two(channel): print('Callback two') GPIO.add_event_detect(channel, GPIO.RISING) GPIO.add_event_callback(channel, my_callback_one) GPIO.add_event_callback(channel, my_callback_two)
注意: この場合、コールバック関数は並行的にではなく、順に実行される。 なぜなら、コールバック用にスレッドが一つしか建てられないからである。 そのスレッドで全部のコールバックが実行されるため、定義された順に実行される。
ボタンを押すたびにコールバックが複数回呼び出されることがある。 これは「スイッチバウンス」と呼ばれる現象のせいである。 この対処法として二通りの方法がある:
ソフトをもいいてデバウンスするには、コールバック関数を指定する関数に
# チャネル上で立ち上がりエッジ検出を追加、検出後200msはスイッチバウンス処理のため、エッジを無視 GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback, bouncetime=200)
もしくは次のようにする:
GPIO.add_event_callback(channel, my_callback, bouncetime=200)
何らかの理由でイベント検出が不要になった場合、イベント検出を停止できる:
GPIO.remove_event_detect(channel)