【Pythonで始めるシリアル通信 第3回】PCとマイコンを対話させる!双方向シリアル通信に挑戦 💬

「Pythonから送った命令が、ちゃんとマイコンに届いたか確認したい!」
「マイコン側で何かが起きたら、その情報をPCに送ってほしい…」
「データを送りながら、同時に受信もするってどうやればいいの?」

こんにちは! Pythonシリアル通信探検隊、隊長のPythonistaです! 第1回ではデータを「送信」する方法を、第2回ではデータを「受信」する方法を学びましたね。これで、通信の基本的な片道通行はマスターしました。

シリーズ第3回となる今回は、いよいよその両方を組み合わせた「双方向通信」に挑戦します! これにより、あなたのPythonスクリプトは、単なる監視役や司令塔ではなく、外部デバイスと「対話」する真のパートナーへと進化します。この記事では、Pythonスクリプトからマイコン(Arduinoなど)に命令を送り、マイコンからの返事を受け取る「LED遠隔操作プロジェクト」を通して、双方向通信の勘所を学んでいきましょう!


1. 双方向通信の難しさ:「ブロッキング」という壁

「送信と受信ができるなら、コードを順番に書けばいいだけでは?」と思うかもしれません。しかし、そこには一つ大きな「壁」があります。それは、前回学んだser.read()ser.readline()といった読み込み関数が、デフォルトでは**ブロッキングモード**で動作するという点です。

ブロッキングとは、「データが来るまで、プログラムの実行を止めて待ち続ける」という動作でしたね。もし、送信と受信のコードを単純に並べると、

  1. Pythonがデータを送信する。
  2. Pythonがマイコンからの返事を待つため、ser.read()で停止する。
  3. マイコン側が、Pythonからの次の命令を待って停止する。

…というように、お互いが相手の応答を待ち続けてしまい、プログラム全体が停止してしまう「デッドロック」という状態に陥る可能性があります。


2. 解決策:タイムアウト設定で「待ちすぎ」を防ぐ!

このブロッキング問題を解決する最も手軽で一般的な方法が、シリアルポートを開く際にtimeout引数を設定することです。

# ポートを開く際に、タイムアウトを1秒に設定
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)

このように設定すると、ser.read()ser.readline()は、データが来るのを**最大で1秒だけ**待ちます。1秒経ってもデータが来なければ、空のデータを返してプログラムは次の処理に進みます。これにより、プログラムが完全に固まってしまうのを防ぐことができるのです。

(より高度な方法として、送信処理と受信処理を別々の「スレッド」で並行して動かす「スレッディング」というテクニックもありますが、まずはタイムアウトを使った方法をマスターするのが良いでしょう。)


3. 実践プロジェクト:LED遠隔操作アプリ

それでは、双方向通信を実践してみましょう! Pythonスクリプトから'on'や'off'というコマンドを送り、Arduinoなどのマイコン側でLEDを制御し、結果をPythonに返すという簡単なアプリケーションを作成します。

3.1. 準備するもの

  • Pythonが動作するPC (Jetson, Raspberry PiなどでもOK)
  • Arduino Unoなどのマイクロコントローラ
  • LED 1個
  • 抵抗器 (220Ω〜330Ω程度) 1個
  • ブレッドボードとジャンパーワイヤ

Arduinoの13番ピンに抵抗とLEDを直列に接続し、GNDピンに繋ぐだけの簡単な回路です。

3.2. マイコン側のプログラム (Arduino IDE)

まず、命令を受け取ってLEDを制御する側のプログラムをArduinoに書き込みます。以下のスケッチをArduino IDEにコピーし、Arduinoにアップロードしてください。

// LEDを接続するピン番号
const int ledPin = 13;

void setup() {
  // LEDピンを出力に設定
  pinMode(ledPin, OUTPUT);
  // シリアル通信を開始 (ボーレートはPython側と合わせる)
  Serial.begin(115200);
  Serial.println("Arduino is ready.");
}

void loop() {
  // シリアルポートにデータが到着しているかチェック
  if (Serial.available() > 0) {
    // 改行までを文字列として読み込む
    String command = Serial.readStringUntil('\n');
    command.trim(); // 前後の空白や改行を削除

    if (command == "on") {
      digitalWrite(ledPin, HIGH); // LEDを点灯
      Serial.println("LED turned ON"); // 確認メッセージをPCに送信
    } 
    else if (command == "off") {
      digitalWrite(ledPin, LOW); // LEDを消灯
      Serial.println("LED turned OFF"); // 確認メッセージをPCに送信
    }
  }
}

3.3. PC側の制御プログラム (Python)

次に、PCからコマンドを送信し、Arduinoからの返信を受け取るPythonスクリプトを作成します。

import serial
import time

# Arduinoが接続されているシリアルポートを指定
# Windowsなら "COM3" など、macOSなら "/dev/cu.usbmodem..." など
SERIAL_PORT = '/dev/ttyACM0' 
BAUD_RATE = 115200

try:
    # ポートを開く。timeoutを1秒に設定
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
    time.sleep(2) # Arduinoがリセットされるのを待つ
    print(f"シリアルポート {SERIAL_PORT} に接続しました。")
    print("コマンドを入力してください ('on', 'off', または 'q'で終了)")

    while True:
        # ユーザーからのキーボード入力を受け取る
        command = input("> ")

        if command.lower() == 'q':
            break # 'q'が入力されたらループを抜ける
        
        if command.lower() in ['on', 'off']:
            # コマンドをバイト列にエンコードして送信(改行コード'\n'を付ける)
            ser.write((command + '\n').encode('utf-8'))
            print(f"送信: {command}")
            
            # マイコンからの返信を待つ
            time.sleep(0.1) # 返信が来るまで少し待つ
            response = ser.readline().decode('utf-8').strip()
            
            if response:
                print(f"受信: {response}")
            else:
                print("マイコンからの応答がありませんでした。")
        else:
            print("無効なコマンドです。'on', 'off', 'q'のいずれかを入力してください。")

except serial.SerialException as e:
    print(f"シリアルポートエラー: {e}")
except KeyboardInterrupt:
    print("\nプログラムを終了します。")
finally:
    if ser and ser.is_open:
        ser.close()
        print(f"シリアルポートを閉じました。")

ArduinoスケッチではreadStringUntil('\n')で改行までを1つのコマンドとして読み取っているため、Python側では送信するコマンドの末尾に改行コード'\n'を付けています。このように、送受信するデータの「区切り」をどうするかを両者で決めておくことが、安定した通信の秘訣です。


4. まとめと次回予告

今回は、Pythonのpyserialライブラリを使い、PCとマイコン(Arduino)が対話する**「双方向通信」**に挑戦しました。

  • データ受信時にプログラムが停止してしまう**ブロッキング**問題と、その解決策である**タイムアウト設定**。
  • Pythonからコマンドを送信し、マイコン側でそれに応じた処理(LED制御)を行い、さらにマイコンから確認メッセージをPythonに返すという、一連の**対話的処理**の実装。
  • 通信を安定させるための、改行コードなどの**データ区切り**の重要性。

これで、あなたはPythonを司令塔として、外部のハードウェアを自由に制御し、その状態を知ることができるようになりました。これは、IoTやフィジカルコンピューティングの世界への大きな一歩です!

さて、これまでのシリーズで通信の基本は完璧にマスターしました。次回、いよいよシリーズ最終回(応用編)です!より実用的な外部デバイスであるGPSモジュールと連携し、そこから送られてくる生のシリアルデータを解析して、意味のある位置情報(緯度・経度)を抽出し、最終的にWebブラウザの地図上に表示するという、データ処理の一連のプロジェクトに挑戦します。お楽しみに!

その他の投稿はこちら

コメント

このブログの人気の投稿

タイトルまとめ

これで迷わない!WindowsでPython環境構築する一番やさしい方法 #0

【Python標準ライブラリ完結!】11の冒険をありがとう!君のPython力が飛躍する「次の一歩」とは? 🚀