PythonからArduinoを制御する方法!LED・サーボ・ブザー・リレーをコマンドで動かす実践ガイド
「PythonからArduinoのLEDやモーターを直接動かしたい」「GUIのボタンでハードウェアを制御するアプリを作りたい」そんな方に向けた記事です。😊
ArduinoとPythonの連携というと「センサーデータを受信して可視化する」イメージが強いですが、今回は逆方向——PythonからArduinoへ命令を送り、実際のデバイスを動かす「出力制御」に特化して解説します。
LED・サーボモーター・ブザー・リレーをPythonからコントロールする方法と、tkinterで作る簡易GUIダッシュボードからボタン一発でArduinoを操作するところまで、実用的なサンプルコードつきで進めていきましょう!
※シリアル通信の基礎やセンサーデータの受信・可視化については別記事で詳しく解説しています。
👉 PythonとArduinoをシリアル通信で連携させる方法【pyserial入門・サンプルコード付き】
👉 Python × Arduino シリアル通信入門!センサーデータをリアルタイム可視化する方法
🎯 この記事で学べること

- ✅ 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コースや電子部品もまとめています。
📚 関連商品・おすすめ書籍
※本記事にはアフィリエイトリンクが含まれます。





