【Pythonで始めるシリアル通信 第3回】PCとマイコンを対話させる!双方向シリアル通信に挑戦 💬
「Pythonから送った命令が、ちゃんとマイコンに届いたか確認したい!」
「マイコン側で何かが起きたら、その情報をPCに送ってほしい…」
「データを送りながら、同時に受信もするってどうやればいいの?」
こんにちは! Pythonシリアル通信探検隊、隊長のPythonistaです! 第1回ではデータを「送信」する方法を、第2回ではデータを「受信」する方法を学びましたね。これで、通信の基本的な片道通行はマスターしました。
シリーズ第3回となる今回は、いよいよその両方を組み合わせた「双方向通信」に挑戦します! これにより、あなたのPythonスクリプトは、単なる監視役や司令塔ではなく、外部デバイスと「対話」する真のパートナーへと進化します。この記事では、Pythonスクリプトからマイコン(Arduinoなど)に命令を送り、マイコンからの返事を受け取る「LED遠隔操作プロジェクト」を通して、双方向通信の勘所を学んでいきましょう!
1. 双方向通信の難しさ:「ブロッキング」という壁
「送信と受信ができるなら、コードを順番に書けばいいだけでは?」と思うかもしれません。しかし、そこには一つ大きな「壁」があります。それは、前回学んだser.read()
やser.readline()
といった読み込み関数が、デフォルトでは**ブロッキングモード**で動作するという点です。
ブロッキングとは、「データが来るまで、プログラムの実行を止めて待ち続ける」という動作でしたね。もし、送信と受信のコードを単純に並べると、
- Pythonがデータを送信する。
- Pythonがマイコンからの返事を待つため、
ser.read()
で停止する。 - マイコン側が、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ブラウザの地図上に表示するという、データ処理の一連のプロジェクトに挑戦します。お楽しみに!
コメント
コメントを投稿