Tema 0 · Entorno ESP32-C3 con VS Code y PlatformIO
Programación Dirigida por Eventos · Grado en Ingeniería Informática · 2026/27
Prepara y verifica Git, VS Code y PlatformIO en Windows o macOS, compila firmware real para la ESP32-C3 sin tener la placa y sigue el recorrido de un evento desde el pin GPIO hasta loop().
Prepara tu entorno: Git, VS Code y PlatformIO
Comprueba tu equipo, instala las herramientas en Windows (Git Bash) o macOS (zsh), entiende platformio.ini y compila la plantilla hasta obtener firmware.elf y firmware.bin. Termina diagnosticando los fallos típicos.
Teoría 1. Qué vas a montar hoy (y qué no)
En esta asignatura vas a perseguir eventos a dos alturas muy distintas. En Android el sistema te entrega un onClick ya masticado; en un sistema empotrado puedes seguir el evento desde el cambio eléctrico en un pin hasta la interrupción, la rutina que la atiende y el código que acaba usando ese dato. Esta unidad deja listo el entorno para la parte empotrada.
La placa de referencia es la ESP32-C3-DevKitM-1, que PlatformIO identifica como esp32-c3-devkitm-1. Su microcontrolador tiene un núcleo RISC-V de 32 bits, Wi-Fi y Bluetooth Low Energy.
Anfitrión frente a objetivo
La idea clave del día es separar dos máquinas:
- Anfitrión (host): tu Windows o tu Mac, donde escribes y compilas.
- Objetivo (target): el núcleo RISC-V de la ESP32-C3, donde se ejecutará el firmware.
Compilas en un sitio para ejecutar en otro. Por eso hace falta una toolchain específica (compilador, ensamblador, enlazador y utilidades para RISC-V), y por eso compilar sin placa es una prueba real: valida el código, el framework, el compilador y el enlazado. Lo único que queda pendiente es la interacción con el hardware.
- Para x86-64, la de tu portátil, y luego la placa lo traduce
- Para el núcleo RISC-V de 32 bits de la ESP32-C3
- Para ARM, porque todos los microcontroladores son ARM
- Para ninguna concreta: el firmware es bytecode portable
El anfitrión (Windows/macOS) solo compila. El código generado es para el objetivo: el núcleo RISC-V de la ESP32-C3. Por eso PlatformIO descarga una toolchain RISC-V.
- Que el compilador y el enlazado funcionan
- Que el botón BOOT genera interrupciones
- Que el framework Arduino-ESP32 está resuelto
- Que el puerto serie de la placa responde
- Que el código fuente no tiene errores de compilación
Compilar valida la cadena de herramientas: código, framework, compilador y enlazado. El comportamiento eléctrico (botón, interrupciones reales) y el puerto serie solo se verifican con la placa.
Práctica 2. Comprueba tu equipo y aprende las reglas
Compilar para una ESP32-C3 pide pocos recursos. Lo esencial: que VS Code sea compatible con tu sistema, espacio para los paquetes de PlatformIO e Internet durante la primera instalación.
| Componente | Mínimo | Recomendado |
|---|---|---|
| Windows | Windows 10/11 de 64 bits | Windows 11 o 10 actualizado |
| macOS | Versión compatible con VS Code actual | macOS 13 o posterior, Apple Silicon o Intel |
| Memoria / disco | 4 GB de RAM y 5 GB libres | 8 GB de RAM y unos 10 GB libres |
| Red | HTTPS durante la instalación | Buena conexión la primera vez; luego se usa la caché |
Autodiagnóstico en dos minutos. En Windows: Win + R → msinfo32 → «Tipo de sistema», y el espacio libre en Este equipo. En macOS: menú Apple → «Acerca de este Mac» y Ajustes del Sistema → General → Almacenamiento. No necesitas virtualización para compilar.
| Caso | Tu equipo | Qué haces |
|---|---|---|
| A | Windows x64 o Mac reciente, ≥ 8 GB, SSD | Instalación estándar (ruta recomendada) |
| B | Windows ARM64 o arquitectura poco habitual | Instaladores de tu arquitectura; PlatformIO resuelve luego la toolchain del objetivo |
| C | Poco disco o red muy limitada | Git y VS Code primero; PlatformIO y la 1.ª compilación desde una red mejor |
Reglas que evitan casi todos los problemas
- Misma plantilla y mismo
platformio.inique el resto de la clase: no cambiesplatform,boardniframework. - En Windows, usa Git Bash para los comandos: así la sintaxis es casi la de macOS.
- Rutas simples, sin tildes:
C:\dev\pdeen Windows,~/dev/pdeen macOS. - No instales a mano GCC para RISC-V, esptool ni Arduino-ESP32: PlatformIO gestiona versiones compatibles en
~/.platformio. - No borres
~/.platformiopara «arreglar» algo sin saber la causa: tendrías que volver a descargarlo todo. - Hoy no lances
pio run -t uploadnipio device monitor: sin placa no se pueden verificar. - Distingue compilación de ejecución.
C:\Users\<tú>\OneDrive\Documentos\Prácticas PDEC:\Program Files\pdeC:\dev\pde- El Escritorio, para tenerla a mano
C:\dev\pde es corta, sin tildes ni espacios y no está sincronizada. OneDrive o iCloud pueden bloquear ficheros temporales de compilación.
- Instalar a mano el compilador GCC para RISC-V
- Usar Git Bash para los comandos en Windows
- Cambiar
boardenplatformio.iniporque «suena mejor otra placa» - Ejecutar
pio run -t uploadhoy para ver qué pasa - Dejar que PlatformIO gestione sus paquetes en
~/.platformio
La toolchain la gestiona PlatformIO, el platformio.ini es común a toda la clase y hoy no se carga nada en la placa. Git Bash y dejar a PlatformIO gestionar ~/.platformio son justo lo recomendado.
Práctica 3. Instalación en Windows con Git Bash
En Windows usamos Git for Windows porque, además de Git, trae Git Bash, una terminal con sintaxis tipo Unix. Luego instalamos VS Code y, desde el propio editor, la extensión PlatformIO IDE.
- Git. Descarga el instalador para tu arquitectura desde
https://git-scm.com/download/winy deja las opciones por defecto (el editor predeterminado da igual). - Abre Inicio → Git Bash y comprueba:
git --version- VS Code desde
https://code.visualstudio.com(«User Installer» basta). Marca Add to PATH si aparece. Cierra y vuelve a abrir Git Bash. - Comprueba:
code --version- PlatformIO IDE. En VS Code,
Ctrl + Shift + X, busca «PlatformIO IDE», verifica que el publicador es PlatformIO y pulsa Install. Espera a que se pueda abrir PIO Home. - La extensión instala su Core en
~/.platformio. Para usarpiofuera de VS Code, añade su carpeta Scripts al PATH de Bash:
echo 'export PATH="$HOME/.platformio/penv/Scripts:$PATH"' >> ~/.bashrc
source ~/.bashrc
pio --version- Si
piosigue sin existir, abre una vez PIO Home, espera a que termine y abre una Git Bash nueva.
La instalación está lista cuando git --version, code --version y pio --version responden. En Windows ARM64 usa instaladores ARM64; no hace falta activar Hyper-V, WHPX ni virtualización.
~/.bashrc?$HOME/.platformio/penv/bin$HOME/.platformio/packages$HOME/.vscode/extensions/platformio$HOME/.platformio/penv/Scripts
En Windows el entorno virtual de PlatformIO deja los ejecutables en penv/Scripts. En macOS la carpeta equivalente es penv/bin.
pio --versionpio --version debe mostrar una versión. Junto con git --version y code --version, es el criterio de que la instalación está lista.
Práctica 4. Instalación en macOS con zsh
En macOS Git llega con las Command Line Tools de Apple y la terminal por defecto es zsh.
- Git. En Terminal:
xcode-select --installPulsa «Instalar», acepta la licencia y espera. Si dice que ya están instaladas, sigue. Después: git --version.
- VS Code desde
https://code.visualstudio.com, versión Apple Silicon o Intel según tu equipo. Arrástralo a Aplicaciones. - Para tener el comando
code: en VS Code,Cmd + Shift + P→ escribe «shell command» → Shell Command: Install 'code' command in PATH. Cierra y abre Terminal y pruebacode --version. - PlatformIO IDE.
Cmd + Shift + X, busca «PlatformIO IDE» del publicador PlatformIO, Install, y espera a que termine PlatformIO Core. - Añade PlatformIO al PATH de zsh:
echo 'export PATH="$HOME/.platformio/penv/bin:$PATH"' >> ~/.zprofile
source ~/.zprofile
pio --version- Si
piono aparece, abre PIO Home, espera a que se inicialice, abre una Terminal nueva y repite.
Windows frente a macOS, de un vistazo
| Windows | macOS | |
|---|---|---|
| Terminal | Git Bash | Terminal (zsh) |
| Git viene de | Git for Windows | Command Line Tools (xcode-select --install) |
Comando code |
Opción Add to PATH del instalador | Shell Command: Install 'code' command in PATH |
| Fichero de perfil | ~/.bashrc |
~/.zprofile |
Carpeta de pio |
~/.platformio/penv/Scripts |
~/.platformio/penv/bin |
xcode-select --installxcode-select --install abre la ventana del sistema que instala las Command Line Tools, que incluyen Git.
~/.bashrc y pio sigue sin aparecer en Terminal. ¿Qué ha pasado?- macOS usa zsh por defecto, que lee
~/.zprofile, no~/.bashrc - En macOS PlatformIO no se puede usar fuera de VS Code
- Tenía que usar
penv/Scriptsen lugar depenv/bin - Le falta instalar Git Bash para macOS
zsh no lee ~/.bashrc. La guía usa ~/.zprofile con penv/bin. penv/Scripts es la carpeta de Windows y Git Bash solo existe allí.
Teoría 5. platformio.ini: la fuente de verdad
PlatformIO separa tres conceptos que conviene no mezclar:
platform
El ecosistema de soporte del fabricante. Aquí, espressif32, fijado a una versión.
board
Una placa concreta: su microcontrolador y parámetros habituales. Aquí, esp32-c3-devkitm-1.
framework
La capa de software con la que programas. Aquí, arduino (Arduino-ESP32).
La plantilla de la asignatura trae exactamente esto:
[env:esp32-c3-devkitm-1]
platform = espressif32@7.0.1
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200Léelo línea a línea como decisiones reproducibles:
[env:...]da nombre al entorno. Ese nombre aparece luego en la salida depio runy en la carpeta.pio/build/esp32-c3-devkitm-1/.platform = espressif32@7.0.1fija la versión de la plataforma, para que toda la clase resuelva el mismo conjunto de paquetes.boardselecciona la ESP32-C3-DevKitM-1.framework = arduinoactiva la API Arduino-ESP32. No significa que uses el IDE de Arduino.monitor_speed = 115200deja preparado el monitor serie para cuando haya placa.
Cómo llega la toolchain
No descargas ningún compilador RISC-V. Al procesar el proyecto, el gestor de paquetes de PlatformIO resuelve plataforma, toolchain, framework y utilidades, y lo guarda en ~/.platformio:
pio pkg install # instala o completa los paquetes del proyecto
pio pkg list # muestra lo que ha quedado resuelto
pio project config # cómo interpreta PlatformIO tu platformio.ini
pio boards esp32-c3 # placas cuyo identificador contiene esp32-c3La primera vez tarda varios minutos y descarga cientos de megabytes; después se reutiliza la caché.
framework = arduino en nuestra plantilla?- Que hay que compilar desde el IDE de Arduino en vez de VS Code
- Que la placa objetivo es una Arduino Uno
- Que el programa se escribe con la API Arduino-ESP32
- Que se usa la toolchain AVR de Arduino
framework = arduino selecciona Arduino-ESP32 como API de programación. Seguimos en VS Code + PlatformIO y la placa sigue siendo la ESP32-C3.
espressif32@7.0.1 y no simplemente espressif32?- Porque sin versión PlatformIO no sabe que es una placa Espressif
- Para que toda la clase resuelva el mismo conjunto de paquetes y los errores sean reproducibles
- Porque la versión 7.0.1 es la única que soporta RISC-V
- Para que la compilación sea más rápida
Fijar la versión hace que todos obtengan las mismas versiones de plataforma y paquetes, así un error es reproducible en cualquier equipo de la clase.
board que usa la plantilla.esp32-c3-devkitm-1board = esp32-c3-devkitm-1, el identificador de PlatformIO para la ESP32-C3-DevKitM-1.
Práctica 6. Carpeta de trabajo y proyecto plantilla
Partimos de una plantilla común en lugar de crear el proyecto desde cero: así todos compilamos para la misma placa, plataforma y framework, y nadie obtiene resultados distintos por haber creado el proyecto otro día.
La plantilla es pde-esp32c3-eventos.zip y contiene platformio.ini, src/main.cpp y un README.md. No trae la toolchain ni binarios: eso lo resuelve PlatformIO en tu equipo.
1. Crea la carpeta
# Windows · Git Bash
mkdir -p /c/dev/pde
cd /c/dev/pde
pwd
# macOS · Terminal
mkdir -p ~/dev/pde
cd ~/dev/pde
pwd2. Descomprime y abre
Copia el ZIP a esa carpeta y extráelo (Windows: clic derecho → «Extraer todo…»; macOS: doble clic). Luego:
cd /c/dev/pde/pde-esp32c3-eventos # en macOS: cd ~/dev/pde/pde-esp32c3-eventos
code .Si PlatformIO reconoce platformio.ini, la barra inferior muestra Build, Upload y Serial Monitor. Hoy solo usas Build.
3. Reconoce la estructura
pde-esp32c3-eventos/
├── platformio.ini
├── README.md
└── src/
└── main.cppAl compilar aparecerá además .pio: paquetes y resultados generados. No la edites ni la entregues. Antes de compilar, confirma que estás en la raíz:
pwd
ls # debe aparecer platformio.iniSi ls no muestra platformio.ini, estás un nivel por encima o por debajo y pio run no sabrá qué proyecto construir.
code .code . abre VS Code con la carpeta actual como proyecto (el punto es «aquí»).
.pio. ¿Qué haces con ella al entregar la práctica?- La entregas: contiene el firmware y es lo que se evalúa
- No la entregas ni la editas: es contenido generado automáticamente
- La renombras a
buildpara que sea más claro - La copias dentro de
src/para que el proyecto sea autocontenido
.pio contiene paquetes y resultados generados. Se regenera con pio run; no forma parte del código fuente.
Práctica 7. Primera compilación sin placa
La compilación no necesita dispositivo: el objetivo ya está descrito en platformio.ini. Desde la raíz del proyecto:
pio pkg install
pio runLa primera vez descargará paquetes y luego compilará y enlazará. Al final debe aparecer SUCCESS para el entorno esp32-c3-devkitm-1.
Los artefactos
ls -lh .pio/build/esp32-c3-devkitm-1/| Fichero | Qué es |
|---|---|
firmware.elf |
Conserva información útil para el enlazado y la depuración (símbolos, secciones). |
firmware.bin |
La imagen binaria que, en otra sesión, se cargará en la memoria flash de la placa. |
De forma simplificada, pio run coordina: fuente C/C++ → preprocesado → compilación → objetos → enlazado → firmware. Tú no invocas gcc, el enlazador ni esptool a mano.
Comandos útiles
pio run -t clean # limpia la compilación sin borrar las dependencias descargadas
pio run # vuelve a compilar desde cero
pio run -v # verbose: compilador, flags, cabeceras y paquetes realesLa salida -v es enorme: úsala solo cuando necesites saber qué está usando PlatformIO de verdad. Desde la interfaz, lo mismo es PlatformIO (barra lateral) → Project Tasks → esp32-c3-devkitm-1 → General → Build.
Verificación completa
git --version && code --version && pio --version && pio system info
pio project config
pio pkg install
pio pkg list
pio run
ls -lh .pio/build/esp32-c3-devkitm-1/firmware.elf, porque incluye los símbolos de depuraciónplatformio.ini, que describe el entornofirmware.bin, la imagen binariamain.cpp, que se compila en la propia placa
firmware.bin es la imagen binaria para la flash. firmware.elf conserva información para enlazado y depuración.
pio project config → pio pkg install → pio pkg list → pio run → ls -lh .pio/build/esp32-c3-devkitm-1/Primero ves cómo interpreta PlatformIO la configuración, luego resuelves y listas paquetes, compilas y, por último, compruebas que existen los artefactos.
pio run -t cleanpio run -t clean borra los resultados de compilación; los paquetes de ~/.platformio se conservan.
Ejercicio 8. Diagnóstico I: «command not found» y carpetas
Cuando algo falla, busca el primer error significativo: los mensajes posteriores suelen ser consecuencia de él. Esta es la parte de la tabla de problemas que tiene que ver con la terminal, el PATH y la carpeta en la que estás.
| Síntoma | Causa probable | Solución |
|---|---|---|
git: command not found |
Git no instalado o terminal abierta antes de instalarlo | Windows: Git for Windows + Git Bash nueva. macOS: xcode-select --install |
code: command not found |
VS Code no está en el PATH | Windows: reinstalar con Add to PATH o terminal nueva. macOS: Shell Command: Install 'code' command in PATH |
pio: command not found |
PlatformIO Core no está en el PATH o la extensión no terminó | Abrir PIO Home, esperar y aplicar el PATH del apartado de tu sistema |
Not a PlatformIO project / no platformio.ini |
Ejecutas pio en otra carpeta |
pwd y ls; entrar en pde-esp32c3-eventos |
Ahora tú. Lee cada caso, decide la causa y la primera acción.
Caso de ejemplo resuelto
Javier instala VS Code con Git Bash ya abierta y escribe code --version: command not found. Causa: la terminal leyó el PATH antes de la instalación. Acción: cerrar Git Bash y abrir una nueva. Si persiste, reinstalar marcando Add to PATH.
git --version y code --version responden, pero pio --version da command not found. ¿Qué haces primero?- Borrar
~/.platformioy reinstalar la extensión - Instalar Python y PlatformIO Core a mano con pip
- Abrir PIO Home, esperar a que termine, aplicar el PATH de PlatformIO y abrir una terminal nueva
- Cambiar
platformenplatformio.ini
La causa probable es que el Core no está en el PATH o no terminó de instalarse. Borrar ~/.platformio sin diagnóstico va contra las reglas, y platformio.ini no tiene nada que ver con que la terminal encuentre pio.
pio run en /c/dev/pde y obtiene Not a PlatformIO project. ¿Cuál es la causa?- Está una carpeta por encima:
platformio.iniestá enpde-esp32c3-eventos - Le falta ejecutar
pio pkg install - El
boardestá mal escrito - No tiene la placa conectada
En /c/dev/pde solo está la carpeta del proyecto, no platformio.ini. Hay que hacer cd pde-esp32c3-eventos y repetir.
pwdpwd (print working directory) muestra la ruta actual; ls lista lo que hay dentro.
Ejercicio 9. Diagnóstico II: red, disco, permisos y falsas alarmas
La segunda mitad de la tabla trata de lo que pasa dentro del proyecto: paquetes, red, disco y hardware.
| Síntoma | Causa probable | Solución |
|---|---|---|
Unknown board ID |
board incorrecto o plataforma no resuelta |
board = esp32-c3-devkitm-1 y pio pkg install |
Could not install package / HTTP error |
Red, proxy, DNS o firewall | Otra red; revisar proxy; repetir pio pkg install |
No space left on device |
Sin espacio para paquetes o temporales | Liberar varios GB y recompilar |
Permission denied en ~/.platformio |
Carpeta creada con otro usuario | No ejecutar PlatformIO como administrador/root; corregir permisos |
Error de compilación en main.cpp |
Código modificado o incompleto | Restaurar src/main.cpp del ZIP y pio run |
| No aparece ningún puerto serie | No hay placa | Normal hoy: no es un fallo |
Upload failed / device not found |
Se intentó cargar sin hardware | No usar upload hasta tener placa |
Red del campus y proxy
Si la descarga va en casa pero falla en el campus, no toques platformio.ini: el problema es de conectividad, no de la placa. Si el centro usa un proxy, se configura de forma temporal en la terminal con los datos reales que te den:
export HTTP_PROXY=http://proxy.ejemplo:8080
export HTTPS_PROXY=http://proxy.ejemplo:8080
pio pkg installNo dejes contraseñas en documentos compartidos ni en el historial. Si una descarga queda a medias, vuelve a lanzar pio pkg install: PlatformIO detecta lo que falta y continúa.
Si tu equipo no puede
PlatformIO Core desde Python (avanzado, no recomendado para la clase), Wokwi (simulador web con ESP32, GPIO, botones e interrupciones), un equipo del aula, o hacer la primera descarga en otra red y conservar el perfil de usuario.
pio pkg install funciona; en la Wi-Fi del campus falla con HTTP error. Un compañero sugiere cambiar platform a otra versión. ¿Qué respondes?- Buena idea: otra versión puede estar en un servidor distinto
- Mejor cambiar
board, que es lo que descarga los paquetes - Hay que borrar
~/.platformioy empezar de cero - Es un problema de conectividad: revisar proxy u otra red y repetir
pio pkg install, sin tocarplatformio.ini
La guía insiste: un fallo de red no se arregla editando platformio.ini. Se revisa proxy/red y se repite pio pkg install, que reanuda lo que falte.
pio device listno muestra ninguna ESP32-C3pio runtermina conNo space left on device- No aparece ningún puerto serie
pio run -t uploadfalla con device not foundUnknown board IDal compilar
Sin placa es normal que no haya puerto ni dispositivo, y que upload falle (por eso no se lanza hoy). Falta de espacio y Unknown board ID sí son problemas reales del entorno.
Permission denied en ~/.platformio. ¿Qué no debes hacer?- Ejecutar PlatformIO como administrador o con
sudo - Revisar los permisos de tu carpeta de usuario
- Comprobar con qué usuario se creó la carpeta
- Leer el primer error significativo del registro
Ejecutar como administrador/root suele ser la causa del problema (carpetas creadas con otro propietario). La solución es corregir los permisos de tu carpeta de usuario.
Repaso 10. Repaso: chuleta del entorno
| Concepto | Lo que debes recordar |
|---|---|
| Anfitrión / objetivo | Compilas en Windows/macOS; el código es para el RISC-V de la ESP32-C3 |
platformio.ini |
Fuente de verdad: env, platform, board, framework, monitor_speed |
| Toolchain | La resuelve PlatformIO en ~/.platformio; nunca a mano |
PATH de pio |
Windows: penv/Scripts en ~/.bashrc. macOS: penv/bin en ~/.zprofile |
| Artefactos | .pio/build/esp32-c3-devkitm-1/firmware.elf y firmware.bin |
| Hoy no | upload, device monitor, puerto serie, BOOT |
Lista final: git, code y pio responden · ruta simple (C:\dev\pde o ~/dev/pde) · VS Code reconoce platformio.ini · pio project config muestra el entorno · pio pkg install y pio pkg list correctos · pio run en SUCCESS · existen los dos artefactos.
- Sí: si el código compila, el comportamiento eléctrico está garantizado
- No: compilar valida la cadena de herramientas y el código, pero el comportamiento eléctrico solo se verifica con la placa
- Sí, siempre que
monitor_speedsea 115200 - No: compilar sin placa no demuestra nada
Hay que distinguir compilación de ejecución. Compilar sin placa es una comprobación real (código, framework, compilador, enlazado), pero no prueba el hardware.
pio pkg installpio pkg install resuelve plataforma, toolchain y framework; si algo quedó a medias, descarga solo lo que falta.
platformio.ini no debes cambiar salvo indicación expresa del profesor?platformboardframework- Comentarios que añadas tú
platform, board y framework se fijan para toda la clase y garantizan una construcción reproducible.
Del pin al loop(): el evento sin hardware
Sigue en el código de la plantilla el recorrido señal → GPIO → interrupción → ISR → estado compartido → loop(): GPIO9 y BOOT, INPUT_PULLUP, flanco FALLING, attachInterrupt, volatile y antirrebote, y compáralo con un onClick de Android.
Teoría 1. Un evento empieza mucho antes del callback
Cuando en Android se ejecuta tu onClick, el sistema ya ha hecho muchísimo trabajo por ti: el táctil detectó el dedo, el sistema decidió a qué vista pertenece, la gestionó el hilo principal... Tú solo ves el final.
En un microcontrolador ese trabajo no está escondido. Puedes seguir el evento desde que cambia la tensión de un pin hasta que tu código decide qué hacer. Y aparece el mismo esquema que en cualquier sistema dirigido por eventos:
Android (onClick) |
ESP32-C3 (GPIO) | |
|---|---|---|
| Nivel | Alto: muchas capas ya han procesado la entrada | Bajo: ves el pin, la interrupción y la rutina |
| Manejador | Un método normal en el hilo principal | Una ISR, que interrumpe lo que se estuviera ejecutando |
| Duración permitida | Corta, para no congelar la interfaz | Extremadamente corta |
Hoy no hay placa, así que no podemos medir la parte eléctrica real: el nivel de tensión, el rebote del pulsador o el instante exacto en que el periférico pide la interrupción. Lo que sí podemos es leer el código y saber qué configura cada etapa cuando el firmware se ejecute.
Suceso → detección → manejador → estado → consumo. En Android las primeras etapas las hacen capas que no ves; en la ESP32 las configuras tú.
- El nivel de tensión real del pin
- Que el código de la ISR compila
- El rebote mecánico del pulsador
- El instante en que el periférico GPIO solicita la interrupción
- Qué variable usa la ISR para dejar constancia del evento
Lo eléctrico y temporal (tensión, rebote, instante de la petición) necesita hardware. Leer y compilar el código, incluida la ISR y sus variables, sí se puede hacer hoy.
Teoría 2. GPIO9, botón BOOT, pull-up y flanco FALLING
El programa de la plantilla está escrito para observar un pulsador en el GPIO9 de la placa de referencia: en la ESP32-C3-DevKitM-1, el botón BOOT lleva el GPIO9 a nivel bajo al pulsarlo.
Un GPIO (General-Purpose Input/Output) es un pin digital configurable como entrada o salida, y puede ser fuente de interrupciones.
pinMode(PIN_BOTON, INPUT_PULLUP);
attachInterrupt(PIN_BOTON, alPulsarBoton, FALLING);INPUT_PULLUP: un nivel de reposo conocido
Una entrada «al aire» no tiene un valor fiable. Una resistencia pull-up la mantiene en nivel alto mientras nadie la toque. INPUT_PULLUP activa una resistencia pull-up interna del chip, así que no hace falta añadirla por fuera.
FALLING: el momento exacto que nos interesa
Al pulsar, el pin pasa de alto a bajo: es un flanco descendente. Justo eso es lo que pide FALLING.
nivel alto ───────┐ ┌──────── alto
│ pulsado │
bajo └───────────┘
↑ FALLING ↑ (RISING, no nos interesa)Fíjate en la combinación: el pull-up deja el pin alto en reposo, y el botón lo baja. Por eso el evento natural «se ha pulsado» es el flanco descendente. Si el circuito estuviera al revés (reposo bajo, pulsado alto), el flanco de pulsación sería el ascendente.
INPUT_PULLUP y el botón sin pulsar, ¿en qué nivel está el GPIO9?- Alto, gracias a la resistencia pull-up interna
- Bajo, porque no hay corriente
- Indefinido, oscila entre alto y bajo
- Alterna a 115200 baudios
La pull-up interna mantiene la entrada en nivel alto en reposo. Al pulsar BOOT, el pin baja.
attachInterrupt indica una transición de nivel alto a nivel bajo?FALLINGFALLING es la condición de disparo para el flanco descendente (alto → bajo).
Teoría 3. attachInterrupt y la ISR mínima
Este es el código esencial de src/main.cpp de la plantilla:
constexpr uint8_t PIN_BOTON = 9;
volatile uint32_t eventosHardware = 0;
volatile bool eventoPendiente = false;
void ARDUINO_ISR_ATTR alPulsarBoton() {
++eventosHardware;
eventoPendiente = true;
}
void setup() {
pinMode(PIN_BOTON, INPUT_PULLUP);
attachInterrupt(PIN_BOTON, alPulsarBoton, FALLING);
}
void loop() {
// Copia el estado compartido y procesa el evento fuera de la ISR.
}Qué hace cada pieza
attachInterrupt(pin, función, modo)registra el manejador: «cuando en este pin ocurra este flanco, ejecuta esta función». Es el equivalente de bajo nivel asetOnClickListener.- Interrupción: el periférico GPIO solicita atención al procesador, que desvía temporalmente la ejecución hacia la rutina asociada y luego vuelve a donde estaba.
- ISR (Interrupt Service Routine): esa rutina, aquí
alPulsarBoton().ARDUINO_ISR_ATTRes la marca que Arduino-ESP32 usa para las funciones de interrupción.
La regla de oro de la ISR: lo mínimo imprescindible
Una ISR puede ejecutarse en cualquier momento respecto al flujo normal. Por eso no conviene meter en ella:
- operaciones lentas o esperas (
delay), - lógica de aplicación compleja,
- accesos que dependan de bloqueos.
La de la plantilla solo hace dos cosas: incrementa un contador y marca un indicador. Detectar y registrar; nada más.
pinMode(PIN_BOTON, INPUT_PULLUP);volatile bool eventoPendiente = false;++eventosHardware;attachInterrupt(PIN_BOTON, alPulsarBoton, FALLING);
attachInterrupt asocia el pin, la función alPulsarBoton y el modo FALLING. pinMode configura el origen, no registra nada.
- Esperar con
delay(500) - Incrementar un contador
- Ejecutar la lógica completa de la aplicación
- Marcar un indicador booleano
- Esperar a que se libere un bloqueo
La ISR debe ser brevísima: registrar (contador, indicador) y salir. Esperas, lógica compleja y bloqueos van fuera, en el contexto normal.
Ejercicio 4. Ejercicio: recorre el evento etapa a etapa
Juntemos todo en una tabla. Cada fila es una etapa del recorrido y, a la derecha, dónde la vemos hoy, sin placa.
| Etapa | Qué ocurre | Dónde lo vemos hoy |
|---|---|---|
| 1. Señal física | La tensión del GPIO pasa de alto a bajo | Solo la razonamos |
| 2. GPIO | La lógica de entrada detecta el nivel y el flanco configurado | pinMode y FALLING |
| 3. Interrupción | El periférico solicita atención al procesador | attachInterrupt registra el manejador |
| 4. ISR | El procesador desvía la ejecución a alPulsarBoton() |
Podemos leer y compilar la función |
| 5. Estado | La ISR deja constancia para el resto del programa | eventoPendiente y eventosHardware |
| 6. Consumo | loop() procesa el evento en contexto normal |
Seguimos la lógica y verificamos que compila |
Haz el ejercicio sin mirar la tabla: tápala con la mano.
alPulsarBoton() → Estado: se actualizan eventoPendiente y eventosHardware → Consumo: loop() procesa el eventoSeñal → GPIO → interrupción → ISR → estado → consumo. La ISR solo llega hasta el estado; el consumo ocurre después, en contexto normal.
eventoPendiente = true;?- GPIO
- Interrupción
- Estado
- Consumo
Se ejecuta dentro de la ISR, pero su efecto es dejar constancia del suceso: es la etapa de estado compartido que luego leerá loop().
Teoría 5. volatile, estado compartido y antirrebote
¿Por qué volatile?
eventosHardware y eventoPendiente se modifican desde la ISR y se leen desde loop(). Para el compilador, loop() es un bucle normal y podría «optimizar» la lectura guardando el valor en un registro y no volviendo a mirar la memoria. volatile le dice: esta variable puede cambiar por fuera del flujo que ves; léela de memoria cada vez.
Detectar y registrar ≠ interpretar y actuar
La plantilla separa dos responsabilidades:
ISR
Detecta y registra. Brevísima. Contador + indicador.
loop()
Copia el estado compartido e interpreta y actúa en contexto normal, donde sí puede tardar.
Esta separación volverá con temporizadores, comunicaciones, sensores y otros periféricos.
Antirrebote (debounce)
Un pulsador mecánico no pasa limpiamente de alto a bajo: sus contactos rebotan y pueden producir varios flancos en una sola pulsación. Cada uno dispara la ISR. Por eso el ejemplo añade un antirrebote temporal en el consumo: se ignoran los avisos que llegan demasiado pronto tras el último aceptado.
Un boceto orientativo de cómo puede quedar loop() (la plantilla trae su propia versión):
void loop() {
static uint32_t ultimaAceptada = 0;
if (eventoPendiente) {
eventoPendiente = false; // consumimos el aviso
uint32_t total = eventosHardware; // copia del estado compartido
uint32_t ahora = millis();
if (ahora - ultimaAceptada >= 50) { // antirrebote: p. ej. 50 ms
ultimaAceptada = ahora;
Serial.printf("Pulsación aceptada (flancos vistos por la ISR: %lu)\n",
(unsigned long) total);
}
}
}Observa que el contador eventosHardware puede crecer más que las pulsaciones aceptadas: la ISR cuenta flancos, loop() decide cuáles son pulsaciones.
eventoPendiente se declara volatile?- Para que se guarde en la flash y sobreviva a un reinicio
- Porque cambia desde la ISR y el compilador no debe suponer que su valor sigue igual entre lecturas
- Para que la ISR se ejecute más rápido
- Porque es obligatorio en todas las variables globales de Arduino
La escribe la ISR y la lee loop(). volatile obliga a leerla de memoria cada vez, en lugar de reutilizar un valor que el compilador creía constante.
eventosHardware pasa de 0 a 3. ¿Qué ha ocurrido?- La ISR tiene un bucle que suma tres veces
- El pull-up interno está mal configurado
FALLINGdispara en los dos flancos y además en el nivel- El rebote mecánico ha producido varios flancos y cada uno ha disparado la ISR
Los contactos rebotan y generan varios flancos descendentes. Por eso el consumo en loop() aplica un antirrebote temporal.
Ejercicio 6. Ejercicio: identifica las piezas en main.cpp
La guía propone este ejercicio para hacer sin placa: localiza en src/main.cpp cada pieza del evento y compáralo después con un onClick, donde las capas inferiores ya están ocultas.
constexpr uint8_t PIN_BOTON = 9; // (A)
volatile uint32_t eventosHardware = 0; // (B)
volatile bool eventoPendiente = false; // (B)
void ARDUINO_ISR_ATTR alPulsarBoton() { // (C)
++eventosHardware;
eventoPendiente = true;
}
void setup() {
pinMode(PIN_BOTON, INPUT_PULLUP); // (D)
attachInterrupt(PIN_BOTON, alPulsarBoton, FALLING); // (E)
}
void loop() { // (F)
// Copia el estado compartido y procesa el evento fuera de la ISR.
}Piensa en el equivalente Android de cada letra:
boton.setOnClickListener { // registro del manejador
pulsaciones++ // manejador que cambia el estado
actualizarVista() // consumo: refleja el estado en la UI
}Ver solución completa
(A) identifica el origen; (D) configura el origen (entrada con pull-up); (E) registra el manejador y el tipo de evento; (C) es la ISR; (B) es el estado compartido; (F) es el consumo. En Android, el «origen» (el táctil, la vista) y la detección ya vienen resueltos por el sistema: solo registras y escribes el manejador.
pinMode(PIN_BOTON, INPUT_PULLUP);?- Registra la ISR
- Configura el origen del evento: el pin como entrada con pull-up
- Consume el evento
- Declara el estado compartido
pinMode prepara el origen: el GPIO9 como entrada con resistencia pull-up interna. El registro lo hace (E).
boton.setOnClickListener { ... }?- (A)
constexpr uint8_t PIN_BOTON = 9; - (F)
void loop() - (E)
attachInterrupt(PIN_BOTON, alPulsarBoton, FALLING); - (B)
volatile bool eventoPendiente = false;
Ambas registran un manejador para un tipo de evento de una fuente. La diferencia es el nivel: attachInterrupt trabaja directamente con el pin y la interrupción.
Repaso 7. Repaso: glosario y preguntas de examen
| Término | Definición breve |
|---|---|
| ESP32-C3 | Microcontrolador de Espressif con CPU RISC-V de 32 bits, Wi-Fi y Bluetooth Low Energy |
| RISC-V | Arquitectura abierta de conjunto de instrucciones |
| Toolchain | Compilador, ensamblador, enlazador y utilidades que generan firmware para una arquitectura |
| Firmware | Programa compilado para ejecutarse en un sistema empotrado |
| Build | Transformar el código fuente en firmware (compilación + enlazado) |
| Artefacto | Fichero generado por la construcción: firmware.elf, firmware.bin |
| Board | Identificador de una placa concreta en PlatformIO |
| Arduino-ESP32 | API de estilo Arduino sobre los ESP32; se descarga con framework = arduino |
| GPIO | Pin digital de entrada/salida, posible fuente de interrupciones |
| Pull-up | Resistencia que mantiene una entrada en nivel alto |
| FALLING | Disparo en transición de alto a bajo |
| Interrupción | Un periférico desvía temporalmente la ejecución a una rutina |
| ISR | Rutina de interrupción; corta y sin trabajo bloqueante |
| Debounce | Filtrar las transiciones rápidas de un pulsador mecánico |
| Serial Monitor | Leer y enviar datos por puerto serie (con placa conectada) |
| Git Bash | Terminal de Git for Windows con entorno tipo Unix |
toolchainEs la toolchain. PlatformIO descarga la de RISC-V automáticamente.
- Rutina que se ejecuta al desviar el procesador la ejecución por una interrupción; debe ser corta y no bloqueante
- Bucle principal del firmware que se repite indefinidamente
- Resistencia interna que fija el nivel de un pin
- Herramienta para leer el puerto serie
ISR = Interrupt Service Routine. El bucle principal es loop(), la resistencia es el pull-up y la herramienta es el Serial Monitor.
- El núcleo de la ESP32-C3 implementa una variante RISC-V de 32 bits
INPUT_PULLUPdeja el pin en nivel bajo en reposofirmware.elfyfirmware.binson artefactos de compilación- El antirrebote se hace dentro de la ISR con
delay boardidentifica una placa concreta dentro de PlatformIO
INPUT_PULLUP deja el pin alto en reposo, y la ISR nunca debe esperar con delay: el antirrebote se aplica al consumir el evento en loop().
loop()?- La ISR interpreta y actúa;
loop()solo duerme - Ambas hacen lo mismo para tener redundancia
- La ISR detecta y registra;
loop()copia el estado, interpreta y actúa loop()detecta el flanco por sondeo y la ISR imprime por serie
Separar «detectar y registrar» (ISR) de «interpretar y actuar» (contexto normal) mantiene la ISR mínima y es la idea que se repetirá con temporizadores y comunicaciones.