Ratgeber · 15 Min. Lesezeit

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.

Arduino Uno R3 mit USB-Buchse
Arduino Uno: Über die USB-Buchse lädst du Code hoch und kommunizierst seriell mit Python; die TX- und RX-LEDs blinken, während Daten fließen.

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:

EinstellungArduino-SeitePython-SeiteAchtung
PortEntsteht automatisch über USBserial.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
Rahmen8 Datenbits, keine Parität, 1 Stoppbit (8N1)Auch der pyserial-Standard ist 8N1Musst du meist nicht ändern
ZeilenendeSerial.println() hängt \r\n anreadline() liest, bis es \n siehtBeim Senden aus Python \n nicht vergessen
TimeoutSerial.setTimeout(), Standard 1000 mstimeout=1 (Sekunden)Mit timeout=None wartet readline() ewig

pyserial installieren (Windows, macOS, Linux)

  1. 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.
  2. Ö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.
  3. Prüfe die Installation: python -c "import serial; print(serial.__version__)" sollte eine Versionsnummer wie 3.5 ausgeben.
  4. Für den Abschnitt mit dem Live-Diagramm brauchst du außerdem matplotlib: python -m pip install matplotlib.
  5. 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.
Auf PyPI gibt es ein anderes Paket namens serial, das nichts mit seriellen Schnittstellen zu tun hat. Gibst du pip install serial ein, kommt dein Code zwar an der Zeile import serial vorbei, bricht dann aber mit AttributeError: module 'serial' has no attribute 'Serial' ab. Das richtige Paket heißt pyserial; entferne das falsche mit pip uninstall serial und installiere pyserial neu. Derselbe Fehler tritt auf, wenn du deine eigene Datei serial.py nennst, denn dann importiert Python deine Datei statt der Bibliothek.

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.

CH340 USB-TTL-Wandler-Modul
Der CH340-Chip: Auf den meisten Arduino-Klonen bildet dieser Chip die Brücke zwischen USB und serieller Schnittstelle; seine Hersteller-ID (VID) ist 1A86.
USB-ChipWo steckt er?VID:PIDName unter WindowsPortname unter Linux
ATmega16U2Original Uno R3, Mega 25602341:0043 (Uno)Arduino Uno (COMx)/dev/ttyACM0
CH340Uno-, Nano- und Mega-Klone1A86:7523USB-SERIAL CH340 (COMx)/dev/ttyUSB0
CP2102ESP32 DevKit, NodeMCU10C4:EA60Silicon Labs CP210x USB to UART Bridge (COMx)/dev/ttyUSB0
FT232RÄltere Nanos, FTDI-Kabel0403:6001USB Serial Port (COMx)/dev/ttyUSB0
RP2040 (integriertes USB)Raspberry Pi Pico mit MicroPython2E8A:0005Serielles 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.

Python
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:

C++ (Arduino IDE)
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

Python
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.

Öffnet Excel die CSV-Datei als eine einzige Spalte, liegt das an den Ländereinstellungen: Mit deutschen Einstellungen erwartet Excel das Semikolon als Listentrennzeichen und das Komma als Dezimaltrennzeichen. Verwende csv.writer(dosya, delimiter=";") und schreibe die Spannung mit str(round(volt, 3)).replace(".", ","), oder importiere die Datei in Excel über Daten > Aus Text/CSV und wähle das Trennzeichen selbst. Weitere Wege, Daten nach Excel zu bringen, zeigt Arduino-Daten nach Excel übertragen.

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?.

C++ (Arduino IDE)
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}
Python
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 OFF

Der 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:

FormatBeispielzeileVorteileNachteile
Einzelwert512Einfachstes Format; der serielle Plotter zeichnet es direktBei mehreren Werten ist unklar, welcher welcher ist
CSV2573,512,23.5Kurz; Excel und pandas lesen es direktDie Spaltenreihenfolge muss auf beiden Seiten gleich bleiben
Schlüssel:Werttemp:23.5,hum:41Gut lesbar; der serielle Plotter zeigt die BezeichnungenLänger und etwas aufwendiger zu zerlegen
JSON{"t":23.5,"h":41}In Python mit json.loads() in einer Zeile dekodiertLängstes Format; belastet den knappen RAM des Uno
Binär2 Bytes: 0x02 0x00Schnellste und kürzeste VarianteNicht 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.

BaudrateBytes pro Sekunde (8N1)12-Byte-Zeilen pro SekundeWann sinnvoll?
960096080Langsame Sensoren, lange Kabel, ältere Beispiel-Sketche
576005.760480Mittelschnelle Messungen
11520011.520960Live-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

Python
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.

Mit eingestecktem 10-µF-Kondensator kann die Arduino IDE keinen Code hochladen, weil das Hochladen dieselbe Auto-Reset-Schaltung nutzt; entferne den Kondensator vor dem Hochladen. Meldet Python device reports readiness to read but returned no data, hat das Board die Verbindung verloren oder zwei Programme nutzen den Port gleichzeitig. Startet das Board im Betrieb von selbst neu, liegt das meist an zu schwacher Stromversorgung; versorge Servos und Motoren aus einer eigenen Quelle statt über die 5V des USB-Anschlusses.

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?

Raspberry Pi Pico Entwicklungsboard
Raspberry Pi Pico: Dank des integrierten USB-Controllers des RP2040 braucht er keinen separaten USB-Seriell-Chip; mit MicroPython erscheint er am Computer direkt als serielle Schnittstelle.

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.

MerkmalArduino + Python am PCPico + MicroPython (eigenständig)Pico + Python am PC
Wo läuft Python?Nur auf dem Computer; das Board führt C++ ausIm Pico selbstAuf beiden Seiten: MicroPython im Pico, Python am Computer
Wird ein Computer gebraucht?Ja, die Daten werden am Computer verarbeitetNein, läuft an USB-Strom oder BatterieJa
Verfügbare Bibliothekenpyserial, matplotlib, pandas, numpy, OpenCVEingeschränkte Module wie machine und timeAlle Bibliotheken des Computers
BaudrateWichtig, muss auf beiden Seiten gleich seinKeine serielle Schnittstelle nötigEgal, Daten fließen mit USB-Geschwindigkeit
Reset beim Öffnen des PortsJa bei Uno, Nano und MegaEntfälltNein
Wann wählen?Arduino ist schon vorhanden, fertige Bibliotheken werden gebrauchtEinfache eigenständige GeräteEine 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.

MicroPython
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 SymptomUrsacheLö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 PythonMit 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.pypip 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 SkriptDas 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 eingestecktMit 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 Schnittstellesudo usermod -a -G dialout $USER, danach ab- und wieder anmelden
Unleserliche Zeichen oder UnicodeDecodeErrorUnterschiedliche Baudraten oder Störungen beim StartAuf 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 nochprintln verwenden, Port und Geschwindigkeit prüfen, Timeout erhöhen
ValueError: invalid literal for int() with base 10Eine halbe, leere oder unerwartete Zeile sollte in eine Zahl umgewandelt werdenUmwandlung in try/except packen und die Spaltenzahl prüfen
Die ersten Befehle werden ignoriertDer Uno hat beim Öffnen des Ports neu gestartet, der Befehl landete im BootloaderAuf die Zeile HAZIR warten oder nach dem Öffnen 2 Sekunden warten
Das Diagramm hinkt der Echtzeit hinterherPro Bild wird nur eine Zeile gelesen, die Daten stauen sich im PufferMit der in_waiting-Schleife alle Zeilen lesen und seltener senden
Bei laufendem Skript lässt sich kein Code hochladenPython hält den Port belegtSkript mit Strg+C beenden, dann hochladen
Wenn etwas nicht klappt, nimm zuerst Python aus dem Spiel und teste den Arduino-Sketch im seriellen Monitor mit derselben Baudrate. Kommen die Zeilen dort sauber an, liegt das Problem auf der Python-Seite (Portname, Geschwindigkeit, Zeilenende oder Auswertung); sind sie auch dort verstümmelt, korrigiere zuerst den Sketch. Lässt sich der Code gar nicht hochladen, folge den Schritten in der Anleitung Arduino-Sketch lässt sich nicht hochladen. Wenn dich die Hardwareseite der seriellen Kommunikation interessiert, also TX, RX und UART, wirf einen Blick auf UART: serielle Kommunikation.

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.

Alle Projekte ansehen
Lichtstärke messen mit LDR
#11Anfänger

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

4.6(81)
139
HC-SR04 Abstandsmessung (serielle Anzeige)
#27Anfänger

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

4.6(46)
71
Temperatur und Luftfeuchtigkeit mit DHT11
#55Mittel

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

4.7(47)
73
Messung der Lichtintensität mit dem ESP32-LDR-Modul
#113Anfänger

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.

4.6(36)
55
Einweg-Kommunikation zwischen 2 Arduinos
#57Mittel
Premium

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

4.4(37)
50
Bidirektionale Kommunikation zwischen 2 Arduinos
#58Mittel
Premium

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

4.5(20)
26
LED per Bluetooth mit dem Handy steuern
#34Anfänger
Premium

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

4.8(68)
89
ESP32 Temperatur-Datenlogger (DHT22 + DS1307 RTC + microSD + OLED)
#144Fortgeschritten
Premium

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

4.7(7)
12