Picoをリモコンで操作したい (3)

前回、ラズパイ → pico の通信に成功したので
今回は双方向通信に挑戦の予定でしたが
前回のプログラムに pico → ラズパイのコードを追加しても全然動かない

pico → ラズパイの通信ができるまで2日かかりました。

pico からラズパイに1秒ごとに送信するプログラムです
送信の目安にLEDを点灯・消灯させています

"""
*** コメントはGitHub Copilotで生成しました ***

Pico から Raspberry Pi へデータを送信するサンプルコード

このスクリプトは Raspberry Pi Pico で実行され、以下の処理を行います:
1. GPIO 15 番ピンの LED をオン/オフに点滅させる
2. UARTシリアル通信でカウント値を Raspberry Pi に送信する
3. 1秒ごとにこれらの処理を繰り返す
"""

from machine import UART, Pin
import time

# ==================== LED 制御用の定数 ====================
LED_ON = 1      # LED を点灯させるときの信号値
LED_OFF = 0     # LED を消灯させるときの信号値
LED_PIN = 15    # LED が接続されている GPIO ピン番号

# ==================== シリアル通信設定 ====================
BAUT_RATE = 9600  # UART 通信のボーレート(通信速度)

# ==================== ループ制御用の定数 ====================
LOOP_INTERVAL = 1  # メインループの待機時間(秒)。1秒ごとに処理を実行

# ==================== LED ステータス管理用の変数 ====================
# LED の現在の状態を表す定数
STATUS_OFF = 0  # LED が消灯している状態を表す値
STATUS_ON = 1   # LED が点灯している状態を表す値

# 前回のループで設定した LED の状態を保存
# 次のループで状態を反転させるために使用する
prev_onoff_status = STATUS_OFF

# ==================== ハードウェア初期化 ====================
# GPIO 15 番ピンを出力モードで初期化(LED 制御用)
led = Pin(LED_PIN, Pin.OUT)

# UART 0 を初期化(ボーレート 9600、TX は GPIO 0、RX は GPIO 1)
uart = UART(0, baudrate=BAUT_RATE, tx=Pin(0), rx=Pin(1))

# データを送信した回数をカウントする変数
count = 0

# ==================== メインループ ====================
# 無限ループで継続的に LED 制御とデータ送信を実行
while True:
    # LED の状態を反転させる処理
    if prev_onoff_status == STATUS_OFF:
        # 前回が消灯していた場合、LED を点灯させる
        led.value(LED_ON)
        prev_onoff_status = STATUS_ON
    else:
        # 前回が点灯していた場合、LED を消灯させる
        led.value(LED_OFF)
        prev_onoff_status = STATUS_OFF
    
    # UART を使用してカウント値をシリアル通信で送信(Raspberry Pi へ)
    # encode('utf-8') で文字列をバイト列に変換して送信
    uart.write(f"count: {count}\n".encode('utf-8'))
    
    # デバッグ用:Pico のターミナルに同じカウント値を表示
    print(f"count: {count}")
    
    # カウント値をインクリメント(増加させる)
    count += 1

    # 次のループまで 1 秒間待機(LOOP_INTERVAL で設定した秒数)
    time.sleep(LOOP_INTERVAL)

このプログラムには問題なかったようで、特に修正不要でした

次に受信側(ラズパイ側)のプログラム。最初に作ったのがこれです

import serial

BAUT_RATE = 9600
SERIAL_TIMEOUT = 2

count = 0
ser = serial.Serial('/dev/serial0', BAUT_RATE, timeout=SERIAL_TIMEOUT)

try:
    while True:
        print(f"[{count}] 受信待機... ", end="")
        received_data = ser.readline()
        print(f"受信データ: {recieved_data}")
        count += 1

・動くけど5~10秒で止まってしまう
・一度止まるとプログラム開始時にエラーが出る
・エラー内容は /dev/serial0 に対する permission denied

いろいろググって、/dev/serial0 の permission を都度 666 にすれば
プログラム開始時のエラーは出なくなるのですが
やはり10秒以内に止まってしまいます

chat 先生に訊ねたら

permission を 666 にするのはダメでしょ

って(ダメだよなあ。。。)

対策としてやった事
(全部 chat 先生のアイデアです)

・permission denied 対策:dialout グループに自分を入れる
sudo usermod -aG dialout $USER

ls -l /dev/serial0
→ /dev/serial0 は /dev/ttyS0 へのシンボリックリンクでした
→ コンソール競合(ログイン用 getty が /dev/serial0 を掴みに来てる可能性あり by chat)
→ 実際、上のコードが落ちた後に fuser -v /dev/ttyS0 すると
  user は root、command は loginでした

raspi-config で
3 Interface Options
→ Serial Port
→ login shell over serial? に 「いいえ」
→ Serial hardware enable? に「はい」

sudo systemctl disable –now serial-getty@ttyS0.service

これでも解決せず、chat 先生から追加の教え

sudo systemctl mask serial-getty@ttyS0.service
mask は disable より強いそうです

/boot/firmware/cmdline.txt 中の console=sirial0, (スピード) の部分を削除

これで再起動したら
ls -l /dev/ttyS0 が 600 root tty から 660 root dialout に変わっていました

さらに確実にするために、ロックファイルの使用を勧められました

ラズパイ側の最終的なコードがこちら。
最初の pico 用コードで送り出した文字列が
ラズパイのコンソールに表示されました。

"""
*** コメントは Github Copilot によって生成されました ***

Raspberry Pi Pico からのシリアル通信受信プログラム

このスクリプトは Raspberry Pi(親機)側で実行され、以下の処理を行います:
1. UART シリアル通信でPicoからのデータを継続的に受信
2. 受信したデータをデコードして画面に表示
3. ファイルロックを使用して複数プロセスからの同時アクセスを防止
"""

# ==================== ライブラリインポート ====================
import serial  # シリアル通信を行うためのライブラリ
import fcntl   # ファイルロック機能を使用するためのライブラリ

# ==================== 通信設定 ====================
BAUT_RATE = 9600          # UART通信のボーレート(通信速度)。Pico側と同じ設定にする必要があります
SERIAL_TIMEOUT = 2        # シリアル通信のタイムアウト時間(秒)。この時間内にデータが来ないと読み込みを終了

# ==================== ファイルロック処理 ====================
# /dev/serial0 への複数プロセスからの同時アクセスを防ぐため、ロックファイルを作成
lock = open('/tmp/ttyS0.lock', 'w')

# 排他的ロック(LOCK_EX)を取得。他のプロセスがロックを解放するまで待機
fcntl.flock(lock, fcntl.LOCK_EX)

# ==================== シリアル通信の初期化 ====================
# Raspberry Pi の GPIO 14(TX)と GPIO 15(RX)を使用するシリアルポート(/dev/serial0)を開く
# ボーレートと タイムアウト時間を設定して通信準備
ser = serial.Serial('/dev/serial0', BAUT_RATE, timeout=SERIAL_TIMEOUT)

# 受信したデータの回数をカウントする変数
count = 0

# ==================== メイン受信ループ ====================
try:
    while True:
        # 受信データを待機中であることをカウント値とともに表示(改行なし)
        print(f"[{count}] 受信待機... ", end="")
        
        # Pico からシリアル通信でデータが来るまで待機
        # readline() はデータを1行分(\n まで)受信するか、タイムアウトするまで待つ
        received_data = ser.readline()
        
        # 受信したバイトデータを UTF-8 文字列にデコードし、末尾の改行を削除
        print_data = received_data.decode('utf-8').rstrip('\n')
        
        # 受信したデータを画面に表示
        print(f"受信データ: {print_data}")
        
        # カウント値をインクリメント(次のループで使用)
        count += 1

# ==================== 終了処理 ====================
# プログラムが異常終了した場合やユーザーが中断した場合の処理
finally:
    # シリアルポートを閉じる(通信を終了)
    ser.close()
    
    # ロックを解放(他のプロセスがシリアルポートを使用できるようにする)
    fcntl.flock(lock, fcntl.LOCK_UN)
    
    # ロックファイルを閉じる
    lock.close()

コメント

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