Arduino Python serielle Kommunikation: pyserial, CSV, Live-Plot
Arduino Python serielle Kommunikation mit pyserial: installieren, Port finden, Sensordaten als CSV speichern, Befehle senden und Live-Diagramm mit matplotlib.
Arduino Python serielle Kommunikation bedeutet, dass ein Python-Programm auf deinem Computer über das USB-Kabel Daten mit einem Arduino austauscht. Der Arduino misst einen Sensor und schreibt das Ergebnis auf die serielle Schnittstelle; Python liest diese Zeilen mit der Bibliothek pyserial, speichert sie in einer CSV-Datei, zeichnet ein Diagramm oder trifft eine Entscheidung und schickt einen Befehl zurück an den Arduino. So verbindest du die präzise Hardwaresteuerung des Arduino mit der Stärke von Python bei Datenverarbeitung, Diagrammen, KI und Automatisierung. Ein zusätzliches Modul brauchst du dafür nicht: Das USB-Kabel, mit dem du Sketche hochlädst, genügt.
In dieser Anleitung lernst du Schritt für Schritt, pyserial zu installieren, den Port des Arduino automatisch zu finden, Sensordaten mit Zeitstempel in einer CSV-Datei zu speichern, von Python aus LED- und PWM-Befehle an den Arduino zu senden und mit matplotlib ein Live-Diagramm zu zeichnen. Danach geht es um die Wahl von Datenformat und Zeilenende, die zwei verwirrendsten Probleme (Port belegt und der Neustart des Boards beim Öffnen des Ports) und darum, was anders ist, wenn du dasselbe mit MicroPython auf einem Raspberry Pi Pico machst. Die Beispiele sind für den Arduino Uno geschrieben; die nötigen Änderungen für ESP32 und Pico findest du jeweils separat.

Wie kommuniziert der Arduino mit Python?
Der USB-Seriell-Chip auf dem Arduino Uno (beim Original ein ATmega16U2, bei Klonen meist ein CH340) erscheint am Computer als virtuelle serielle Schnittstelle: unter Windows als COM3, COM4 usw., unter macOS als /dev/cu.usbmodem… oder /dev/cu.usbserial-…, unter Linux als /dev/ttyACM0 oder /dev/ttyUSB0. Jedes Zeichen, das der Arduino mit Serial.print() schreibt, fließt als Byte über diesen Port zum Computer; auf der Python-Seite öffnet pyserial den Port wie eine Datei, liest daraus und schreibt hinein. Ein wichtiger Unterschied: Der Arduino Uno führt keinen Python-Code aus. Auf dem Board läuft in C++ geschriebener Arduino-Code, auf dem Computer läuft Python, und beide verständigen sich nur über die serielle Leitung. Damit beide Seiten dieselbe Sprache sprechen, müssen diese Einstellungen übereinstimmen:
| Einstellung | Arduino-Seite | Python-Seite | Achtung |
|---|---|---|---|
| Port | Entsteht automatisch über USB | serial.Serial("COM3", ...) | Der Portname hängt vom Betriebssystem ab |
| Geschwindigkeit (Baud) | Serial.begin(115200) | serial.Serial(port, 115200) | Weichen sie ab, kommen unleserliche Zeichen an |
| Rahmen | 8 Datenbits, keine Parität, 1 Stoppbit (8N1) | Auch der pyserial-Standard ist 8N1 | Musst du meist nicht ändern |
| Zeilenende | Serial.println() hängt \r\n an | readline() liest, bis es \n sieht | Beim Senden aus Python \n nicht vergessen |
| Timeout | Serial.setTimeout(), Standard 1000 ms | timeout=1 (Sekunden) | Mit timeout=None wartet readline() ewig |
pyserial installieren (Windows, macOS, Linux)
- Python 3 muss installiert sein. Setze unter Windows auf dem ersten Bildschirm des Installers von der offiziellen Python-Website das Häkchen bei Add python.exe to PATH; unter macOS und Linux ist Python 3 meist schon vorhanden.
- Öffne ein Terminal (unter Windows die Eingabeaufforderung oder PowerShell) und gib python -m pip install pyserial ein. Erkennt Windows den Befehl python nicht, versuche py -m pip install pyserial; unter macOS und Linux kann der Befehl python3 -m pip install pyserial lauten.
- Prüfe die Installation: python -c "import serial; print(serial.__version__)" sollte eine Versionsnummer wie 3.5 ausgeben.
- Für den Abschnitt mit dem Live-Diagramm brauchst du außerdem matplotlib: python -m pip install matplotlib.
- Unter Linux fügst du deinen Benutzer der Gruppe dialout hinzu, damit du den Port öffnen darfst: sudo usermod -a -G dialout $USER, danach ab- und wieder anmelden. Meldet pip unter Raspberry Pi OS oder neueren Ubuntu-Versionen den Fehler externally-managed-environment, nimm sudo apt install python3-serial oder lege mit python3 -m venv eine virtuelle Umgebung an und starte pip darin.
An welchem Port hängt der Arduino? Port mit Python finden
Am einfachsten schaust du in der Arduino IDE unter Werkzeuge > Port nach, aber auch Python kann die Ports auflisten. Gib im Terminal python -m serial.tools.list_ports -v ein, dann siehst du für jeden Port Namen, Beschreibung und Hardware-ID (VID:PID). Wenn du nicht erkennst, welche Zeile der Arduino ist, zieh das Board ab, führe den Befehl erneut aus und merke dir den Port, der verschwunden ist. Unter macOS erscheint dasselbe Board sowohl als /dev/tty… als auch als /dev/cu…; verwende in Python den Eintrag, der mit cu beginnt.

| USB-Chip | Wo steckt er? | VID:PID | Name unter Windows | Portname unter Linux |
|---|---|---|---|---|
| ATmega16U2 | Original Uno R3, Mega 2560 | 2341:0043 (Uno) | Arduino Uno (COMx) | /dev/ttyACM0 |
| CH340 | Uno-, Nano- und Mega-Klone | 1A86:7523 | USB-SERIAL CH340 (COMx) | /dev/ttyUSB0 |
| CP2102 | ESP32 DevKit, NodeMCU | 10C4:EA60 | Silicon Labs CP210x USB to UART Bridge (COMx) | /dev/ttyUSB0 |
| FT232R | Ältere Nanos, FTDI-Kabel | 0403:6001 | USB Serial Port (COMx) | /dev/ttyUSB0 |
| RP2040 (integriertes USB) | Raspberry Pi Pico mit MicroPython | 2E8A:0005 | Serielles USB-Gerät (COMx) | /dev/ttyACM0 |
Die folgende Funktion listet alle Ports auf und gibt den Portnamen zurück, sobald sie ein bekanntes Board an seiner VID erkennt. Speicherst du den Code als port_bul.py, kannst du in deinen anderen Skripten from port_bul import port_bul und PORT = port_bul() schreiben und musst den Portnamen nicht jedes Mal von Hand ändern.
1import serial.tools.list_ports
2
3# Bilinen USB yongalarının üretici kimlikleri (VID) / Vendor IDs of common USB chips
4BILINEN = {
5 0x2341: "Arduino (orijinal / genuine)",
6 0x2A03: "Arduino (orijinal / genuine)",
7 0x1A86: "CH340 (klon / clone)",
8 0x10C4: "CP210x (ESP32, NodeMCU)",
9 0x0403: "FTDI",
10 0x2E8A: "Raspberry Pi Pico",
11}
12
13
14def port_bul():
15 portlar = sorted(serial.tools.list_ports.comports())
16 for p in portlar:
17 print(p.device, "|", p.description, "|", p.hwid) # tüm portları listele / list every port
18 for p in portlar:
19 if p.vid in BILINEN:
20 print("Bulundu / found:", p.device, "->", BILINEN[p.vid])
21 return p.device
22 return None
23
24
25if __name__ == "__main__":
26 if port_bul() is None:
27 print("Kart yok: kabloyu ve surucuyu kontrol et / no board: check cable and driver")Daten vom Arduino senden: der erste Sketch
Die Beispiele verwenden ein Potentiometer an Pin A0: Der mittlere Anschluss geht an A0, einer der äußeren an 5V, der andere an GND. Statt des Potentiometers kannst du auch einen LDR mit einem 10-kΩ-Widerstand als Spannungsteiler anschließen. Der Sketch misst alle 200 ms und sendet das Ergebnis als einzelne Zeile im Format Zeit,Wert:
1// A0 değerini "millis,deger" CSV satırı olarak gönderir / Sends the A0 reading as a "millis,value" CSV line
2const int SENSOR_PIN = A0; // potansiyometrenin orta ucu / potentiometer wiper
3const unsigned long ARALIK = 200; // gönderim aralığı, ms / send interval, ms
4unsigned long sonGonderim = 0;
5
6void setup() {
7 Serial.begin(115200); // Python'daki hızla aynı olmalı / must match the baud rate in Python
8}
9
10void loop() {
11 if (millis() - sonGonderim >= ARALIK) { // delay() olmadan zamanlama / timing without delay()
12 sonGonderim = millis();
13 int deger = analogRead(SENSOR_PIN); // 0-1023
14 Serial.print(sonGonderim); // zaman damgası / timestamp
15 Serial.print(','); // ayırıcı / separator
16 Serial.println(deger); // sonuna \r\n ekler / appends \r\n
17 }
18}Mit millis() statt delay() kannst du später leichter Befehle aus Python in dieselbe Schleife einbauen; Details findest du unter millis und delay. Öffnest du nach dem Hochladen den seriellen Monitor mit 115200 Baud, siehst du Zeilen wie 2573,512 durchlaufen. Das ist nur ein Test: Schließe den seriellen Monitor unbedingt, bevor du das Python-Skript startest, sonst ist der Port belegt. Wenn du den seriellen Monitor zum ersten Mal benutzt, hilft dir die Anleitung zum seriellen Monitor.
Arduino-Daten mit Python lesen und als CSV speichern
1import csv
2import time
3from datetime import datetime
4
5import serial
6
7PORT = "COM3" # macOS: /dev/cu.usbmodem14101, Linux: /dev/ttyACM0
8BAUD = 115200 # Arduino'daki Serial.begin ile aynı / same as Serial.begin on the Arduino
9
10with serial.Serial(PORT, BAUD, timeout=1) as ser, \
11 open("olcumler.csv", "w", newline="", encoding="utf-8") as dosya:
12 time.sleep(2) # Uno port açılınca resetlenir / the Uno resets when the port opens
13 ser.reset_input_buffer() # yarım kalmış eski veriyi at / drop stale, partial data
14 yazici = csv.writer(dosya)
15 yazici.writerow(["tarih", "zaman_ms", "deger", "volt"]) # başlık satırı / header row
16 print("Kayit basladi, durdurmak icin Ctrl+C / logging, press Ctrl+C to stop")
17 try:
18 while True:
19 satir = ser.readline().decode("utf-8", errors="ignore").strip()
20 parcalar = satir.split(",")
21 if len(parcalar) != 2:
22 continue # boş ya da beklenmeyen satır / empty or unexpected line
23 try:
24 zaman_ms, deger = int(parcalar[0]), int(parcalar[1])
25 except ValueError:
26 continue # bozuk satır / corrupted line
27 volt = deger * 5.0 / 1023 # Uno ADC: 0-1023 -> 0-5 V
28 yazici.writerow([datetime.now().isoformat(timespec="seconds"),
29 zaman_ms, deger, round(volt, 3)])
30 dosya.flush() # satırı hemen diske yaz / write the row to disk now
31 print(zaman_ms, deger, f"{volt:.2f} V")
32 except KeyboardInterrupt:
33 print("Durduruldu / stopped")Dieses Skript öffnet den Port mit timeout=1: readline() wartet höchstens 1 Sekunde auf ein Zeilenende, liefert ein leeres Bytes-Objekt, wenn nichts kommt, und die Schleife hängt nie fest. Die empfangenen Daten sind vom Typ bytes; decode() macht Text daraus, strip() entfernt das \r\n am Ende und split(",") teilt die Zeile in Spalten. time.sleep(2) und reset_input_buffer() nach dem Öffnen des Ports verwerfen die halben Zeilen, die beim Neustart des Uno ankommen; warum, siehst du weiter unten. Öffnest du die Datei mit newline="", entstehen unter Windows keine Leerzeilen, und flush() sorgt dafür, dass die bisherigen Messwerte auf der Festplatte bleiben, auch wenn das Programm plötzlich beendet wird. Mit Strg+C im Terminal beendest du die Aufzeichnung.
Befehle von Python an den Arduino senden
Die Kommunikation funktioniert in beide Richtungen. Der folgende Arduino-Sketch wartet auf zeilenweise eintreffende Befehle: LED ON und LED OFF schalten die eingebaute LED, PWM 128 stellt die Helligkeit einer LED an Pin 9 ein, und READ schickt den Wert an A0 zurück. Auf jeden Befehl folgt eine kurze Antwort, damit Python weiß, dass er wirklich ausgeführt wurde. Den passenden Vorwiderstand für die LED an Pin 9 findest du mit dem LED-Vorwiderstandsrechner; wie PWM funktioniert, erklärt Was ist PWM?.
1// Python'dan gelen satır komutlarını işler / Handles line-based commands from Python
2const int LED_PIN = LED_BUILTIN; // Uno'da pin 13 / pin 13 on the Uno
3const int PWM_PIN = 9; // 220 ohm dirençli LED / LED with a 220 ohm resistor
4
5void setup() {
6 pinMode(LED_PIN, OUTPUT);
7 pinMode(PWM_PIN, OUTPUT);
8 Serial.begin(115200);
9 Serial.setTimeout(50); // readStringUntil en fazla 50 ms bekler / waits at most 50 ms
10 Serial.println("HAZIR"); // Python bu satırı bekler / Python waits for this line
11}
12
13void loop() {
14 if (Serial.available() > 0) {
15 String komut = Serial.readStringUntil('\n'); // satır sonuna kadar oku / read up to the newline
16 komut.trim(); // \r ve boşlukları sil / strip \r and spaces
17
18 if (komut == "LED ON") {
19 digitalWrite(LED_PIN, HIGH);
20 Serial.println("OK LED ON");
21 } else if (komut == "LED OFF") {
22 digitalWrite(LED_PIN, LOW);
23 Serial.println("OK LED OFF");
24 } else if (komut.startsWith("PWM ")) {
25 int deger = komut.substring(4).toInt(); // "PWM 128" -> 128
26 deger = constrain(deger, 0, 255); // 0-255 aralığında tut / keep it within 0-255
27 analogWrite(PWM_PIN, deger);
28 Serial.print("OK PWM ");
29 Serial.println(deger);
30 } else if (komut == "READ") {
31 Serial.println(analogRead(A0)); // A0 ölçümünü gönder / send the A0 reading
32 } else if (komut.length() > 0) {
33 Serial.print("ERR "); // bilinmeyen komut / unknown command
34 Serial.println(komut);
35 }
36 }
37}1import time
2
3import serial
4
5
6def komut_gonder(ser, komut):
7 ser.write((komut + "\n").encode("ascii")) # satır sonu şart / the newline is required
8 return ser.readline().decode("ascii", errors="ignore").strip() # cevabı bekle / wait for the reply
9
10
11with serial.Serial("COM3", 115200, timeout=2) as ser:
12 bitis = time.time() + 3 # en fazla 3 sn bekle / wait at most 3 s
13 while time.time() < bitis:
14 if ser.readline().strip() == b"HAZIR": # Arduino açıldı / the Arduino is up
15 break
16 print(komut_gonder(ser, "LED ON")) # OK LED ON
17 time.sleep(1)
18 print(komut_gonder(ser, "PWM 128")) # OK PWM 128
19 print("A0 =", komut_gonder(ser, "READ")) # A0 = 512
20 print(komut_gonder(ser, "LED OFF")) # OK LED OFFDer häufigste Fehler auf der Python-Seite ist das fehlende \n am Ende eines Befehls: Findet readStringUntil('\n') kein Zeilenende, wartet der Arduino die mit Serial.setTimeout() festgelegte Zeit ab und verarbeitet den Befehl erst danach. In diesem Sketch sind das 50 ms; mit dem Standardwert von 1000 ms käme jeder Befehl eine Sekunde zu spät. write() akzeptiert nur Bytes, deshalb musst du den Text mit encode() oder mit einem vorangestellten b (b"LED ON\n") in Bytes umwandeln. Das Skript wartet zu Beginn bis zu 3 Sekunden auf die Zeile HAZIR (bereit): Weil der Uno beim Öffnen des Ports neu startet, landet ein sofort gesendeter Befehl im Bootloader und geht verloren. Nach jedem Befehl die Antwort zu lesen, also das Prinzip Anfrage und Antwort, hält beide Seiten synchron und verhindert volle Puffer; der Empfangspuffer des Uno fasst nur 64 Bytes.
Datenformat und Zeilenende: Was solltest du wählen?
Die serielle Leitung liefert dir nur einen Strom von Bytes; wo ein Messwert endet und der nächste beginnt, musst du selbst festlegen. Am robustesten ist es, jeden Messwert in eine eigene Zeile zu schreiben und jede Zeile mit \n abzuschließen. Welches Format du innerhalb der Zeile verwendest, hängt von der Größe deines Projekts ab:
| Format | Beispielzeile | Vorteile | Nachteile |
|---|---|---|---|
| Einzelwert | 512 | Einfachstes Format; der serielle Plotter zeichnet es direkt | Bei mehreren Werten ist unklar, welcher welcher ist |
| CSV | 2573,512,23.5 | Kurz; Excel und pandas lesen es direkt | Die Spaltenreihenfolge muss auf beiden Seiten gleich bleiben |
| Schlüssel:Wert | temp:23.5,hum:41 | Gut lesbar; der serielle Plotter zeigt die Bezeichnungen | Länger und etwas aufwendiger zu zerlegen |
| JSON | {"t":23.5,"h":41} | In Python mit json.loads() in einer Zeile dekodiert | Längstes Format; belastet den knappen RAM des Uno |
| Binär | 2 Bytes: 0x02 0x00 | Schnellste und kürzeste Variante | Nicht menschenlesbar; braucht das Modul struct und ein Startbyte |
Auch Zeilenenden sorgen oft für Verwirrung. Serial.println() hängt an jede Zeile zwei Zeichen an: Wagenrücklauf (\r, CR) und Zeilenvorschub (\n, LF). Pythons readline() wartet nur auf \n, deshalb bleibt am Zeilenende ein \r stehen; strip() entfernt beide. Die Zeilenende-Liste im seriellen Monitor der Arduino IDE bietet Kein Zeilenende, Neue Zeile, Zeilenumbruch (CR) und Beides CR/LF (englisch No Line Ending, New Line, Carriage Return, Both NL & CR); wenn du den Sketch vor Python im seriellen Monitor testest, wähle Neue Zeile, damit dasselbe Zeichen gesendet wird wie aus Python. Gib bei Dezimalzahlen die Nachkommastellen an, etwa Serial.print(temperatur, 1); auf dem Uno unterstützt sprintf das Gleitkommaformat (%f) nicht, deshalb erscheint statt der Zahl ein Fragezeichen, wenn du Dezimalzahlen mit sprintf ausgeben willst.
| Baudrate | Bytes pro Sekunde (8N1) | 12-Byte-Zeilen pro Sekunde | Wann sinnvoll? |
|---|---|---|---|
| 9600 | 960 | 80 | Langsame Sensoren, lange Kabel, ältere Beispiel-Sketche |
| 57600 | 5.760 | 480 | Mittelschnelle Messungen |
| 115200 | 11.520 | 960 | Live-Diagramme, ESP32 und die meisten neuen Projekte |
Auf der seriellen Leitung wird jedes Byte samt Start- und Stoppbit als 10 Bit übertragen, 115200 Baud entsprechen also rund 11.520 Bytes pro Sekunde. Für eine 12 Byte lange Zeile aus 25730,1023 und Zeilenende (\r\n) reicht das für bis zu 960 Zeilen pro Sekunde. Stimmt die Geschwindigkeit auf beiden Seiten nicht überein, kommen unleserliche Zeichen an und Python meldet einen UnicodeDecodeError. 5 bis 20 Messwerte pro Sekunde reichen für das menschliche Auge und die meisten Sensoren völlig aus; unnötig häufiges Senden bremst das Diagramm und bläht die Datei auf.
Live-Diagramm mit matplotlib zeichnen
1from collections import deque
2
3import matplotlib.pyplot as plt
4import serial
5from matplotlib.animation import FuncAnimation
6
7ser = serial.Serial("COM3", 115200, timeout=0.1)
8N = 200 # ekrandaki ölçüm sayısı / readings on screen
9veri = deque([0] * N, maxlen=N) # eskiler kendiliğinden düşer / old values drop out
10
11fig, ax = plt.subplots()
12cizgi, = ax.plot(range(N), list(veri))
13ax.set_ylim(0, 1023) # Uno ADC aralığı / Uno ADC range
14ax.set_xlabel("Ornek / sample")
15ax.set_ylabel("A0")
16
17
18def guncelle(_):
19 while ser.in_waiting: # biriken bütün satırları oku / read every queued line
20 parca = ser.readline().decode("utf-8", errors="ignore").strip().split(",")
21 if len(parca) == 2 and parca[1].isdigit():
22 veri.append(int(parca[1]))
23 cizgi.set_ydata(list(veri))
24 return (cizgi,)
25
26
27ani = FuncAnimation(fig, guncelle, interval=50, blit=True, cache_frame_data=False) # ani silinmemeli / keep ani
28plt.show()
29ser.close()Das Diagramm basiert auf einer deque mit den letzten 200 Messwerten: Jeder neue Wert kommt rechts hinzu, der älteste fällt links automatisch heraus. FuncAnimation ruft die Funktion guncelle (aktualisieren) alle 50 ms auf. Wichtig ist die Schleife while ser.in_waiting in der Funktion: Der Arduino sendet derzeit 5 Zeilen pro Sekunde, doch wenn du die Rate erhöhst und pro Bild nur eine Zeile liest, hinkt das Diagramm der Echtzeit hinterher; diese Schleife liest alle wartenden Zeilen und hält das Diagramm aktuell. Lösche die Variable ani nicht: Verweist keine Variable mehr auf das Animationsobjekt, räumt Python es weg und das Fenster friert ein. Für den ESP32 schreibst du set_ylim(0, 4095), für den Pico set_ylim(0, 65535). Für einen schnellen Blick genügt auch der serielle Plotter der Arduino IDE; der Vorteil von Python ist, dass du das Diagramm speichern, mehrere Quellen kombinieren und die Daten gleichzeitig verarbeiten kannst.
Fehler „Port belegt“ und das Auto-Reset-Problem
Eine serielle Schnittstelle kann immer nur ein Programm gleichzeitig öffnen. Ist während deines Python-Skripts der serielle Monitor oder der serielle Plotter der Arduino IDE offen, hält ein anderes Python-Skript den Port, oder läuft im Hintergrund Software, die Ports durchsucht, etwa 3D-Drucker-Programme, bekommst du unter Windows could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) und unter macOS und Linux Resource busy bzw. Device or resource busy. Umgekehrt gilt dasselbe: Solange ein Python-Skript den Port offen hält, kann die Arduino IDE keinen Code hochladen. Nutze deshalb in deinen Skripten die Konstruktion with serial.Serial(...) as ser:; der Port wird automatisch geschlossen, wenn der Block endet oder ein Fehler auftritt. Gewöhne dir an, das Skript vor dem Hochladen mit Strg+C zu beenden.
Das zweite Problem ist tückischer: Beim Arduino Uno, Nano und Mega ist die DTR-Leitung des USB-Chips über einen 100-nF-Kondensator mit dem RESET-Pin des Prozessors verbunden. Diese Schaltung gibt es, damit die IDE das Board beim Hochladen automatisch neu starten kann, doch auch jedes Mal, wenn pyserial den Port öffnet, ändert sich DTR und das Board startet neu. Es wartet zuerst ein bis zwei Sekunden im Bootloader und führt dann deinen Sketch von vorn aus. In dieser Zeit gesendete Befehle gehen verloren, und die ersten Zeilen, die der Arduino in setup() ausgibt, verpasst du womöglich. Die Lösung: Nach dem Öffnen des Ports mit time.sleep(2) warten oder, robuster, darauf warten, dass der Arduino eine Zeile wie HAZIR sendet; das Befehlsbeispiel oben macht genau das. Öffne den Port einmal am Programmanfang und lass ihn bis zum Ende offen: Eine Schleife, die den Port für jeden Messwert öffnet und schließt, setzt das Board ständig zurück.
Willst du den Reset ganz verhindern, gibt es zwei Wege. Per Software: Erzeuge den Port geschlossen mit ser = serial.Serial(), setze ser.port und ser.baudrate, schreibe ser.dtr = False und rufe dann ser.open() auf. Unter Windows klappt das meistens; unter Linux und macOS kann das Betriebssystem DTR beim Öffnen trotzdem umschalten. Per Hardware: Setze einen 10-µF-Kondensator zwischen RESET und GND des Uno, mit dem Pluspol an RESET. Boards mit integriertem USB wie Leonardo, Micro und Raspberry Pi Pico starten beim Öffnen des Ports nicht neu; bei ihnen können Zeilen verloren gehen, die dein Code ausgibt, bevor Python verbunden ist, deshalb ist es sicherer, wichtige Meldungen erst auf einen Befehl hin zu senden.
Serielle Kommunikation zwischen ESP32 und Python
Der gesamte Python-Code hier funktioniert auch mit dem ESP32; auf der Arduino-Seite musst du nur ein paar Dinge ändern. Nimm für den Analogeingang einen der ADC1-Pins (z. B. GPIO 34). Der ADC des ESP32 hat 12 Bit und bildet 0–3,3 V auf 0–4095 ab, berechne die Spannung in Python also mit deger * 3.3 / 4095. Beim Start gibt der ESP32 mit 115200 Baud eigene Bootmeldungen aus, etwa rst:0x1 (POWERON_RESET),boot:0x13; da die Skripte oben Zeilen überspringen, die sich nicht in Zahlen umwandeln lassen, stören diese Meldungen nicht. Je nach Auto-Reset-Schaltung des Boards und Betriebssystem kann auch der ESP32 beim Öffnen des Ports neu starten, deshalb helfen die Wartezeit und die Zeile HAZIR auch hier. Als USB-Chip ist meist ein CP2102 oder CH340 verbaut. Willst du auf das Kabel verzichten, kann der ESP32 dieselben Daten auch per WLAN senden; sieh dir dazu die Anleitungen ESP32 WebSocket und ESP32 Google Sheets an. Ist es dein erster ESP32, lies zuerst Was ist der ESP32?.
Raspberry Pi Pico und MicroPython: Was ist der Unterschied?

Eine weitere Möglichkeit, Python und Hardware zusammenzubringen, ist, Python direkt auf dem Board auszuführen. Der Raspberry Pi Pico lässt sich mit MicroPython programmieren: Sensoren auslesen, LEDs schalten und Zeitsteuerung erledigst du mit Python-Syntax direkt im Pico. MicroPython ist allerdings eine abgespeckte Version des Desktop-Python; matplotlib, pandas oder OpenCV laufen auf dem Pico nicht. Willst du Daten speichern, Diagramme zeichnen oder sie mit KI verarbeiten, verbindest du also auch den Pico mit Python auf dem Computer. Der pyserial-Code aus diesem Artikel funktioniert auch mit dem Pico; du musst nur Portname, Wertebereich und das Lesen der Befehlsantworten anpassen, wie unten beschrieben.
| Merkmal | Arduino + Python am PC | Pico + MicroPython (eigenständig) | Pico + Python am PC |
|---|---|---|---|
| Wo läuft Python? | Nur auf dem Computer; das Board führt C++ aus | Im Pico selbst | Auf beiden Seiten: MicroPython im Pico, Python am Computer |
| Wird ein Computer gebraucht? | Ja, die Daten werden am Computer verarbeitet | Nein, läuft an USB-Strom oder Batterie | Ja |
| Verfügbare Bibliotheken | pyserial, matplotlib, pandas, numpy, OpenCV | Eingeschränkte Module wie machine und time | Alle Bibliotheken des Computers |
| Baudrate | Wichtig, muss auf beiden Seiten gleich sein | Keine serielle Schnittstelle nötig | Egal, Daten fließen mit USB-Geschwindigkeit |
| Reset beim Öffnen des Ports | Ja bei Uno, Nano und Mega | Entfällt | Nein |
| Wann wählen? | Arduino ist schon vorhanden, fertige Bibliotheken werden gebraucht | Einfache eigenständige Geräte | Eine Sprache (Python) auf beiden Seiten gewünscht |
Der folgende MicroPython-Code ist die Pico-Version des Arduino-Beispiels oben: Er liest das Potentiometer an GP26 alle 200 ms, sendet das Ergebnis im gleichen Format Zeit,Wert und versteht dieselben LED-Befehle. Das Gegenstück zu Serial beim Arduino sind hier print() und sys.stdin; dank select.poll() läuft die Messschleife weiter, während auf Befehle gewartet wird.
1# Pico'ya main.py adıyla kaydet / save it on the Pico as main.py
2import sys
3import select
4import time
5from machine import ADC, Pin
6
7led = Pin("LED", Pin.OUT) # Pico ve Pico W dahili LED / onboard LED on Pico and Pico W
8pot = ADC(26) # GP26 = ADC0, potansiyometre orta ucu / potentiometer wiper
9okuyucu = select.poll()
10okuyucu.register(sys.stdin, select.POLLIN) # USB'den veri gelince haber ver / notify when USB data arrives
11
12son = time.ticks_ms()
13while True:
14 if okuyucu.poll(0): # bekleyen komut var mı? / is a command waiting?
15 komut = sys.stdin.readline().strip()
16 if komut == "LED ON":
17 led.on()
18 print("OK LED ON")
19 elif komut == "LED OFF":
20 led.off()
21 print("OK LED OFF")
22 if time.ticks_diff(time.ticks_ms(), son) >= 200: # 200 ms'de bir ölçüm / one reading every 200 ms
23 son = time.ticks_ms()
24 print("{},{}".format(son, pot.read_u16())) # zaman,değer (0-65535) / time,value (0-65535)Speichere den Code mit Thonny als main.py auf dem Pico, schließe dann Thonny und stecke das USB-Kabel des Pico einmal aus und wieder ein: Thonny belegt die serielle Schnittstelle des Pico selbst, solange es verbunden ist, kann dein Python-Skript den Port nicht öffnen, und beim Verbinden hält Thonny das laufende Programm an; nach dem Neustart läuft main.py automatisch. Am USB-Port des Pico spielt die Baudrate keine Rolle; egal welchen Wert du in Python einträgst, die Daten fließen mit USB-Geschwindigkeit. read_u16() liefert Werte von 0 bis 65535, berechne die Spannung also mit deger * 3.3 / 65535. Weil der Pico ununterbrochen Messzeilen sendet, kann readline() im Befehlsskript oben statt der Antwort eine Messzeile liefern; lies beim Pico so lange weiter, bis eine Zeile mit OK beginnt. Eine HAZIR-Zeile sowie die Befehle PWM und READ gibt es in diesem Beispiel nicht; ergänze sie bei Bedarf im Pico-Code. Vorsicht, wenn du Binärdaten vom Computer sendest: Das Byte 0x03 bedeutet in MicroPython Strg+C und stoppt dein Programm; mit micropython.kbd_intr(-1) schaltest du das ab. Bist du neu beim Pico, sind Was ist der Raspberry Pi Pico? und MicroPython-Grundlagen ein guter Einstieg.
Projektideen mit Arduino und Python
- Temperatur- und Feuchte-Logger: Schreibe die Messwerte eines DHT11 einmal pro Minute in eine CSV-Datei und stelle das Raumklima einer ganzen Woche in Excel oder mit pandas dar.
- Lampe vom Computer aus steuern: Schalte ein Relaismodul mit ON- und OFF-Befehlen aus Python und kombiniere das mit einem Timer oder Sprachbefehlen.
- Live-Abstandsdiagramm: Zeichne den mit einem HC-SR04 gemessenen Abstand mit matplotlib. Drehst du den Sensor mit einem Servo, kannst du auf dieselbe Weise auch einen Radarschirm zeichnen.
- PC-Statusanzeige: Python liest mit der Bibliothek psutil die CPU- und Speicherauslastung und schickt sie an den Arduino, der sie auf einem OLED-Display anzeigt.
- Kamera mit Gesichtsverfolgung: OpenCV findet die Position eines Gesichts im Bild, Python rechnet sie in einen Winkel um und sendet ihn, und der Arduino dreht die Kamera mit einem Servomotor.
- Eigene Shortcut-Tastatur: Taster am Arduino senden beim Drücken eine Zeile, Python liest sie und öffnet ein Programm oder löst ein Tastenkürzel am Computer aus.
Häufige Fehler und ihre Lösungen
| Fehler oder Symptom | Ursache | Lösung |
|---|---|---|
| ModuleNotFoundError: No module named 'serial' | pyserial ist nicht für den Python-Interpreter installiert, der das Skript ausführt; VS Code oder Thonny nutzen eventuell ein anderes Python | Mit demselben Interpreter python -m pip install pyserial ausführen und im Editor den richtigen Interpreter wählen |
| AttributeError: module 'serial' has no attribute 'Serial' | Das falsche Paket serial ist installiert oder deine Datei heißt serial.py | pip uninstall serial und pip install pyserial; Datei umbenennen |
| could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) | Der Port ist in einem anderen Programm offen: serieller Monitor, serieller Plotter oder ein zweites Skript | Das andere Programm schließen; im Skript with verwenden |
| could not open port 'COM5': FileNotFoundError(2, 'The system cannot find the file specified.', None, 2) | Falscher Portname oder das Board ist nicht eingesteckt | Mit python -m serial.tools.list_ports -v den richtigen Namen finden |
| [Errno 13] Permission denied: '/dev/ttyACM0' | Dein Linux-Benutzer hat keine Zugriffsrechte auf die serielle Schnittstelle | sudo usermod -a -G dialout $USER, danach ab- und wieder anmelden |
| Unleserliche Zeichen oder UnicodeDecodeError | Unterschiedliche Baudraten oder Störungen beim Start | Auf beiden Seiten dieselbe Geschwindigkeit nutzen; decode(errors="ignore") und reset_input_buffer() ergänzen |
| readline() liefert immer b'' | Der Arduino sendet kein Zeilenende (print statt println), falscher Port oder das Board startet noch | println verwenden, Port und Geschwindigkeit prüfen, Timeout erhöhen |
| ValueError: invalid literal for int() with base 10 | Eine halbe, leere oder unerwartete Zeile sollte in eine Zahl umgewandelt werden | Umwandlung in try/except packen und die Spaltenzahl prüfen |
| Die ersten Befehle werden ignoriert | Der Uno hat beim Öffnen des Ports neu gestartet, der Befehl landete im Bootloader | Auf die Zeile HAZIR warten oder nach dem Öffnen 2 Sekunden warten |
| Das Diagramm hinkt der Echtzeit hinterher | Pro Bild wird nur eine Zeile gelesen, die Daten stauen sich im Puffer | Mit der in_waiting-Schleife alle Zeilen lesen und seltener senden |
| Bei laufendem Skript lässt sich kein Code hochladen | Python hält den Port belegt | Skript mit Strg+C beenden, dann hochladen |
Arduino und Python über die serielle Schnittstelle sprechen zu lassen, ist der kürzeste Weg, aus einem einfachen Sensorprojekt ein System zu machen, das Daten aufzeichnet, Diagramme zeichnet und sich vom Computer aus steuern lässt. Lies zuerst einen einzelnen Wert und gib ihn aus, speichere ihn dann als CSV und ergänze zuletzt Befehle und Diagramme; achte bei jedem Schritt darauf, dass Baudrate und Zeilenende auf beiden Seiten übereinstimmen. In der BlueGrays-App findest du Schritt-für-Schritt-Projekte mit Schaltplänen und Code zum Messen von Licht mit einem LDR, von Temperatur und Feuchte mit einem DHT11, von Abständen mit einem HC-SR04 sowie zur seriellen Kommunikation zwischen zwei Arduinos, damit du die Datenquelle für dein Python-Skript schnell aufbauen kannst.
Häufige Fragen
Kann man Arduino mit Python programmieren?
Auf einem Arduino Uno, Nano oder Mega läuft kein Python; diese Boards werden in der Arduino IDE mit der C++-basierten Arduino-Sprache programmiert. Python läuft auf dem Computer und spricht über pyserial und die serielle Schnittstelle mit dem Board; alternativ lädst du das Beispiel StandardFirmata hoch und steuerst die Pins mit einer Bibliothek wie pyFirmata2 direkt aus Python. Soll Python auf dem Board selbst laufen, nimm einen Raspberry Pi Pico oder ESP32 mit MicroPython.
Wie installiere ich pyserial?
Führe im Terminal python -m pip install pyserial aus und schreibe im Code import serial. Das Paket heißt pyserial, das importierte Modul aber serial; pip install serial installiert ein völlig anderes Paket und führt zu einem AttributeError. Unter Linux musst du deinen Benutzer außerdem der Gruppe dialout hinzufügen.
Wie finde ich heraus, an welchem COM-Port mein Arduino hängt?
Schau in der Arduino IDE unter Werkzeuge > Port nach oder führe im Terminal python -m serial.tools.list_ports -v aus. Unter Windows zeigt auch der Geräte-Manager unter Anschlüsse (COM & LPT) den Port an. Bist du unsicher, zieh das Board ab und liste die Ports erneut auf; der verschwundene Port gehört zum Arduino.
Wie sende ich Daten von Python an den Arduino?
Öffne den Port mit serial.Serial und sende Bytes, etwa ser.write(b"LED ON\n"); vergiss nicht, Text mit encode() umzuwandeln und ein Zeilenende anzuhängen. Auf der Arduino-Seite prüfst du mit Serial.available(), ob Daten da sind, und liest die Zeile mit Serial.readStringUntil('\n'). Da der Uno beim Öffnen des Ports neu startet, wartest du vor dem ersten Befehl etwa 2 Sekunden.
Kann ich den seriellen Monitor und Python gleichzeitig nutzen?
Nein, eine serielle Schnittstelle kann nur von einem Programm gleichzeitig geöffnet werden; das zweite bekommt den Fehler Access is denied oder Resource busy. Du kannst die Daten aber in Python verarbeiten und gleichzeitig mit print auf dem Bildschirm ausgeben. Brauchst du zwei getrennte Kanäle, schließe einen zweiten USB-TTL-Wandler an den Arduino an und richte mit SoftwareSerial eine zweite serielle Schnittstelle ein.
Warum startet mein Arduino neu, wenn Python den Port öffnet?
Beim Uno, Nano und Mega ist die DTR-Leitung des USB-Chips über einen Kondensator mit dem RESET-Pin verbunden, deshalb startet das Board bei jedem Öffnen des Ports neu; die IDE braucht diese Schaltung zum Hochladen. Warte nach dem Öffnen 2 Sekunden oder warte auf eine Zeile, mit der der Arduino meldet, dass er bereit ist. Um den Reset ganz zu verhindern, kannst du einen 10-µF-Kondensator zwischen RESET und GND setzen, musst ihn aber vor dem Hochladen wieder entfernen.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
Lichtstärke messen mit LDR
Willst du sehen, wie hell dein Zimmer ist, und zwar als Zahl? Der LDR, ein kleines lichtempfindliches Bauteil, misst das Umgebungslicht, und der Arduino schreibt den Wert jede halbe Sekunde auf deinen

HC-SR04 Abstandsmessung (serielle Anzeige)
Der Sensor HC-SR04 funktioniert wie eine Fledermaus, die im Dunkeln ihren Weg findet: Er sendet einen Ton, den du nicht hörst, lauscht auf das Echo und misst, wie weit der Gegenstand vor ihm entfernt

Temperatur und Luftfeuchtigkeit mit DHT11
Wie warm ist dein Zimmer, und ist die Luft feucht oder trocken? Der DHT11-Sensor misst Temperatur und Luftfeuchtigkeit, und der Arduino schreibt die Werte auf ein zweizeiliges LCD und aktualisiert sie

Messung der Lichtintensität mit dem ESP32-LDR-Modul
Echtzeit-Anzeige des ADC-Rohwerts des LDR-Moduls und des anhand von Dunkel- und Hellreferenzwerten berechneten relativen Lichtwerts in Prozent im Serial Monitor. Dieser Prozentwert ist keine Lux-Messung. Vermittelt die Kalibrierungslogik des Moduls. Sollte idealerweise vor Projekt 10 durchgeführt werden, da es zur Festlegung des Schwellenwerts dient.

Einweg-Kommunikation zwischen 2 Arduinos
Du bringst zwei Arduinos über ein einziges Kabel zum Reden: Drückst du den Knopf am einen, geht die LED am anderen an, drückst du noch einmal, geht sie aus. Die sendende Platine meldet den Knopfdruck

Bidirektionale Kommunikation zwischen 2 Arduinos
Zwei Arduinos werden per Kabel verbunden und unterhalten sich: Drückst du den Knopf am einen, geht am anderen das Licht an, drückst du noch einmal, geht es aus. Auf beiden Platinen läuft dasselbe Prog

LED per Bluetooth mit dem Handy steuern
Eine kleine Probe dafür, das Licht in deinem Zimmer vom Handy aus ein- und auszuschalten. Das Bluetooth-Modul leitet die Buchstaben, die du vom Handy schickst, an den Arduino weiter; ein Buchstabe sch

ESP32 Temperatur-Datenlogger (DHT22 + DS1307 RTC + microSD + OLED)
Baue einen zeitgestempelten Temperaturlogger mit ESP32! Lese Temperatur/Feuchtigkeit vom DHT22 jede Minute, hole die aktuelle Zeit vom DS1307 RTC und protokolliere auf microSD-Karte als CSV. Das OLED zeigt live Temperatur (große Schrift), Luftfeuchtigkeit, Zeitstempel und Logzähler. Die /temp_log.csv-Datei auf der SD-Karte öffnet sich in Excel, Google Sheets oder Python pandas zur Analyse — perfekt für Gewächshausüberwachung, Raumtemperaturverfolgung, Laboraufzeichnung, Lebensmittellageru
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 200 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

