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.

¿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:
| Ajuste | Lado Arduino | Lado Python | Atención |
|---|---|---|---|
| Puerto | Se crea automáticamente por USB | serial.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 |
| Trama | 8 bits de datos, sin paridad, 1 bit de parada (8N1) | El valor por defecto de pyserial también es 8N1 | Normalmente no hace falta cambiarlo |
| Fin de línea | Serial.println() añade \r\n al final | readline() lee hasta encontrar \n | No olvides añadir \n al enviar desde Python |
| Tiempo de espera | Serial.setTimeout(), 1000 ms por defecto | timeout=1 (segundos) | Con timeout=None, readline() espera para siempre |
Instalar pyserial (Windows, macOS, Linux)
- 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.
- 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.
- Comprueba la instalación: python -c "import serial; print(serial.__version__)" debería mostrar un número de versión como 3.5.
- Para la sección de la gráfica en tiempo real también necesitas matplotlib: python -m pip install matplotlib.
- 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 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.

| Chip USB | Dónde lo encuentras | VID:PID | Nombre en Windows | Puerto en Linux |
|---|---|---|---|---|
| ATmega16U2 | Uno R3 y Mega 2560 originales | 2341:0043 (Uno) | Arduino Uno (COMx) | /dev/ttyACM0 |
| CH340 | Clones de Uno, Nano y Mega | 1A86:7523 | USB-SERIAL CH340 (COMx) | /dev/ttyUSB0 |
| CP2102 | ESP32 DevKit, NodeMCU | 10C4:EA60 | Silicon Labs CP210x USB to UART Bridge (COMx) | /dev/ttyUSB0 |
| FT232R | Nanos antiguos, cables FTDI | 0403:6001 | USB Serial Port (COMx) | /dev/ttyUSB0 |
| RP2040 (USB integrado) | Raspberry Pi Pico con MicroPython | 2E8A:0005 | Dispositivo 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.
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:
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
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.
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.
1// Python'dan gelen satır komutlarını işler / Handles line-based commands from Python
2const int LED_PIN = LED_BUILTIN; // Uno'da pin 13 / pin 13 on the Uno
3const int PWM_PIN = 9; // 220 ohm dirençli LED / LED with a 220 ohm resistor
4
5void setup() {
6 pinMode(LED_PIN, OUTPUT);
7 pinMode(PWM_PIN, OUTPUT);
8 Serial.begin(115200);
9 Serial.setTimeout(50); // readStringUntil en fazla 50 ms bekler / waits at most 50 ms
10 Serial.println("HAZIR"); // Python bu satırı bekler / Python waits for this line
11}
12
13void loop() {
14 if (Serial.available() > 0) {
15 String komut = Serial.readStringUntil('\n'); // satır sonuna kadar oku / read up to the newline
16 komut.trim(); // \r ve boşlukları sil / strip \r and spaces
17
18 if (komut == "LED ON") {
19 digitalWrite(LED_PIN, HIGH);
20 Serial.println("OK LED ON");
21 } else if (komut == "LED OFF") {
22 digitalWrite(LED_PIN, LOW);
23 Serial.println("OK LED OFF");
24 } else if (komut.startsWith("PWM ")) {
25 int deger = komut.substring(4).toInt(); // "PWM 128" -> 128
26 deger = constrain(deger, 0, 255); // 0-255 aralığında tut / keep it within 0-255
27 analogWrite(PWM_PIN, deger);
28 Serial.print("OK PWM ");
29 Serial.println(deger);
30 } else if (komut == "READ") {
31 Serial.println(analogRead(A0)); // A0 ölçümünü gönder / send the A0 reading
32 } else if (komut.length() > 0) {
33 Serial.print("ERR "); // bilinmeyen komut / unknown command
34 Serial.println(komut);
35 }
36 }
37}1import time
2
3import serial
4
5
6def komut_gonder(ser, komut):
7 ser.write((komut + "\n").encode("ascii")) # satır sonu şart / the newline is required
8 return ser.readline().decode("ascii", errors="ignore").strip() # cevabı bekle / wait for the reply
9
10
11with serial.Serial("COM3", 115200, timeout=2) as ser:
12 bitis = time.time() + 3 # en fazla 3 sn bekle / wait at most 3 s
13 while time.time() < bitis:
14 if ser.readline().strip() == b"HAZIR": # Arduino açıldı / the Arduino is up
15 break
16 print(komut_gonder(ser, "LED ON")) # OK LED ON
17 time.sleep(1)
18 print(komut_gonder(ser, "PWM 128")) # OK PWM 128
19 print("A0 =", komut_gonder(ser, "READ")) # A0 = 512
20 print(komut_gonder(ser, "LED OFF")) # OK LED OFFEl 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:
| Formato | Línea de ejemplo | Ventajas | Inconvenientes |
|---|---|---|---|
| Valor único | 512 | El formato más simple; el Serial Plotter de Arduino lo dibuja directamente | Con varios valores no se sabe cuál es cuál |
| CSV | 2573,512,23.5 | Corto; Excel y pandas lo leen directamente | El orden de las columnas debe ser igual en ambos lados |
| Clave:valor | temp:23.5,hum:41 | Legible; el Serial Plotter muestra las etiquetas | Má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 Python | El formato más largo; exige mucho a la escasa RAM del Uno |
| Binario | 2 bytes: 0x02 0x00 | La opción más rápida y compacta | No 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? |
|---|---|---|---|
| 9600 | 960 | 80 | Sensores lentos, cables largos, ejemplos antiguos |
| 57600 | 5760 | 480 | Mediciones de velocidad media |
| 115200 | 11 520 | 960 | Grá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
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.
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?

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ística | Arduino + Python en el PC | Pico + MicroPython (autónoma) | Pico + Python en el PC |
|---|---|---|---|
| ¿Dónde se ejecuta Python? | Solo en el ordenador; la placa ejecuta C++ | Dentro de la Pico | En los dos lados: MicroPython en la Pico y Python en el ordenador |
| ¿Hace falta un ordenador? | Sí, los datos se procesan en el ordenador | No, funciona con alimentación USB o con batería | Sí |
| Librerías disponibles | pyserial, matplotlib, pandas, numpy, OpenCV | Módulos limitados como machine y time | Todas las librerías del ordenador |
| Velocidad en baudios | Importante, debe coincidir en ambos lados | No se usa puerto serie | Indiferente, los datos van a velocidad USB |
| Reinicio al abrir el puerto | Sí en Uno, Nano y Mega | No aplica | No |
| ¿Cuándo elegirla? | Ya tienes un Arduino y necesitas librerías listas | Dispositivos sencillos e independientes | Quieres 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.
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íntoma | Causa | Solució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 Python | Ejecuta 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.py | pip 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 script | Cierra 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á conectada | Busca 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 serie | sudo usermod -a -G dialout $USER y luego cierra la sesión y vuelve a entrar |
| Caracteres sin sentido o UnicodeDecodeError | Velocidades distintas o ruido durante el arranque | Usa 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á arrancando | Usa println, revisa el puerto y la velocidad, aumenta el timeout |
| ValueError: invalid literal for int() with base 10 | Se intentó convertir en número una línea incompleta, vacía o inesperada | Pon la conversión dentro de try/except y comprueba el número de columnas |
| Los primeros comandos se ignoran | El Uno se reinició al abrir el puerto y el comando fue al bootloader | Espera la línea HAZIR o espera 2 segundos tras abrir el puerto |
| La gráfica va por detrás del tiempo real | Solo se lee una línea por fotograma y los datos se acumulan en el búfer | Lee 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 marcha | Python tiene el puerto ocupado | Detén el script con Ctrl+C y después sube el código |
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.
Proyectos para hacer después de leer esto
Ver todos los proyectos
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

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

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

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.

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

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

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

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
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

