domingo, 4 de octubre de 2026

Alternativa al IDE de Arduino: Arduino Editor

Arduino Editor LogoCansado de ver como el IDE oficial de Arduino, más concretamente la versión 2.3.10 se come la RAM como si no hubiera mañana.

Como alternativa a este pozo sin fondo he encontrado Arduino Editor.
Tiene una interfaz moderna y de código abierto basada en el motor Clang.
El motor Clang tiene como objetivo el resolver la lentitud y el consumo excesivo de recursos del IDE oficial de Arduino (basado enElectron), manteniendo al mismo tiempo la compatibilidad completa con el ecosistema Arduino original.

A diferencia del IDE 2.x oficial que está construido sobre Electron, Arduino Editor está basado en la cadena de herramientas Clang/LLVM y arduino-cli, proporcionando análisis y finalización de código con mayor precisión y rapidez mientras mantiene un peso mucho menor que el IDE oficial

Arduino Editor, por otro lado, utiliza Clang como motor central de análisis. Clang es un compilador de C/C++ de nivel industrial, ampliamente utilizado en el desarrollo de macOS, iOS y proyectos de código abierto de alta calidad, y su capacidad de análisis semántico es mucho más precisa que los analizadores de texto tradicionales

Test consumo RAM

Para comprobar he realizado la siguiente medición en igualdad de condiciones: Mismo equipo (16GB RAM), el mismo sistema operativo un Debian 13. Y con el mismo sketch con dos pestañas abiertas.
He usado el programa btop para medir el consumo total del proceso principal y de sus procesos hijos.


IDE Consumo de RAM Diferencia Arquitectura
Arduino IDE 2.x ~1660 MB — Electron (Chromium)
Arduino Editor ~225 MB ~7,4 veces menos Nativa + Clang/LLVM

La diferencia es aplastante: Arduino Editor consume aproximadamente 7 veces menos memoria que el IDE oficial en la misma situación de uso.
Estamos hablando de pasar de 1,6 GB a poco más de 200 MB, una cifra que en equipos con recursos limitados marca la diferencia entre poder trabajar con comodidad o tener que cerrar todo lo demás.

Consumo de RAM Arduino IDE 2.3.10

Consumo de RAM Arduino Editor



Entre las cosas positivas en la elección de esta arquitectura:

  • Finalización de código rápida y precisa: basada en análisis semántico real, no en coincidencia de texto
  • Navegación inteligente: soporte para "ir a definición", "seguir símbolo", información detallada al pasar el ratón
  • Subrayado de errores en tiempo real: los problemas se descubren mientras se escribe, sin necesidad de compilar primero
  • Interfaz siempre fluida: el procesamiento asíncrono garantiza que al abrir proyectos grandes la interfaz no se congele

Características funcionales principales

Soporte completo para proyectos:
  • Soporte completo para estructuras de sketch con múltiples archivos .ino, .cpp, .hpp
  • Integración con arduino-cli para gestión de compilación, carga y cadenas de herramientas, totalmente compatible con las placas y bibliotecas Arduino existentes
Refactorización y edición avanzadas
  • Transformación de código basada en semántica segura
  • Funciones de edición con vista previa diff y confirmación explícita (incluida asistencia de IA)
Editor personalizable
  • Podemos ajustar de manera libre la fuente, color, formato y comportamiento del editor
  • Incluye herramientas de refactorización y organización de código basadas en Clang

Arduino Editor está diseñado específicamente para "personas que sienten que el IDE oficial ya no es suficiente, pero no quieren migrar a una cadena de herramientas completamente diferente como VS Code + PlatformIO" el cual es mi caso.


Plataformas disponibles e instalación

Arduino Editor soporta las siguientes plataformas:
  • macOS: instalador .pkg (con firma y notarización)
  • Linux: .AppImage portable, que no requiere instalación (Recuerda darle permiso de ejecución).
  • Raspberry Pi: paquete .deb (ARM64)
  • Windows: archivo .zip portable

El código fuente está alojado en GitHub en el repositorio ppet36/ArduinoEditor, y las versiones precompiladas se pueden descargar directamente desde la página de Releases

Limitaciones y advertencias

  • Madurez del proyecto: comparado con el IDE oficial o PlatformIO, Arduino Editor es un proyecto relativamente joven, con una comunidad y documentación en crecimiento
  • Estabilidad: puede encontrarse con algunos problemas menores en etapas tempranas, se recomienda probarlo primero en proyectos no críticos
  • Compatibilidad con placas especiales: aunque está integrado con arduino-cli, para algunos paquetes de terceros muy específicos (como ciertas variantes de ESP32) puede requerir ajustes adicionales

Si estás buscando un IDE que sea "más rápido que el oficial, más ligero que PlatformIO, y que se pueda usar directamente sin cambiar la forma de trabajar", Arduino Editor es la opción que vale la pena probar primero.

Problema al ejecutar el archivo AppImage libjpeg.so.8


./ArduinoEditor-Linux-x86_64-1.0.5.AppImage 
/tmp/.mount_ArduinlcAJKG/usr/bin/ArduinoEditor: error while loading shared libraries: libjpeg.so.8: cannot open shared object file: No such file or directory

Ese error nos indica que el AppImage de Arduino Editor fue empaquetado esperando una versión antigua de la librería JPEG libjpeg.so.8 que tu distribución Linux moderna ya no incluye por defecto. Es un problema de compatibilidad común cuando se usan binarios precompilados en sistemas actuales

Para una solución más segura sin reemplazar la que ya tenemos instalada en el sistema es instalar la librería heredada libjpeg8 desde el archivo de Debian.

Para instalar libjpeg.so.8 abriremos una terminal y ejecutaremos los siguientes comandos uno por uno:


# 1. Descargamos el paquete heredado desde el archivo de Debian.
wget https://archive.debian.org/debian/pool/main/libj/libjpeg8/libjpeg8_8b-1_amd64.deb

# 2. Instalarlo con apt para resolver cualquier dependencia básica.
sudo apt install ./libjpeg8_8b-1_amd64.deb

Una vez instalado, vuelve a lanzar tu AppImage de Arduino Editor y el programa debería abrir sin problemas.

¿Y tú que opinas? ¿También crees que el IDE oficial de Arduino consume demasiada RAM?


Otros articulos sobre Arduino que te pueden intersar:


Saludos a todos!!!


jueves, 3 de septiembre de 2026

Primera aplicación KDE

Si estás empezando en el desarrollo de aplicaciones para el escritorio KDE Plasma, lo más tentador es instalar un IDE como KDevelop o Qt Creator y usar sus asistentes para generar el código automáticamente.

Pero hay un problema: cuando usas asistentes, te pierdes la parte más importante: entender cómo funciona realmente tu aplicación.
Los IDEs son excelentes herramientas, pero ocultan mucha de la lógica detrás de la compilación, las dependencias y la estructura del proyecto.

Por eso, en este tutorial vamos a hacer las cosas a la antigua usanza: sin IDEs, sin asistentes, sin generar código automáticamente. Solo con tu editor de texto favorito y la terminal.

Crearemos una aplicación de escritorio tradicional para KDE Plasma, un mensaje "Hola Mundo" en una ventana y su barra de menús. Y lo más importante: entenderemos cada línea de código y cada paso del proceso.

Este es el punto de partida perfecto para cualquier desarrollador que quiera iniciarse en el ecosistema KDE. Una vez que domines esto, pasar a un IDE será coser y cantar. ¡Empezamos!



🎯 Objetivo

Crear una aplicación de escritorio tradicional para KDE Plasma que muestre una ventana con el mensaje "Hola Mundo", utilizando exclusivamente un editor de texto (con el que más cómodo estes) y la terminal.



📁 1. Estructura del proyecto

Primero tenemos que crear una carpeta para el proyecto. Dentro de ella, tenemos que crear estos cuatro archivos:


mi_aplicacion/
├── CMakeLists.txt
└── main.cpp

Podemos crear la estructura con los siguientes comandos:


mkdir mi_aplicacion
cd mi_aplicacion
touch CMakeLists.txt main.cpp

Con los archivos ya podemos editarlos para escribir en ellos el código que necesitamos para ejecutar nuestro programa.



📄 2. Código fuente

A. main.cpp (Punto de entrada)


#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    // 1. Crear la aplicación Qt
    QApplication app(argc, argv);

    // 2. Crear una etiqueta con el texto
    QLabel label("¡Infotronikblog, ¡Hola Mundo!");

    // 3. Configurar la etiqueta/ventana
    label.setAlignment(Qt::AlignCenter);  // Centrar el texto
    label.setStyleSheet(
        "font-size: 48px;"
        "font-weight: bold;"
        "color: #2c3e50;"
        "background-color: #ecf0f1;"
        "border: 2px solid #3498db;"
        "border-radius: 10px;"
        "padding: 20px;"
    );
    label.resize(500, 300);    // Tamaño de la ventana
    label.setWindowTitle("Infotronikblog, ¡Hola Mundo!");

    // 4. Mostrar la ventana
    label.show();

    // 5. Iniciar el bucle de eventos de la aplicación
    return app.exec();
}



🔧 3. Archivo de compilación: CMakeLists.txt


# Versión mínima de CMake
cmake_minimum_required(VERSION 3.16)

# Nombre del proyecto
project(holamundo)

# Buscar Qt5 (necesitamos el módulo Widgets)
find_package(Qt5 REQUIRED COMPONENTS Widgets)

# Crear el ejecutable a partir de main.cpp
add_executable(holamundo main.cpp)

# Enlazar con las librerías de Qt5
target_link_libraries(holamundo Qt5::Widgets)



🏗️ 4. Compilar la aplicación

Abre una terminal en la carpeta del proyecto y ejecuta estos comandos:


# 1. Crear una carpeta para los archivos de compilación
mkdir build
cd build

En la carpeta build:


# 2. Configurar el proyecto con CMake
cmake ..


# 3. Compilar
make


# 4. Ejecutar
./holamundo

Salida esperada: Si todo sale bien, (spoiler, no suele pasar) verás una ventana con el mensaje "Infotronikblog, ¡Hola Mundo!" centrado, y con el título "Infotronikblog KDE".




📚 5. Conceptos clave del programa

Concepto Explicación
QApplication Es la clase que gestiona la aplicación en Qt.
Se encarga del bucle principal de eventos, de los recursos y de la configuración global.
Es obligatoria en cualquier programa con interfaz gráfica en Qt.
QWidget Es la clase base para todas las ventanas y widgets. En este caso, la usamos como ventana principal.
QWidget ventana;
QLabel Es un widget que muestra texto o imágenes.
En este caso, lo usamos para mostrar el mensaje "¡Hola Mundo!". También funciona como ventana porque hereda de QWidget.
QLabel *label = new QLabel("");
Qt::AlignCenter Es una constante que indica que el texto debe alinearse al centro, tanto horizontal como verticalmente, dentro del área del widget.
label->setAlignment(Qt::AlignCenter);
setStyleSheet() Permite aplicar estilos CSS a los widgets de Qt.
Con esta función podemos cambiar el color, la fuente, el tamaño, los bordes, el fondo y muchos otros aspectos visuales del widget.
label->setStyleSheet(
        "font-size: 28px;"
        "font-weight: bold;"
        "color: #2c3e50;"
        "background-color: #ecf0f1;"
        "border: 2px solid #3498db;"
        "border-radius: 10px;"
        "padding: 20px;"
        "min-height: 60px;"
    );
setWindowTitle() Define el título que aparecerá en la barra superior de la ventana.
ventana.setWindowTitle("Mi Aplicación con Botón");
resize() Establece el tamaño de la ventana en píxeles.
Los dos parámetros son: ancho y alto.
ventana.resize(400, 250);
show() Hace visible el widget.
Sin esta llamada, la ventana se crearía pero permanecería oculta.
ventana.show();
app.exec() Inicia el bucle de eventos de Qt.
La aplicación se mantendrá ejecutándose hasta que el usuario cierre la ventana. Es el equivalente a "poner la aplicación en marcha".


6. ¿Cual es el siguiente paso?

Con esta base ya podemos expandir la aplicación ya que disponemos de más widgets que podemos utilizar:

  • Añadir widgets: Botones, cajas de texto, listas, etc.
  • Crear un layout: Organiza varios widgets con QVBoxLayout o QHBoxLayout.
  • Conectar señales y slots: Para que los botones respondan a clics.
  • Añadir un diálogo de preferencias usando KConfigDialog.
  • Guardar configuración usando KConfig para guardar preferencias entre sesiones.

Resumen

Por el momento nos vamos a quedar con lo que hemos aprendido:

  1. Estructura básica de una aplicación KDE (main.cpp, CMakeLists.txt).
  2. Configuración de CMake para encontrar y enlazar Qt5 y KDE Frameworks.
  3. Compilación manual desde la terminal con CMake y Make.

Este es un punto de partida sólido y funcional para empezar a crear tus propias aplicaciones en KDE Plasma.


¡Hasta el próximo programa!, ¡¡¡Saludos!!!


jueves, 27 de agosto de 2026

Lista de comando MS-DOS 6.22 OS

Introducción

Esta página recoge los comandos y sus parámetros más importantes del sistema operativo MS-DOS 6.22.
Muchos de estos comandos son similares en versiones anteriores del MS-DOS.
La información se presenta en tablas para que se pueda consultar de una manera rápida y clara.

También puedes descargar la Guia MS-DOS 6.22 en castellano en Archive.org.

📂 Categorías: Archivos y directorios Configuración del sistema Gestión de discos Memoria y rendimiento Procesamiento por lotes Utilidades varias Edición y visualización de texto Red y comunicaciones Idioma y teclado Seguridad y antivirus Copias de seguridad

Índice de Comandos



jueves, 20 de agosto de 2026

Emuladores para MS-DOS

Muy buenas a todos y todas y Bienvenidos a los 90's!!!

Hoy en día tener un emulador instalado en nuestro PC es algo normal, incluso hay recopilatorios de estos tipo RetroPie que reúnen una gran variedad de este tipo de programas.

Incluso emuladores del propio MS-DOS como DosBox.

Pero cuando hablamos de emuladores en en sistema operativo MS-DOS nos estamos adentrando en el origen de cualquier emulador.

Estamos hablando de 1995 o 1996 y la consola elegida fue la nintendo NES.
Luego vinieron muchos más como la GameBoy, GameBoy Color, GameBoy Advance y también las consolas de Sega allá por el año 1997. Ah! y también para ordenadores de 8 bits de los 80 como Amstrad CPC.

Por supuesto esto fue un gran hito en la informática y, pese a sus limitaciones, democratizo poder jugar a cualquier consola de la época en el PC que tenías en el salón.

Si hablamos del primer emulador NES gratuito estamos hablando de NESticle creado por Bloodlust Software en Abril de 1997. Fue el primer emulador de NES freeware de gran impacto mundial que hizo que pudiéramos jugar a títulos de consola en MS-DOS y Windows.


Máquina de prueba

lunes, 17 de agosto de 2026

Listado de funciones para Arduino y ESP32

Listado de funciones para Arduino y ESP32

Muy buenas a todos y todas!!!

Como memorizar todos los comandos con sus variantes en el IDE de Arduino he creado esta lista con los comandos más habituales, las variables que admite la función y si nos retorna algún tipo de dato.

Cada comando aparece en un bloque con un color que indica el periférico al que pertenece GPIO, ADC, I2C, etc.

Empecemos!!!



Descripción de los comandos


🔹 abs(int x)

Periférico: Utilidad / Matemáticas

Variables:

  • int x: Número entero del que se quiere obtener el valor absoluto.

Descripción: Calcula el valor absoluto de un número entero. Por ejemplo, el valor absoluto de -5 es 5.

Devuelve: Valor absoluto de un número entero.

Ejemplo:


x = -42;
Serial.println(abs(x)); // el valor absoluto de -42  
Serial Monitor: 42
  

🔹 int analogRead()

Periférico: ADC

Variables:

  • uint8_t pin: Número del pin ADC.

Descripción: Lee el valor raw (sin calibrar) del convertidor analógico-digital.

Devuelve: Un valor de 12 bits (0 a 4095).

Ejemplo:


int val = analogRead(analogPin);  // Lectura de un pin analógico  
  

🔹 uint32_t analogReadMilliVolts()

Periférico: ADC

Variables: Ninguna

  • uint8_t pin: Número del pin ADC.

Descripción: Lee el valor del ADC y lo convierte directamente a milivoltios (valor calibrado). Ideal para obtener lecturas en unidades de voltaje sin necesidad de hacer cálculos manuales.

Ejemplo:


uint32_t mv  = analogReadMilliVolts(PIN_ADC); // Lee y convierte automáticamente a milivoltios, considerando atenuación y calibración.  
  

🔹 analogReadResolution(uint8_t bits)

Periférico: ADC

Variables:

  • uint8_t bits: Resolución en bits (1 a 16).

Descripción: Cambia la resolución de analogRead(). El valor por defecto es 12 bits. Si se cambia a 10 bits, devolverá valores de 0 a 1023, igual que un Arduino clásico.

Ejemplo:


analogReadResolution(10);  // La lectura del pin ADC con una resolución de 10bits  
  

🔹 constrain()

Periférico: Utilidad / Matemáticas

Variables:

  • int/float amt: El valor a restringir [citation:2].
  • int/float low: Límite mínimo.
  • int/float high: Límite máximo.

Descripción: Restringe un número para que se mantenga dentro de un rango definido.

Devuelve: Si el valor es menor que el límite mínimo, devuelve el mínimo; si es mayor que el máximo, devuelve el limite máximo.

Ejemplo:


int sensVal = 50;
sensVal= constrain(sensVal, 10, 150);  // Limita el rango entre 10 y 150  
  

🔹 dacWrite(uint8_t pin, uint8_t value)

Periférico: DAC (Digital To Analog Converter)

Variables:

  • uint8_t pin: Pin DAC, solo disponibles en algunos ESP32.
  • uint8_t value: Valor de 0 a 255 (8 bits).

Descripción: Genera una salida analógica real en pines DAC (si el modelo lo soporta). Nota: No disponible en ESP32-C3.

Ejemplo:



#define DAC_PIN 25 // GPIO25 corresponde al Canal 1 del DAC

void loop() {
  dacWrite(DAC_PIN, 128); // Escribe un valor de 128 (1.65V)  
}

🔹 digitalWrite(uint8_t pin, uint8_t value)

Periférico: GPIO

Variables:

  • uint8_t pin: Número del pin GPIO.
  • uint8_t value: HIGH (1) o LOW (0).

Descripción: Escribe un valor HIGH o LOW en el pin digital especificado. Si el pin está configurado como salida, establecerá el voltaje correspondiente (3.3V o 0V).

Ejemplo:


digitalWrite(3, HIGH);  

// o

digitalWrite(3, LOW);  
  

🔹 digitalRead(uint8_t pin)

Periférico: GPIO

Variables:

  • uint8_t pin: Número del pin GPIO.

Descripción: Lee el estado de un pin digital.

Devuelve HIGH o LOW.

Ejemplo:


bool estado = digitalRead(3); // Lee el estado del pin 3  
  

🔹 bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);

Periférico: LEDC (PWM)

Variables:

  • uint8_t pin: Número del pin GPIO.
  • uint32_t freq: Frecuencia para PWM.
  • uint8_t resolution: Selecciona la resolución del canal LEDC. Rango 1-14 bits (1-20 bits para ESP32).

Descripción: Esta función se utiliza para configurar el pin LEDC con la frecuencia y resolución especificadas. El canal LEDC se seleccionará automáticamente.

Devuelve: true si la configuración es exitosa. Si se produce un error devuelve false y el canal LEDC no se configura.

Ejemplo:


const int ledPin = 16;
const int ledChannel = 0;
void setup(){
  ledcAttach(ledPin, freq, resolution);  
}
  


🔹 ledcAttachPin(uint8_t pin, uint8_t channel) deprecated

Periférico: LEDC (PWM)

Variables:

  • uint8_t pin: Número del pin GPIO.
  • uint8_t channel: Canal PWM previamente configurado.

Descripción: Asigna un pin GPIO a un canal PWM. El pin empezará a generar la señal PWM con los parámetros configurados en ledcSetup() deprecated.

Ejemplo:


const int ledPin = 16;
const int ledChannel = 0;
void setup(){
  ledcAttachPin(ledPin, ledChannel);  
}
  

🔹 ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits) deprecated

Periférico: LEDC (PWM)

Variables:

  • uint8_t channel: Canal PWM (0-7 en ESP32-C3).
  • uint32_t freq: Frecuencia en Hz.
  • uint8_t resolution_bits: Resolución en bits (1 a 14 en ESP32-C3).

Descripción: Configura un canal PWM con una frecuencia y resolución determinadas. Debe llamarse antes de usar ledcAttachPin() deprecated.

Ejemplo:


const int ledChannel = 0;
const int freq = 5000; 
const int res = 8;
void setup() {
  ledcSetup(ledChannel, freq, res);  
}
  

🔹 ledcWrite(uint8_t channel, uint32_t duty)

Periférico: LEDC (PWM)

Variables:

  • uint8_t channel: Canal PWM.
  • uint32_t duty: Valor del ciclo de trabajo (0 a 2^resolution-1).

Descripción: Escribe un valor de ciclo de trabajo (duty cycle) en el canal PWM especificado, controlando así el ancho del pulso.

Ejemplo:


// Bucle
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {  
  ledcWrite(ledPin, dutyCycle);
  delay(15);
}  
  

🔹 long map(long value, long fromLow, long fromHigh, long toLow, long toHigh)

Periférico: Utilidad

Variables:

  • long value: El número a re-mapear.
  • long fromLow: Límite inferior del rango de entrada.
  • long fromHigh: Límite superior del rango de entrada.
  • long toLow: Límite inferior del rango de salida.
  • long toHigh: Límite superior del rango de salida.

Descripción: Re-mapea un número desde un rango de entrada a un rango de salida. Se usa comúnmente para convertir valores de un ADC (0-4095) a un rango útil, como ángulos para un servo (0-180).

Devuelve: Valor long mapeado con un rango entre en limite inferior de salida y el limiter superior de salida.

Ejemplo:


int valor = map(val, 0, 1023, 0, 255); // Entrada 0-1023 ; Salida 0-255  
  

🔹 millis()

Periférico: Tiempo

Variables: Ninguna.

Descripción: Devuelve el número de milisegundos desde que el programa empezó a ejecutarse. Se almacena como un unsigned long, con un valor máximo de 4,294,967,296, lo que equivale a casi 50 días. Es la función recomendada para temporizaciones no bloqueantes.

Devuelve: El número de milisegundos desde que el programa empezó a ejecutarse.

Ejemplo:


unsigned long myTime;
myTime = millis();
Serial.println(myTime); // Imprime el tiempo desde que empezo el programa  

🔹 pinMode(uint8_t pin, uint8_t mode)

Periférico: GPIO

Variables:

  • uint8_t pin: Número del pin GPIO a configurar.
  • uint8_t mode: Modo de funcionamiento (INPUT, OUTPUT, INPUT_PULLUP, etc.).

Descripción: Configura el modo de un pin digital. Es una de las funciones más básicas del Arduino, idéntica en su uso al Arduino original pero adaptada a los pines del ESP32.

Ejemplo:


pinMode(13, OUTPUT);      // Pone el pin 13 como salida
pinMode(8, INPUT);        // Pone el pin 8 como entrada
pinMode(9, INPUT_PULLUP); // Pone el pin 9 como entrada con resistencia interna  
  

🔹 random(long max, long min)

Periférico: Utilidad

Variables:

  • long max: Límite superior exclusivo.
  • long min: Límite inferior inclusivo (opcional).

Descripción: Genera un número pseudo-aleatorio dentro de un rango. En el ESP32, usa el generador de números aleatorios por hardware (HW RNG) para mejorar la aleatoriedad.

Ejemplo:


long randNumber;
randNumber = random(300); // Carga un número random entre 0 y 299

o

randNumber = random(100, 220); // Carga un número random entre 100 y 219  
  

🔹 randomSeed(long seed)

Periférico: Utilidad

Variables:

  • long seed: Número que sirve como semilla para el generador pseudo-aleatorio.

Descripción: Inicializa el generador de números pseudo-aleatorios para que comience en un punto arbitrario de su secuencia. Si se usa una semilla fija, la secuencia de números será siempre la misma.

Devuelve: long con semilla.

Ejemplo:


long randNumber = random(300);  
Serial.println(randNumber);
  

🔹 SPI.begin(int sck, int miso, int mosi, int ss)

Periférico: SPI

Variables:

  • int sck (opcional): Pin de reloj SPI.
  • int miso (opcional): Pin MISO (Master In Slave Out).
  • int mosi (opcional): Pin MOSI (Master Out Slave In).
  • int ss (opcional): Pin SS (Slave Select).

Descripción: Inicializa el periférico SPI en modo maestro. Si no se especifican pines, usa los valores por defecto.

Ejemplo:


const int SCK_PIN = 18;
const int MISO_PIN = 19;
const int MOSI_PIN = 23;
const int SS_PIN = 5;

void setup() {
  // Inicializa SPI con un mapeo de pines personalizado 
  SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN);
}
  

🔹 SPI.transfer(uint8_t data)

Periférico: SPI

Variables:

  • uint8_t data: Byte a enviar.

Descripción: Envía un byte y recibe simultáneamente otro byte (comunicación full-duplex). Es la función principal para comunicarse con periféricos SPI.

Ejemplo:


uint8_t valor = 0;
SPI.transfer(valor);  
  

🔹 Serial.begin(uint32_t baudrate)

Periférico: UART (Serial)

Variables:

  • uint32_t baudrate: Velocidad de transmisión en baudios (ej. 115200).

Descripción: Inicializa el puerto serie con la velocidad de baudios especificada. Es la función más usada para depuración y comunicación con el PC.

Ejemplo:


Serial.begin(9600);

// O

Serial.begin(38400);

// O

Serial.begin(115200);  

// O

Serial.begin(4800);  
  

🔹 Serial.print(ver descripción)

Periférico: UART (Serial)

Variables:

  • any data: Dato a imprimir (string, int, float, etc.).

Descripción: Envía texto o datos al puerto serie, sin añadir un salto de línea al final. Ideal para construir mensajes de depuración.
Un segundo parámetro opcional especifica la base (formato) a utilizar; los valores permitidos son: BIN (binario o base 2), OCT (octal o base 8), DEC (decimal o base 10), HEX (hexadecimal o base 16).
Para números de coma flotante, este parámetro especifica el número de decimales que se utilizarán.

Ejemplo:


Serial.print(78, BIN); // Imprime "1001110"

Serial.print(78, OCT); // Imprime "116"

Serial.print(78, DEC)  // Imprime "78"

Serial.print(78, HEX)  // Imprime "4E"

Serial.print(1.23456, 0)  // Imprime "1"

Serial.print(1.23456, 2)  // Imprime "1.23"

Serial.print(1.23456, 4)  // Imprime "1.2346"  
  

🔹 Serial.println(ver descripción)

Periférico: UART (Serial)

Variables:

  • any data: Dato a imprimir (string, int, float, etc.).

Descripción: Igual que Serial.print(), pero añade un salto de línea (\r\n) al final. Útil para mensajes en líneas separadas. También le podemos indicar el tipo de dato con:

  • DEC - Un decimal ASCII-encoded
  • HEX - Un hexadecimal ASCII-encoded
  • OCT - Un octal ASCII-encoded
  • BIN - Un binario ASCII-encoded

Ejemplo:


Serial.println(analogValue);       // Imprime un decimal en ASCII-encoded
Serial.println(analogValue, DEC);  // Imprime un decimal en ASCII-encoded
Serial.println(analogValue, HEX);  // Imprime un hexadecimal en ASCII-encoded  
Serial.println(analogValue, OCT);  // Imprime un octal en ASCII-encoded
Serial.println(analogValue, BIN);  // Imprime en binario en ASCII-encoded  
  

🔹 Serial.available()

Periférico: UART (Serial)

Variables: Ninguna.

Devuelve: El número de bytes disponibles para leer en el buffer del puerto serie. Útil para comprobar si ha llegado algún dato.

Ejemplo:


// Cuando los bytes disponibles son mayores que 0
if (Serial.available() > 0) {
  // Leemos el byte de entrada:
  int incomingByte = Serial.read();
}
  

🔹 int Serial.read()

Periférico: UART (Serial)

Variables: Ninguna.

Descripción: Lee un byte del buffer del puerto serie.

Devuelve: El primer byte de los datos seriales entrantes disponibles o -1 si no hay datos disponibles.

Ejemplo:


int incomingByte = Serial.read();  
  

🔹 timerBegin(uint32_t frequency)

Periférico: Timer

Variables:

  • uint32_t frequency: Frecuencia del temporizador en Hz.

Descripción: Configura y pone en marcha un temporizador de hardware con la frecuencia especificada. El temporizador se inicia automáticamente.

Devuelve: La estructura del temporizador si la configuración es correcta. Si se produce un erro devuelve NULL y el temporizador no se configura.

Ejemplo:


int timer = timerBegin(1000000);  // Configuramos la frecuencia a 1Mhz  
  

🔹 timerAttachInterrupt(hw_timer_t * timer, void (*fn)(void))

Periférico: Timer

Variables:

  • hw_timer_t *timer: Función a ejecutar cuando se dispara el temporizador.
  • void (*fn)(void): Función a ejecutar cuando se dispara el temporizador.

Descripción: Asocia una función de interrupción al temporizador. La función se ejecutará cada vez que el temporizador alcance el valor de alarma.

Ejemplo:


hw_timer_t *timer = NULL;

void ARDUINO_ISR_ATTR onTimer() {  
  // Código
}

void setup(){

  timer = timerBegin(1000000);  // Configuramos la frecuencia a 1Mhz

  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &onTimer);
}
  

🔹 bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t memsize, uint32_t frequency_Hz);

Periférico: RMT (Remote Control Transceiver)

Variables:

  • int pin: Pin GPIO a configurar.
  • rmt_ch_dir_t channel_direction: true para transmisor, false para receptor. También se puede usar: RMT_RX_MODE o RMT_TX_MODE.
  • rmt_reserve_memsize_t memsize: Número de bloques de memoria reservado para este canal:
    • RMT_MEM_NUM_BLOCKS_1
    • RMT_MEM_NUM_BLOCKS_2
    • RMT_MEM_NUM_BLOCKS_3 - Solo ESP32
    • RMT_MEM_NUM_BLOCKS_4 - Solo ESP32
    • RMT_MEM_NUM_BLOCKS_5 hasta RMT_MEM_NUM_BLOCKS_5 - Solo ESP32
    .
  • uint32_t frequency_Hz: Frecuencia del canal RMT en Hz (frecuencia de tick). Debe estar entre 312,5 kHz y 80 MHz.
    La frecuencia determina la resolución de la duración de los pulsos. Por ejemplo:
    • 10 MHz (tick de 100 ns) - Alta precisión, ideal para LEDs WS2812.
    • 1 MHz (tick de 1 µs) - Adecuado para la mayoría de los protocolos IR
    • 400 kHz (tick de 2,5 µs) - Adecuado para protocolos más lentos.

Devuelve: Esta función devuelve true si la inicialización es exitosa, false en caso contrario.

Descripción: Inicializa el periférico RMT (Remote Control), útil para generar y recibir señales con temporización precisa como infrarrojos o para controlar NeoPixels.

Ejemplo:


#define RMT_FREQ_HZ 10000000  // 10 MHz (tick de 100 ns)
bool isOk = rmtInit(4, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ_HZ));  
  

🔹 bool rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t timeout_ms);

Periférico: RMT (Remote Control Transceiver)

Variables:

  • int pin: Pin GPIO configurado en moto TX para RMT TX mode.
  • rmt_data_t * data: punter al array de datos a transmitir.
  • size_t * num_rmt_symbols: Longitud del array.
  • uint32_t timeout_ms: Tiempo de espera en milisegundos. Podemos usar RMT_WAIT_FOR_EVER para una espera indefinida..

Descripción: Transmite datos a través del periférico RMT. Es la función principal para enviar señales codificadas.

Ejemplo:


bool rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t timeout_ms);  
  

🔹 int WiFi.begin(const char* ssid, const char* password)

Periférico: Wi-Fi

Variables:

  • const char* ssid: Nombre de la red Wi-Fi.
  • const char* password: Contraseña de la red.

Descripción: Conecta el ESP32 a una red Wi-Fi como estación (cliente).

Devuelve: WL_CONNECTED cuando la conexión se ha establecido correctamente.

Ejemplo:


int status = WiFi.begin("YOUR_SSID", "YOUR_PASS");  // Configuración para conectar router 
  

🔹 WiFi.localIP()

Periférico: Wi-Fi

Variables: Ninguna.

Descripción: Muestra la dirección IP local asignada al ESP32 en la red Wi-Fi a la que está conectado.

Devuelve: La dirección IP local asignada al ESP32

Ejemplo:


Serial.println(WiFi.localIP());

o

String localip = WiFi.localIP().toString(); // Cambio a String de la IP  
Serial.println(localip);	// Imprime el String "localip"
  

🔹 Wire.begin(int sdaPin, int sclPin, uint32_t frequency)

Periférico: I2C (Wire)

Variables:

  • int sdaPin (opcional): Pin para SDA.
  • int sclPin (opcional): Pin para SCL.
  • uint32_t frequency (opcional): Frecuencia del bus I2C (por defecto 100kHz).

Descripción: Inicializa el periférico I2C en modo maestro. Si no se especifican pines, usa los valores por defecto de la placa.

Ejemplo:


Wire.begin(0x3C); // Inicia el periferico con la dirección hex_address  
  

🔹 Wire.beginTransmission(uint16_t address)

Periférico: I2C (Wire)

Variables:

  • uint16_t address: Dirección del dispositivo esclavo (7 o 10 bits).

Descripción: Inicia una transmisión al dispositivo esclavo con la dirección especificada. Tras llamarla, se pueden escribir datos con Wire.write().

Ejemplo:


Wire.beginTransmission(0x3C);  // Inicia los datos desde el I2C principal al secundario  
  

🔹 int Wire.endTransmission(bool sendStop)

Periférico: I2C (Wire)

Variables:

  • bool sendStop (opcional): Si es true (por defecto), envía la señal STOP.

Descripción: Envía los datos del búfer al esclavo.

Devuelve: 0 si todo es correcto, o un código de error si falla.

Ejemplo:


int isOk = Wire.endTransmission(true);  // Envía la señal de stop y carga la respuesta en la variable "isOk"  
  

🔹 int Wire.requestFrom(uint16_t address, uint8_t size, bool sedStop)

Periférico: I2C (Wire)

Variables:

  • uint16_t address: Dirección del esclavo.
  • uint8_t size: Número de bytes a solicitar.
  • bool sendStop (opcional): Si envía o no la señal STOP.

Descripción: Solicita datos del dispositivo esclavo.

Devuelve: El número de bytes leídos. Los datos se pueden leer con Wire.read().

Ejemplo:


int nBytes = Wire.requestFrom(address, quantity, stop); // Solicita una cantidad de bytes a una dirección y deja libre el canal I2C  
  

Y hasta aquí llegamos, estas son las funciones más comunes que usamos de manera general en el IDE de Arduino


Si te interesa más información sobre Arduino y su IDE, aquí tienes otros articulos relacionados:


Saludos!!!


domingo, 9 de agosto de 2026

Encender LED del ESP32-C3 desde página web

Muy buenas a todos y todas!!!

En este post quiero explicar como crear una pagina web dentro de nuestro ESP32C3.
La idea es un proyecto sencillo pero completo que te permita encender y apagar el LED integrado de tu ESP32-C3 desde cualquier navegador.

Como extra, en la pantalla integrada en el ESP32 también nos indicará el estado del LED On o Off

Voy a intentar explicar cada parte del programa por separado antes de ver el programa completo.


Sistema de archivos

El ESP32 dispone de varios métodos para almacenar una página web, desde una pequeña, como en este ejemplo, donde usaremos PROGMEM o usar la memoria Flash interna (unos 4MB) para almacenar el HTML, CSS o registros.

PROGMEM, LittleFS, SPIFFS o incluso las tarjetas SD son las opciones de almacenamiento más comunes:


Sistema de archivos Descripción Cuándo usarlo
PROGMEM Los archivos se incluyen como cadenas de texto en el código y se compilan junto con el programa. Para páginas pequeñas y estáticas que no van a cambiar. Es la opción más simple si solo tienes un archivo HTML pequeño.
LittleFS Un sistema de archivos en la flash, diseñado para ser ligero y resistente a cortes de energía. Es la opción más equilibrada para la mayoría de los proyectos. Soporta archivos, carpetas y es fácil de usar con un plugin en el IDE de Arduino.
Puedes ver un ejemplo de como usar LittleFS aquí.
SPIFFS El predecesor de LittleFS. También funciona en la flash, pero es menos eficiente y ya no se recomienda para nuevos proyectos. Para mantener compatibilidad con código antiguo. Se recomienda migrar a LittleFS.
Tarjeta SD Usa una tarjeta microSD externa conectada al ESP32 por SPI o SDIO. Para proyectos que necesitan almacenar muchos archivos (imágenes, vídeos, logs) o cuando el espacio en la flash es insuficiente.


🛠️ Componentes necesarios
ESP32-C3 con OLED de 0.42, Antena Cerámica, Wifi, Bluetooth
ESP32-C3OLED de 0.42" Placa de Desarrollo ESP32-C3 con OLED de 0.42", Antena Cerámica, Wifi, Bluetooth.

¿Qué necesitamos saber primero?

El LED integrado en placa o LED_BUILTIN

En la mayoría de las placas basadas en el ESP32-C3, el LED integrado está conectado al GPIO 8.

En algunas placas como la XIAO ESP32-C3, puede estar en el GPIO 21, consulta la documentación de tu placa para confirmar.

Nota: Este pin esta invertido, es decir que para apagar hay que ponerlo en estado HIGH y para encender el LED hay que ponerlo en LOW.


Librerías para el proyecto

Una librería Arduino es un paquete de código que simplifica la programación.
Además nos proporciona funciones y ejemplos para controlar hardware complejo como sensores, pantallas o motores sin necesidad de conocer los detalles técnicos internos.


Librería ¿Para qué sirve?
WiFi.h Permite que el ESP32-C3 se conecte a tu red o cree su propio punto de acceso
WebServer.h Crea un servidor HTTP en el ESP32-C3 para servir páginas web y recibir peticiones

Explicación del código

Paso 1: Incluir las librerías y definir credenciales


#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>


// Credenciales de tu red Wi-Fi
const char* ssid = "TU_SSID";
const char* password = "TU_CONTRASEÑA";

Librerías:

#include <WiFi.h> nos da acceso a las funciones de conectividad inalámbrica.
#include <WebServer.h> nos permite crear un servidor web que escuche peticiones HTTP en el puerto 80 que es el estándar para páginas web.
Gestiona peticiones GET y POST, y se configura fácilmente asignando funciones a diferentes rutas URL


Credenciales:

const char* ssid = "TU_SSID"; El nombre de tu red domestica.
const char* password = "TU_CONTRASEÑA"; La contraseña de la red.


Pantalla:

#include <U8g2lib.h> Puedes revisar la información sobre como usar la pantalla 0.42" aquí

Paso 2: Configurar el LED y el servidor


// El LED integrado en la mayoría de placas ESP32-C3 está en el GPIO 8
const int LED_PIN = 8;

// Creamos el objeto del servidor web en el puerto 80
WebServer server(80);

// Variable para guardar el estado del LED
bool ledState = false;

Definir el LED:

const int LED_PIN = 8; El pin GPIO8 sera el LED.


Crear el servidor:

WebServer server(80); crea un servidor que escuchará peticiones HTTP en el puerto 80. Cuando un navegador se conecte a la IP del ESP32, el servidor procesará la petición


Paso 3: La página HTML (el "frontend")

El código de la página web se guarda como una cadena de texto en el programa del ESP32. Cuando alguien visita la IP del ESP32, el servidor envía este HTML al navegador




const char HTML_PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Control LED ESP32-C3</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      background: #1a1a2e;
      color: #eee;
    }
    button {
      padding: 20px 50px;
      font-size: 1.5rem;
      border: none;
      border-radius: 10px;
      cursor: pointer;
      margin: 10px;
    }
    .btn-on {
      background: #4CAF50;
      color: white;
    }
    .btn-off {
      background: #f44336;
      color: white;
    }
    .btn-on:hover { background: #45a049; }
    .btn-off:hover { background: #d32f2f; }
    .estado {
      font-size: 1.2rem;
      margin: 20px;
      padding: 10px 30px;
      border-radius: 20px;
      background: #16213e;
    }
  </style>
</head>
<body>
  <h1>⚡ Control LED</h1>
  <div class="estado" id="estadoLED">
    LED: <span id="estadoTexto">Desconocido</span>
  </div>
  <div>
    <button class="btn-on" onclick="controlarLED('on')">🔆 ENCENDER</button>
    <button class="btn-off" onclick="controlarLED('off')">🔅 APAGAR</button>
  </div>

  <script>
    // Función que se ejecuta al hacer clic en un botón
    function controlarLED(accion) {
      // Envía una petición GET al ESP32: /led?accion=on o /led?accion=off
      fetch('/led?accion=' + accion)
        .then(response => response.text())
        .then(data => {
          // Actualiza el texto del estado con la respuesta del ESP32
          document.getElementById('estadoTexto').textContent = data;
        });
    }

    // Al cargar la página, consulta el estado actual del LED
    window.onload = function() {
      fetch('/estado')
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        });
    };
  </script>
</body>
</html>
)HTML";


Explicación del código HTML/JavaScript:

  1. La página tiene dos botones: "ENCENDER" y "APAGAR".

  2. Al hacer clic, la función JavaScript controlarLED() envía una petición fetch() a la URL /led?accion=on o /led?accion=off.

  3. El ESP32 recibe esta petición, cambia el estado del LED y responde con el nuevo estado.

  4. También hay una función que consulta el estado actual al cargar la página (window.onload).


Paso 4: Los "handlers" (el "backend")

Los Handlers o controladores son funciones que se ejecutan en respuesta a un evento específico.
En el contexto de un servidor web en un ESP32, un handler es una función que se asigna a una ruta URL por ejemplo, /led y se ejecuta automáticamente cuando el servidor recibe una petición HTTP a esa dirección, procesando la solicitud y generando una respuesta.


// Maneja la petición a la raíz "/" enviando la página HTML
void handleRoot() {
  server.send(200, "text/html", HTML_PAGE);
}

// Maneja la petición para cambiar el estado del LED
void handleLED() {
  String accion = server.arg("accion"); // Obtiene el valor del parámetro "accion"

  if (accion == "on") {
    digitalWrite(LED_PIN, LOW);  // Enciende el LED (recordemos que el pin esta invertido)
    ledState = true;
    printScreen = true;
    server.send(200, "text/plain", "ENCENDIDO");
  } else if (accion == "off") {
    digitalWrite(LED_PIN, HIGH);   // Apaga el LED
    ledState = false;
    printScreen = true;
    server.send(200, "text/plain", "APAGADO");
  } else {
    server.send(400, "text/plain", "Acción no válida");
  }
}

// Maneja la petición para consultar el estado actual
void handleEstado() {
  if (ledState) {
    server.send(200, "text/plain", "ENCENDIDO");
  } else {
    server.send(200, "text/plain", "APAGADO");
  }
}

Explicación de los handlers:

handleRoot(): Cuando alguien visita la URL raíz (/), el servidor envía el código HTML que hemos guardado.

handleLED(): Cuando se visita /led?accion=on, el servidor extrae el valor del parámetro accion, enciende o apaga el LED físicamente con digitalWrite(), y responde con el nuevo estado.

handleEstado(): Cuando se visita /estado, el servidor responde con el estado actual del LED sin modificarlo.



Paso 5: Configuración inicial (setup)

El setup() de nuestro programa es el "director de orquesta" que prepara todo el escenario: configura el LED, conecta el ESP32-C3 a la red Wi-Fi, asigna las rutas del servidor web y lo pone en marcha.
Sin esta configuración inicial, el servidor no sabría qué hacer cuando reciba una petición HTTP, ni el LED estaría listo para ser controlado.


void setup() {
  Serial.begin(115200);

  // Configurar el pin del LED como salida
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // Inicia apagado

  // Conectar a la red Wi-Fi
  WiFi.begin(ssid, password);
  Serial.print("Conectando a Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConectado!");
  Serial.print("Dirección IP: ");
  Serial.println(WiFi.localIP());

  // Asignar funciones a las rutas del servidor
  server.on("/", handleRoot);
  server.on("/led", handleLED);
  server.on("/estado", handleEstado);

  // Iniciar el servidor
  server.begin();
  Serial.println("Servidor web iniciado.");
}


Paso 6: El bucle principal (loop)

El loop() en nuestro programa del LED es el "guardia de la puerta" que está siempre atento.
Se ejecuta sin parar, y su única misión es llamar a server.handleClient() para asegurarse de que cualquier petición web que llegue sea atendida al instante.
Sin él, el ESP32-C3 no podría responder a los clics en los botones de la página web, y el proyecto dejaría de funcionar.


void loop() {
  server.handleClient(); // Atiende las peticiones HTTP entrantes
}

Explicación:

El método server.handleClient() es el que escucha y procesa las peticiones HTTP.
Si no se llama continuamente, el servidor no responderá a las peticiones del navegador


Programa completo

Aquí tienes el código completo listo para copiar y pegar en la Arduino IDE:


/*
  Control del LED integrado del ESP32-C3 desde página web
  Basado en los ejemplos de Fab Academy y proyectos de la comunidad
  Fuente: Fab Academy 2026, Week 15 [citation:12]
*/

#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>


// ===== CONFIGURACIÓN =====
// Reemplaza con las credenciales de tu red Wi-Fi
const char* ssid = "TU_SSID";
const char* password = "TU_CONTRASEÑA";

// ========== PANTALLA ==========
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);
const int X_OFFSET = 28;
const int Y_OFFSET = 24;
bool printScreen = true;

// En la mayoría de placas ESP32-C3, el LED integrado está en GPIO 8
// Si usas XIAO ESP32-C3, usa GPIO 21
const int LED_PIN = 8;

// ===== VARIABLES GLOBALES =====
WebServer server(80);
bool ledState = false;

// ===== PÁGINA HTML =====
const char HTML_PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Control LED ESP32-C3</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      background: #1a1a2e;
      color: #eee;
    }
    h1 {
      font-weight: 300;
      letter-spacing: 0.1em;
    }
    .estado {
      font-size: 1.2rem;
      margin: 20px;
      padding: 10px 30px;
      border-radius: 20px;
      background: #16213e;
    }
    .botones {
      display: flex;
      gap: 20px;
      flex-wrap: wrap;
      justify-content: center;
    }
    button {
      padding: 20px 50px;
      font-size: 1.5rem;
      border: none;
      border-radius: 12px;
      cursor: pointer;
      transition: transform 0.2s, box-shadow 0.2s;
      min-width: 150px;
    }
    button:active {
      transform: scale(0.95);
    }
    .btn-on {
      background: #4CAF50;
      color: white;
      box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
    }
    .btn-on:hover {
      background: #45a049;
      box-shadow: 0 6px 20px rgba(76, 175, 80, 0.5);
    }
    .btn-off {
      background: #f44336;
      color: white;
      box-shadow: 0 4px 15px rgba(244, 67, 54, 0.3);
    }
    .btn-off:hover {
      background: #d32f2f;
      box-shadow: 0 6px 20px rgba(244, 67, 54, 0.5);
    }
    .footer {
      margin-top: 30px;
      font-size: 0.8rem;
      color: #666;
    }
  </style>
</head>
<body>
  <h1>⚡ Control LED</h1>
  <div class="estado" id="estadoLED">
    LED: <span id="estadoTexto">Desconocido</span>
  </div>
  <div class="botones">
    <button class="btn-on" onclick="controlarLED('on')">🔆 ENCENDER</button>
    <button class="btn-off" onclick="controlarLED('off')">🔅 APAGAR</button>
  </div>
  <div class="footer">ESP32-C3 - Servidor Web</div>

  <script>
    function controlarLED(accion) {
      fetch('/led?accion=' + accion)
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        })
        .catch(error => {
          console.error('Error:', error);
          document.getElementById('estadoTexto').textContent = 'Error';
        });
    }

    window.onload = function() {
      fetch('/estado')
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        })
        .catch(error => {
          console.error('Error:', error);
          document.getElementById('estadoTexto').textContent = 'Error de conexión';
        });
    };
  </script>
</body>
</html>
)HTML";

// ===== HANDLERS DEL SERVIDOR =====

void handleRoot() {
  server.send(200, "text/html", HTML_PAGE);
}

void handleLED() {
  String accion = server.arg("accion");

  if (accion == "on") {
    digitalWrite(LED_PIN, LOW);
    ledState = true;
    printScreen = true;
    server.send(200, "text/plain", "ENCENDIDO");
  } else if (accion == "off") {
    digitalWrite(LED_PIN, HIGH);
    ledState = false;
    printScreen = true;
    server.send(200, "text/plain", "APAGADO");
  } else {
    server.send(400, "text/plain", "Acción no válida");
  }
}

void handleEstado() {
  if (ledState) {
    server.send(200, "text/plain", "ENCENDIDO");
  } else {
    server.send(200, "text/plain", "APAGADO");
  }
}

void handleNotFound() {
  server.send(404, "text/plain", "Página no encontrada");
}

// ===== SETUP =====
void setup() {
  delay(200);
  Serial.begin(115200);
  delay(200);
  
  // Configurar pantalla
  u8g2.begin();
  u8g2.setContrast(200);

  // Configurar LED
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  // Conectar a Wi-Fi
  WiFi.begin(ssid, password);
  Serial.print("Conectando a Wi-Fi");
  
  int intentos = 0;
  while (WiFi.status() != WL_CONNECTED && intentos < 30) {
    delay(500);
    Serial.print(".");
    intentos++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\n✅ Conectado!");
    Serial.print("📡 Dirección IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("\n❌ No se pudo conectar a Wi-Fi");
    Serial.println("Verifica las credenciales en el código");
  }

  // Configurar rutas del servidor
  server.on("/", handleRoot);
  server.on("/led", handleLED);
  server.on("/estado", handleEstado);
  server.onNotFound(handleNotFound);

  // Iniciar servidor
  server.begin();
  Serial.println("🌐 Servidor web iniciado en el puerto 80");
  Serial.println("Abre un navegador y visita la IP indicada");
}

// ===== LOOP =====
void loop() {
  server.handleClient();
  if (printScreen)
  {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_6x10_tf);
    if (ledState)
      u8g2.drawStr(X_OFFSET + 10, Y_OFFSET + 20, "ON");
  	else
      u8g2.drawStr(X_OFFSET + 10, Y_OFFSET + 20, "OFF");
    u8g2.sendBuffer();
    printScreen = false;
  }
}

Cómo usar el programa

  1. Configura las credenciales Wi-Fi: Reemplaza "TU_SSID" y "TU_CONTRASEÑA" con los datos de tu red.

  2. Verifica el pin del LED: Si tu placa no usa el GPIO 8 (como la XIAO ESP32-C3 que usa GPIO 21), cambia la línea const int LED_PIN = 8; al pin correcto.

  3. Carga el programa a tu ESP32-C3 desde la Arduino IDE.

  4. Abre el Monitor Serie para ver la IP que obtiene el ESP32.

  5. Desde cualquier navegador en la misma red, visita http://[IP_del_ESP32]/.

  6. Haz clic en los botones para encender o apagar el LED.


ESP32C3 Interfaz web led

Resumen de conceptos clave y conclusión

Concepto Explicación
Servidor web El ESP32-C3 actúa como un pequeño servidor que aloja una página web en su memoria.
Peticiones HTTP El navegador envía peticiones GET al ESP32 (ej: /led?accion=on) para controlar el LED.
fetch() Función JavaScript que permite hacer peticiones HTTP sin recargar la página (AJAX).
server.on() Asigna una función a una ruta URL. Cuando alguien visita esa URL, se ejecuta la función correspondiente.
server.handleClient() Escucha y procesa las peticiones HTTP entrantes. Debe llamarse constantemente en el loop().

Ya tenemos todo lo necesario para controlar el LED integrado en el ESP32-C3 funcional desde cualquier navegador.


Saludos a todos y todas!!!