Open19

ラズパイPico🍓(二次情報を活用しながらプロトタイプを作成)

VanVan

以前、以下のハンズオンに参加してラズパイPicoを購入した。

ハンズオンではラズパイPico内蔵のLチカと温度測定をした。
だから何?って感じで終わったので積みボードになってしまっていた。

obnizにはないネットワークに接続しなくてもスタンドアロンで動作できる点を活かしたプロダクトを作りたい。

https://hannari-python.connpass.com/event/313457/

VanVan

久しぶりに電源を入れてみたら、ラズパイがPCに認識されない。多分積みボードあるある。

USBケーブルを買い直して接続したら認識した。ラズパイの方の故障じゃなくて良かった。

VanVan

Qiita記事を参考にGitHub CodespacesでLチカを試そうと思ったが、ラズパイとの接続が確立しない。
PCのデバイスマネージャーをみるとポートは認識されているのになぜ??

Thonnyを使えば問題なく動作したので、Thonnyを使ってコーディングしてみる。

VanVan

ラズパイはobnizと違ってどこに部品を挿してもOKというわけではないので注意が必要。
最低でも電源ピンとGNDピンは避ける必要がある。

VanVan

スタンドアロンでネットワーク使わないとすると、手持ちの部品でできそうなアウトプットは、
アクチュエータとしてサーボモータ、インジゲータとしてLEDブザーくらい

VanVan

アート思考で考えた。
使ったことないけどマトリックスLEDも積んであるのでそれを使って気温当てゲームを作ろう。

ブレッドボードにラズパイPicoとマトリックスLEDとプッシュSWを接続する。

ラズパイに電源投入後、一定間隔でラズパイPico内蔵の温度センサで気温を測定させる。

ラズパイPicoボード上のタクトSWを押下してゲームスタート。

気温を予想して、タクトSWを押下してから気温の数値分の秒数が経った時点でプッシュSWを押下する。(例えば気温30度と予想したらタクトSWを押下してから30秒後にプッシュSWを押下する。)

±0.3秒範囲内であったらマトリックスLED(Keyestudio_HT16K33)に○の表示、範囲外であったらマトリックスLED(Keyestudio_HT16K33)に×の表示をさせる。

プロンプト
## 前提
あなたはRaspberry Piのプロのエンジニアです。
以下の手順のプロダクトを完成させたい。

## 命令
* 手順を満たすPythonのソースコードを教えて。
* 初心者でも分かりやすい様に各行にコメントを書いて
* 自分の中で問い直して良く考えてから出力して
* 誤った情報が含まれている場合、罰金100万ドル
* 分からないことは教えるので正直に教えて

## 手順
* ブレッドボードにRaspberry Pi Pico Wと8x8のマトリックスLEDとプッシュSWを接続する。 
* ラズパイに電源投入後、一定間隔でRaspberry Pi Pico W内蔵の温度センサで気温を測定させる。 
* Raspberry Pi Pico W内蔵のタクトSWを押下してゲームスタート。 
* 気温を予想して、タクトSWを押下してから気温の数値分の秒数が経った時点でプッシュSWを押下する。(例えば気温30度と予想したらタクトSWを押下してから30秒後にプッシュSWを押下する。) 
* ±0.3秒範囲内であったら8x8のマトリックスLED(Keyestudio_HT16K33)に"○"の表示、範囲外であったら8x8のマトリックスLED(Keyestudio_HT16K33)に"×"の表示をさせる。
VanVan

とりあえずClaude 3.5 Sonnetからの回答
これをベースに修正していく。
Pythonのプログラミングスクール通ってたけどdefとprintしか覚えていない。

main.py
from machine import Pin, I2C, ADC  # 必要なモジュールをインポート
import utime  # 時間関連の機能を使用するためのモジュール

# ピンの設定
push_sw = Pin(15, Pin.IN, Pin.PULL_UP)  # 外付けプッシュスイッチを GPIO15 に接続
bootsel = Pin(23, Pin.IN, Pin.PULL_UP)  # 内蔵BOOTSELボタン(通常は23番ピン)

# 内蔵温度センサーの設定
sensor_temp = ADC(4)  # ADC4 は内蔵温度センサーに接続されている

# I2Cの設定
i2c = I2C(0, sda=Pin(21), scl=Pin(20))  # I2C0 を使用、SDA=GPIO21, SCL=GPIO20

# HT16K33の初期化(アドレスは0x70)
HT16K33_ADDR = 0x70  # HT16K33 のI2Cアドレス

def init_display():
    i2c.writeto(HT16K33_ADDR, b'\x21')  # ディスプレイの電源を入れる
    i2c.writeto(HT16K33_ADDR, b'\xE7')  # ディスプレイの明るさを設定(最大)

def display_pattern(pattern):
    buffer = [0] * 16  # 16バイトのバッファを作成
    for i, row in enumerate(pattern):
        buffer[i*2] = row  # 各行のデータをバッファに設定
    i2c.writeto(HT16K33_ADDR, bytes([0x00] + buffer))  # バッファをデバイスに送信

# パターン定義
circle = [0b00111100, 0b01000010, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b01000010, 0b00111100]  # ○のパターン
cross = [0b10000001, 0b01000010, 0b00100100, 0b00011000, 0b00011000, 0b00100100, 0b01000010, 0b10000001]  # ×のパターン
arrow_up = [0b00011000, 0b00111100, 0b01111110, 0b11011011, 0b00011000, 0b00011000, 0b00011000, 0b00011000]  # 上向き矢印
arrow_down = [0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b11011011, 0b01111110, 0b00111100, 0b00011000]  # 下向き矢印

def get_temperature():
    conversion_factor = 3.3 / (65535)  # ADC値を電圧に変換する係数
    reading = sensor_temp.read_u16() * conversion_factor  # ADC値を読み取り、電圧に変換
    return 27 - (reading - 0.706)/0.001721  # 電圧を温度に変換

def display_result(success):
    display_pattern(circle if success else cross)  # 成功なら○、失敗なら×を表示

def display_temperature(temp):
    if temp < 20:
        display_pattern(arrow_down)  # 20度未満なら下向き矢印
    elif temp > 30:
        display_pattern(arrow_up)  # 30度超なら上向き矢印
    else:
        # 20-30度の範囲内の場合、中央の行を点灯
        middle_row = [0, 0, 0, 0b11111111, 0b11111111, 0, 0, 0]
        display_pattern(middle_row)

def main():
    init_display()  # ディスプレイを初期化

    while True:
        temperature = get_temperature()  # 温度を取得
        display_temperature(temperature)  # 温度に応じたパターンを表示
        utime.sleep(5)  # 5秒間隔で測定

        if bootsel.value() == 0:  # BOOTSELボタンが押されたら
            display_pattern(circle)  # ゲーム開始を示す○を表示
            utime.sleep(1)  # 1秒待機
            display_pattern([0] * 8)  # ディスプレイを消灯

            start_time = utime.ticks_ms()  # 開始時間を記録
            while push_sw.value() == 1:  # プッシュスイッチが押されるまで待機
                utime.sleep_ms(10)  # 10ミリ秒ごとにチェック

            elapsed_time = (utime.ticks_ms() - start_time) / 1000  # 経過時間を秒単位で計算

            success = abs(elapsed_time - temperature) <= 0.3  # 成功判定(±0.3秒以内)
            display_result(success)  # 結果を表示
            utime.sleep(3)  # 結果表示を3秒間維持

if __name__ == "__main__":
    main()  # メイン関数を実行
VanVan

下方修正
幼いころにだれでもやったことがある、ストップウォッチで10秒ピッタリだったら勝ちのゲーム作る。
昔通天閣行ったとき、11.11秒ピッタリ止められたらポッキーもらえるゲームあったの思い出した。

プロンプト
## 前提
あなたはRaspberry Pi Pico Wのプロのエンジニアです。
以下の手順のプロダクトを完成させたい。
プログラムはThonnyというエディタ上で実行する。

## 命令
* 手順を満たすPythonのソースコードを教えて。
* 初心者でも分かりやすい様に各行にコメントを書いて
* 自分の中で問い直して良く考えてから出力して
* 誤った情報が含まれている場合、罰金100万ドル
* 分からないことは教えるので正直に教えて

## 手順
* ブレッドボードにRaspberry Pi Pico Wと赤色LEDと黄色LEDと青色LEDとタクトスイッチを接続する。 
* ラズパイに電源投入後、 Raspberry Pi Pico WのBOOTSELボタンを押下してゲームスタート。 
* ユーザーは BOOTSELボタンを押下してから10秒経ったことを予想してタクトスイッチを押下する。
* タクトスイッチが押下された時点で次の手順に移行
* 10秒±0.1秒内であれば青色LED、10秒±0.3秒内であれば黄色LED、それ以外であれば赤色LEDを3秒間点灯させて、最初の状態に戻る。
VanVan

Claude 3.5 Sonnetからの回答
ここから修正していく。

main.py
import machine
import time
import random

# GPIOピンの設定
led_red = machine.Pin(15, machine.Pin.OUT)    # 赤色LEDを15番ピンに接続
led_yellow = machine.Pin(14, machine.Pin.OUT) # 黄色LEDを14番ピンに接続
led_blue = machine.Pin(13, machine.Pin.OUT)   # 青色LEDを13番ピンに接続
button = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)  # タクトスイッチを16番ピンに接続
bootsel = machine.Pin(23, machine.Pin.IN, machine.Pin.PULL_UP)   # BOOTSELボタンを23番ピンに接続

# すべてのLEDを消灯
def turn_off_leds():
    led_red.value(0)
    led_yellow.value(0)
    led_blue.value(0)

# ゲームの主要ロジック
def game():
    while True:
        turn_off_leds()  # 初期状態: すべてのLEDを消灯
        
        print("BOOTSELボタンを押してゲームを開始してください。")
        while bootsel.value() == 1:  # BOOTSELボタンが押されるまで待機
            time.sleep(0.1)
        
        print("ゲーム開始!10秒経ったと思ったらボタンを押してください。")
        start_time = time.ticks_ms()  # 開始時間を記録
        
        while button.value() == 0:  # タクトスイッチが押されるまで待機
            time.sleep(0.01)
        
        end_time = time.ticks_ms()  # 終了時間を記録
        elapsed_time = (end_time - start_time) / 1000  # 経過時間を秒単位で計算
        
        print(f"経過時間: {elapsed_time:.3f}秒")
        
        # 結果に基づいてLEDを点灯
        if abs(elapsed_time - 10) <= 0.1:
            led_blue.value(1)
            print("素晴らしい!ぴったり10秒です!")
        elif abs(elapsed_time - 10) <= 0.3:
            led_yellow.value(1)
            print("惜しい!もう少しでした。")
        else:
            led_red.value(1)
            print("残念!もっと練習しましょう。")
        
        time.sleep(3)  # 結果を3秒間表示

# メインループ
while True:
    game()
VanVan

【初期コードからの修正内容】

  • ラズパイPico内蔵のBOOTSELボタンと外付けのタクトSWを使い分けるの面倒だから、外付けのタクトSWに機能集約
  • タクトスイッチが内臓プルダウンになっていたけど、内臓プルアップに変更(完全に好み)
  • ゲームスタートの合図としてすべてのLEDを0.2秒間隔の3回点滅に変更
  • 答え合わせのLEDの点灯を3秒点灯⇒0.2秒間隔の5回点滅に変更
  • 10秒±0.1秒の場合、スペシャルな点滅を追加 ←「スペシャルな点滅を追加」ってそのままClaude君にお願いしたら実装してくれた
  • ゲーム開始前の待機時はRaspberry Pi Pico内蔵の緑LEDを1秒間隔で点滅させて、待機状態であることを示す
  • スピーカーを接続して、答え合わせのLED点滅の際、スピーカーも鳴動させる。(スピーカーの音調はClaude君おまかせ。
  • ゲーム中もRaspberry Pi Pico内蔵の緑LEDが点滅していたので、修正
  • 割り込み処理の関係でタクトSWの反応が効かないことがあった。よくわからんけどお願いしたら直してくれた。
VanVan

最終的なコード
スタンドアロンでも動作することを確認した。

main.py
from machine import Pin, PWM, Timer
import time

# GPIOピンの設定
led_red = Pin(15, Pin.OUT)
led_yellow = Pin(14, Pin.OUT)
led_blue = Pin(13, Pin.OUT)
button = Pin(16, Pin.IN, Pin.PULL_UP)
speaker = PWM(Pin(18))
led_onboard = Pin('LED', Pin.OUT)

# グローバル変数
button_pressed = False
last_press_time = 0
DEBOUNCE_TIME = 50  # デバウンス時間(ミリ秒)

def turn_off_leds():
    led_red.value(0)
    led_yellow.value(0)
    led_blue.value(0)

def turn_on_leds():
    led_red.value(1)
    led_yellow.value(1)
    led_blue.value(1)

def blink_led_with_sound(led, times, interval, freq):
    for _ in range(times):
        led.value(1)
        beep(freq, interval)
        led.value(0)
        time.sleep(interval)

def beep(frequency, duration):
    speaker.freq(frequency)
    speaker.duty_u16(32768)
    time.sleep(duration)
    speaker.duty_u16(0)

def special_blink():
    leds = [led_blue, led_yellow, led_red]
    freqs = [523, 587, 659]
    for _ in range(2):
        for led, freq in zip(leds, freqs):
            blink_led_with_sound(led, 2, 0.1, freq)
    for _ in range(3):
        turn_on_leds()
        beep(784, 0.1)
        turn_off_leds()
        time.sleep(0.1)

def button_handler(pin):
    global button_pressed, last_press_time
    current_time = time.ticks_ms()
    if time.ticks_diff(current_time, last_press_time) > DEBOUNCE_TIME:
        button_pressed = True
        last_press_time = current_time

def wait_for_button_press():
    global button_pressed
    led_onboard.value(1)
    button_pressed = False
    while not button_pressed:
        led_onboard.toggle()
        time.sleep(1)
    led_onboard.value(0)
    time.sleep(0.1)  # ボタンを離すのを待つ

def game():
    global button_pressed
    
    while True:
        turn_off_leds()
        
        print("タクトスイッチを押してゲームを開始してください。")
        wait_for_button_press()
        
        print("ゲーム開始!")
        for _ in range(3):
            turn_on_leds()
            beep(440, 0.1)
            turn_off_leds()
            time.sleep(0.1)
        
        print("10秒経ったと思ったらもう一度タクトスイッチを押してください。")
        start_time = time.ticks_ms()
        
        button_pressed = False
        while not button_pressed:
            time.sleep(0.01)
        
        end_time = time.ticks_ms()
        elapsed_time = (end_time - start_time) / 1000
        
        print(f"経過時間: {elapsed_time:.3f}秒")
        
        if abs(elapsed_time - 10) <= 0.1:
            print("素晴らしい!ぴったり10秒です!")
            special_blink()
        elif 9.7 <= elapsed_time <= 10.3:
            print("惜しい!もう少しで10秒でした。")
            blink_led_with_sound(led_blue, 5, 0.2, 659)
        elif 9 <= elapsed_time <= 11:
            print("まあまあです。もう少し頑張りましょう。")
            blink_led_with_sound(led_yellow, 5, 0.2, 587)
        else:
            print("残念!もっと練習しましょう。")
            blink_led_with_sound(led_red, 5, 0.2, 523)
        
        time.sleep(1)

# 割り込み設定
button.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)

# メインループ
while True:
    game()
VanVan

ミニブレッドボードしか手持ちにないので2つに分かれてしまった。
これを電池を含めて一つにまとめてケースに組めば立派な製品になる。
そのために3Dプリンターを学んでいきたい。