Pico PR

【質問回答】RaspberryPi Pico MicroPython イベントドリブンなプログラムの作り方【ビデオの自動録画】 

記事内に商品プロモーションを含む場合があります

はじめに

RaspberryPi Pico・MicroPythonを使って、ビデオの自動録画を行う(イベントドリブンな)プログラムについて質問をいただいたので、解説したいと思います。

これまでの記事は、センサから温度・距離を取得するなどの、「単体の処理」についての解説をしてきました。「処理を組み合わせて目的を達成する」ような「システムの作り方」については解説してこなかったので「ビデオの自動録画」を題材に、システムの作り方について解説してみたいと思います。

こんなことに使える
  • 複数のセンサを組み合わせたシステムを作る
  • Picoから外部のセンサを動かす
  • 外部センサからの入力(I/O)をPicoで取得する
  • PicoからPWM信号を出力する

環境

この記事は以下の環境で作成しています。

環境 バージョン 備考
開発用PCのOS Windows11 Windows10でもOKです
言語 MicroPython
開発環境 Thonny 3.3.13
ボード RaspberryPi Pico

※ Picoのセットアップ方法については、こちらの記事をご覧ください。

Picoピンアサイン(Pin-Out)

RaspberryPi Picoのピンアサイン(pinout)の画像

Pico公式サイトより引用

使用する機材や部品

この記事では以下の機器や部品を使用します。リンクには同等品や人気・評判がよく、高価(ぼったくり価格)でないものを選んで掲載しています。

¥1,210 (2022/02/22 09:28時点 | Amazon調べ)

ビデオ自動録画のシステム構成

コメントでいただいた自動録画システムの構成は以下の通りです。

ビデオ自動録画のシステム構成の図

※ この記事では入力をタクトスイッチ。出力をLEDに置き換えた回路を使って解説します。

ビデオ自動録画システムの流れ

自動録画システムの動作内容は以下の通りです。

自動録画システムの動作内容
  1. 人感センサに反応があったら、ソレノイドでビデオカメラの電源ボタンを押す。
  2. 10秒待機する(ビデオカメラの起動待ち)
  3. ソレノイドで録画ボタンを押す(録画を開始する)
  4. 10秒待機する(録画中)
  5. ソレノイドで録画ボタンを押す(録画を停止する)
  6. 人感センサに反応があったら③に戻る。そうでない場合は次に進む
  7. 10秒待機する(録画データの書き込み待ち)
  8. ソレノイドでビデオカメラの電源ボタンを押す。
  9. ①に戻る

また、上記の動作に以下の機能も付け加えます。

  • 録画ボタン操作時はスピーカーから音を鳴らす。
  • スピーカ駆動はPWMを使う(1kHz, Duty50%)
  • 録画中に暗い場合は、LED照明をつける ※

※ コメントでは「次回の録画時」となっていますが、簡素化しています。

実行結果

システムを簡易化した回路で、後述するプログラムを実行した際の動画です。

簡易回路

自動録画のシステムを簡素化した回路の画像

プログラム実行時の動画

実行時の動画です。見やすさのためPWMの周波数を10Hzにしています。

全体コード

全体コードは以下の通りです。

メインの処理をシンプルにするため、センサとのやり取りを「関数」にまとめています。(関数は細かい処理をまとめた部品・ブロックのようなものと考えてください)

前述した自動録画システムの動作内容と合うように、プログラムのメイン処理を記載しています。その他、詳細な内容は後述のコードのポイントをご覧ください。

import machine
import time
from machine import PWM

#
# --- 入力ピンの設定です -----------
#
# 人感センサ
Motion = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)
# CDS(明るさセンサ)
CDS    = machine.Pin(27, machine.Pin.IN, machine.Pin.PULL_DOWN)

#
# --- 出力ピンの設定です -----------
#
# ビデオ電源操作用
VideoPwr = machine.Pin(17, machine.Pin.OUT)
# ビデオ録画操作用
VideoRec = machine.Pin(19, machine.Pin.OUT)
# スピーカ(PWM)
Speaker  = PWM( machine.Pin(20, machine.Pin.OUT))
# PWMを1kHzに設定
Speaker.freq(1000)
#Speaker.freq(10)

# LED照明
LED      = machine.Pin(21, machine.Pin.OUT)

#
# 人感センサ確認用関数
#
def isMotionSensorDetect():
    
    if Motion.value() == 1:
        # 人感センサ検知
        return True
    
    return False

#
# CDS(明るさセンサ)の確認とLEDを点灯する関数
#
def checkNight():
    
    if CDS.value() == 0:
        # CDSが0(暗かったら) LEDを光らせる
        LED.value(1)
        print("LED照明を点けました")
        
    else:
        print("LED照明はいりません")

#
# ビデオの電源用ソレノイドの動作関数
# ソレノイドを「onTime」で指定した時間、Highにして戻します。
#
def videoPowerPush(onTime):
    VideoPwr.value(1)
    time.sleep(onTime)
    VideoPwr.value(0)
    
#
# ビデオの録画用ソレノイドの動作関数
# ソレノイドを「onTime」で指定した時間、Highにして戻します。
# ピンがHighの間は、スピーカーから音が出ます
#
def videoRecPush(onTime):     
    VideoRec.value(1)
    # スピーカーから音を出します
    Sound(True)
    time.sleep(onTime)
    # スピーカーの音を止めます
    Sound(False)
    VideoRec.value(0)

#
# スピーカーから音を鳴らす関数
#
def Sound(switch):
    if switch == True:
        # Duty比を50にセットする。(100は65536)
        Speaker.duty_u16(32768)
        
    else:
        # Duty比0で出力は止まります
        Speaker.duty_u16(0)
        
#
# LED照明をOFFにする関数
#
def ledOff():
    LED.value(0)

# ------------ プログラムのメインの処理内容です -----------
    
print("メイン開始")

# 無限ループで終了を防止します。
while(True):
    
    # 人感センサの反応確認
    if( isMotionSensorDetect() == True ):
        
        print("人感センサ 検知")
        videoPowerPush(1)
        time.sleep(10)
        
        # 再録画用のループです
        while(True):
            # 明るさを確認してLEDを点けます
            checkNight()
            
            # ビデオの録画ボタンを押します
            videoRecPush(1)
            
            # 録画している間は待機します
            time.sleep(10)
            
            # ビデオの録画ボタンを押して、録画を止めます。
            videoRecPush(1)
            
            # LED照明を消灯します
            ledOff()
            
            # 録画完了語、再度人感セセンサをチェックします。
            if( isMotionSensorDetect() == True ):
                print("人感センサを再度検知しました")
                
            else:
                print("人感センサは、再度検知されませんでした")
                #録画停止
                break
        
        time.sleep(10)
        videoPowerPush(1)
        
        print("電源OFFしました")
        
    # 人感センサ非検知
    else:
        print("人感センサ 非検知")
        time.sleep(1)

コードのポイント

無限ループと人感センサの確認

以下の部分がプログラムのフレームになります。センサの反応があるまでプログラムを動かし続けるため、while(True)でプログラムを無限ループさせます。

無限ループの中でisMotionSensorDetect関数を使って人感センサを確認し、反応があった場合はその下の処理に。ない場合はループの最後のelseに飛びます。elseに飛んだあとは、1秒待機するのみで、無限ループの冒頭に戻ります。

上記「無限ループ」と「反応がない場合は何もしない」という仕組みで、「人感センサの反応があるまで待ち続ける」という処理を作ることができます自動録画システムの動作内容の①と⑨に該当します。

# 無限ループで終了を防止します。
while(True):
    
    # 人感センサの反応確認
    if( isMotionSensorDetect() == True ):
        print("人感センサ 検知")
        
    // ~~~~~ 中略 ~~~~~~~
        
    # 人感センサ非検知
    else:
        print("人感センサ 非検知")
        time.sleep(1)

isMotionSensorDetect関数

関数の詳細です。「Motion」という名前を付けた人感センサ用のGPIO(GP16, IN)から、センサ情報を取得して、判定結果を返します。

このように関数化しておくと、センサの論理(High/Low)の仕様が変わったり、センサを変更したい場合に、関数内の修正だけで対応できます。バグの発生防止や問題切り分けに有効なのでおすすめです。

#
# 人感センサ確認用関数
#
def isMotionSensorDetect():
    
    if Motion.value() == 1:
        # 人感センサ検知
        return True

    return False

ビデオカメラの電源ON

人感センサを検知すると、videoPowerPush関数を使ってビデオカメラの電源用ソレノイドを動作させます。

関数の後ろの(1)は「引数」というものです。数値・文字・True/Falseなどを関数にセットした上で、関数を実行します。以下のコードはvideoPowerPush関数のの「onTime」に「1」がセットされた状態で、関数が実行されます。

    # 人感センサの反応確認
    if( isMotionSensorDetect() == True ):
        print("人感センサ 検知")
        videoPowerPush(1) 
        time.sleep(10)

videoPowerPush関数

関数の詳細です。「onTime」に1がセットされると「ビデオ電源用のソレノイドを1秒間Highにして、Lowに戻す」という動作になります。

#
# CDS(明るさセンサ)の確認とLEDを点灯する関数
#
def checkNight():
    
    if CDS.value() == 0:
        # CDSが0(暗かったら) LEDを光らせる
        LED.value(1)
        print("LED照明を点けました")
        
    else:
        print("LED照明はいりません")

再録画のループ

録画に関する処理内容です。自動録画システムの動作内容の③~⑥に該当します。

以下の録画に関する処理は、繰り返し行われる可能性があるため、ループで囲っています。ループを終了する条件については、後述します。

        # 再録画用のループです
        while(True):
            # 明るさを確認してLEDを点けます
            checkNight()
            
            # ビデオの録画ボタンを押します
            videoRecPush(1)
            
            # 録画している間は待機します
            time.sleep(10)
            
            # ビデオの録画ボタンを押して、録画を止めます。
            videoRecPush(1)
            
            # LED照明を消灯します
            ledOff()

明るさのチェック

checkNight関数を使って、明るさの確認とLED照明の点灯を行います。人感センサのチェックと同様ですが、暗い場合のみLED照明をON(GPIOをHigh)にしています。

            # 明るさを確認してLEDを点けます
            checkNight()
#
# CDS(明るさセンサ)確認とLED ON用関数
#
def checkNight():
    # CDSが0(暗かったら) LEDを光らせる
    if CDS.value() == 0:
        LED.value(1)
        print("LED照明を点けました")
    else:
        print("LED照明はいりません")

ビデオ録画ボタン操作とブザー

videoRecPush関数を使って、録画ボタンの操作と操作時のブザーを鳴らします。

            videoRecPush(1)

videoRecPush関数

ビデオ電源操作用の関数と同様ですが、ソレノイドをONにしている間、後述のSound()関数を使ってブザーを鳴らします。

#
# ビデオの録画用ソレノイドの動作関数
# ソレノイドを「onTime」で指定した時間、Highにして戻します。
# ピンがHighの間は、スピーカーから音が出ます
#
def videoRecPush(onTime):   
    VideoRec.value(1)
    # スピーカーから音を出します
    Sound(True)
    time.sleep(onTime)
    # スピーカーの音を止めます
    Sound(False)
    VideoRec.value(0)

Sound関数

duty_u16という関数を使ってPWM信号を出力します。

引数でデューティー比は、16bit(0~65536)で指定する必要があるため、デューティー比50となる「32768」を指定しています。PWM信号を止める場合は「0」を指定します。

# スピーカーから音を鳴らす関数
def Sound(switch):
    if switch == True:
        # Dutyを50でON。(100は65536)
        Speaker.duty_u16(32768)
        
    else:
        # Duty0で出力されなくなる
        Speaker.duty_u16(0)

録画終了とLEDの消灯

前述したvideoRecPush関数を使って録画を停止します。checkNight関数によって、LED照明が点灯している場合があるので消灯します。
(すでに消灯している場合でもピンがLow->Lowになるだけなので、分岐は入れていません)

            # 録画している間は待機します
            time.sleep(10)
            
            # ビデオの録画ボタンを押して、録画を止めます。
            videoRecPush(1)
            
            # LED照明を消灯します
            ledOff()

再録画ループの終了判定

録画処理を行ったら、再度isMotionSensorDetect関数を使って、人感センサの反応を確認します。検知した場合は再録画のループの先頭に戻って、録画処理を繰り返します。

人感センサの反応がない場合は、ループを終了する(抜ける)breakという構文を使って、再録画のループの外側に移動します。

        # 再録画用のループです
        while(True):
            checkNight() 

            ~~~~~ 中略 ~~~~~
                        
            if( isMotionSensorDetect() == True ):
                print("人感センサを再度検知しました")
                continue
            else:
                print("人感センサは、再度検知されませんでした")
                #録画停止
                break

ビデオの電源OFFと最初に戻る

前述した再録画のループがbreakになると、以下に移ります。

録画データの保存を10秒待ったのち、videoPowerPush関数を使ってビデオの電源をOFFにします。

上記の処理で電源ON~録画の処理が完了し、無限ループの先頭に戻ます。戻った後は再度、人感センサ反応があるまで待つ。という流れになります。

        time.sleep(10)
        videoPowerPush(1)
        
        print("電源OFFしました")
        

まとめ

RaspberryPi Pico・MicroPythonを使って、ビデオの自動録画を行う(イベントドリブンな)プログラムについて解説しました。

今回の方法以外にも、タイマーやスレッド、状態遷移を使った方法など、いろいろな実現方法はあるかと思います。今回はシンプルで入門者にも優しいSleepを使った同期的な方法で作成してみました。(H/W・S/W的エラー処理も省略しています)

つたないコードと解説ですが、お役に立てればうれしいです。

お知らせ

今月号のInterfaceは『仕事のChatGPT』!

今週号の日経Interfaceは「仕事のChatGPT」。
イントロとして、ChatGPTをはじめとした大規模言語モデル(LLM)の現在や、エンジニア向け生成AIの活用法。その後の内容で、ChatGPTを使ったPythonでゲームの作り方や生成AIを使ったシステムの製作例が学べます。

全ての内容はここでは紹介しきれないので、詳細は以下のAmazonページをご覧ください。

5月25日発売です。

CQ出版
¥1,430 (2024/04/25 12:53時点 | Amazon調べ)

Pico/Pico W関連のおすすめ本

RaspberryPi Pico / Pico W関連のおすすめ本を独断と偏見で3つ選んでみました。Picoやるならとりあえずこれ買っとけ的な本や、電子工作全般で使える本などを厳選しています。

見た目のいい工具をお探しの方へ

よく使うツールは『見た目』が重要。モチベもあがりますし、仲間との話のネタにもなります。以下のサイトは、デザイン性の高い工具が集めたサイトなので、興味がある方はぜひご覧ください。

質問・要望 大歓迎です

「こんな解説記事作って」「こんなことがしたいけど、〇〇で困ってる」など、コメント欄で教えてください。 質問・要望に、中の人ができる限り対応します。

使えたよ・設定できたよの一言コメントも大歓迎。気軽に足跡を残してみてください。記事を紹介したい方はブログ、SNSにバシバシ貼ってもらってOKです。

ABOUT ME
えす
現役のソフトウェアエンジニアです。 C++ C# Python を使ってます。10年ちょい設計/開発部門にいましたが、今はQAエンジニアっぽいことをしています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です