PythonからArduinoのLEDやモーターを直接動かしたい」「GUIのボタンでハードウェアを制御するアプリを作りたい」そんな方に向けた記事です。😊

ArduinoとPythonの連携というと「センサーデータを受信して可視化する」イメージが強いですが、今回は逆方向——PythonからArduinoへ命令を送り、実際のデバイスを動かす「出力制御」に特化して解説します。

LED・サーボモーター・ブザー・リレーをPythonからコントロールする方法と、tkinterで作る簡易GUIダッシュボードからボタン一発でArduinoを操作するところまで、実用的なサンプルコードつきで進めていきましょう!

※シリアル通信の基礎やセンサーデータの受信・可視化については別記事で詳しく解説しています。
👉 PythonとArduinoをシリアル通信で連携させる方法【pyserial入門・サンプルコード付き】
👉 Python × Arduino シリアル通信入門!センサーデータをリアルタイム可視化する方法

🎯 この記事で学べること

arduino LED servo control
arduino LED servo control / Photo by Chengxin Zhao via Pexels
  • ✅ PythonからLED・サーボ・ブザー・リレーを制御するコマンド設計
  • ✅ 複数デバイスを1つのArduinoスケッチで一括管理する方法
  • ✅ tkinterでGUIダッシュボードを作り、ボタンでArduinoを操作する方法
  • ✅ 難易度:初〜中級者向け

「Pythonからハードウェアをコントロールしたい」という方に、ズバリ刺さる内容になっています。では早速始めましょう!

⚙️ 準備するもの

  • Arduino本体(Arduino Uno など)
  • USBケーブル(ArduinoとPCを接続)
  • LED・抵抗・ブレッドボード
  • サーボモーター(SG90など)
  • アクティブブザー(電子ブザー)※パッシブブザーを使う場合は、digitalWriteの代わりに tone(BUZZ_PIN, 1000) / noTone(BUZZ_PIN) で鳴らしてください
  • リレーモジュール(5V対応)
  • Arduino IDE
  • Python 3.x がインストール済みのPC
  • Pythonライブラリ:pyserial(シリアル通信用)

pyserialのインストールはターミナルで1行です。

pip install pyserial

tkinterはPython標準ライブラリなので追加インストール不要です。これで準備はOK!

🗂️ コマンド設計の考え方

PythonからArduinoを制御するとき、最初に決めるべきなのが「どんな文字列コマンドを送るか」の設計です。

今回は以下のようなシンプルな文字列コマンドを使います。

コマンド文字列 動作
LED_ON LEDをONにする
LED_OFF LEDをOFFにする
SERVO:90 サーボを90°に動かす
BUZZ_ON ブザーを鳴らす
BUZZ_OFF ブザーを止める
RELAY_ON リレーをONにする
RELAY_OFF リレーをOFFにする

ポイントは「デバイス名_動作」という命名規則に統一することです。あとからコマンドが増えてもスケッチ側で管理しやすくなります。サーボのように値を伴うコマンドは SERVO:90 のようにコロンで角度を渡す設計にします。

🛠️ STEP 1:Arduinoスケッチ(複数デバイスを一括管理)

まずはArduino側のコードです。上記のコマンドをシリアルで受け取り、対応するデバイスを制御するスケッチを書きます。

#include <Servo.h>

// ピン番号の定義
const int LED_PIN   = 13;  // 内蔵LED
const int BUZZ_PIN  = 8;   // ブザー
const int RELAY_PIN = 7;   // リレーモジュール
const int SERVO_PIN = 9;   // サーボモーター

Servo myServo;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN,   OUTPUT);
  pinMode(BUZZ_PIN,  OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  myServo.attach(SERVO_PIN);
  myServo.write(0);  // 初期角度0度
}

void loop() {
  if (Serial.available() > 0) {
    // 改行までを1コマンドとして読み取る
    String command = Serial.readStringUntil('\n');
    command.trim();  // 余分な空白・改行を除去

    if (command == "LED_ON") {
      digitalWrite(LED_PIN, HIGH);
      Serial.println("OK:LED_ON");

    } else if (command == "LED_OFF") {
      digitalWrite(LED_PIN, LOW);
      Serial.println("OK:LED_OFF");

    } else if (command.startsWith("SERVO:")) {
      // "SERVO:90" -> 角度90を取り出す
      int angle = command.substring(6).toInt();
      angle = constrain(angle, 0, 180);  // 0〜180度に制限
      myServo.write(angle);
      Serial.println("OK:SERVO:" + String(angle));

    } else if (command == "BUZZ_ON") {
      digitalWrite(BUZZ_PIN, HIGH);
      Serial.println("OK:BUZZ_ON");

    } else if (command == "BUZZ_OFF") {
      digitalWrite(BUZZ_PIN, LOW);
      Serial.println("OK:BUZZ_OFF");

    } else if (command == "RELAY_ON") {
      digitalWrite(RELAY_PIN, HIGH);
      Serial.println("OK:RELAY_ON");

    } else if (command == "RELAY_OFF") {
      digitalWrite(RELAY_PIN, LOW);
      Serial.println("OK:RELAY_OFF");

    } else {
      Serial.println("ERR:UNKNOWN_CMD");
    }
  }
}

スケッチのポイントをまとめます👇

  • Serial.readStringUntil(‘\n’):改行文字を区切りに1コマンド丸ごと受け取ります。1文字ずつ読むより扱いやすいです
  • command.startsWith(“SERVO:”):コロン区切りで角度値を柔軟に受け取れます
  • constrain(angle, 0, 180):サーボに無理な角度を送らないよう範囲を制限する安全処置です
  • OK: / ERR: のレスポンス返信:Python側でコマンドが届いたか確認できるようにしています

このスケッチをArduino IDEでボードに書き込んでおきましょう。

🐍 STEP 2:PythonからコマンドをArduinoに送る


次にPython側です。pyserialを使ってコマンドを送り、Arduinoからのレスポンスを確認します。まずはシンプルなCLI(コマンドライン)版で動作確認をしましょう。

import serial
import time

# --- 設定 ---
# Windows: 'COM3' など / Mac・Linux: '/dev/ttyUSB0' など
PORT      = '/dev/ttyUSB0'  # ← 自分の環境に合わせて変更!
BAUD_RATE = 9600

def send_command(ser, command):
    """Arduinoにコマンドを送信してレスポンスを返す"""
    # 改行コード付きでバイト列として送信
    ser.write((command + '\n').encode('utf-8'))
    time.sleep(0.1)  # Arduinoの処理待ち

    response = ''
    if ser.in_waiting > 0:
        response = ser.readline().decode('utf-8').strip()
    return response

# シリアルポートを開く
ser = serial.Serial(PORT, BAUD_RATE, timeout=1)
time.sleep(2)  # Arduinoのリセット待ち(重要!)
print('Arduinoに接続しました')

try:
    # LEDをON
    res = send_command(ser, 'LED_ON')
    print(f'LED ON  → レスポンス: {res}')
    time.sleep(1)

    # LEDをOFF
    res = send_command(ser, 'LED_OFF')
    print(f'LED OFF → レスポンス: {res}')
    time.sleep(1)

    # サーボを90度に動かす
    res = send_command(ser, 'SERVO:90')
    print(f'SERVO 90° → レスポンス: {res}')
    time.sleep(1)

    # ブザーを1秒鳴らして止める
    res = send_command(ser, 'BUZZ_ON')
    print(f'BUZZ ON → レスポンス: {res}')
    time.sleep(1)
    res = send_command(ser, 'BUZZ_OFF')
    print(f'BUZZ OFF → レスポンス: {res}')

finally:
    ser.close()
    print('接続を閉じました')

実行すると、LEDが光り→消え→サーボが動き→ブザーが1秒鳴る、という流れをPythonスクリプトから制御できます。send_command()関数を1つ用意しておくだけで、どのデバイスも同じ書き方で操作できるのがポイントです。

🖥️ STEP 3:tkinterでGUI制御ダッシュボードを作る

CLIで動作確認できたら、いよいよGUIダッシュボードを作りましょう。tkinterを使えば、ボタンをクリックするだけでArduinoを操作できる本格的なアプリになります。

import tkinter as tk
from tkinter import ttk
import serial
import time
import threading

# --- 設定 ---
PORT      = '/dev/ttyUSB0'  # ← 自分の環境に合わせて変更!
BAUD_RATE = 9600

class ArduinoController:
    """シリアル通信をラップするクラス"""
    def __init__(self, port, baud_rate):
        self.ser = serial.Serial(port, baud_rate, timeout=1)
        time.sleep(2)  # Arduinoのリセット待ち

    def send(self, command):
        """コマンド送信 → レスポンス文字列を返す"""
        self.ser.write((command + '\n').encode('utf-8'))
        time.sleep(0.1)
        if self.ser.in_waiting > 0:
            return self.ser.readline().decode('utf-8').strip()
        return ''

    def close(self):
        self.ser.close()


class App(tk.Tk):
    def __init__(self, controller):
        super().__init__()
        self.controller = controller
        self.title('Arduino 制御ダッシュボード')
        self.resizable(False, False)
        self._build_ui()

    def _build_ui(self):
        pad = {'padx': 10, 'pady': 6}

        # ---- LED セクション ----
        led_frame = ttk.LabelFrame(self, text='LED 制御')
        led_frame.grid(row=0, column=0, **pad, sticky='ew')
        ttk.Button(led_frame, text='LED ON',  width=14,
                   command=lambda: self._send('LED_ON')).grid(row=0, column=0, **pad)
        ttk.Button(led_frame, text='LED OFF', width=14,
                   command=lambda: self._send('LED_OFF')).grid(row=0, column=1, **pad)

        # ---- サーボ セクション ----
        servo_frame = ttk.LabelFrame(self, text='サーボ制御 (0〜180°)')
        servo_frame.grid(row=1, column=0, **pad, sticky='ew')
        self.servo_var = tk.IntVar(value=90)
        self.servo_scale = ttk.Scale(servo_frame, from_=0, to=180,
                                     orient='horizontal', variable=self.servo_var,
                                     length=200)
        self.servo_scale.grid(row=0, column=0, columnspan=2, **pad)
        self.servo_label = ttk.Label(servo_frame, text='角度: 90°')
        self.servo_label.grid(row=1, column=0, **pad)
        ttk.Button(servo_frame, text='角度を送信',
                   command=self._send_servo).grid(row=1, column=1, **pad)
        # スライダーの値表示をリアルタイム更新
        self.servo_var.trace_add('write', self._update_servo_label)

        # ---- ブザー セクション ----
        buzz_frame = ttk.LabelFrame(self, text='ブザー制御')
        buzz_frame.grid(row=2, column=0, **pad, sticky='ew')
        ttk.Button(buzz_frame, text='BUZZ ON',  width=14,
                   command=lambda: self._send('BUZZ_ON')).grid(row=0, column=0, **pad)
        ttk.Button(buzz_frame, text='BUZZ OFF', width=14,
                   command=lambda: self._send('BUZZ_OFF')).grid(row=0, column=1, **pad)

        # ---- リレー セクション ----
        relay_frame = ttk.LabelFrame(self, text='リレー制御')
        relay_frame.grid(row=3, column=0, **pad, sticky='ew')
        ttk.Button(relay_frame, text='RELAY ON',  width=14,
                   command=lambda: self._send('RELAY_ON')).grid(row=0, column=0, **pad)
        ttk.Button(relay_frame, text='RELAY OFF', width=14,
                   command=lambda: self._send('RELAY_OFF')).grid(row=0, column=1, **pad)

        # ---- ログ表示 ----
        log_frame = ttk.LabelFrame(self, text='ログ')
        log_frame.grid(row=4, column=0, **pad, sticky='ew')
        self.log_text = tk.Text(log_frame, height=6, width=40, state='disabled',
                                font=('Courier', 10))
        self.log_text.pack(**pad)

        # ウィンドウを閉じたときにポートを閉じる
        self.protocol('WM_DELETE_WINDOW', self._on_close)

    def _send(self, command):
        """コマンドをスレッドで非同期送信(UIがフリーズしないように)"""
        threading.Thread(target=self._send_thread, args=(command,), daemon=True).start()

    def _send_thread(self, command):
        response = self.controller.send(command)
        # tkinterはスレッドセーフではないため、afterでメインスレッド経由で更新
        self.after(0, self._log, f'送信: {command}  受信: {response}')

    def _send_servo(self):
        angle = self.servo_var.get()
        self._send(f'SERVO:{angle}')

    def _update_servo_label(self, *args):
        self.servo_label.config(text=f'角度: {self.servo_var.get()}°')

    def _log(self, message):
        """ログウィジェットに追記"""
        self.log_text.config(state='normal')
        self.log_text.insert('end', message + '\n')
        self.log_text.see('end')
        self.log_text.config(state='disabled')

    def _on_close(self):
        self.controller.close()
        self.destroy()


if __name__ == '__main__':
    ctrl = ArduinoController(PORT, BAUD_RATE)
    app  = App(ctrl)
    app.mainloop()

GUIの構成はこんな感じです👇

  • 🔴 LEDセクション:ON / OFF ボタン
  • 🔧 サーボセクション:スライダーで0〜180°を選んで「角度を送信」ボタン
  • 🔔 ブザーセクション:ON / OFF ボタン
  • リレーセクション:ON / OFF ボタン
  • 📝 ログ表示:送信コマンドとArduinoからのレスポンスをリアルタイム表示

threadingで送信を非同期処理しているのがポイントです。シリアル通信の待ち時間中もGUIがフリーズしません。これを入れ忘れると、ボタンを押すたびにウィンドウが一瞬固まるような挙動になるので注意してください⚠️

💡 よくあるトラブルと対処法


実際に動かしてみると詰まりやすいポイントをまとめました。

  • ポートが見つからない → Arduino IDEの「ツール→ポート」で確認。Arduino IDEのシリアルモニタを閉じてからPythonを実行してください(同時使用不可)
  • コマンドが届かない / ERR:UNKNOWN_CMDが返ってくる → baud rateがArduinoとPythonで一致しているか確認。コマンド文字列の末尾に\nが付いているか確認
  • 最初のコマンドが無視されるtime.sleep(2)を忘れずに。接続直後はArduinoがリセット中のため、少し待たないとコマンドが届きません
  • サーボがビリビリ震える → サーボへの電源供給が不足している可能性あり。Arduino本体からではなく外部5V電源から給電し、GNDを共通にしてみてください
  • リレーがONにならない → リレーモジュールによってはHIGH / LOWの論理が逆転している(アクティブLOW)場合があります。RELAY_ONでLOWを出力する設計に変更してみてください

🚀 応用:もっと発展させるには

今回の仕組みを土台にすれば、さまざまな方向に発展させられます。

  • タイマー制御:Pythonのscheduleライブラリで「毎日18時にリレーON(照明を自動点灯)」などの定時制御
  • 🌐 Web制御:FlaskやFastAPIでAPI化すれば、スマホのブラウザからArduinoを操作可能に
  • 🎵 音楽演奏:ブザーに周波数コマンドを加えてBUZZ:440のようにすればドレミ演奏も実現できます
  • 🏠 スマートホーム化:リレーで家電をON/OFFするスマートスイッチとして応用

センサーデータを同時に受信してダッシュボードに表示したい場合は、以下の記事も参考にしてみてください👇

👉 Python × Arduino シリアル通信入門!センサーデータをリアルタイム可視化する方法
👉 ArduinoとPythonで作るIoT温湿度モニタリングシステム【ゼロから動かす入門ガイド】

✅ まとめ

今回学んだことをまとめます。

  • 📌 コマンド文字列の設計(LED_ON / SERVO:90 など)を最初に決めると、スケッチもPythonコードもスッキリ管理できる
  • 📌 ArduinoスケッチでreadStringUntil('\n')を使うと、複数デバイスを1つのスケッチで一括制御できる
  • 📌 Pythonにsend_command()関数を用意するだけで、どのデバイスも同じ書き方で操作できる
  • 📌 tkinterのGUIにthreadingを組み合わせれば、フリーズしないボタン操作ダッシュボードが作れる

「Pythonがハードウェアを動かす司令塔になる」この感覚、一度体験すると電子工作の楽しさが一気に広がります。ぜひ手を動かして試してみてください!😊

📡 Arduinoをもっと深く学ぼう!

Arduino・ラズパイ・ロボットプログラミングを体系的に学びたい方へ。おすすめのUdemyコースや電子部品もまとめています。

Arduinoロボット入門のおすすめコース・部品を見る →

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

Arduinoをはじめよう 第4版 (Make: PROJECTS)

もしも

Arduinoをはじめよう 第4版 (Make: PROJECTS)

Arduino公式推薦の定番入門書

Amazonで見る

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

もしも

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

電子工作とプログラミングを同時に学べる

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

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