PTENES
MÓDULO 1.3 · PRÁCTICA

💻 ESP32: el primer programa

Del simulador a la placa real. Instalas Arduino IDE 2.x con el paquete esp32 3.x, haces la primera carga, aprendes lo mínimo de C++ para escribir programas de robot, usas el Serial Monitor como ventana al interior de la placa y controlas el brillo y el sonido con PWM con la nueva API.

6
Temas
Principiante
Nivel
Práctica
Tipo
ESP32
Hardware (o Wokwi)
0%
0 de 0 temas leídos · Sección 1 de 6

Contenido detallado

1

🧰 Arduino IDE 2.x y el paquete esp32 3.x

A Arduino IDE 2.x es el programa donde escribes el código, lo compilas y lo envías a la placa. Por sí sola, solo reconoce las placas Arduino; para ESP32 instalas el paquete "esp32" de Espressif, que actualmente está en la generación 3.x. Esta versión cambió varias funciones, por eso muchos tutoriales de 2022 o 2023 dan errores de compilación: se escribieron para la API 2.x.

🌱 ¿Eres nuevo aquí?

IDE (entorno de desarrollo) es el editor con botones para compilar y cargar. Compilar es traducir tu texto en C++ al lenguaje de máquina del chip. Un paquete de placas (core) es el conjunto de herramientas y bibliotecas que enseña a la IDE a comunicarse con una familia de chips. API es la lista de funciones que puedes llamar, como digitalWrite(); cuando cambia, el código antiguo puede dejar de compilar.

Instalación paso a paso

1

Descarga la IDE 2.x

En arduino.cc/en/software, elige tu sistema (Windows, macOS o Linux AppImage). Instálala y ábrela.

2

Abre el Gestor de placas

Ícono de placa en la barra lateral izquierda (o Herramientas → Placa → Gestor de placas). Escribe esp32.

3

Instala "esp32 by Espressif Systems" 3.x

Elige la versión 3 más reciente y haz clic en Instalar. Si el paquete no aparece, ve a Archivo → Preferencias y pega en "URLs adicionales": https://espressif.github.io/arduino-esp32/package_esp32_index.json

4

Comprueba la versión

En el Gestor de placas, el paquete debe mostrar "INSTALLED" con un número que empiece por 3. Ese número es el que debes indicar cuando pidas ayuda en un foro.

API 2.x (antigua) canal 0freq + bits pin 2 escribir en el CANAL, no en el pinno compila con el paquete 3.x API 3.x (actual)ledcAttach(2, 5000, 8) pin, frecuencia, bits ledcWrite(2, 128)recibe el PINel canal se elige automáticamente tutorial con "canal" = tutorial antiguo

Cómo leer: a la izquierda, el camino antiguo (línea gris discontinua) pasaba por un canal numerado que creabas y luego conectabas al pin. A la derecha, el camino actual: una llamada prepara el pin y la escritura usa el propio pin. Si un tutorial te pide elegir "canal 0" del PWM, es de la API 2.x; adapta el código a las dos funciones verdes.

✓ Señales de un tutorial actual

  • ✓Usa ledcAttach(pino, freq, bits) e ledcWrite(pino, valor).
  • ✓Menciona "arduino-esp32 3.x" o ESP-IDF 5.
  • ✓Usa un servomotor con la biblioteca ESP32Servo, no con Servo.h de Uno.

✗ Señales de un tutorial antiguo

  • ✗Funciones de PWM con un número de canal separado del pin.
  • ✗Te dice que "vuelvas a la versión 2.0.x del paquete" para que compile.
  • ✗Afirma que el ESP32 no tiene analogWrite (el paquete actual sí lo tiene).

Por qué aprender

Saber la versión del paquete evita la peor frustración del comienzo: copiar un código "que funcionaba" y ver diez errores en rojo. Con la regla "canal separado = API antigua", corriges cualquier tutorial en un minuto.

Conceptos clave

Gestor de placas
donde se instala el paquete
Paquete esp32
Espressif, generación 3.x
Versión de la API
decide si el código compila
Compilar
texto → código de máquina
2

🔌 Placa, puerto y primera carga

Para cargar un programa, el IDE necesita dos datos: qué placa (para compilar para el chip correcto) y qué puerto (por dónde enviarlo). Para el DevKit común (WROOM-32), elige ESP32 Dev Module; para placas con ESP32-S3, ESP32S3 Dev Module o el nombre exacto de tu placa. El puerto aparece cuando conectas el cable USB.

🌱 ¿Eres nuevo aquí?

A puerto es la "dirección" del cable en la computadora: COM3, COM4 en Windows; /dev/ttyUSB0 o /dev/ttyACM0 en Linux; /dev/cu.usbserial… en macOS. Muchas placas tienen un chip convertidor USB-serial (CP2102 o CH340) que puede necesitar un controlador en Windows. El botón BOOT pone el ESP32 en modo de recepción de programa; el EN (o RST) reinicia la placa.

1 compilar IDE + paquete2 cable USB puerto seleccionado3 grabar modo BOOT4 ejecutar se reinicia solo dónde suele fallar error en rojo:placa o API incorrecta sin puerto:cable solo de carga "Connecting...":mantén presionado BOOT texto extraño:velocidad en baudios distinta

Cómo leer: la fila verde muestra el recorrido del programa, de izquierda a derecha. Debajo de cada etapa está el fallo que más suele ocurrir. Cuando falle la carga, lee el último mensaje del IDE y encuentra en qué cuadro estás: eso ya te indica qué probar.

Objetivo: cargar en la placa real el mismo parpadeo del LED que ejecutaste en Wokwi en el módulo 1.1 (el LED externo en GPIO 2 con 220 Ω, o solo el LED azul de la placa, si tiene uno).

pisca-led.ino
// Primera carga en la placa real (ESP32, paquete esp32 3.x)
const int PINO_LED = 2;

void setup() {
  Serial.begin(115200);
  pinMode(PINO_LED, OUTPUT);
  Serial.println("Upload OK: o ESP32 esta rodando seu programa");
}

void loop() {
  digitalWrite(PINO_LED, HIGH);
  delay(500);
  digitalWrite(PINO_LED, LOW);
  delay(500);
}

Cómo verificar

  • ✓El IDE termina con "Hard resetting via RTS pin..." y el LED empieza a parpadear.
  • ✓En el Monitor Serie (115200 baud) aparece "Upload OK..."; si no aparece, presiona EN para reiniciar.
  • ✗En Linux, si aparece "Permission denied" en el puerto, ejecuta sudo usermod -aG dialout $USER, cierra la sesión y vuelve a iniciarla.

✓ Cuando se atasque, sigue este orden

  • ✓Cambia el cable por uno de datos que hayas comprobado.
  • ✓Comprueba si el puerto desaparece y vuelve a aparecer al desconectar y conectar el cable.
  • ✓Mantén presionado BOOT cuando aparezca "Connecting..." y suéltalo cuando veas el porcentaje.

✗ Lo que no lo resuelve

  • ✗Reinstalar todo el IDE antes de probar el cable.
  • ✗Dejar que el Monitor Serie de otro programa use el mismo puerto.
  • ✗Conectar en GPIO 0 o 12 algo que fuerce un nivel durante el arranque y luego culpar al IDE.

Por qué aprender

La primera carga es donde más gente se rinde, casi siempre por el cable o el puerto. Con el mapa de las cuatro etapas, el error se vuelve predecible y la solución toma unos minutos.

Conceptos clave

Puerto COM/tty
dirección del cable
Controlador USB
CP2102 o CH340
Botón BOOT
modo de grabación
Cable de datos
no solo de carga
3

🧱 setup, loop, variables y funciones

Todo sketch tiene dos funciones obligatorias. setup() se ejecuta una vez cuando se enciende la placa: aquí configuras los pines y el Serial. loop() se ejecuta otra vez apenas termina, para siempre: es el "pensar" del ciclo sentir-pensar-actuar. Dentro de ellas usas variables para guardar valores, if para decidir, for para repetir y funciones propias para darle un nombre a cada parte del comportamiento.

enciendevariables globales setup()una vez loop()para siempre repite lerDistancia() decidir()mover() tus funciones

Cómo leer: de izquierda a derecha indica el orden en el tiempo. La flecha discontinua muestra que el loop() nunca termina. A la derecha, los cuadros cian son funciones que escribes y que el loop llama: el loop queda corto y cada función hace una sola cosa, con un nombre que la explica.

TipoGuardaEjemplo en el robot
intnúmero enteropin, lectura del ADC
floatnúmero decimaldistancia en cm, voltaje
boolverdadero o falso¿hay un obstáculo adelante?
unsigned longnúmero entero grande, sin signotiempo en ms con millis()
constvalor que no cambianúmero del pin, límites

Objetivo: un semáforo de un solo LED, escrito con funciones y un for, para que veas lo legible que queda el loop. Ejecútalo en Wokwi con un LED en GPIO 2.

funcoes.ino
// Variables, if, for y funciones (ESP32, paquete esp32 3.x)
const int PINO_LED = 2;
int ciclo = 0;                 // variable global: tiene validez en todo el programa

void piscar(int vezes, int intervaloMs) {
  for (int i = 0; i < vezes; i++) {
    digitalWrite(PINO_LED, HIGH); delay(intervaloMs);
    digitalWrite(PINO_LED, LOW);  delay(intervaloMs);
  }
}

bool cicloPar(int n) {         // función que devuelve un valor
  return (n % 2 == 0);
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_LED, OUTPUT);
}

void loop() {
  ciclo++;
  if (cicloPar(ciclo)) {
    Serial.println("ciclo par: 2 parpadeos lentos");
    piscar(2, 400);
  } else {
    Serial.println("ciclo impar: 4 parpadeos rápidos");
    piscar(4, 120);
  }
  delay(1000);
}

Cómo verificar: el LED alterna entre 4 parpadeos rápidos y 2 lentos, con una pausa de 1 s entre los grupos; el Serial Monitor escribe "ciclo impar..." y "ciclo par..." alternadamente.

Por qué aprender

El rover del módulo 1.6 tendrá un loop de pocas líneas que llama a lerDistancia(), decidir() e mover(). El código organizado así es lo que te permite ajustar una parte sin romper las demás.

Conceptos clave

Tipo de variable
int, float, bool
const
nombre fijo para un valor
if / for
decidir y repetir
Función
un nombre, una tarea
4

🔍 Serial Monitor para depurar

Un robot no tiene pantalla. El Serial Monitor es tu ventana al interior del robot: el programa escribe con Serial.print() y tú lo lees en la computadora. El Serial Plotter, en el mismo menú Herramientas, dibuja los números como un gráfico en vivo, lo que permite ver el ruido y las tendencias de un vistazo. Depurar es exactamente esto: imprimir lo que el programa está pensando hasta encontrar dónde se equivoca.

🌱 ¿Eres nuevo aquí?

Baud rate es la velocidad de la comunicación serial, en bits por segundo. El número de Serial.begin(115200) debe coincidir con el seleccionado en la esquina de Serial Monitor; si es diferente, aparecen símbolos sin sentido. print escribe y continúa en la misma línea; println escribe y salta de línea.

Objetivo: generar dos curvas (una onda y una rampa) en el formato que entiende Serial Plotter, nombre:valor separados por comas, y un mensaje de estado en Serial Monitor. Funciona en Wokwi sin conectar nada.

serial-plotter.ino
// Serial Monitor y Serial Plotter (ESP32, paquete esp32 3.x)
unsigned long amostra = 0;

void setup() {
  Serial.begin(115200);           // mismo valor en la esquina de Monitor/Plotter
  delay(500);
  Serial.println("Depuración iniciada");
}

void loop() {
  float onda  = 50.0 * sin(amostra * 0.1);   // sube y baja entre -50 y 50
  int   rampa = amostra % 100;               // sube de 0 a 99 y vuelve a empezar

  // formato de Plotter: nombre:valor,nombre:valor y salto de línea
  Serial.print("onda:");  Serial.print(onda);
  Serial.print(",rampa:"); Serial.println(rampa);

  amostra++;
  delay(50);                      // 20 puntos por segundo
}

Cómo verificar

  • ✓En Serial Monitor: "Depuración iniciada" y luego líneas como "onda:29.55,rampa:6".
  • ✓Cierra Monitor y abre Herramientas → Serial Plotter: verás una onda sinusoidal y un diente de sierra, con las leyendas "onda" y "rampa".
  • ✗¿Símbolos extraños? El baud de Monitor no está en 115200.

💡 Depuración útil

Imprime el nombre y el valor ("dist=23.4", no solo "23.4") y un mensaje en cada rama del if para saber por dónde pasó el código. Evita imprimir miles de líneas por segundo sin delay (la ventana se congela) y recuerda que Monitor y Plotter no pueden usar el puerto al mismo tiempo.

Por qué aprender

Cuando el robot haga algo inesperado, el serial te permitirá descubrir si el sensor leyó mal, si la decisión fue incorrecta o si el motor no obedeció. En la Ruta 2, el mismo hábito se convierte en telemetría por Wi-Fi.

Conceptos clave

Baud rate
115200 en ambos extremos
print / println
misma línea / nueva línea
Serial Plotter
gráfico en vivo
nombre:valor
leyenda en el gráfico
5

🔘 Entradas: botón y lectura analógica

Hasta ahora, la placa solo había "hablado". Ahora escucha. Un botón con INPUT_PULLUP (visto en el módulo 1.2) se lee con digitalRead(); un potenciómetro (resistor con perilla giratoria) se lee con analogRead(), que devuelve valores de 0 a 4095. Los botones mecánicos tienen un detalle: el contacto "rebota" durante unos milisegundos y el programa puede contar varias pulsaciones como una sola.

🌱 ¿Eres nuevo aquí?

Antirrebote consiste en ignorar los cambios que duran menos de un tiempo mínimo (unos 30 ms), para contar solo las pulsaciones reales. millis() devuelve cuántos milisegundos han pasado desde que se encendió la placa; comparar dos valores de millis() permite medir intervalos sin detener el programa. El potenciómetro tiene tres patas: las de los extremos van a 3,3 V y GND; la del medio entrega el voltaje variable.

ESP32GPIO 4 GNDGPIO 343,3 V GNDGPIO 2 botónpull-up interno potenció- metro GND

Cómo leer: a la izquierda, el botón conecta GPIO 4 a GND al presionarlo (sin resistor externo: el pull-up está dentro del chip). A la derecha, el potenciómetro recibe 3,3 V (rojo) y GND (cian) en los extremos; la flecha indica el cursor central, que va a GPIO 34. Abajo, el LED de GPIO 2 muestra el resultado. Nunca conectes el potenciómetro a 5 V: GPIO 34 recibiría más de 3,3 V al final del recorrido.

Objetivo: contar las pulsaciones del botón con antirrebote (cada pulsación invierte el LED) y mostrar la posición del potenciómetro en porcentaje. Arma el circuito como en la figura de Wokwi ("Pushbutton", "Potentiometer", "LED" y "Resistor").

botao-e-potenciometro.ino
// Botón con antirrebote + potenciómetro (ESP32, paquete esp32 3.x)
const int PINO_BOTAO = 4;    // botón conectado a GND, pull-up interno
const int PINO_POT   = 34;   // ADC1, solo entrada
const int PINO_LED   = 2;
const unsigned long DEBOUNCE_MS = 30;

int estadoEstavel = HIGH;     // suelto
int ultimaLeitura = HIGH;
unsigned long mudouEm = 0;
int apertos = 0;
bool ledLigado = false;
unsigned long ultimoPrint = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PINO_BOTAO, INPUT_PULLUP);
  pinMode(PINO_LED, OUTPUT);
  Serial.println("Presiona el boton y gira el potenciometro");
}

void loop() {
  int leitura = digitalRead(PINO_BOTAO);
  if (leitura != ultimaLeitura) { mudouEm = millis(); ultimaLeitura = leitura; }
  if (millis() - mudouEm > DEBOUNCE_MS && leitura != estadoEstavel) {
    estadoEstavel = leitura;
    if (estadoEstavel == LOW) {          // acaba de presionarse
      apertos++;
      ledLigado = !ledLigado;
      digitalWrite(PINO_LED, ledLigado ? HIGH : LOW);
      Serial.print("Pulsacion numero "); Serial.println(apertos);
    }
  }
  if (millis() - ultimoPrint >= 500) {   // pot cada medio segundo, sin delay
    ultimoPrint = millis();
    int bruto = analogRead(PINO_POT);    // 0 a 4095
    int pct = map(bruto, 0, 4095, 0, 100);
    Serial.print("pot="); Serial.print(bruto);
    Serial.print(" ("); Serial.print(pct); Serial.println("%)");
  }
}

Cómo verificar

  • ✓Cada pulsación invierte el LED y suma 1 en "Pulsacion numero N", nunca 2 o 3 de una vez.
  • ✓Al girar el potenciómetro de un extremo al otro, "pot=" va de cerca de 0 (0%) a 4095 (100%).
  • ✗¿Cuenta pulsaciones por sí solo? Comprueba que el botón esté en columnas diferentes de la protoboard y que el pin sea el 4.

Por qué aprender

Es la misma lectura que usarás con sensores reales en el módulo 1.4: digital para el sensor IR de obstáculos, analógica para el sensor de línea. Y el truco de millis() en lugar de delay() vuelve, a lo grande, en el último tema de sensores.

Conceptos clave

INPUT_PULLUP
botón sin resistor externo
analogRead
de 0 a 4095 en ESP32
Antirrebote
ignorar el rebote
map()
convierte rangos
6

🎚️ PWM con la API 3.x: brillo y sonido

Un pin digital solo sabe dar 0 o 3,3 V. Para que un LED tenga brillo medio, el truco consiste en encenderlo y apagarlo muy rápido: si permanece encendido la mitad del tiempo, el ojo percibe la mitad del brillo. Esto es PWM (modulación por ancho de pulso). En el ESP32, el periférico LEDC genera el PWM y, en la API 3.x, bastan dos funciones: ledcAttach(pino, frequência, bits) e ledcWrite(pino, valor). Para el sonido, ledcWriteTone(pino, frequência) cambia la frecuencia y mantiene el pulso a la mitad.

🌱 ¿Eres nuevo aquí?

Ciclo de trabajo (duty cycle) es el porcentaje del tiempo durante el cual la señal permanece encendida. Frecuencia es cuántas veces por segundo se repite el ciclo (Hz). Resolución es en cuántos niveles divides el ciclo: con 8 bits, de 0 a 255; con 10 bits, de 0 a 1023. Un buzzer pasivo solo suena si recibe una frecuencia; el activo pita por sí solo cuando se enciende.

25% · bajoledcWrite(p, 64) 50% · medioledcWrite(p, 128)75% · alto ledcWrite(p, 191)mismo período, distinto ancho de pulso

Cómo leer: las tres líneas tienen el mismo ritmo (frecuencia); solo cambia cuánto tiempo permanece alto cada pulso. A la derecha, el brillo que percibe el ojo y el valor de ledcWrite en 8 bits (255 = 100%). En el módulo 1.5, el mismo número se convertirá en velocidad del motor.

Objetivo: el potenciómetro del tema 5 controla el brillo del LED del GPIO 2, y el botón reproduce una escala corta en un buzzer pasivo en el GPIO 25. En Wokwi, usa la pieza «Buzzer» (es pasiva).

pwm-brilho-e-som.ino
// PWM na API 3.x: brilho pelo potenciômetro e som no buzzer (ESP32)
const int PINO_LED    = 2;
const int PINO_BUZZER = 25;
const int PINO_POT    = 34;
const int PINO_BOTAO  = 4;
const int NOTAS[] = {262, 294, 330, 349, 392};   // dó, ré, mi, fá, sol (Hz)

void tocarEscala() {
  for (int i = 0; i < 5; i++) {
    ledcWriteTone(PINO_BUZZER, NOTAS[i]);   // muda a frequência, 50% de ciclo
    delay(180);
  }
  ledcWrite(PINO_BUZZER, 0);                // silêncio
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_BOTAO, INPUT_PULLUP);
  ledcAttach(PINO_LED, 5000, 8);      // LED: 5 kHz, 8 bits (0 a 255)
  // canais 0-1, 2-3, 4-5, 6-7 dividem um temporizador, e ledcWriteTone muda a
  // frequência do temporizador inteiro: o buzzer vai para o canal 2, longe do LED
  ledcAttachChannel(PINO_BUZZER, 2000, 10, 2);
  ledcWrite(PINO_BUZZER, 0);
  Serial.println("PWM pronto: gire o pot, aperte o botao");
}

void loop() {
  int bruto = analogRead(PINO_POT);            // 0 a 4095
  int brilho = map(bruto, 0, 4095, 0, 255);    // 0 a 255
  ledcWrite(PINO_LED, brilho);                  // recebe o PINO

  if (digitalRead(PINO_BOTAO) == LOW) {
    Serial.println("Tocando escala");
    tocarEscala();
    while (digitalRead(PINO_BOTAO) == LOW) delay(10);  // espera soltar
  }

  Serial.print("brilho:"); Serial.println(brilho);
  delay(50);
}

Cómo verificar

  • ✓Girar el potenciómetro cambia el brillo suavemente, sin parpadeos; el Serial Plotter muestra «brilho» de 0 a 255.
  • ✓Al presionar el botón, suenan cinco notas ascendentes y el Monitor muestra «Tocando escala».
  • ✗Error de compilación en ledcAttach? El paquete instalado es 2.x: actualízalo a 3.x (tema 1).

💡 Frecuencia adecuada para cada uso

LED: por encima de 1 kHz (5 kHz es un buen valor estándar) para que no parpadee en cámara. Motor DC: entre 1 y 20 kHz, según el controlador. Servo: 50 Hz, pero usa la biblioteca ESP32Servo en el módulo 1.5. Cuanto mayor sea la frecuencia, menor será la resolución máxima disponible; 8 a 10 bits son suficientes para robots.

Por qué aprender

El PWM es la forma en que el ESP32 controla la velocidad de los motores mediante el puente H en el módulo 1.5 y el ángulo de los servos. La bocina del rover, en los próximos pasos del módulo 1.6, usa el mismo ledcWriteTone de este ejemplo.

Conceptos clave

Ciclo de trabajo
% del tiempo encendido
Frecuencia
pulsos por segundo
Resolución en bits
8 bits = 0 a 255
ledcWriteTone
nota en el buzzer

✅ Resumen del módulo

✓
IDE 2.x + paquete esp32 3.x: el tutorial con canal PWM separado es de la API antigua.
✓
Carga en 4 pasos: compilar, cable de datos, BOOT, ejecutar; cada uno con su falla típica.
✓
setup una vez, loop para siempre, y funciones con nombre para cada tarea.
✓
Serial a 115200: Monitor para texto, Plotter para curvas con el formato nombre:valor.
✓
Entradas: botón con INPUT_PULLUP y debounce; potenciómetro en el ADC1 de 0 a 4095.
✓
PWM 3.x: ledcAttach(pino, freq, bits) y ledcWrite(pino, valor); ledcWriteTone para sonido.

Para profundizar

Siguiente módulo

1.4 · Sensores: el robot percibe. Distancia con HC-SR04, línea y obstáculos con infrarrojos, filtro de ruido, I2C y lectura de varios sensores sin bloquearse.