Guías · 17 min de lectura

Comunicación serial Arduino Python: pyserial, CSV y gráficas

Comunicación serial Arduino Python con pyserial: instalación, encontrar el puerto, guardar datos en CSV, enviar comandos y gráfica en vivo con matplotlib.

La comunicación serial Arduino Python consiste en que un programa de Python que se ejecuta en tu ordenador intercambie datos con un Arduino a través del cable USB. El Arduino mide un sensor y escribe el resultado en el puerto serie; Python lee esas líneas con la librería pyserial, las guarda en un archivo CSV, las representa en una gráfica o toma una decisión y envía un comando de vuelta al Arduino. Así combinas el control preciso del hardware del Arduino con la potencia de Python para procesar datos, hacer gráficas, usar inteligencia artificial y automatizar tareas. Tampoco necesitas ningún módulo adicional: basta con el cable USB con el que subes los programas.

En esta guía aprenderás paso a paso a instalar pyserial, encontrar automáticamente el puerto del Arduino, guardar datos de sensores con marca de tiempo en un archivo CSV, enviar comandos LED y PWM desde Python al Arduino y dibujar una gráfica en tiempo real con matplotlib. Después veremos cómo elegir el formato de datos y el fin de línea, los dos problemas más confusos (el error de puerto ocupado y el reinicio de la placa al abrir el puerto) y en qué se diferencia hacer lo mismo con MicroPython en una Raspberry Pi Pico. Los ejemplos están escritos para el Arduino Uno; verás por separado los cambios necesarios para el ESP32 y la Pico.

Placa Arduino Uno R3 y conector USB
Arduino Uno: el conector USB sirve tanto para subir código como para la comunicación serial con Python; los LED TX y RX parpadean mientras circulan los datos.

¿Cómo se comunica Arduino con Python?

El chip USB-serie del Arduino Uno (un ATmega16U2 en las placas originales y casi siempre un CH340 en los clones) aparece en el ordenador como un puerto serie virtual: COM3, COM4, etc. en Windows; /dev/cu.usbmodem… o /dev/cu.usbserial-… en macOS; /dev/ttyACM0 o /dev/ttyUSB0 en Linux. Cada carácter que el Arduino escribe con Serial.print() viaja al ordenador por este puerto en forma de bytes; en el lado de Python, pyserial abre el puerto como si fuera un archivo, lee de él y escribe en él. Una distinción importante: el Arduino Uno no ejecuta código Python. En la placa se ejecuta código de Arduino escrito en C++, en el ordenador se ejecuta Python, y ambos solo se entienden a través de la línea serie. Para que los dos lados hablen el mismo idioma, estos ajustes deben coincidir:

AjusteLado ArduinoLado PythonAtención
PuertoSe crea automáticamente por USBserial.Serial("COM3", ...)El nombre del puerto depende del sistema operativo
Velocidad (baudios)Serial.begin(115200)serial.Serial(port, 115200)Si no coinciden, llegan caracteres sin sentido
Trama8 bits de datos, sin paridad, 1 bit de parada (8N1)El valor por defecto de pyserial también es 8N1Normalmente no hace falta cambiarlo
Fin de líneaSerial.println() añade \r\n al finalreadline() lee hasta encontrar \nNo olvides añadir \n al enviar desde Python
Tiempo de esperaSerial.setTimeout(), 1000 ms por defectotimeout=1 (segundos)Con timeout=None, readline() espera para siempre

Instalar pyserial (Windows, macOS, Linux)

  1. Necesitas tener Python 3 instalado. En Windows, marca la casilla Add python.exe to PATH en la primera pantalla del instalador descargado de la web oficial de Python; en macOS y Linux, Python 3 suele venir ya instalado.
  2. Abre una terminal (Símbolo del sistema o PowerShell en Windows) y escribe python -m pip install pyserial. Si Windows no reconoce el comando python, prueba py -m pip install pyserial; en macOS y Linux el comando puede ser python3 -m pip install pyserial.
  3. Comprueba la instalación: python -c "import serial; print(serial.__version__)" debería mostrar un número de versión como 3.5.
  4. Para la sección de la gráfica en tiempo real también necesitas matplotlib: python -m pip install matplotlib.
  5. En Linux, añade tu usuario al grupo dialout para poder abrir el puerto: sudo usermod -a -G dialout $USER, y luego cierra la sesión y vuelve a entrar. Si en Raspberry Pi OS o en versiones recientes de Ubuntu pip muestra el error externally-managed-environment, usa sudo apt install python3-serial o crea un entorno virtual con python3 -m venv y ejecuta pip dentro de él.
En PyPI existe otro paquete llamado serial que no tiene nada que ver con los puertos serie. Si escribes pip install serial, tu código pasa la línea import serial pero luego falla con AttributeError: module 'serial' has no attribute 'Serial'. El paquete correcto es pyserial; elimina el equivocado con pip uninstall serial y vuelve a instalar pyserial. El mismo error aparece si llamas a tu propio archivo serial.py, porque Python importa tu archivo en lugar de la librería.

¿En qué puerto está el Arduino? Encontrar el puerto con Python

Lo más fácil es mirar el menú Herramientas > Puerto del IDE de Arduino, pero Python también puede listar los puertos. Escribe python -m serial.tools.list_ports -v en una terminal y verás el nombre, la descripción y el identificador de hardware (VID:PID) de cada puerto. Si no sabes qué línea es el Arduino, desconecta la placa, vuelve a ejecutar el comando y anota el puerto que ha desaparecido. En macOS la misma placa aparece como /dev/tty… y como /dev/cu…; en Python usa el que empieza por cu.

Módulo conversor USB-TTL CH340
El chip CH340: en la mayoría de los clones de Arduino es el que hace de puente entre el USB y el puerto serie; su identificador de fabricante (VID) es 1A86.
Chip USBDónde lo encuentrasVID:PIDNombre en WindowsPuerto en Linux
ATmega16U2Uno R3 y Mega 2560 originales2341:0043 (Uno)Arduino Uno (COMx)/dev/ttyACM0
CH340Clones de Uno, Nano y Mega1A86:7523USB-SERIAL CH340 (COMx)/dev/ttyUSB0
CP2102ESP32 DevKit, NodeMCU10C4:EA60Silicon Labs CP210x USB to UART Bridge (COMx)/dev/ttyUSB0
FT232RNanos antiguos, cables FTDI0403:6001USB Serial Port (COMx)/dev/ttyUSB0
RP2040 (USB integrado)Raspberry Pi Pico con MicroPython2E8A:0005Dispositivo serie USB (COMx)/dev/ttyACM0

La siguiente función lista todos los puertos y, si reconoce una placa por su VID, devuelve el nombre de su puerto. Si guardas el código como port_bul.py, en tus otros scripts puedes escribir from port_bul import port_bul y PORT = port_bul() en lugar de cambiar el nombre del puerto a mano cada vez.

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

Enviar datos desde el Arduino: el primer sketch

Los ejemplos usan un potenciómetro conectado al pin A0: la patilla central va a A0, una de las laterales a 5V y la otra a GND. En lugar del potenciómetro también puedes conectar una LDR con una resistencia de 10 kΩ formando un divisor de tensión. El sketch hace una lectura cada 200 ms y envía el resultado en una sola línea con el formato tiempo,valor:

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}

Usar millis() en lugar de delay() facilita añadir más adelante la lectura de comandos de Python en el mismo bucle; tienes los detalles en millis y delay. Después de subir el sketch, abre el Monitor Serie a 115200 baudios y verás pasar líneas como 2573,512. Es solo una comprobación: cierra siempre el Monitor Serie antes de ejecutar el script de Python, o el puerto estará ocupado. Si es la primera vez que usas el Monitor Serie, la guía del Monitor Serie de Arduino te será útil.

Leer los datos del Arduino con Python y guardarlos en 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")

Este script abre el puerto con timeout=1: readline() espera como máximo 1 segundo a que llegue un fin de línea, devuelve un objeto bytes vacío si no llega nada y el bucle nunca se queda colgado. Los datos recibidos son de tipo bytes; decode() los convierte en texto, strip() elimina el \r\n del final y split(",") divide la línea en columnas. El time.sleep(2) y el reset_input_buffer() después de abrir el puerto sirven para descartar las líneas a medias que llegan mientras el Uno se reinicia; verás el motivo más abajo. Abrir el archivo con newline="" evita que aparezcan filas vacías en Windows, y flush() mantiene en el disco las lecturas guardadas hasta ese momento aunque el programa se cierre de golpe. Pulsa Ctrl+C en la terminal para detener el registro.

Si Excel abre el archivo CSV en una sola columna, la culpa es de la configuración regional: con la configuración española, Excel espera el punto y coma como separador de listas y la coma como separador decimal. Usa csv.writer(dosya, delimiter=";") y escribe la tensión con str(round(volt, 3)).replace(".", ","), o importa el archivo en Excel con Datos > Desde el texto/CSV y elige tú el separador. Para otras formas de pasar datos a Excel, consulta enviar datos de Arduino a Excel.

Enviar comandos desde Python al Arduino

La comunicación funciona en los dos sentidos. El siguiente sketch de Arduino espera comandos que llegan línea a línea: LED ON y LED OFF encienden y apagan el LED integrado, PWM 128 ajusta el brillo de un LED conectado al pin 9 y READ devuelve el valor de A0. Cada comando recibe una respuesta corta, así Python sabe que se ha ejecutado de verdad. Puedes calcular la resistencia adecuada para el LED del pin 9 con la calculadora de resistencia para LED; ¿Qué es PWM? explica cómo funciona el 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

El error más habitual en el lado de Python es no poner \n al final del comando: si readStringUntil('\n') no encuentra el fin de línea, el Arduino espera el tiempo fijado con Serial.setTimeout() y solo entonces procesa el comando. En este sketch ese tiempo es de 50 ms; con el valor por defecto de 1000 ms, cada comando llegaría con un segundo de retraso. write() solo acepta bytes, así que debes convertir el texto con encode() o poniendo una b delante (b"LED ON\n"). Al principio, el script espera hasta 3 segundos la línea HAZIR (listo): como el Uno se reinicia al abrir el puerto, un comando enviado de inmediato acaba en el bootloader y se pierde. Leer la respuesta después de cada comando, es decir, el esquema de petición y respuesta, mantiene sincronizados los dos lados y evita que se llenen los búferes; el búfer de recepción del Uno es de solo 64 bytes.

Formato de datos y fin de línea: ¿cuál elegir?

La línea serie solo te da un flujo de bytes; eres tú quien debe marcar dónde termina una lectura y empieza la siguiente. La forma más fiable es escribir cada lectura en su propia línea y terminar cada línea con \n. El formato que uses dentro de la línea depende del tamaño de tu proyecto:

FormatoLínea de ejemploVentajasInconvenientes
Valor único512El formato más simple; el Serial Plotter de Arduino lo dibuja directamenteCon varios valores no se sabe cuál es cuál
CSV2573,512,23.5Corto; Excel y pandas lo leen directamenteEl orden de las columnas debe ser igual en ambos lados
Clave:valortemp:23.5,hum:41Legible; el Serial Plotter muestra las etiquetasMás largo y algo más laborioso de analizar
JSON{"t":23.5,"h":41}Se decodifica en una línea con json.loads() en PythonEl formato más largo; exige mucho a la escasa RAM del Uno
Binario2 bytes: 0x02 0x00La opción más rápida y compactaNo es legible; necesita el módulo struct y un byte de inicio

Los finales de línea también confunden a menudo. Serial.println() añade dos caracteres al final de cada línea: retorno de carro (\r, CR) y salto de línea (\n, LF). El método readline() de Python solo espera \n, así que al final de la línea queda un \r; strip() elimina los dos. La lista de fin de línea del Monitor Serie del IDE de Arduino ofrece Sin ajuste de línea, Nueva línea, Retorno de carro y Ambos NL y RC (en inglés No Line Ending, New Line, Carriage Return y Both NL & CR); cuando pruebes el sketch en el Monitor Serie antes de usar Python, elige Nueva línea para que se envíe el mismo carácter que desde Python. En los números decimales indica la cantidad de decimales, como en Serial.print(temperatura, 1); en el Uno, sprintf no admite el formato de coma flotante (%f), así que intentar escribir decimales con sprintf produce un signo de interrogación en lugar del número.

Velocidad (baudios)Bytes por segundo (8N1)Líneas de 12 bytes por segundo¿Cuándo usarla?
960096080Sensores lentos, cables largos, ejemplos antiguos
576005760480Mediciones de velocidad media
11520011 520960Gráficas en tiempo real, ESP32 y la mayoría de proyectos nuevos

En la línea serie cada byte viaja como 10 bits contando los bits de inicio y de parada, así que 115200 baudios equivalen a unos 11 520 bytes por segundo. Para una línea de 12 bytes formada por 25730,1023 y el fin de línea (\r\n), hay sitio para hasta 960 líneas por segundo. Si la velocidad no coincide en los dos lados, llegan caracteres sin sentido y Python lanza un UnicodeDecodeError. Entre 5 y 20 lecturas por segundo son más que suficientes para el ojo humano y para la mayoría de sensores; enviar con más frecuencia de la necesaria ralentiza la gráfica y engorda el archivo.

Gráfica en tiempo real 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()

La gráfica se basa en un deque que guarda las últimas 200 lecturas: cada valor nuevo entra por la derecha y el más antiguo sale por la izquierda automáticamente. FuncAnimation llama a la función guncelle (actualizar) cada 50 ms. El bucle while ser.in_waiting dentro de la función es importante: ahora el Arduino envía 5 líneas por segundo, pero si aumentas la frecuencia y lees solo una línea por fotograma, la gráfica se queda atrás respecto al tiempo real; este bucle lee todas las líneas acumuladas y mantiene la gráfica al día. No borres la variable ani: si ninguna variable apunta al objeto de animación, Python lo elimina y la ventana se queda congelada. Para el ESP32 escribe set_ylim(0, 4095) y para la Pico set_ylim(0, 65535). Si solo quieres echar un vistazo rápido, el Serial Plotter del IDE de Arduino también sirve; la ventaja de Python es que puedes guardar la gráfica, combinar varias fuentes y procesar los datos al mismo tiempo.

Error de puerto ocupado y problema del reinicio automático

Un puerto serie solo puede abrirlo un programa a la vez. Si mientras se ejecuta tu script de Python está abierto el Monitor Serie o el Serial Plotter del IDE de Arduino, otro script de Python tiene el puerto, o en segundo plano funciona un programa que explora los puertos, como el software de las impresoras 3D, obtendrás could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) en Windows y Resource busy o Device or resource busy en macOS y Linux. También ocurre al revés: mientras un script de Python mantenga el puerto abierto, el IDE de Arduino no puede subir código. Por eso conviene usar en tus scripts la estructura with serial.Serial(...) as ser:; el puerto se cierra solo cuando termina el bloque o se produce un error. Acostúmbrate a detener el script con Ctrl+C antes de subir código.

El segundo problema es más traicionero: en el Arduino Uno, Nano y Mega, la línea DTR del chip USB está conectada al pin RESET del procesador a través de un condensador de 100 nF. Este circuito existe para que el IDE pueda reiniciar la placa automáticamente al subir código, pero cada vez que pyserial abre el puerto, DTR también cambia y la placa se reinicia. Primero espera uno o dos segundos en el bootloader y luego ejecuta tu sketch desde el principio. Los comandos que envíes durante ese tiempo se pierden, y puede que no recibas las primeras líneas que el Arduino imprime en setup(). La solución es esperar con time.sleep(2) después de abrir el puerto o, de forma más fiable, esperar a que el Arduino envíe una línea como HAZIR; el ejemplo de comandos de arriba hace justo eso. Abre el puerto una sola vez al principio del programa y mantenlo abierto hasta el final: un bucle que abre y cierra el puerto en cada lectura reinicia la placa una y otra vez.

Si quieres evitar el reinicio por completo, hay dos caminos. Por software: crea el puerto cerrado con ser = serial.Serial(), asigna ser.port y ser.baudrate, escribe ser.dtr = False y después llama a ser.open(). En Windows suele funcionar; en Linux y macOS el sistema operativo puede cambiar DTR igualmente al abrir el puerto. Por hardware: coloca un condensador de 10 µF entre los pines RESET y GND del Uno, con la pata positiva en RESET. Las placas con USB integrado, como Leonardo, Micro y Raspberry Pi Pico, no se reinician al abrir el puerto; en ellas pueden perderse las líneas que tu código imprime antes de que Python se conecte, así que es más seguro enviar los mensajes importantes cuando llegue un comando.

Con el condensador de 10 µF puesto, el IDE de Arduino no puede subir código, porque la subida depende del mismo circuito de reinicio automático; quita el condensador antes de subir. Si Python muestra device reports readiness to read but returned no data, la placa ha perdido la conexión o dos programas están usando el puerto a la vez. Si la placa se reinicia sola mientras funciona, la causa suele ser una alimentación insuficiente; alimenta los servos y motores con una fuente aparte en lugar de con los 5V del USB.

Comunicación serial entre ESP32 y Python

Todo el código Python de este artículo funciona también con el ESP32; solo tienes que cambiar algunas cosas en el lado de Arduino. Para la entrada analógica usa uno de los pines ADC1 (por ejemplo, GPIO 34). El ADC del ESP32 es de 12 bits y convierte 0–3,3 V en 0–4095, así que calcula la tensión en Python con deger * 3.3 / 4095. Al arrancar, el ESP32 imprime sus propios mensajes de inicio a 115200 baudios, como rst:0x1 (POWERON_RESET),boot:0x13; como los scripts de arriba ignoran las líneas que no se pueden convertir en números, estos mensajes no causan problemas. Según el circuito de reinicio automático de la placa y el sistema operativo, el ESP32 también puede reiniciarse al abrir el puerto, así que la espera y la línea HAZIR también son útiles aquí. El chip USB suele ser un CP2102 o un CH340. Si prefieres prescindir del cable, el ESP32 puede enviar los mismos datos por Wi-Fi; consulta las guías ESP32 WebSocket y ESP32 Google Sheets. Si es tu primer ESP32, lee antes ¿Qué es el ESP32?.

Raspberry Pi Pico y MicroPython: ¿cuál es la diferencia?

Placa de desarrollo Raspberry Pi Pico
Raspberry Pi Pico: gracias al USB integrado del RP2040 no necesita un chip USB-serie aparte; con MicroPython instalado aparece en el ordenador directamente como puerto serie.

Otra forma de unir Python y hardware es ejecutar Python directamente en la placa. La Raspberry Pi Pico se puede programar con MicroPython: leer sensores, encender LED y gestionar tiempos se hace dentro de la Pico con sintaxis de Python. Sin embargo, MicroPython es una versión reducida del Python de escritorio; matplotlib, pandas u OpenCV no funcionan en la Pico. Por eso, si quieres guardar datos, dibujar gráficas o procesarlos con inteligencia artificial, también conectarás la Pico al Python de tu ordenador. El código pyserial de este artículo también funciona con la Pico; solo tienes que adaptar el nombre del puerto, el rango de valores y la forma de leer las respuestas a los comandos, como se explica más abajo.

CaracterísticaArduino + Python en el PCPico + MicroPython (autónoma)Pico + Python en el PC
¿Dónde se ejecuta Python?Solo en el ordenador; la placa ejecuta C++Dentro de la PicoEn los dos lados: MicroPython en la Pico y Python en el ordenador
¿Hace falta un ordenador?Sí, los datos se procesan en el ordenadorNo, funciona con alimentación USB o con bateríaSí
Librerías disponiblespyserial, matplotlib, pandas, numpy, OpenCVMódulos limitados como machine y timeTodas las librerías del ordenador
Velocidad en baudiosImportante, debe coincidir en ambos ladosNo se usa puerto serieIndiferente, los datos van a velocidad USB
Reinicio al abrir el puertoSí en Uno, Nano y MegaNo aplicaNo
¿Cuándo elegirla?Ya tienes un Arduino y necesitas librerías listasDispositivos sencillos e independientesQuieres un solo lenguaje (Python) en ambos lados

El siguiente código MicroPython es la versión para Pico del ejemplo de Arduino anterior: lee el potenciómetro de GP26 cada 200 ms, envía el resultado con el mismo formato tiempo,valor y acepta los mismos comandos LED. Aquí el equivalente del Serial de Arduino son print() y sys.stdin; gracias a select.poll(), el bucle de medición no se detiene mientras espera comandos.

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)

Guarda el código en la Pico como main.py con Thonny, luego cierra Thonny y desconecta y vuelve a conectar el cable USB de la Pico: Thonny usa el puerto serie de la Pico, así que mientras está conectado tu script de Python no puede abrirlo, y además detiene el programa en marcha al conectarse; tras el reinicio, main.py arranca solo. En el puerto USB de la Pico la velocidad en baudios no importa; pongas el valor que pongas en Python, los datos viajan a velocidad USB. read_u16() devuelve valores de 0 a 65535, así que calcula la tensión con deger * 3.3 / 65535. Como la Pico envía líneas de medida sin parar, en el script de comandos de arriba readline() puede devolver una medida en lugar de la respuesta; con la Pico, sigue leyendo hasta recibir una línea que empiece por OK. Este ejemplo no incluye la línea HAZIR ni los comandos PWM y READ; añádelos al código de la Pico si los necesitas. Ten cuidado si envías datos binarios desde el ordenador: el byte 0x03 equivale a Ctrl+C en MicroPython y detiene tu programa; puedes desactivarlo con micropython.kbd_intr(-1). Si estás empezando con la Pico, ¿Qué es la Raspberry Pi Pico? y fundamentos de MicroPython son un buen punto de partida.

Ideas de proyectos con Arduino y Python

  • Registrador de temperatura y humedad: escribe en un CSV las lecturas de un DHT11 una vez por minuto y representa el clima de tu habitación durante una semana en Excel o con pandas.
  • Control de una lámpara desde el ordenador: maneja un módulo de relé con comandos ON y OFF enviados desde Python y combínalo con un temporizador o con órdenes de voz.
  • Gráfica de distancia en tiempo real: dibuja con matplotlib la distancia que mide un HC-SR04. Si haces girar el sensor con un servo, con el mismo método puedes dibujar también una pantalla de radar.
  • Pantalla de estado del PC: Python lee el uso de CPU y memoria con la librería psutil y lo envía al Arduino, que lo muestra en una pantalla OLED.
  • Cámara que sigue caras: OpenCV encuentra la posición de una cara en la imagen, Python la convierte en un ángulo y la envía, y el Arduino gira la cámara con un servomotor.
  • Tu propio teclado de atajos: haz que los pulsadores del Arduino envíen una línea al pulsarlos, y que Python la lea y abra un programa o ejecute un atajo en el ordenador.

Errores frecuentes y cómo solucionarlos

Error o síntomaCausaSolución
ModuleNotFoundError: No module named 'serial'pyserial no está instalado en el intérprete de Python que ejecuta el script; VS Code o Thonny pueden estar usando otro PythonEjecuta python -m pip install pyserial con el mismo intérprete y elige el intérprete correcto en el editor
AttributeError: module 'serial' has no attribute 'Serial'Está instalado el paquete serial equivocado o tu archivo se llama serial.pypip uninstall serial y pip install pyserial; cambia el nombre de tu archivo
could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5)El puerto está abierto en otro programa: Monitor Serie, Serial Plotter u otro scriptCierra el otro programa; usa with en el script
could not open port 'COM5': FileNotFoundError(2, 'The system cannot find the file specified.', None, 2)Nombre de puerto incorrecto o la placa no está conectadaBusca el nombre correcto con python -m serial.tools.list_ports -v
[Errno 13] Permission denied: '/dev/ttyACM0'Tu usuario de Linux no tiene permiso para acceder al puerto seriesudo usermod -a -G dialout $USER y luego cierra la sesión y vuelve a entrar
Caracteres sin sentido o UnicodeDecodeErrorVelocidades distintas o ruido durante el arranqueUsa la misma velocidad en ambos lados; añade decode(errors="ignore") y reset_input_buffer()
readline() siempre devuelve b''El Arduino no envía fin de línea (print en lugar de println), el puerto es incorrecto o la placa aún está arrancandoUsa println, revisa el puerto y la velocidad, aumenta el timeout
ValueError: invalid literal for int() with base 10Se intentó convertir en número una línea incompleta, vacía o inesperadaPon la conversión dentro de try/except y comprueba el número de columnas
Los primeros comandos se ignoranEl Uno se reinició al abrir el puerto y el comando fue al bootloaderEspera la línea HAZIR o espera 2 segundos tras abrir el puerto
La gráfica va por detrás del tiempo realSolo se lee una línea por fotograma y los datos se acumulan en el búferLee todas las líneas con el bucle de in_waiting y envía con menos frecuencia
No se puede subir código con el script en marchaPython tiene el puerto ocupadoDetén el script con Ctrl+C y después sube el código
Cuando algo falle, saca primero a Python de la ecuación y prueba el sketch de Arduino en el Monitor Serie con la misma velocidad. Si las líneas llegan bien ahí, el problema está en el lado de Python (nombre del puerto, velocidad, fin de línea o análisis); si también llegan mal, corrige primero el sketch. Si el código ni siquiera se sube, sigue los pasos de la guía error al subir código a Arduino. Si te interesa la parte de hardware de la comunicación serial, es decir, TX, RX y UART, echa un vistazo a comunicación serial UART.

Hacer que Arduino y Python hablen por el puerto serie es el camino más corto para convertir un proyecto sencillo con sensores en un sistema que registra datos, dibuja gráficas y se controla desde el ordenador. Primero lee un solo valor y muéstralo, después guárdalo en CSV y por último añade comandos y gráficas; en cada paso, asegúrate de que la velocidad y el fin de línea coinciden en ambos lados. En la app BlueGrays encontrarás proyectos paso a paso con esquemas y código para medir la luz con una LDR, la temperatura y la humedad con un DHT11, la distancia con un HC-SR04 y para la comunicación serial entre dos Arduino, así podrás montar rápidamente la fuente de los datos que leerás con Python.

Preguntas frecuentes

¿Se puede programar Arduino con Python?

En un Arduino Uno, Nano o Mega no se ejecuta Python; estas placas se programan en el IDE de Arduino con el lenguaje de Arduino, basado en C++. Python se ejecuta en el ordenador y se comunica con la placa por el puerto serie mediante pyserial; también puedes subir el ejemplo StandardFirmata y controlar los pines directamente desde Python con una librería como pyFirmata2. Si quieres que Python se ejecute en la propia placa, elige una Raspberry Pi Pico o un ESP32 con MicroPython.

¿Cómo se instala pyserial?

Ejecuta python -m pip install pyserial en una terminal y escribe import serial en tu código. El paquete se llama pyserial, pero el módulo que importas se llama serial; pip install serial instala un paquete totalmente distinto y provoca un AttributeError. En Linux, además, debes añadir tu usuario al grupo dialout.

¿Cómo sé en qué puerto COM está mi Arduino?

Mira en Herramientas > Puerto del IDE de Arduino o ejecuta python -m serial.tools.list_ports -v en una terminal. En Windows, el apartado Puertos (COM y LPT) del Administrador de dispositivos también muestra el puerto. Si no estás seguro, desconecta la placa y vuelve a listar los puertos; el que desaparece es el del Arduino.

¿Cómo envío datos de Python a Arduino?

Abre el puerto con serial.Serial y envía bytes, por ejemplo ser.write(b"LED ON\n"); no olvides convertir el texto con encode() y añadir un fin de línea. En el Arduino comprueba si han llegado datos con Serial.available() y lee la línea con Serial.readStringUntil('\n'). Como el Uno se reinicia al abrir el puerto, espera unos 2 segundos antes de enviar el primer comando.

¿Puedo usar el Monitor Serie de Arduino y Python a la vez?

No, un puerto serie solo puede abrirlo un programa a la vez; el segundo recibe el error Access is denied o Resource busy. Puedes procesar los datos en Python y, al mismo tiempo, mostrarlos en pantalla con print. Si necesitas dos canales separados, conecta un segundo conversor USB-TTL al Arduino y crea un segundo puerto serie con SoftwareSerial.

¿Por qué se reinicia mi Arduino cuando Python abre el puerto?

En el Uno, Nano y Mega, la línea DTR del chip USB está conectada al pin RESET a través de un condensador, por lo que la placa se reinicia cada vez que se abre el puerto; el IDE necesita este circuito para subir código. Espera 2 segundos después de abrir el puerto o espera una línea del Arduino que indique que está listo. Para evitar el reinicio por completo puedes poner un condensador de 10 µF entre RESET y GND, pero tendrás que quitarlo antes de subir código.

Ver todos los proyectos
Medir la intensidad de luz con LDR
#11Principiante

Medir la intensidad de luz con LDR

¿Quieres ver en números cuánta luz hay en tu habitación? El LDR, una pequeña pieza sensible a la luz, mide la luz ambiente y el Arduino escribe el valor en la pantalla del ordenador cada medio segundo

4.6(81)
139
HC-SR04 Medición de distancia (pantalla en serie)
#27Principiante

HC-SR04 Medición de distancia (pantalla en serie)

El sensor HC-SR04 funciona igual que un murciélago que se orienta en la oscuridad: emite un sonido que no puedes oír, escucha el eco y mide a qué distancia está el objeto que tiene delante. El Arduino

4.6(46)
71
Temperatura y humedad con DHT11
#55Intermedio

Temperatura y humedad con DHT11

¿Qué temperatura hace en tu habitación, y el aire está húmedo o seco? El sensor DHT11 mide la temperatura y la humedad, y el Arduino escribe los valores en una pantalla LCD de dos líneas, actualizándo

4.7(47)
73
Lectura de la intensidad luminosa con el módulo LDR del ESP32
#113Principiante

Lectura de la intensidad luminosa con el módulo LDR del ESP32

Visualización en tiempo real en el monitor serie del valor bruto del ADC del módulo LDR y del porcentaje relativo de luz calculado a partir de referencias de oscuridad y claridad. Este porcentaje no es una medición en lux. Explica el funcionamiento de la calibración del módulo. Es recomendable realizarlo antes del Proyecto 10, ya que se utiliza para determinar el valor umbral.

4.6(36)
55
Comunicación unidireccional entre 2 Arduino
#57Intermedio
Premium

Comunicación unidireccional entre 2 Arduino

Haces que dos Arduino hablen por un solo cable: pulsas el botón de uno y se enciende el LED del otro, vuelves a pulsar y se apaga. La placa que envía avisa del botón con un mensaje corto, y la que rec

4.4(37)
50
Comunicación bidireccional entre 2 Arduino
#58Intermedio
Premium

Comunicación bidireccional entre 2 Arduino

Dos Arduino se conectan con cables y hablan entre sí: pulsas el botón de uno y se enciende la luz del otro, lo pulsas otra vez y se apaga. Las dos placas llevan el mismo programa, así que cada una env

4.5(20)
26
Controlar un LED desde el móvil por Bluetooth
#34Principiante
Premium

Controlar un LED desde el móvil por Bluetooth

Un pequeño ensayo para encender y apagar las luces de tu habitación desde el móvil. El módulo Bluetooth pasa al Arduino las letras que envías desde el teléfono; una letra enciende el LED rojo, otra en

4.8(68)
89
Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avanzado
Premium

Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

¡Construye un registrador de temperatura con marca de tiempo con ESP32! Lee temperatura/humedad del DHT22 cada minuto, obtén la hora real del DS1307 RTC y registra en tarjeta microSD como CSV. El OLED muestra en vivo la temperatura (fuente grande), porcentaje de humedad, marca de tiempo y contador de registros. El archivo /temp_log.csv en la tarjeta SD se abre en Excel, Google Sheets o Python pandas para análisis — perfecto para monitoreo de invernadero, seguimiento de temperatura del hog

4.7(7)
12