Guide · 17 min di lettura

Comunicazione seriale Arduino Python: pyserial, CSV e grafici

Comunicazione seriale Arduino Python con pyserial: installazione, trovare la porta, salvare i dati in CSV, inviare comandi e grafico dal vivo con matplotlib.

La comunicazione seriale Arduino Python consiste in un programma Python che gira sul tuo computer e scambia dati con un Arduino attraverso il cavo USB. Arduino misura un sensore e scrive il risultato sulla porta seriale; Python legge queste righe con la libreria pyserial, le salva in un file CSV, le mostra in un grafico oppure prende una decisione e rimanda un comando ad Arduino. Così unisci il controllo preciso dell'hardware di Arduino alla potenza di Python per l'elaborazione dei dati, i grafici, l'intelligenza artificiale e l'automazione. Non serve nemmeno un modulo aggiuntivo: basta il cavo USB con cui carichi gli sketch.

In questa guida imparerai passo dopo passo a installare pyserial, a trovare automaticamente la porta di Arduino, a salvare i dati di un sensore con il timestamp in un file CSV, a inviare comandi LED e PWM da Python ad Arduino e a disegnare un grafico in tempo reale con matplotlib. Poi vedremo come scegliere il formato dei dati e il fine riga, i due problemi che confondono di più (l'errore di porta occupata e il reset della scheda all'apertura della porta) e che cosa cambia facendo lo stesso lavoro in MicroPython su un Raspberry Pi Pico. Gli esempi sono scritti per Arduino Uno; le modifiche necessarie per ESP32 e Pico sono indicate a parte.

Scheda Arduino Uno R3 e presa USB
Arduino Uno: la presa USB serve sia per caricare il codice sia per la comunicazione seriale con Python; i LED TX e RX lampeggiano mentre passano i dati.

Come comunica Arduino con Python?

Il chip USB-seriale di Arduino Uno (un ATmega16U2 sulle schede originali, quasi sempre un CH340 sui cloni) compare sul computer come una porta seriale virtuale: COM3, COM4 e così via su Windows; /dev/cu.usbmodem… o /dev/cu.usbserial-… su macOS; /dev/ttyACM0 o /dev/ttyUSB0 su Linux. Ogni carattere scritto da Arduino con Serial.print() arriva al computer attraverso questa porta sotto forma di byte; dal lato Python, pyserial apre la porta come un file, ci legge e ci scrive. Una distinzione importante: Arduino Uno non esegue codice Python. Sulla scheda gira codice Arduino scritto in C++, sul computer gira Python, e i due si capiscono solo tramite la linea seriale. Perché entrambi i lati parlino la stessa lingua, queste impostazioni devono coincidere:

ImpostazioneLato ArduinoLato PythonAttenzione
PortaCreata automaticamente tramite USBserial.Serial("COM3", ...)Il nome della porta dipende dal sistema operativo
Velocità (baud)Serial.begin(115200)serial.Serial(port, 115200)Se sono diverse arrivano caratteri senza senso
Formato del frame8 bit di dati, nessuna parità, 1 bit di stop (8N1)Anche il valore predefinito di pyserial è 8N1Di solito non serve cambiarlo
Fine rigaSerial.println() aggiunge \r\n alla finereadline() legge finché non trova \nNon dimenticare di aggiungere \n quando invii da Python
TimeoutSerial.setTimeout(), predefinito 1000 mstimeout=1 (secondi)Con timeout=None, readline() aspetta all'infinito

Installare pyserial (Windows, macOS, Linux)

  1. Serve Python 3 installato. Su Windows, spunta la casella Add python.exe to PATH nella prima schermata del programma di installazione scaricato dal sito ufficiale di Python; su macOS e Linux Python 3 di solito è già presente.
  2. Apri un terminale (Prompt dei comandi o PowerShell su Windows) e digita python -m pip install pyserial. Se Windows non riconosce il comando python, prova py -m pip install pyserial; su macOS e Linux il comando può essere python3 -m pip install pyserial.
  3. Verifica l'installazione: python -c "import serial; print(serial.__version__)" dovrebbe stampare un numero di versione come 3.5.
  4. Per la sezione sul grafico in tempo reale serve anche matplotlib: python -m pip install matplotlib.
  5. Su Linux aggiungi il tuo utente al gruppo dialout per poter aprire la porta: sudo usermod -a -G dialout $USER, poi esci dalla sessione e rientra. Se su Raspberry Pi OS o sulle versioni recenti di Ubuntu pip mostra l'errore externally-managed-environment, usa sudo apt install python3-serial oppure crea un ambiente virtuale con python3 -m venv ed esegui pip al suo interno.
Su PyPI esiste un altro pacchetto chiamato serial che non ha nulla a che fare con le porte seriali. Se digiti pip install serial, il tuo codice supera la riga import serial ma poi si blocca con AttributeError: module 'serial' has no attribute 'Serial'. Il pacchetto giusto è pyserial; rimuovi quello sbagliato con pip uninstall serial e reinstalla pyserial. Lo stesso errore compare se chiami il tuo file serial.py, perché Python importa il tuo file al posto della libreria.

Su quale porta è Arduino? Trovare la porta con Python

Il modo più semplice è guardare il menu Strumenti > Porta dell'IDE di Arduino, ma anche Python può elencare le porte. Digita python -m serial.tools.list_ports -v in un terminale e vedrai nome, descrizione e identificativo hardware (VID:PID) di ogni porta. Se non capisci quale riga è Arduino, scollega la scheda, esegui di nuovo il comando e annota la porta che è sparita. Su macOS la stessa scheda compare sia come /dev/tty… sia come /dev/cu…; in Python usa quella che inizia con cu.

Modulo convertitore USB-TTL CH340
Il chip CH340: sulla maggior parte dei cloni di Arduino è lui a fare da ponte tra USB e porta seriale; il suo identificativo del produttore (VID) è 1A86.
Chip USBDove si trova?VID:PIDNome in WindowsPorta su Linux
ATmega16U2Uno R3 e Mega 2560 originali2341:0043 (Uno)Arduino Uno (COMx)/dev/ttyACM0
CH340Cloni di Uno, Nano e Mega1A86:7523USB-SERIAL CH340 (COMx)/dev/ttyUSB0
CP2102ESP32 DevKit, NodeMCU10C4:EA60Silicon Labs CP210x USB to UART Bridge (COMx)/dev/ttyUSB0
FT232RVecchi Nano, cavi FTDI0403:6001USB Serial Port (COMx)/dev/ttyUSB0
RP2040 (USB integrato)Raspberry Pi Pico con MicroPython2E8A:0005Dispositivo seriale USB (COMx)/dev/ttyACM0

La funzione qui sotto elenca tutte le porte e, se riconosce una scheda dal suo VID, restituisce il nome della sua porta. Se salvi il codice come port_bul.py, negli altri script puoi scrivere from port_bul import port_bul e PORT = port_bul() invece di cambiare a mano il nome della porta ogni volta.

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")

Inviare dati da Arduino: il primo sketch

Gli esempi usano un potenziometro collegato al pin A0: il piedino centrale va ad A0, uno dei piedini laterali a 5V e l'altro a GND. Al posto del potenziometro puoi collegare anche una fotoresistenza (LDR) con una resistenza da 10 kΩ come partitore di tensione. Lo sketch esegue una lettura ogni 200 ms e invia il risultato su una sola riga nel formato tempo,valore:

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}

Usare millis() al posto di delay() rende più semplice aggiungere in seguito, nello stesso loop, la lettura dei comandi in arrivo da Python; trovi i dettagli in millis e delay. Dopo il caricamento, apri il Monitor seriale a 115200 baud e vedrai scorrere righe come 2573,512. È solo una verifica: chiudi sempre il Monitor seriale prima di avviare lo script Python, altrimenti la porta risulterà occupata. Se usi il Monitor seriale per la prima volta, ti sarà utile la guida al Monitor seriale di Arduino.

Leggere i dati di Arduino con Python e salvarli in CSV

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")

Questo script apre la porta con timeout=1: readline() aspetta al massimo 1 secondo l'arrivo di un fine riga, restituisce un oggetto bytes vuoto se non arriva nulla e il ciclo non si blocca mai. I dati ricevuti sono di tipo bytes; decode() li trasforma in testo, strip() elimina il \r\n finale e split(",") divide la riga in colonne. time.sleep(2) e reset_input_buffer() dopo l'apertura della porta servono a scartare le righe a metà che arrivano mentre la Uno si riavvia; più sotto vedrai perché. Aprire il file con newline="" evita righe vuote su Windows, e flush() mantiene sul disco le letture salvate fino a quel momento anche se il programma si chiude all'improvviso. Premi Ctrl+C nel terminale per fermare la registrazione.

Se Excel apre il file CSV in un'unica colonna, la causa sono le impostazioni internazionali: con le impostazioni italiane Excel si aspetta il punto e virgola come separatore di elenco e la virgola come separatore decimale. Usa csv.writer(dosya, delimiter=";") e scrivi la tensione con str(round(volt, 3)).replace(".", ","), oppure importa il file in Excel con Dati > Da testo/CSV e scegli tu il separatore. Per altri modi di portare i dati in Excel, leggi inviare i dati di Arduino a Excel.

Inviare comandi da Python ad Arduino

La comunicazione funziona in entrambe le direzioni. Lo sketch Arduino qui sotto aspetta comandi che arrivano riga per riga: LED ON e LED OFF accendono e spengono il LED integrato, PWM 128 regola la luminosità di un LED collegato al pin 9 e READ rimanda il valore letto su A0. Ogni comando riceve una breve risposta, così Python sa che è stato davvero eseguito. Puoi trovare la resistenza giusta per il LED sul pin 9 con il calcolatore di resistenza per LED; Cos'è il PWM? spiega come funziona il 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

L'errore più comune dal lato Python è dimenticare il \n alla fine del comando: se readStringUntil('\n') non trova il fine riga, Arduino aspetta il tempo impostato con Serial.setTimeout() e solo dopo elabora il comando. In questo sketch il tempo è di 50 ms; con il valore predefinito di 1000 ms ogni comando arriverebbe con un secondo di ritardo. write() accetta solo byte, quindi devi convertire il testo con encode() oppure anteponendo una b (b"LED ON\n"). All'inizio lo script aspetta fino a 3 secondi la riga HAZIR (pronto): poiché la Uno si riavvia all'apertura della porta, un comando inviato subito finisce nel bootloader e va perso. Leggere la risposta dopo ogni comando, cioè lo schema richiesta e risposta, mantiene sincronizzati i due lati ed evita che i buffer si riempiano; il buffer di ricezione della Uno è di soli 64 byte.

Formato dei dati e fine riga: quale scegliere?

La linea seriale ti fornisce solo un flusso di byte; sta a te stabilire dove finisce una lettura e dove inizia la successiva. Il modo più affidabile è scrivere ogni lettura su una riga a sé e chiudere ogni riga con \n. Il formato da usare all'interno della riga dipende dalle dimensioni del progetto:

FormatoRiga di esempioVantaggiSvantaggi
Valore singolo512Il formato più semplice; il Plotter seriale di Arduino lo disegna direttamenteCon più valori non si capisce quale sia quale
CSV2573,512,23.5Corto; Excel e pandas lo leggono direttamenteL'ordine delle colonne deve restare uguale sui due lati
Chiave:valoretemp:23.5,hum:41Leggibile; il Plotter seriale mostra le etichettePiù lungo e un po' più laborioso da analizzare
JSON{"t":23.5,"h":41}Si decodifica in una riga con json.loads() in PythonIl formato più lungo; mette alla prova la poca RAM della Uno
Binario2 byte: 0x02 0x00L'opzione più veloce e compattaNon leggibile da una persona; richiede il modulo struct e un byte di inizio

Anche i fine riga creano spesso confusione. Serial.println() aggiunge due caratteri alla fine di ogni riga: ritorno a capo (\r, CR) e avanzamento di riga (\n, LF). Il metodo readline() di Python aspetta solo \n, quindi alla fine della riga resta un \r; strip() li elimina entrambi. L'elenco dei fine riga nel Monitor seriale dell'IDE di Arduino offre Nessun fine riga, A capo, Ritorno carrello (CR) ed Entrambi NL & CR (in inglese No Line Ending, New Line, Carriage Return e Both NL & CR); quando provi lo sketch nel Monitor seriale prima di passare a Python, scegli A capo così viene inviato lo stesso carattere che invia Python. Per i numeri decimali indica le cifre dopo la virgola, come in Serial.print(temperatura, 1); sulla Uno sprintf non supporta il formato in virgola mobile (%f), quindi provare a scrivere decimali con sprintf produce un punto interrogativo al posto del numero.

Velocità (baud)Byte al secondo (8N1)Righe da 12 byte al secondoQuando usarla?
960096080Sensori lenti, cavi lunghi, vecchi esempi
576005.760480Misure a velocità media
11520011.520960Grafici in tempo reale, ESP32 e la maggior parte dei progetti nuovi

Sulla linea seriale ogni byte viaggia come 10 bit contando i bit di start e di stop, quindi 115200 baud corrispondono a circa 11.520 byte al secondo. Per una riga di 12 byte formata da 25730,1023 più il fine riga (\r\n), c'è spazio per un massimo di 960 righe al secondo. Se la velocità non coincide sui due lati, arrivano caratteri senza senso e Python solleva un UnicodeDecodeError. Da 5 a 20 letture al secondo sono più che sufficienti per l'occhio umano e per la maggior parte dei sensori; inviare più spesso del necessario rallenta il grafico e appesantisce il file.

Grafico in tempo reale con matplotlib

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()

Il grafico si basa su una deque che conserva le ultime 200 letture: ogni nuovo valore entra da destra e il più vecchio esce automaticamente da sinistra. FuncAnimation chiama la funzione guncelle (aggiorna) ogni 50 ms. Il ciclo while ser.in_waiting nella funzione è importante: per ora Arduino invia 5 righe al secondo, ma se aumenti la frequenza e leggi una sola riga per fotogramma, il grafico resta indietro rispetto al tempo reale; questo ciclo legge tutte le righe accumulate e mantiene il grafico aggiornato. Non eliminare la variabile ani: se nessuna variabile punta all'oggetto dell'animazione, Python lo distrugge e la finestra resta bloccata. Per ESP32 scrivi set_ylim(0, 4095), per Pico set_ylim(0, 65535). Se vuoi solo dare un'occhiata veloce, va bene anche il Plotter seriale dell'IDE di Arduino; il vantaggio di Python è che puoi salvare il grafico, combinare più sorgenti ed elaborare i dati nello stesso momento.

Errore di porta occupata e problema del reset automatico

Una porta seriale può essere aperta da un solo programma alla volta. Se mentre lo script Python è in esecuzione è aperto il Monitor seriale o il Plotter seriale dell'IDE di Arduino, se un altro script Python tiene occupata la porta o se in background gira un software che scansiona le porte, come i programmi per stampanti 3D, ottieni could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) su Windows e Resource busy o Device or resource busy su macOS e Linux. Vale anche il contrario: finché uno script Python tiene aperta la porta, l'IDE di Arduino non riesce a caricare il codice. Per questo conviene usare nei tuoi script la struttura with serial.Serial(...) as ser:; la porta si chiude da sola quando il blocco termina o si verifica un errore. Prendi l'abitudine di fermare lo script con Ctrl+C prima di caricare.

Il secondo problema è più subdolo: su Arduino Uno, Nano e Mega la linea DTR del chip USB è collegata al pin RESET del processore tramite un condensatore da 100 nF. Questo circuito esiste perché l'IDE possa riavviare automaticamente la scheda durante il caricamento, ma ogni volta che pyserial apre la porta anche DTR cambia e la scheda si riavvia. Prima resta uno o due secondi nel bootloader, poi esegue il tuo sketch dall'inizio. I comandi inviati in questo intervallo vanno persi e potresti non ricevere le prime righe che Arduino stampa in setup(). La soluzione è aspettare con time.sleep(2) dopo aver aperto la porta o, in modo più affidabile, attendere che Arduino invii una riga come HAZIR; l'esempio dei comandi qui sopra fa proprio questo. Apri la porta una sola volta all'inizio del programma e tienila aperta fino alla fine: un ciclo che apre e chiude la porta a ogni lettura riavvia la scheda di continuo.

Se vuoi evitare del tutto il reset, hai due strade. Via software: crea la porta chiusa con ser = serial.Serial(), imposta ser.port e ser.baudrate, scrivi ser.dtr = False e poi chiama ser.open(). Su Windows di solito funziona; su Linux e macOS il sistema operativo può comunque cambiare DTR all'apertura della porta. Via hardware: metti un condensatore da 10 µF tra i pin RESET e GND della Uno, con il piedino positivo su RESET. Le schede con USB integrato come Leonardo, Micro e Raspberry Pi Pico non si riavviano all'apertura della porta; su queste schede le righe stampate dal tuo codice prima che Python si colleghi possono andare perse, quindi è più sicuro inviare i messaggi importanti quando arriva un comando.

Con il condensatore da 10 µF inserito, l'IDE di Arduino non riesce a caricare il codice, perché il caricamento si basa sullo stesso circuito di reset automatico; togli il condensatore prima di caricare. Se Python mostra device reports readiness to read but returned no data, la scheda ha perso la connessione oppure due programmi usano la porta contemporaneamente. Se la scheda si riavvia da sola durante il funzionamento, la causa è di solito un'alimentazione insufficiente; alimenta servo e motori con una fonte separata invece che con i 5V dell'USB.

Comunicazione seriale tra ESP32 e Python

Tutto il codice Python di questo articolo funziona anche con ESP32; devi solo cambiare alcune cose dal lato Arduino. Per l'ingresso analogico usa uno dei pin ADC1 (ad esempio GPIO 34). L'ADC di ESP32 è a 12 bit e converte 0–3,3 V in 0–4095, quindi in Python calcola la tensione con deger * 3.3 / 4095. All'avvio ESP32 stampa i propri messaggi di boot a 115200 baud, come rst:0x1 (POWERON_RESET),boot:0x13; gli script qui sopra ignorano le righe che non si possono convertire in numeri, quindi questi messaggi non creano problemi. A seconda del circuito di reset automatico della scheda e del sistema operativo, anche ESP32 può riavviarsi all'apertura della porta, quindi l'attesa e la riga HAZIR servono anche qui. Il chip USB di solito è un CP2102 o un CH340. Se preferisci fare a meno del cavo, ESP32 può inviare gli stessi dati via Wi-Fi; leggi le guide ESP32 WebSocket e ESP32 Google Sheets. Se è il tuo primo ESP32, leggi prima Cos'è l'ESP32?.

Raspberry Pi Pico e MicroPython: qual è la differenza?

Scheda di sviluppo Raspberry Pi Pico
Raspberry Pi Pico: grazie all'USB integrato dell'RP2040 non ha bisogno di un chip USB-seriale separato; con MicroPython installato compare sul computer direttamente come porta seriale.

Un altro modo per unire Python e hardware è eseguire Python direttamente sulla scheda. Il Raspberry Pi Pico si può programmare in MicroPython: leggere sensori, accendere LED e gestire i tempi avviene dentro il Pico con la sintassi di Python. MicroPython però è una versione ridotta del Python per computer; matplotlib, pandas o OpenCV non funzionano sul Pico. Perciò, se vuoi salvare i dati, disegnare grafici o elaborarli con l'intelligenza artificiale, colleghi anche il Pico al Python del computer. Il codice pyserial di questo articolo funziona anche con il Pico; devi solo adattare il nome della porta, l'intervallo dei valori e la lettura delle risposte ai comandi, come spiegato qui sotto.

CaratteristicaArduino + Python sul PCPico + MicroPython (autonomo)Pico + Python sul PC
Dove gira Python?Solo sul computer; la scheda esegue C++Dentro il PicoSu entrambi i lati: MicroPython sul Pico, Python sul computer
Serve un computer?Sì, i dati vengono elaborati sul computerNo, funziona con l'alimentazione USB o a batteriaSì
Librerie disponibilipyserial, matplotlib, pandas, numpy, OpenCVModuli limitati come machine e timeTutte le librerie del computer
Velocità in baudImportante, deve coincidere sui due latiNessuna porta seriale usataIrrilevante, i dati viaggiano alla velocità dell'USB
Reset all'apertura della portaSì su Uno, Nano e MegaNon applicabileNo
Quando sceglierlo?Hai già un Arduino e ti servono librerie pronteDispositivi semplici e indipendentiVuoi un solo linguaggio (Python) su entrambi i lati

Il codice MicroPython qui sotto è la versione per Pico dell'esempio Arduino precedente: legge il potenziometro su GP26 ogni 200 ms, invia il risultato nello stesso formato tempo,valore e accetta gli stessi comandi LED. Qui l'equivalente del Serial di Arduino sono print() e sys.stdin; grazie a select.poll() il ciclo di misura non si ferma mentre aspetta i comandi.

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)

Salva il codice sul Pico come main.py con Thonny, poi chiudi Thonny e scollega e ricollega il cavo USB del Pico: Thonny usa la porta seriale del Pico, quindi finché è collegato il tuo script Python non può aprirla, e quando si collega ferma anche il programma in esecuzione; dopo il riavvio main.py parte da solo. Sulla porta USB del Pico la velocità in baud non conta; qualunque valore metti in Python, i dati viaggiano alla velocità dell'USB. read_u16() restituisce valori da 0 a 65535, quindi calcola la tensione con deger * 3.3 / 65535. Poiché il Pico invia righe di misura senza sosta, nello script dei comandi qui sopra readline() può restituire una misura invece della risposta; con il Pico continua a leggere finché non arriva una riga che inizia con OK. Questo esempio non ha la riga HAZIR né i comandi PWM e READ; aggiungili al codice del Pico se ti servono. Fai attenzione se invii dati binari dal computer: il byte 0x03 in MicroPython equivale a Ctrl+C e ferma il programma; puoi disattivarlo con micropython.kbd_intr(-1). Se sei alle prime armi con il Pico, Cos'è il Raspberry Pi Pico? e le basi di MicroPython sono un buon punto di partenza.

Idee di progetto con Arduino e Python

  • Registratore di temperatura e umidità: scrivi in un CSV le letture di un DHT11 una volta al minuto e rappresenta il clima della tua stanza per una settimana in Excel o con pandas.
  • Controllo di una lampada dal computer: comanda un modulo relè con i comandi ON e OFF inviati da Python e abbinalo a un timer o ai comandi vocali.
  • Grafico della distanza in tempo reale: disegna con matplotlib la distanza misurata da un HC-SR04. Se fai ruotare il sensore con un servo, con lo stesso metodo puoi disegnare anche uno schermo radar.
  • Display di stato del PC: Python legge l'uso di CPU e memoria con la libreria psutil e lo invia ad Arduino, che lo mostra su un display OLED.
  • Telecamera che segue i volti: OpenCV trova la posizione di un volto nell'immagine, Python la converte in un angolo e la invia, e Arduino orienta la telecamera con un servomotore.
  • La tua tastiera di scorciatoie: fai in modo che i pulsanti collegati ad Arduino inviino una riga quando li premi, e che Python la legga per aprire un programma o lanciare una scorciatoia sul computer.

Errori comuni e soluzioni

Errore o sintomoCausaSoluzione
ModuleNotFoundError: No module named 'serial'pyserial non è installato per l'interprete Python che esegue lo script; VS Code o Thonny potrebbero usare un altro PythonEsegui python -m pip install pyserial con lo stesso interprete e scegli l'interprete giusto nell'editor
AttributeError: module 'serial' has no attribute 'Serial'È installato il pacchetto serial sbagliato o il tuo file si chiama serial.pypip uninstall serial e pip install pyserial; rinomina il file
could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5)La porta è aperta in un altro programma: Monitor seriale, Plotter seriale o un secondo scriptChiudi l'altro programma; usa with nello script
could not open port 'COM5': FileNotFoundError(2, 'The system cannot find the file specified.', None, 2)Nome della porta sbagliato o scheda non collegataTrova il nome giusto con python -m serial.tools.list_ports -v
[Errno 13] Permission denied: '/dev/ttyACM0'Il tuo utente Linux non ha i permessi per accedere alla porta serialesudo usermod -a -G dialout $USER, poi esci dalla sessione e rientra
Caratteri senza senso o UnicodeDecodeErrorVelocità diverse o disturbi durante l'avvioUsa la stessa velocità sui due lati; aggiungi decode(errors="ignore") e reset_input_buffer()
readline() restituisce sempre b''Arduino non invia il fine riga (print invece di println), porta sbagliata o scheda ancora in avvioUsa println, controlla porta e velocità, aumenta il timeout
ValueError: invalid literal for int() with base 10Si è provato a convertire in numero una riga incompleta, vuota o inattesaMetti la conversione in un try/except e controlla il numero di colonne
I primi comandi vengono ignoratiLa Uno si è riavviata all'apertura della porta e il comando è finito nel bootloaderAspetta la riga HAZIR o attendi 2 secondi dopo aver aperto la porta
Il grafico resta indietro rispetto al tempo realeSi legge una sola riga per fotogramma e i dati si accumulano nel bufferLeggi tutte le righe con il ciclo su in_waiting e invia meno spesso
Non riesci a caricare il codice con lo script avviatoPython tiene occupata la portaFerma lo script con Ctrl+C, poi carica
Quando qualcosa non va, togli prima di mezzo Python e prova lo sketch Arduino nel Monitor seriale alla stessa velocità. Se lì le righe arrivano correttamente, il problema è dal lato Python (nome della porta, velocità, fine riga o analisi); se sono illeggibili anche lì, correggi prima lo sketch. Se il codice non si carica affatto, segui i passaggi della guida errore di caricamento su Arduino. Se ti interessa il lato hardware della comunicazione seriale, cioè TX, RX e UART, dai un'occhiata a comunicazione seriale UART.

Far dialogare Arduino e Python attraverso la porta seriale è la strada più breve per trasformare un semplice progetto con sensori in un sistema che registra dati, disegna grafici e si controlla dal computer. Prima leggi un solo valore e stampalo, poi salvalo in CSV e solo alla fine aggiungi comandi e grafici; a ogni passo assicurati che velocità e fine riga coincidano sui due lati. Nell'app BlueGrays trovi progetti passo passo con schemi e codice per misurare la luce con una LDR, la temperatura e l'umidità con un DHT11, la distanza con un HC-SR04 e per la comunicazione seriale tra due Arduino, così puoi costruire in fretta la sorgente dei dati che leggerai con Python.

Domande frequenti

Si può programmare Arduino in Python?

Su Arduino Uno, Nano o Mega Python non gira; queste schede si programmano nell'IDE di Arduino con il linguaggio Arduino, basato sul C++. Python gira sul computer e comunica con la scheda attraverso la porta seriale grazie a pyserial; in alternativa puoi caricare l'esempio StandardFirmata e controllare i pin direttamente da Python con una libreria come pyFirmata2. Se vuoi che Python giri sulla scheda stessa, scegli un Raspberry Pi Pico o un ESP32 con MicroPython.

Come si installa pyserial?

Esegui python -m pip install pyserial in un terminale e scrivi import serial nel codice. Il pacchetto si chiama pyserial, ma il modulo che importi si chiama serial; pip install serial installa un pacchetto completamente diverso e causa un AttributeError. Su Linux devi anche aggiungere il tuo utente al gruppo dialout.

Come trovo la porta COM a cui è collegato Arduino?

Guarda in Strumenti > Porta nell'IDE di Arduino oppure esegui python -m serial.tools.list_ports -v in un terminale. Su Windows anche la voce Porte (COM e LPT) di Gestione dispositivi mostra la porta. Se hai dubbi, scollega la scheda ed elenca di nuovo le porte; quella che sparisce è di Arduino.

Come invio dati da Python ad Arduino?

Apri la porta con serial.Serial e invia byte, per esempio ser.write(b"LED ON\n"); non dimenticare di convertire il testo con encode() e di aggiungere un fine riga. Su Arduino controlla l'arrivo dei dati con Serial.available() e leggi la riga con Serial.readStringUntil('\n'). Dato che la Uno si riavvia all'apertura della porta, aspetta circa 2 secondi prima di inviare il primo comando.

Posso usare il Monitor seriale di Arduino e Python insieme?

No, una porta seriale può essere aperta da un solo programma alla volta; il secondo riceve l'errore Access is denied o Resource busy. Puoi però elaborare i dati in Python e stamparli a schermo con print nello stesso momento. Se ti servono due canali separati, collega un secondo convertitore USB-TTL ad Arduino e crea una seconda porta seriale con SoftwareSerial.

Perché Arduino si riavvia quando Python apre la porta?

Su Uno, Nano e Mega la linea DTR del chip USB è collegata al pin RESET tramite un condensatore, quindi la scheda si riavvia ogni volta che la porta viene aperta; all'IDE serve questo circuito per caricare il codice. Aspetta 2 secondi dopo l'apertura della porta oppure attendi una riga con cui Arduino segnala di essere pronto. Per evitare del tutto il reset puoi mettere un condensatore da 10 µF tra RESET e GND, ma dovrai toglierlo prima di ogni caricamento.

Vedi tutti i progetti
Misurare l'intensità luminosa con LDR
#11Principiante

Misurare l'intensità luminosa con LDR

Vuoi vedere quanto è luminosa la tua stanza sotto forma di numero? L'LDR, un piccolo componente sensibile alla luce, misura la luce ambiente e l'Arduino scrive il valore sullo schermo del computer ogn

4.6(81)
139
HC-SR04 Misura della distanza (display seriale)
#27Principiante

HC-SR04 Misura della distanza (display seriale)

Il sensore HC-SR04 funziona come un pipistrello che trova la strada al buio: manda un suono che non puoi sentire, ne ascolta l'eco e misura quanto è lontano l'oggetto davanti a sé. L'Arduino scrive qu

4.6(46)
71
Temperatura e umidità con DHT11
#55Intermedio

Temperatura e umidità con DHT11

Quanti gradi ci sono nella tua stanza, e l'aria è umida o secca? Il sensore DHT11 misura temperatura e umidità, e l'Arduino scrive i valori su uno schermo LCD a due righe, aggiornandoli ogni paio di s

4.7(47)
73
Lettura dell'intensità luminosa con il modulo LDR ESP32
#113Principiante

Lettura dell'intensità luminosa con il modulo LDR ESP32

Monitoraggio in tempo reale sul Serial Monitor del valore grezzo dell'ADC del modulo LDR e della percentuale relativa di luce calcolata dai valori di riferimento al buio e alla luce. Questa percentuale non è una misurazione in lux. Spiega il principio di calibrazione del modulo. È consigliabile eseguirlo prima del Progetto 10, poiché serve a determinare il valore di soglia.

4.6(36)
55
Comunicazione unidirezionale tra 2 Arduino
#57Intermedio
Premium

Comunicazione unidirezionale tra 2 Arduino

Fai parlare due Arduino con un solo filo: premi il pulsante sull'uno e il LED dell'altro si accende, premi di nuovo e si spegne. La scheda che invia segnala la pressione con un breve messaggio, e quel

4.4(37)
50
Comunicazione bidirezionale tra 2 Arduino
#58Intermedio
Premium

Comunicazione bidirezionale tra 2 Arduino

Due Arduino collegati con dei fili parlano tra loro: premi il pulsante di uno e si accende la luce dell'altro, premilo di nuovo e si spegne. Entrambe le schede hanno lo stesso programma, quindi ognuna

4.5(20)
26
Controllare un LED dal telefono tramite Bluetooth
#34Principiante
Premium

Controllare un LED dal telefono tramite Bluetooth

Una piccola prova per accendere e spegnere le luci della tua stanza dal telefono. Il modulo Bluetooth passa all'Arduino le lettere che invii dal telefono; una lettera accende il LED rosso, un'altra qu

4.8(68)
89
Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avanzato
Premium

Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Costruisci un data logger di temperatura con timestamp usando ESP32! Leggi temperatura/umidità dal DHT22 ogni minuto, ottieni l'ora reale dal DS1307 RTC e registra su scheda microSD in formato CSV. L'OLED mostra dal vivo temperatura (font grande), percentuale di umidità, timestamp e contatore di log. Il file /temp_log.csv sulla scheda SD si apre in Excel, Google Sheets o Python pandas per l'analisi — perfetto per il monitoraggio di serre, tracciamento della temperatura domestica, raccolta

4.7(7)
12