IoTで使うPython入門Step0-(5) 定番のLチカの改良(その2)

 前回、スイッチ入力、前々回はLEDの点滅を実行しました。Pythonでプログラミングしていますが、ほかの言語でも書けそうですね。

Lチカをスイッチで開始/停止

 CTRL-Cでプログラムの中断を行うのではなく、当初の目標、スイッチの入力でLEDの点滅、停止を行います。組み込み用のプログラムは、電源が切れるまで永久に動きます。

  • sw1が押されるとLチカを中断する
  • sw2が押されるとLチカを始める。中断していたら再開する
  • sw3が押されたら、プログラムを終了する

接続

 前回検討したハードウェアによるチャタリングは実装していません。スイッチは、ラズパイのGPIO入力と3.3Vをショートします。GPIO端子は電源が入ったときにLow状態です。今回のプログラムでは、GPIOをプルダウンありの入力ポートに設定した時点でLowです。どちらも内部で約50kΩがプルダウン抵抗に用いられています。

 一つのポートの回路図です。入力ポートは、プルダウン抵抗ありで初期設定をします。もし、なにもなしプルアップ抵抗ありに設定しても、過大な電流が流れることはないと思われます。
 もしまちがって、出力に指定したときはどうでしょうか。オープン・ドレイン出力だったとき、スイッチを押すと電源へ直結することになるので、過大な電流が流れるかもしれません。ポートとスイッチの間に1kΩ程度の抵抗を入れておくと防げるかもしれません。前回のチャタリング防止回路では、目的は異なりますが直列に1kΩが入っていました。

プログラム

 割り込み処理の中のprint文は動作確認用です。確認できたら、コメント・アウトしてください。
 割り込み処理内では、スイッチが押されて飛んでくるので、フラグsw1_flag、sw2_flag、sw3_flagを1にするだけでメインのプログラムに戻ります。
 三つのフラグはグローバル変数です。関数内で使うときは、globalをつけて利用します。つけないと、関数内のローカル変数として扱われます。
 初期値でsw2_flagは1にしてあります。これは、このフラグが1のときに点滅をするように設計したからです。sw1は点滅を止めるので、sw1_flagが1のときは、sw2_flagを0にします。

import RPi.GPIO as gpio
from time import sleep

LED_pin = 21 # 40pin
sw1 = 20 # 38pin
sw2 = 16 # 36pin
sw3 = 12 # 32pin
sw1_flag = 0
sw2_flag = 1
sw3_flag = 0

def my_callback1(sw1):
print('at GPIO20-sw1on restart sw2')
global sw1_flag
sw1_flag = 1

def my_callback2(sw2):
print('\n at GPIO16-sw2on Restated')
global sw2_flag
sw2_flag = 1

def my_callback3(sw3):
print('\n at GPIO12-sw3on Shutdown')
global sw3_flag
sw3_flag = 1

print("start. Stop;sw1 Shutdown;sw3")
gpio.setmode(gpio.BCM)
gpio.setup(sw1, gpio.IN, pull_up_down=gpio.PUD_DOWN)
gpio.add_event_detect(sw1, gpio.RISING, callback=my_callback1, bouncetime=200)
gpio.setup(sw2, gpio.IN, pull_up_down=gpio.PUD_DOWN)
gpio.add_event_detect(sw2, gpio.RISING, callback=my_callback2, bouncetime=200)
gpio.setup(sw3, gpio.IN, pull_up_down=gpio.PUD_DOWN)
gpio.add_event_detect(sw3, gpio.RISING, callback=my_callback3, bouncetime=200)
gpio.setup(LED_pin, gpio.OUT)

try:
while 1:
if sw1_flag: # True
gpio.output(LED_pin, False)
sleep(2.0)
gpio.output(LED_pin, True)
elif sw2_flag:
gpio.output(LED_pin, True)
sleep(2.0)
gpio.output(LED_pin, False)
sleep(2.0)
if (sw1_flag != 0):
sw1_flag = 0
sw2_flag = 0
if sw3_flag:
print("shutdown")
break
gpio.cleanup()
except KeyboardInterrupt:
print('\n end')
gpio.cleanup()

 実行中の様子です。
 ソフトウェアによるチャタリング対策をしていますが、ときどき一度押したのに、2回入力されたように表示が出ることがあります。チャタリングを防止できていないタイミングがあることがわかります。今回のプログラムでは、チャタリングによって複数回同じスイッチが押されても動作が変わらないので、このままでよしとします。

 どうしてもチャタリングを避けたいときは、ハードウェアとソフトウェアのタイミングの両方でその回路にあった定数を実験で選びたいと思います。

(※)print()は、最後に改行コードを出力します。バージョン2と3で共通です。改行したくないとき、

  • 2では print <文字列>, とすると、カンマは半角スペースに置き換えられ、改行しない
  • 3では print(<文字列>,end='') とシングル・クオテーションを二つ並べると、改行はしない

 print文(バージョン2)、print関数(バージョン3)の文字列の途中に改行を入れたいときは\nや\rを入れます。

(※)フラグは0と1がよく使われます。本文のプログラムでは、1と2でも何ら支障はありません。0、1、2というように複数の値のフラグを使うこともできるでしょう。割り込みは、頻繁にかかるのが一般的です。なので割り込み処理プログラムからメイン・プログラムに戻るのは速いほど良いのです。アセンブラのコードで、レジスタの内容が0と判断できる機構が用意されているのかもしれません。そうであれば、0もしくは0以外という判断が一番高速に処理されると思われます。