ArduinoとPythonをシリアル通信で連携させ、センサーデータをリアルタイムでグラフ表示する方法をゼロから解説します。「数値を眺めるだけじゃつまらない」——そんな気持ち、すごくわかります。せっかくハードウェアを動かしているのに、シリアルモニターの数字だけじゃ達成感が半分以下ですよね。

この記事では、ArduinoとPythonのシリアル通信の仕組みから、pyserialによるデータ受信、matplotlibを使ったリアルタイム可視化、さらにPythonからArduinoを逆に操作する双方向通信まで一気に通して解説します。IoT入門として最初の一歩にぴったりの内容です。ぜひ手を動かしながら読み進めてみてください。

必要な部品・環境

  • Arduino(UnoやNanoなど何でもOK)
  • 温度センサー(今回はLM35を使用)
  • USBケーブル
  • Python 3.x がインストールされたPC

Pythonのバージョンは3.8以上を推奨します。pyserialおよびmatplotlibはpipでインストールできます(後述)。Windowsでもmacでも同じ手順で動作確認済みです。

ArduinoとPythonがシリアル通信で繋がる仕組み

まず「なぜArduinoとPythonがシリアル通信でデータをやり取りできるのか」を簡単に押さえておきましょう。

ArduinoをUSBでPCに繋ぐと、PC側には仮想COMポート(シリアルポート)が作られます。Arduino側はこのポートにデータをテキスト形式で送信でき、Python側はpyserialというライブラリを使ってそのデータをリアルタイムに受け取れます。

イメージとしては「ArduinoがUSB経由でPCにメモを渡し続けて、Pythonがそのメモを読み続ける」感じです。これがArduino Python連携の基本となるシリアル通信の仕組みです。実はこのやり取りは一方通行ではなく、Python側からArduinoにメモを送り返すことも可能です。記事の後半では、この双方向の仕組みを使ってPythonからArduinoを操作するところまで挑戦します。

通信速度はボーレート(baud rate)と呼ばれる数値で管理されており、Arduino側とPython側で必ず同じ値を設定する必要があります。今回は一般的な9600を使用します。

STEP 1:Arduinoのスケッチ(温度センサーのデータをシリアル送信)

まずArduino側のプログラムです。LM35温度センサーのアナログ値を読み取って、シリアルポートに送り続けるだけのシンプルな内容です。

// 温度センサー(LM35)のデータをシリアル送信するスケッチ

const int sensorPin = A0;  // LM35をA0ピンに接続

void setup() {
  Serial.begin(9600);  // ボーレートを9600に設定(Python側と揃える)
}

void loop() {
  int rawValue = analogRead(sensorPin);  // アナログ値を0〜1023で取得

  // LM35は10mV/℃の特性を持つため、電圧→温度に変換
  float voltage = rawValue * (5.0 / 1023.0);
  float temperature = voltage * 100.0;

  // 温度をシリアルポートに1行ずつ送信
  Serial.println(temperature);

  delay(500);  // 0.5秒ごとに送信
}

ポイント:Serial.begin(9600)のボーレートは、後述するPython側の設定と必ず一致させてください。値が違うと文字化けしたデータが届き、Pythonでの変換時にエラーが発生します。

スケッチを書き込んだら、まずArduino IDEのシリアルモニターを開いて温度の数値(例:25.34)が0.5秒おきに流れてくることを確認しましょう。この動作確認を先に済ませておくと、Python側でトラブルが起きたときの切り分けが楽になります。

STEP 2:PythonでArduinoのシリアルデータを受信する(pyserial)

次にPython側の準備です。まずpyserialをインストールします。pyserialはPythonからシリアルポートを操作するための定番ライブラリです。

pip install pyserial

インストールできたら、まずデータを受信してターミナルに表示するだけのシンプルなスクリプトを動かしてみましょう。可視化の前に「ちゃんとデータが来ているか」を確認するためのステップです。

import serial
import time

# COMポートは環境に合わせて変更してください
# Windowsなら 'COM3' など、Macなら '/dev/cu.usbmodem...' など
SERIAL_PORT = 'COM3'
BAUD_RATE = 9600

try:
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
    print(f"{SERIAL_PORT} に接続しました。データ受信を開始します...")
    time.sleep(2)  # Arduino起動待ち(重要:これを省くと最初のデータが壊れる場合がある)

    while True:
        line = ser.readline().decode('utf-8').strip()
        if line:
            temperature = float(line)
            print(f"温度: {temperature:.1f} ℃")

except KeyboardInterrupt:
    print("\n終了しました")
except serial.SerialException as e:
    print(f"接続エラー: {e}")
finally:
    if 'ser' in locals() and ser.is_open:
        ser.close()
        print("シリアルポートを閉じました")

time.sleep(2)はArduinoのリセット完了を待つための待機処理です。これを省略すると最初の数バイトが欠けることがあるので、必ず入れておきましょう。COMポートが分からない場合は、Arduino IDEの「ツール → ポート」から確認できます。

ターミナルに「温度: 25.3 ℃」などの数値が流れてくれば、ArduinoとPythonのシリアル通信は成功です。次のステップでこのデータをリアルタイムにグラフ化します。

STEP 3:matplotlibでセンサーデータをリアルタイムにグラフ表示する

いよいよメインの可視化パートです。matplotlibのアニメーション機能を使って、受信した温度データをリアルタイムで折れ線グラフに描画します。まずmatplotlibをインストールします。

pip install matplotlib

以下がリアルタイム可視化の完成スクリプトです。

import serial
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from collections import deque

# --- 設定 ---
SERIAL_PORT = 'COM3'   # 環境に合わせて変更
BAUD_RATE   = 9600
MAX_POINTS  = 60       # グラフに表示するデータ点数(直近60件)

# --- シリアル接続 ---
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2)  # Arduino起動待ち

# --- データバッファ ---
temperatures = deque([0.0] * MAX_POINTS, maxlen=MAX_POINTS)

# --- グラフの初期設定 ---
fig, ax = plt.subplots(figsize=(10, 4))
line, = ax.plot([], [], 'r-', linewidth=1.5, label='温度 (℃)')

ax.set_xlim(0, MAX_POINTS)
ax.set_ylim(0, 50)          # 表示温度範囲:0〜50℃(必要に応じて変更)
ax.set_xlabel('経過時間(サンプル数)')
ax.set_ylabel('温度 (℃)')
ax.set_title('Arduino LM35 リアルタイム温度モニタ(Python / matplotlib)')
ax.legend(loc='upper right')
ax.grid(True, linestyle='--', alpha=0.5)

# --- アニメーション更新関数 ---
def update(frame):
    """FuncAnimationから呼び出される更新関数"""
    if ser.in_waiting > 0:
        try:
            raw = ser.readline().decode('utf-8').strip()
            if raw:
                temp = float(raw)
                temperatures.append(temp)
        except (ValueError, UnicodeDecodeError):
            pass  # 変換失敗時はスキップ

    line.set_data(range(MAX_POINTS), list(temperatures))

    # 最新値をタイトルにも表示
    ax.set_title(
        f'Arduino LM35 リアルタイム温度モニタ '
        f'現在: {temperatures[-1]:.1f} ℃'
    )
    return line,

# --- アニメーション開始 ---
try:
    ani = animation.FuncAnimation(
        fig, update,
        interval=500,   # 500ms ごとに update() を呼び出す
        blit=True,
        cache_frame_data=False
    )
    plt.tight_layout()
    plt.show()
finally:
    ser.close()
    print("シリアルポートを閉じました")

コードの要点を整理します。

  • deque(両端キュー)maxlen=MAX_POINTSを指定することで、古いデータを自動的に捨てながら最新N件だけを保持します。リストより効率的です。
  • FuncAnimation:指定した間隔(ここでは500ms)でupdate()関数を繰り返し呼び出し、グラフを再描画します。matplotlibでリアルタイム表示を行う標準的な方法です。
  • blit=True:変化した部分だけを再描画することで、CPU負荷を下げてグラフをスムーズに更新します。
  • 例外処理(ValueError / UnicodeDecodeError):シリアル通信では稀にデータが乱れることがあります。try/exceptで囲んでスキップすることで、スクリプトが途中で落ちるのを防ぎます。

よくあるエラーと対処法

SerialException: could not open port

COMポート名が間違っているか、Arduino IDEやシリアルモニターが同じポートを占有しています。Arduino IDEのシリアルモニターを閉じてから実行してください。Windowsのデバイスマネージャー(またはmacのls /dev/cu.*)でポート名を確認しましょう。

ValueError: could not convert string to float

Arduinoが起動直後に送信する不完全なデータが原因であることがほとんどです。time.sleep(2)を忘れずに入れてください。また、Arduino側のボーレートとPython側のBAUD_RATEが一致しているかも確認してください。

グラフウィンドウが固まる / 更新されない

Jupyter Notebookから実行している場合、%matplotlib notebookマジックコマンドが必要です。通常のPythonスクリプト(.py)としてターミナルから実行するのが最も安定します。

「Permission denied」が出る(Mac / Linux)

シリアルポートへのアクセス権限が不足している場合に発生します。以下のコマンドでポートの権限を変更してから再実行してみてください。

sudo chmod 666 /dev/ttyUSB0

ポート名は環境によって異なるので、ls /dev/tty*で実際のデバイス名を確認してから置き換えてください。

応用1:データをCSVに保存しながら可視化する

リアルタイム表示と並行して、データをCSVに保存しておくと後の分析に便利です。update()関数内に以下の処理を追加するだけで対応できます。

import csv
from datetime import datetime

# ファイルをオープン(追記モード)
csv_file = open('temperature_log.csv', 'a', newline='', encoding='utf-8')
writer = csv.writer(csv_file)
writer.writerow(['timestamp', 'temperature'])  # ヘッダー(初回のみ手動で書く場合)

# update()関数内、temp取得後に追加
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
writer.writerow([timestamp, temp])
csv_file.flush()  # バッファを即時書き出し

保存したCSVはpandasで読み込んで分析したり、Excelで開いてグラフ化したりできます。IoTデータ活用の次の一歩として、ぜひ試してみてください。

応用2:PythonからArduinoへコマンドを送る(双方向通信)

ここまでは「Arduino → Python」への一方向のデータ受信でした。実はシリアル通信は双方向です。今度は逆に、PythonからArduinoへ命令を送ってLEDをON/OFF制御してみましょう。「センサーの値を見る」から「Arduinoを操作する」へ、できることが一気に広がります。

まずArduino側のスケッチを、コマンドを受信できる内容に書き換えます(LM35接続用のUSBポートはそのまま、実験用に別のArduino/別スケッチとして試すのがおすすめです)。

// Arduino側:Pythonから'1'を受信したらLED ON、'0'でOFF

const int LED_PIN = 13;  // 内蔵LEDのピン番号

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);  // LEDピンを出力モードに設定
}

void loop() {
  if (Serial.available() > 0) {      // 受信データがあれば
    char received = Serial.read();   // 1文字読み取る
    if (received == '1') {
      digitalWrite(LED_PIN, HIGH);   // LED ON
      Serial.println("LED: ON");     // Pythonへ確認メッセージを返す
    } else if (received == '0') {
      digitalWrite(LED_PIN, LOW);    // LED OFF
      Serial.println("LED: OFF");
    }
  }
}

次にPython側のコードです。キーボードから入力したコマンドをArduinoに送信し、Arduinoからの返信も受け取ります。

import serial
import time

SERIAL_PORT = 'COM3'   # 環境に合わせて変更
BAUD_RATE = 9600

ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2)  # Arduino起動待ち

print("LED制御スタート! 'q' で終了")

try:
    while True:
        command = input("コマンドを入力(1: ON / 0: OFF): ")
        if command == 'q':
            break
        if command in ['0', '1']:
            ser.write(command.encode('utf-8'))  # 文字列をバイト列に変換して送信
            time.sleep(0.1)  # 返信を少し待つ
            response = ser.readline().decode('utf-8').strip()
            print(f"Arduino: {response}")
        else:
            print("0 か 1 を入力してください")
except KeyboardInterrupt:
    pass
finally:
    ser.close()  # 必ずポートを閉じる
    print("接続を終了しました")

実際に動かして「1」を打ち込んだ瞬間にArduinoのLEDが光ると、なんとも言えない達成感がありますよ。ポイントは以下の3つです。

  • ser.write() に渡すのはバイト列。encode('utf-8') を忘れずに
  • 送信後に少しだけ time.sleep() を入れると、Arduinoの返信を安定して受け取れます
  • finally ブロックで ser.close() することで、ポートを確実に解放できます

この仕組みを応用すれば、Pythonで作ったGUIボタンからArduinoのモーターやリレーを操作する、といった発展も可能です。

まとめ:Arduino Python シリアル通信 + リアルタイム可視化の全体像

この記事でやったことを整理します。

  • ✅ LM35温度センサーの値をArduinoで読み取り、シリアルポートに送信
  • ✅ Pythonのpyserialでシリアルデータを受信・デコード
  • ✅ matplotlibのFuncAnimationでリアルタイムグラフを描画
  • ✅ エラー処理・CSV保存など実用的なポイントも網羅
  • ✅ PythonからArduinoへコマンドを送る双方向通信にも挑戦

「Arduinoからデータを取る → Pythonで受け取る → 可視化する → 逆にArduinoを操作する」というこの流れは、IoTプロジェクトの基本パターンです。センサーを湿度センサー(DHT11)や照度センサー(フォトレジスタ)に変えても、ほぼ同じコードで動かせます。まずは今回のコードをそのままコピーして、動く状態を手元に作ることから始めてみてください。

よくある質問(FAQ)

Q. Arduino UnoとNanoで動作の違いはありますか?

A. スケッチのコードは同じで動きます。COMポート番号がArduino IDEで変わるだけです。

Q. LM35以外のセンサー(DHT11など)でも使えますか?

A. はい。Arduino側のスケッチをセンサーに合わせて変更し、Serial.println()で数値を送信する部分は同じです。Python側はほぼ変更不要です。

Q. Raspberry Piでも同じ方法は使えますか?

A. 使えます。Raspberry PiにPythonとpyserialをインストールし、シリアルポート名を/dev/ttyUSB0などに変更するだけで動作します。

Q. センサーを読むだけでなく、Arduino側を操作することもできますか?

A. できます。「応用2」で紹介した通り、ser.write()でArduinoにコマンド(文字)を送り、Arduino側でSerial.available()Serial.read()を使って受信すれば、LEDやモーターなどをPythonから制御できます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。