Python超初心者の躓くところ

 朝からLEDの並列接続やら制限抵抗を実践しながらお勉強して、午後はプログラム言語のPythonのお勉強。

 お勉強というか、

・ラズパイ初心者向きの本
・アマゾンで購入したラズパイ用電子工作キットの説明書
・最近購入した中級者?向きのラズパイでのパーツ制御の解説書
・他、インターネットの参考ページ等

 それらのエルチカ(LEDを光らせよう!)のプログラム(Python)の記述が、結構違うんですよね。

 2年前にラズパイを触って、いつの間にか挫折してしまった原因の一つではあると思います。

 特に違うのが

if __name__ == “__main__”:

が有るか、無いかの違いと、

メインプログラムのループ方法やそれの離脱方法なんですよね。

 で、私的には色々調べまして、この

if __name__ == "__main__":

 は、「別のプログラムでimportを宣言したとき、メインの実行を行わないためにある」と理解しました。
 Pythonでは、プログラムをimport宣言した時に、一度中身を実行するらしく、これを記述することによって、それ以降のメインプログラムがimportの宣言時では動かず、関数として呼び出したとき、またはOSから直接呼び出した時だけ動くようになるとのこと。

 これをグーグル先生に検索してもらっても、なかなか納得のいく回答を得られるページに辿り着けず、1時間はなんだよこれは~!!という状態でした。

 で、そもそもこの記述の必要性は、簡単な電子工作をプログラム一つで実行する際は、必要ないんですよね。
 必要になるのは、いろんな機能を持った電子工作(ロボットとか)の制御プログラムを制御の種類毎に別途用意して、それを呼び出して使うときだと思います。たとえば、液晶LCDモジュールの表示用プログラムとかですね。

ループ処理

 ループ処理の記述も、参考にする本やネット記事によって全然違いますが、こちらもラズパイに標準で付随している、「Thonny」というアプリ上で、Pythonを記述、実行する際には、あまり必要性というか、正確性というか、なんとでもなるんですよね。
 クラッシュとか無限ループしても、「Thonny」というアプリ上で止めれますので。

 で、私としては理解できないプログラムを真似して使うよりは、自分で理解して使いたいので、前回のエルチカの記事では、5回ループして終了という、自分でも理解できる方法で記述しました。

 で、今日の午後で、「CTRL + C」を押したら、ループ解除。ついでに、電子工作の「押しボタン」を押したら、ループ解除。という2つの終了条件を備えたループプログラムを自分で納得できるまでお勉強。

 おそらく、4時間はグーグル先生のお世話になりました。

 参考にしたサイトPythonでデバイスを制御しよう プッシュボタンを扱う

 このサイトの、関連コラムを2回は読み返しました。
 もうね、他人様の書いたPythonって、オリジナルの変数名なのか、それとも標準というかPythonに指示をするための単語なのか、理解に苦しむんですよね。
 私はMSXでBASICを覚えて、それ以降プログラム言語を全然触ってこなかったので、やたらと長い変数名が使えるのに頭が慣れないんですよね~。
 繰り返しのforに使う変数は、iとかjとかhあたりという固定概念が残っているというか。

 今日、一番??になったのは、その参考サイトで記述されていた

    sys.exit(main())

 です。

 sys.exit(0)とsys.exit(1)を説明しているサイトはすぐに見つかるのですが、このカッコの中に関数としてメインプログラムを入れる意味と、入れてもいいのか??という疑問を解消するのに、時間がかかりました。
 
 ようは、これも、プログラムを他のプログラム上で呼び出して使ったときに、正常に処理が終わりました(= sys.exit(0) )という記録をOS上に残すためですね。
 で、sys.exit(main())は、main()関数を実行して、main関数の戻し値(return 0)が代入されるということを理解するのに小一時間。
 
 試しに、print(main())を記述して、適当にmain関数の戻し値をreturn 129 とかにして実行したら、main関数が実行後(エルチカ後)に129と表示されました。

 ま~なので、おそらく実践的なプログラムの記述方法なんでしょうね。

うまく動かないのがソフトなのかハードなのか

 で、最終的に物理ボタンでループを解除するプログラムを書いて、実行してみたら、どうにもボタンを押しても反応が悪いんですよね。

 LEDを点灯させるのに、time.sleepで0.5秒とか止めるので、その間にボタンを押したかどうかの判定が止まって、うまく作動しないのかな。
 でも、0.001秒を500回繰り返すとかで判定の間を用意してみたり、逆に10秒程度連続でsleepしてみたり、bouncetimeという待ち時間の数値を極端に変化させてみたりしても、同じような不安定な挙動で、

 「あ~意味わからん!!」

 と、匙を投げそうになったのですが、

 ふと、

 「これって、ちゃんと基盤に刺さってないのでは?」

 と、気づいて、少々強引に基盤に押し込んだら、見事に改善されました。
 結果として、time.sleepで10秒点灯させ続けても、好きなタイミングで押しボタンの割り込み処理はできました。

 で、ここからもまだ壁がたくさんありまして、どうやってループを止めるのか?という部分で、失敗を繰り返しました。

 とりあえず、ボタンを押されたときに return を 実行すれば、メイン関数が終了するかなと思いきや、ループは生き続け、GPIOのエラー発生。
 では、return の代わりに sys.exit(0) を記述すれば、終了できるかと思えば、こちらもループは生き続け、「Thonny」からエラー警告発生。

 なので、晩御飯を食べながら模索した結果が、while True: の無限ループの記述の True を 変数にして判定すればいいのでは閃き、それを実践。
 正直、グローバル変数とかの設定とか、宣言とか、今一歩理解できていませんが、我ながら良い感じに出来上がりました。

動画と使用プログラム(Python)

 私の覚書用です。
 興味ない人が動画見ても、何をやっているか不明かと思います^^;

#ラズパイのGPIOを制御するために、Rpi.GPIOライブラリーを読み出し
import RPi.GPIO as GPIO
#時間に関する制御をするために、time関数を読み出し
import time
#キーボード(ctrl+c)の監視用
import sys

#使用するLEDのGPIO番号(セットモードはBCM)(物理ピン番号はBOARD)
LedPin1 = 17
LedPin2 = 27
LedPin3 = 22
   
#使用する終了ボタンのGPIO番号
endBotann = 26

#終了ボタンが押されたら0false ループ解除用
G_break_ch = True

#初期設定 setup()関数の定義
def setup():
    #立ち上がり時のGPIOのエラーメッセージを非表示
    GPIO.setwarnings(False)
    #GPIOのピン番号指定方法(通常はGPIO番号 BCM)(物理ピン番号での指定は BOARD)
    GPIO.setmode(GPIO.BCM)
    #使用するGPIOピンを入力(IN)で使用するのか、出力(OUT 3.3v)で使用するのかの設定
    #initialは初期の状態 1=GPIO.HIGH=True
    #初期設定がHighなのは、LEDのカソード(ー)をGPIOに繋げるため
    GPIO.setup(LedPin1,GPIO.OUT,initial=1)
    GPIO.setup(LedPin2,GPIO.OUT,initial=1)
    GPIO.setup(LedPin3,GPIO.OUT,initial=1)
    
    #終了ボタンの設定 GPIO内部のプルアップ抵抗を使用 ボタンの先はGND
    GPIO.setup(endBotann,GPIO.IN,pull_up_down=GPIO.PUD_UP)
    # 立ち下がり(GPIO.FALLING)を検出する(プルアップなので通常時1/押下時0)
    GPIO.add_event_detect(endBotann,GPIO.FALLING,bouncetime=100)
    # イベント発生時のコールバック関数を登録
    GPIO.add_event_callback(endBotann,Botann_pressed)    

# ボタンが押された時に呼び出されるコールバック関数
# gpio_no: イベントの原因となったGPIOピンの番号
def Botann_pressed(gpio_no):
     
    global G_break_ch
    # メッセージを表示
    print('GPIO番号',gpio_no,'に接続のボタンが押されました')
    # 基幹プログラムのループ解除用
    G_break_ch = False   #False=0

#LED 点灯時間調整 (割り込み用のスキマ時間には関係なかった time.sleep(10)とかでも瞬時に割り込める)
def led_time(ledtime):
    for loop_i in range(ledtime):
        time.sleep(0.1)
    
#メインプログラム1 main1()関数の定義 LEDの点灯と消灯
def main1():
    #1番目 LED ON 0.5秒点灯
    print('LED1 ON')
    GPIO.output(LedPin1,GPIO.LOW)
    led_time(5)
 
    #1番目消灯 2番目 LED ON 0.5秒点灯 
    print('LED2 ON')
    GPIO.output(LedPin1,GPIO.HIGH)
    GPIO.output(LedPin2,GPIO.LOW)
    led_time(5)
 
    #2番目消灯 3番目 LED ON 0.5秒点灯
    print('LED3 ON')
    GPIO.output(LedPin2,GPIO.HIGH)
    GPIO.output(LedPin3,GPIO.LOW)
    led_time(5)

    #3番目消灯 0.5秒待機
    print('LED OFF')
    GPIO.output(LedPin3,GPIO.HIGH)
    led_time(5)

    #1番目 LED ON 0.5秒点灯
    print('LED1 ON')
    GPIO.output(LedPin1,GPIO.LOW)
    led_time(5)
 
    #2番目 LED ON 0.5秒点灯
    print('LED2 ON')
    GPIO.output(LedPin2,GPIO.LOW)
    led_time(5)
 
    #3番目 LED ON 0.5秒点灯
    print('LED3 ON')
    GPIO.output(LedPin3,GPIO.LOW)
    led_time(5)

    #LED3個ともOFF 0.5秒消灯
    print('LED OFF')
    GPIO.output(LedPin1,GPIO.HIGH)
    GPIO.output(LedPin2,GPIO.HIGH)
    GPIO.output(LedPin3,GPIO.HIGH)
    led_time(5)

#エンディングプログラム destroy()関数の定義
def destroy():
    #LED OFF
    GPIO.output(LedPin1,GPIO.HIGH)
    GPIO.output(LedPin2,GPIO.HIGH)
    GPIO.output(LedPin3,GPIO.HIGH)
    #GPIOの初期化
    GPIO.cleanup()
    
#基幹プログラム main1をループ [clrl+c] でループ離脱
def main():
    try:
        setup()
        while G_break_ch:
            main1()
    except KeyboardInterrupt:
        print('ctrl+c が検出されました')

    print('プログラムを終了します')
    destroy()
    
    return 0

#importで他のプログラム等から呼び出し時に、実行しないための記述
if __name__ == "__main__":
    sys.exit(main())
    #return 0 でエラーなく終了したことになる。sys.exit(0)という結果

コメント

タイトルとURLをコピーしました