De Arduino Uno a ESP32 ya que no tenía la placa para el curso
El año pasado saqué un par de cursos básicos de electrónica analógica y hace poco me inscribí en un curso básico de Arduino. La idea era sencilla: aprender de la mano de un instructor en lugar de seguir armando cosas a tientas por mi cuenta. Hasta entonces yo había hecho experimentos sueltos con un ESP32, sobre todo en MicroPython con Thonny, leyendo sensores de temperatura y manejando una pantalla OLED. Tenía noción de qué era un sensor y qué era un actuador, pero nunca había seguido una ruta de aprendizaje ordenada. Quería eso.
El proyecto principal de la primera sesión era: tres botones, un LED, un buzzer y un motor, todo armado en Tinkercad sobre un Arduino Uno. No se suponía que un estudiante lo resolviera completo en el primer intento; era una excusa para mostrar varias cosas a la vez, como reconocer pines, distinguir entradas de salidas, ver cómo un sensor (el botón) controla un actuador (el LED, el buzzer, el motor).
No lo logré en la clase. Me quedé a medias y la sesión terminó antes de que el circuito funcionara. Pero unos días después me senté solo frente a Tinkercad y lo repliqué hasta que corrió. Ese pequeño triunfo me dejó con ganas de más: quería verlo funcionando en hardware real, no en un simulador. Y ahí apareció el problema.
No tengo un Arduino Uno
Tengo pensado comprar uno en los próximos días, probablemente un genérico tipo Funduino, para tener mi propia placa de pruebas más allá de los materiales que presta el instructor. Pero en ese momento no tenía ninguno. Lo que sí tenía sobre el escritorio era mi ESP32, esa misma placa con la que ya había trasteado en MicroPython.
Así que me planteé un reto que terminó enseñándome más que el proyecto original: adaptar el código y el circuito de Arduino Uno al ESP32. Sobre el papel suena trivial, “es el mismo C++, solo cambian los pines”. En la práctica hay varias diferencias que, si no las conocés, te hacen perder horas. Este post documenta ese proceso completo, tropiezos incluidos, porque fueron justo los tropiezos los que me enseñaron cómo funciona realmente la placa.
Por qué el ESP32 no es un Arduino Uno con otro nombre
Antes de tocar un solo cable conviene entender tres diferencias que importan de verdad.
La primera y más peligrosa: el ESP32 trabaja a 3.3V de lógica, no a 5V. El Arduino Uno es una placa de 5V. Si conectás una señal de 5V directamente a un pin del ESP32, lo podés dañar. Esto cambia cómo se piensa cada conexión.
La segunda: el ESP32 tiene pines que el Uno no tiene y, sobre todo, tiene pines que parecen normales pero no lo son. Algunos son solo de entrada (no sirven como salida). Otros, los llamados pines de arranque o strapping pins, pueden impedir que la placa encienda si tienen algo conectado en el momento del arranque. Y hay un grupo de pines que ni siquiera hay que tocar porque están conectados a la memoria flash interna.
La tercera: el ESP32 puede activar resistencias internas tanto de pull-up como de pull-down. El Arduino Uno solo tiene pull-up interno. Esto da más flexibilidad, aunque en mi proyecto decidí construir las resistencias físicamente para entender mejor qué hace cada una.
Equivalencia de pines: Arduino Uno a ESP32
Esta es la tabla que fui armando para traducir el proyecto. Asume un ESP32 DevKit de 38 pines con el módulo WROOM-32, que es el más común.
| Función Arduino Uno | Pin Uno | GPIO ESP32 sugerido | Notas |
|---|---|---|---|
| Digital I/O genérico | D2–D13 | GPIO 4, 5, 13, 14, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33 | Seguros como entrada/salida |
| LED integrado | D13 | GPIO 2 | LED onboard del DevKit |
| Entradas analógicas | A0–A5 | GPIO 32, 33, 34, 35, 36, 39 | 34/35/36/39 son solo entrada |
| I2C SDA | A4 | GPIO 21 | Por defecto |
| I2C SCL | A5 | GPIO 22 | Por defecto |
| SPI MOSI | D11 | GPIO 23 | VSPI |
| SPI MISO | D12 | GPIO 19 | VSPI |
| SPI SCK | D13 | GPIO 18 | VSPI |
| PWM | D3,5,6,9,10,11 | Casi cualquier GPIO de salida | El ESP32 enruta PWM con libertad |
Y la lista de pines que conviene evitar o tratar con cuidado, que es la información que más dolores de cabeza me ahorró:
- Solo entrada (no sirven como salida ni tienen pull-up interno): GPIO 34, 35, 36, 39.
- Pines de arranque (problemáticos si tienen algo conectado al encender): GPIO 0, 2, 5, 12, 15.
- Nunca usar (conectados a la flash interna): GPIO 6, 7, 8, 9, 10, 11.
- ADC2 inutilizable con WiFi: si vas a usar WiFi, leé los analógicos solo en GPIO 32, 33, 34, 35, 36, 39 (ADC1).
Primer tropiezo que Windows no reconocía la placa
Con la teoría clara, conecté el ESP32 por USB y abrí el Arduino IDE. Antes de eso ya había instalado el soporte de placas ESP32 agregando la URL de Espressif en el gestor de tarjetas, y había seleccionado “ESP32 Dev Module” como placa. Todo parecía en orden. Le di a subir el código de prueba más básico, un parpadeo del LED integrado, y obtuve esto:
A fatal error occurred: Failed to connect to ESP32: No serial data received.
Mi primera reacción fue pensar que era el clásico problema del modo de flasheo. En muchos DevKit hay que mantener presionado el botón BOOT mientras el IDE intenta conectar. Lo intenté de varias formas: mantener BOOT y subir, hacer la secuencia BOOT + EN + soltar. Nada.
Después sospeché del shield que estaba usando como intermediario (una placa de expansión con puertos USB-C y micro USB). Conecté el ESP32 directo, bajé la velocidad de subida a 115200. Seguía fallando.
La pista real apareció en el Administrador de dispositivos de Windows: la placa aparecía como “USB Serial” con un signo de advertencia. Windows no tenía el driver del convertidor USB a serial. Mirando los identificadores de hardware encontré VID_1A86&PID_7523, que luego de una búsqueda rápida en la web encontré que corresponde al chip CH340, muy común en placas de fabricación china.
La solución fue instalar el driver CH340. Lo descargué de un tutorial de Industriales Andes que explica la instalación paso a paso. Como siempre que descargo un ejecutable de un sitio que no conozco, le pasé un antivirus antes de instalarlo para asegurarme de que no fuera malware. Una vez instalado, desconecté y reconecté la placa, y por fin apareció como “USB-SERIAL CH340 (COM4)” sin advertencias.
La lección: cuando una placa no sube código, antes de pelearte con botones y velocidades, verificá que el sistema operativo la reconozca de verdad. El driver es el primer eslabón. Tal vez no me hubiera pasado si hubiera usado una de mis computadoras con Linux pero no lo sé.
Segundo tropiezo con MicroPython estorbando el arranque
Aquí hay un detalle que conviene mencionar aunque en mi caso terminó resolviéndose junto con el driver. Mi ESP32 no era nuevo: tenía MicroPython grabado de mis experimentos previos con Thonny. El firmware de MicroPython ocupa el espacio de arranque de una forma que puede interferir con la herramienta de subida del Arduino IDE.
Si después de instalar el driver el problema de conexión hubiera persistido, el siguiente paso habría sido borrar la flash por completo con esptool:
pip install esptool
python -m esptool --chip esp32 --port COM4 erase_flashImportante: eso borra todo, incluidos los scripts de MicroPython. Conviene respaldar antes lo que uno quiera conservar. En mi caso el driver resolvió la conexión y el primer sketch de Arduino sobrescribió MicroPython sin más, pero dejo anotado el procedimiento por si sirve en el futuro.
Con el driver puesto, el parpadeo subió sin problemas. El log mostró la placa identificada correctamente:
Chip type: ESP32-D0WD-V3 (revision v3.1)
Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz
El LED integrado en GPIO 2 empezó a parpadear. El entorno funcionaba.
LED, botones y un shield con filas S, V y G
Una vez con el entorno andando, fui armando el circuito por partes. El shield que uso tiene una distribución muy cómoda: filas de pines macho marcadas como S (señal), V (3.3V) y G (tierra) para cada GPIO, además de una isla de alimentación con pines de GND, 3.3V y 5V. Todos los puntos de tierra están conectados entre sí, y la fila V comparte los mismos 3.3V que los pines 3.3V de la isla.
El LED externo fue lo primero. La conexión correcta es señal del GPIO hacia una resistencia de 330 ohmios, de ahí al ánodo del LED (la pata larga), y el cátodo a tierra. Un error común aquí es conectar el LED a la línea de alimentación V pensando que “necesita corriente”. Si se hace eso, el LED queda siempre encendido y el ESP32 no puede controlarlo. El LED se alimenta desde la señal del pin, no desde V.
Para los botones decidí construir las resistencias de pull físicamente en lugar de usar las internas. Esto me obligó a entender la diferencia real entre un pull-up y un pull-down que mostraba el proyecto original:
- Pull-up (lógica invertida, reposo en HIGH): resistencia de 10k (pero sólo tenía de 1k) entre V y la señal, el botón conecta la señal a tierra. En reposo el pin lee HIGH; al presionar, LOW.
- Pull-down (lógica directa, reposo en LOW): resistencia de 10k (pero sólo tenía de 1k) entre tierra y la señal, el botón conecta la señal a V. En reposo el pin lee LOW; al presionar, HIGH.
En el código original había dos botones con pull-up y uno con pull-down, así que respeté esa misma mezcla.
El motor fue donde el proyecto se puso interesante
El motor fue, de lejos, la parte que más me hizo pensar. Es un motorreductor amarillo típicos (aparentemente) de robótica, de 3 a 12V, con corriente de carga de hasta 250mA en su pico.
Un GPIO de un microcontrolador no puede mover un motor directamente: no entrega suficiente corriente y el motor, además, genera picos de voltaje que dañan la electrónica. Hace falta un transistor que actúe de interruptor, manejado por el pin pero con la corriente del motor pasando por otro camino.
Empecé con un transistor 2N3904, un NPN muy común. La conexión es: señal del GPIO hacia una resistencia hacia la base, emisor a tierra, colector al lado negativo del motor, y el lado positivo del motor a la alimentación. Además, un diodo 1N4007 en paralelo con el motor (cátodo hacia el positivo, ánodo hacia el colector) para absorber el pico inverso que el motor genera al apagarse. Ese diodo, llamado flyback o de rueda libre, protege al transistor.
Y aquí vino el tropiezo. Con el motor conectado a 5V (alimentando el shield desde un cargador externo por el puerto USB-C, porque la línea de 3.3V se quedaba corta), el LED de prueba que puse en paralelo soltaba un destello y se apagaba, y el motor no se movía nada. Probé con un cargador de 5V y 1A: el LED destellaba más fuerte, pero el motor seguía sin girar.
Entonces nuevamente a la web. Resulta que el problema era de saturación del transistor. Para que un 2N3904 conduzca de lleno con unos 200mA en el colector necesita alrededor de 20mA en la base. El ESP32 entrega como máximo unos 12mA por pin, que no alcanzan. El transistor quedaba en una zona intermedia donde no terminaba de “abrirse”.
La primera solución fue bajar la resistencia de base de 1k a 470 ohmios, para meterle más corriente a la base. Funcionó: el motor arrancó incluso alimentado solo desde el USB de la computadora. Pero era un parche que dejaba el transistor trabajando en su límite, con riesgo de calentarse o quemarse si el motor llegaba a trabarse.
Entonces la solución decente fue un par Darlington
Como tenía un segundo 2N3904 a mano, armé una configuración Darlington: dos transistores encadenados donde el primero maneja la base del segundo, de modo que las ganancias se multiplican. Así, una corriente de base pequeña, la que el GPIO sí puede dar cómodamente, basta para controlar todo el conjunto.
Las conexiones quedaron así:
- Señal del GPIO hacia resistencia de 1k (de vuelta a 1k, ya sin forzar nada) hacia la base del transistor 1.
- Emisor del transistor 1 hacia la base del transistor 2.
- Emisor del transistor 2 a tierra.
- Los dos colectores unidos, al lado negativo del motor.
- Lado positivo del motor a 5V.
- Diodo 1N4007 en paralelo con el motor, igual que antes.
Con el Darlington el motor gira sin esfuerzo, el GPIO trabaja dentro de sus límites y cada transistor conduce la mitad del esfuerzo, así que el calentamiento es menor. Es la diferencia entre “funciona de milagro” y “funciona porque está bien dimensionado”.
Aclaro algo: la solución realmente robusta para manejar motores es usar un driver dedicado como un L298N o un L293D, que están hechos exactamente para esto y protegen el microcontrolador por completo. Pero parte de la gracia de este ejercicio era resolverlo con lo que tenía en el cajón, y el Darlington fue una lección de electrónica que un módulo prefabricado no me habría dado.
El código final
Aquí está el código adaptado completo. La lógica es idéntica a la del proyecto original de Arduino Uno; lo único que cambió fueron los números de pines. El truco bonito del código es la función clk(), que detecta el flanco de un botón (el momento exacto de la pulsación) con un pequeño antirrebote, y permite que cada botón alterne el estado de su actuador.
// LED, BUZZER Y MOTOR - adaptado a ESP32
const int pins[] = {4, 16, 17, 15, 18, 19};
// [0]LED [1]Buzzer [2]Motor [3]BtnLED [4]BtnMotor [5]BtnBuzzer
bool habL = 0, habB = 0, estM = 0, antL = 1, antM = 1, antB = 0;
void setup() {
for (int i = 0; i < 3; i++) pinMode(pins[i], OUTPUT);
for (int i = 3; i < 6; i++) pinMode(pins[i], INPUT);
}
bool clk(bool act, bool &ant, bool ok) {
bool r = (ant == !ok && act == ok); ant = act;
if (r) delay(50); return r;
}
void loop() {
unsigned long t = millis();
// Botón LED (pull-up, lógica LOW): activa/desactiva parpadeo
if (clk(digitalRead(pins[3]), antL, LOW)) if (!(habL = !habL)) digitalWrite(pins[0], LOW);
// Botón Motor (pull-up, lógica LOW): enciende/apaga
if (clk(digitalRead(pins[4]), antM, LOW)) digitalWrite(pins[2], estM = !estM);
// Botón Buzzer (pull-down, lógica HIGH): activa/desactiva parpadeo
if (clk(digitalRead(pins[5]), antB, HIGH)) if (!(habB = !habB)) digitalWrite(pins[1], LOW);
if (habL) digitalWrite(pins[0], (t / 1000) % 2);
if (habB) digitalWrite(pins[1], (t / 1000) % 2);
}
La asignación de pines final fue: LED en GPIO 4, buzzer en GPIO 16, motor en GPIO 17 (vía el Darlington), botón del LED en GPIO 15, botón del motor en GPIO 18 y botón del buzzer en GPIO 19. Todos elegidos para evitar los pines problemáticos de la tabla de más arriba.
Un par de notas sobre componentes: el buzzer que usé es activo (trae oscilador propio, suena con solo darle voltaje). Si fuera pasivo, habría necesitado una señal PWM para generar el tono.
Para qué sirvió todo esto
El proyecto en sí es de iniciación: encender un LED, hacer sonar un buzzer, mover un motor. Visto así, no parece gran cosa. Pero el ejercicio de adaptarlo de una placa que no tengo a una que sí tengo me enseñó bastante más que si simplemente hubiera copiado el circuito tal cual sobre un Arduino Uno prestado.
Aprendí por qué importa el voltaje de lógica, qué pines de un ESP32 son trampas, cómo Windows reconoce (o no) una placa, cómo el firmware viejo puede estorbar, y sobre todo entendí de forma muy concreta por qué un GPIO no mueve un motor solo y qué hace realmente un transistor cuando lo pones de interruptor. La falla del 2N3904 y la solución con el Darlington valieron por sí solas todo el rato invertido.
Sigo con el plan de comprar mi Arduino Uno para tener mi propia placa de pruebas. Pero me llevo una idea que no tenía al empezar: la limitación de no tener exactamente el hardware que pide un tutorial no siempre es un obstáculo. A veces es la mejor parte del aprendizaje, porque te obliga a entender en lugar de copiar.