sábado, 17 de octubre de 2026

Escudo Motor ESP12E para ESP8266 NODEMCU

Escudo ESP12E Shield
Escudo ESP12E

Muy buenas a todos y todas!!!

Revolviendo cajas de la mudanza y junto a placas que no recordaba tener, apareció este escudo con un chip L293D. El ESP12E Motor Shield.
Un clásico entre los aficionados al ESP8266 que, pese a su antigüedad, sigue siendo una puerta de entrada perfecta al control de motores con WiFi.



¿Qué es exactamente este escudo?

El ESP12E Motor Shield es una placa de expansión diseñada exclusivamente para el NodeMCU V2-V3 (la versión con módulo ESP-12E y chip CP2102). Se encaja directamente sobre el NodeMCU y aporta, fundamentalmente, dos canales de control de motores DC o un canal para motor paso a paso, gracias al driver L293D.

No es un escudo nuevo ni revolucionario, pero su combinación de simplicidad, precio contenido y el hecho de que expone todos los pines del NodeMCU lo convierten en una pieza todavía muy útil para robótica básica, maquetas ferroviarias y automatizaciones caseras.

Lo que trae de serie:

  • Driver L293D: Doble puente H, capaz de controlar 2 motores DC o 1 motor paso a paso bipolar.
  • Alimentación separada: Terminales para VM (alimentación de motores, 4.5V a 36V) y VIN (alimentación de lógica, 4.5V a 9V).
  • Jumper VM-VIN: Para usar una sola fuente de alimentación cuando el voltaje es compatible (4.5V–9V).
  • Pines de acceso: Todos los GPIO, SPI, UART, AI (entrada analógica) y 3.3V del NodeMCU quedan expuestos.
  • Terminales de tornillo: Para conectar los cables de los motores (A+, A-, B+, B-).


Especificaciones técnicas que importan

Parámetro Valor Notas
Driver L293D (doble puente H) Protección térmica integrada
Voltaje de motores (VM) 4.5V ~ 36V Alimentación externa obligatoria
Voltaje de lógica (VIN) 4.5V ~ 9V (10V máx) Puede alimentar el NodeMCU
Corriente por canal 1.2A (pico) 600mA continuos recomendados
Corriente lógica ≤60mA (nivel bajo), ≤22mA (nivel alto) Consumo del chip controlador
Disipación máxima 4W (T=90°C) El L293D se calienta; se recomienda disipador
Rango de temperatura -25°C a +125°C Funcionamiento seguro

Un detalle importante: el L293D no soporta frenado activo en este diseño concreto. La placa está cableada de forma que al detener el motor, simplemente se corta la energía (coasting), no se produce un frenado dinámico.


🛠️ Componentes necesarios
ESP32-C3 con OLED de 0.42, Antena Cerámica, Wifi, Bluetooth
ESP8266 NODEMCU V3 Placa de Desarrollo ESP8266 Wifi.
Pantalla TFT 3,5 pulgadas
Escudo ESP12E L293D L293D para ESP8266 ESP-12E doble puente H.

Mapa de pines del escudo

Los pines de control están fijos por hardware. No puedes cambiarlos por software:

Función Pin NodeMCU GPIO Descripción
Motor A - PWM D1 GPIO5 Control de velocidad motor A
Motor A - Dirección D3 GPIO0 Sentido de giro motor A
Motor B - PWM D2 GPIO4 Control de velocidad motor B
Motor B - Dirección D4 GPIO2 Sentido de giro motor B

ESP8266 NodeMCU pinoutPines libres del NodeMCU 

El escudo solo "ocupa" D1, D2, D3 y D4. El resto de pines quedan accesibles, aunque algunos tienen consideraciones especiales:

Pin GPIO ¿Disponible? Notas
D0 GPIO16 ✅ Sin INPUT_PULLUP interno. Requiere resistencia externa
D5 GPIO14 ✅ Totalmente libre
D6 GPIO12 ✅ Totalmente libre
D7 GPIO13 ✅ Totalmente libre
D8 GPIO15 ⚠️ Debe estar LOW al arrancar. Usar con precaución
RX GPIO3 ⚠️ Se pierde monitor serie si se usa
TX GPIO1 ⚠️ Se pierde monitor serie si se usa
A0 ADC ✅ Solo entrada analógica (0-3.3V)


Diagrama del escudo

Aquí tienes una representación visual generada programáticamente de la disposición del escudo y sus conexiones principales:



Tres ejemplos prácticos

Ejemplo 1: Control de dos motores DC

Este ejemplo usa los canales del escudo A y B. Los motores girarán en una secuencia de avance, giro y retroceso.




/*
 * Control de dos motores DC con ESP12E Motor Shield
 * 
 * Pines fijos:
 * - Motor A: D1 (PWM velocidad), D3 (dirección)
 * - Motor B: D2 (PWM velocidad), D4 (dirección)
 * 
 * Alimentación:
 * - VM: fuente externa (4.5V-36V) para los motores
 * - VIN: 4.5V-9V para lógica (o puentear VM-VIN si <10V)
 */

// Pines del Motor A
const int pwmA = D1;
const int dirA = D3;

// Pines del Motor B
const int pwmB = D2;
const int dirB = D4;

// Velocidad (0-1023 para ESP8266)
const int velocidad = 800;

void setup() {
  // Configurar pines como salida
  pinMode(pwmA, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(pwmB, OUTPUT);
  pinMode(dirB, OUTPUT);
  
  // Asegurar estado inicial: motores detenidos
  analogWrite(pwmA, 0);
  analogWrite(pwmB, 0);
  
  Serial.begin(115200);
  Serial.println("Test de dos motores DC");
}

void loop() {
  // 1. Ambos motores hacia adelante
  Serial.println("Avanzando...");
  digitalWrite(dirA, HIGH);
  digitalWrite(dirB, HIGH);
  analogWrite(pwmA, velocidad);
  analogWrite(pwmB, velocidad);
  delay(2000);
  
  // 2. Detener
  analogWrite(pwmA, 0);
  analogWrite(pwmB, 0);
  delay(1000);
  
  // 3. Girar (A adelante, B atrás)
  Serial.println("Girando...");
  digitalWrite(dirA, HIGH);
  digitalWrite(dirB, LOW);
  analogWrite(pwmA, velocidad);
  analogWrite(pwmB, velocidad);
  delay(2000);
  
  // 4. Detener
  analogWrite(pwmA, 0);
  analogWrite(pwmB, 0);
  delay(1000);
  
  // 5. Ambos hacia atrás
  Serial.println("Retrocediendo...");
  digitalWrite(dirA, LOW);
  digitalWrite(dirB, LOW);
  analogWrite(pwmA, velocidad);
  analogWrite(pwmB, velocidad);
  delay(2000);
  
  // 6. Detener antes de reiniciar el ciclo
  analogWrite(pwmA, 0);
  analogWrite(pwmB, 0);
  delay(2000);
}

Ejemplo 2: Control de motor paso a paso

El L293D puede controlar un motor paso a paso bipolar. Usaremos los cuatro pines de control (D1, D2, D3, D4) para generar la secuencia de pasos.




/*
 * Motor paso a paso bipolar con ESP12E Motor Shield
 * 
 * El motor se conecta a los terminales de Motor A y Motor B.
 * La secuencia usa los 4 pines de control:
 * - D1, D3: bobina 1
 * - D2, D4: bobina 2
 * 
 * Para motores paso a paso de 200 pasos (1.8°/paso)
 */

// Definición de pines
const int bobina1A = D1;
const int bobina1B = D3;
const int bobina2A = D2;
const int bobina2B = D4;

// Secuencia para motor bipolar (full-step)
const int secuencia[4][4] = {
  {1, 0, 1, 0},  // Paso 1
  {0, 1, 1, 0},  // Paso 2
  {0, 1, 0, 1},  // Paso 3
  {1, 0, 0, 1}   // Paso 4
};

int pasoActual = 0;

void setup() {
  pinMode(bobina1A, OUTPUT);
  pinMode(bobina1B, OUTPUT);
  pinMode(bobina2A, OUTPUT);
  pinMode(bobina2B, OUTPUT);
  
  digitalWrite(bobina1A, LOW);
  digitalWrite(bobina1B, LOW);
  digitalWrite(bobina2A, LOW);
  digitalWrite(bobina2B, LOW);
  
  Serial.begin(115200);
  Serial.println("Control de motor paso a paso");
}

void loop() {
  // Girar 200 pasos en sentido horario
  Serial.println("Girando 200 pasos...");
  for (int i = 0; i < 200; i++) {
    darPaso(1);
    delay(2);
  }
  
  delay(1000);
  
  // Girar 200 pasos en sentido antihorario
  Serial.println("Revirtiendo...");
  for (int i = 0; i < 200; i++) {
    darPaso(-1);
    delay(2);
  }
  
  delay(1000);
}

// Ejecuta un paso en la dirección indicada
void darPaso(int direccion) {
  pasoActual += direccion;
  
  if (pasoActual >= 4) pasoActual = 0;
  if (pasoActual < 0) pasoActual = 3;
  
  digitalWrite(bobina1A, secuencia[pasoActual][0]);
  digitalWrite(bobina1B, secuencia[pasoActual][1]);
  digitalWrite(bobina2A, secuencia[pasoActual][2]);
  digitalWrite(bobina2B, secuencia[pasoActual][3]);
}


Un paso más allá: control por WiFi con interfaz web

Los dos ejemplos anteriores funcionan bien, pero se quedan cortos cuando quieres controlar el motor desde el sofá, sin cables colgando ni potenciómetros. Aquí es donde el ESP8266 despliega todo su potencial: WiFi integrado y un servidor web embebido.

Control analógico para tren a escala con ESP8266
Control analógico para tren a escala con ESP8266

Este tercer ejemplo no es solo "otro programa". Es una aplicación completa que convierte tu NodeMCU en un punto de acceso autónomo con una interfaz web responsive, pensada para usarse tanto desde el móvil como desde el navegador del ordenador. No necesitas router, ni app, ni configuración de red. Solo alimentar el NodeMCU, conectarte a su red y abrir una dirección IP.


¿Cómo funciona?

El ESP8266 crea su propia red WiFi (modo Access Point). Cuando te conectas a ella y abres la dirección 192.168.4.1 en el navegador, el ESP te sirve una página HTML que él mismo tiene guardada en su memoria flash. Desde esa página puedes:

  • Mover un slider para ajustar la velocidad del motor.
  • Pulsar botones para cambiar de dirección (adelante / atrás).
  • Detener el motor con un botón de parada.

Toda la comunicación entre la web y el ESP se hace con peticiones HTTP simples. El ESP interpreta esas peticiones, actualiza sus variables internas y aplica los cambios al motor mediante PWM. Además, una rampa de aceleración y frenado suaviza las transiciones para que el motor no arranque ni pare de golpe.

Datos de conexión

Parámetro Valor
SSID (nombre de red) MotorControl
Contraseña motor1234
Dirección IP 192.168.4.1
Tipo de red Access Point (el ESP crea la red)

Descripción de la interfaz

La página web está diseñada con un estilo oscuro y controles táctiles grandes, pensados para usarse con el dedo en el móvil. Estos son los elementos que encontrarás:

  • Indicador de velocidad: un número grande en la parte superior que muestra el porcentaje actual (0% a 100%).
  • Slider de velocidad: barra deslizante con degradado verde → amarillo → rojo. Al moverlo, el número de arriba se actualiza en tiempo real.
  • Botón "Adelante": verde, cambia el sentido de giro del motor.
  • Botón "Atrás": azul, invierte el sentido.
  • Botón "Parar": rojo, ancho completo, detiene el motor suavemente.
  • Línea de estado: texto inferior que confirma cada acción ("Adelante", "Velocidad: 50%", "Detenido"...).


Un detalle importante: la página es responsive. Se adapta automáticamente al tamaño de la pantalla, ya sea un móvil Android, un iPhone, una tablet o un monitor de escritorio. No necesitas instalar nada.

Los handlers: el puente entre la web y el motor

Detrás de cada botón o slider hay una ruta HTTP que el ESP8266 escucha. Estas rutas se llaman handlers y son funciones que se ejecutan cuando llega una petición concreta. En este proyecto hay cuatro:

Ruta HTTP Función Qué hace
/ handleRaiz() Sirve la página HTML al navegador
/velocidad?valor=X handleVelocidad() Ajusta la velocidad objetivo (0-100%)
/direccion?valor=X handleDireccion() Cambia el sentido de giro (1 o -1)
/parar handleParar() Pone la velocidad objetivo a 0

Cada handler responde con un texto plano "OK" que el JavaScript de la página usa para confirmar que la orden llegó correctamente. Si no hay respuesta, la web muestra un mensaje de error.

¿Por qué separar la web en un archivo aparte?

En proyectos pequeños, es tentador meter todo el HTML dentro del .ino como una cadena gigante. Pero cuando la web crece (y esta ya tiene casi 100 líneas entre CSS, HTML y JavaScript), el archivo principal se vuelve ilegible.

La solución es usar un archivo .h separado con la página embebida en PROGMEM. Esto tiene dos ventajas enormes:

  • Organización: el .ino queda limpio, solo con la lógica del programa.
  • Memoria: PROGMEM guarda la web en la memoria flash del ESP8266, no en RAM. El ESP8266 solo tiene unos 40 KB de RAM libre, así que meter una web de varios KB en RAM lo colgaría.

La estructura de archivos en el IDE de Arduino queda así:

ControlTren/
├── ControlTren.ino
└── web_page.h

Para crear el archivo web_page.h en el IDE:
pulsa la flecha junto al botón de verificar → Nueva pestaña → nómbrala web_page.h. El IDE la colocará automáticamente en la misma carpeta que el .ino.

El programa completo

Archivo web_page.h:



/*
 * web_page.h
 * Página HTML/CSS/JS embebida en PROGMEM.
 * Se sirve desde el ESP8266 sin consumir RAM dinámica.
 */
#ifndef WEB_PAGE_H
#define WEB_PAGE_H

#include <Arduino.h>

const char PAGINA_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>Control de Tren</title>
  <style>
    * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
    body {
      margin: 0; padding: 20px;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: #1a1a2e; color: #eee;
      display: flex; flex-direction: column;
      align-items: center; min-height: 100vh;
    }
    h1 { font-size: 1.5em; margin: 10px 0 20px; color: #4ade80; }
    .panel {
      background: #16213e; border-radius: 16px;
      padding: 20px; width: 100%; max-width: 420px;
      box-shadow: 0 8px 24px rgba(0,0,0,0.4);
    }
    .velocidad-label {
      font-size: 2.5em; font-weight: bold; text-align: center;
      color: #4ade80; margin: 10px 0;
      font-variant-numeric: tabular-nums;
    }
    input[type=range] {
      width: 100%; height: 40px;
      -webkit-appearance: none; background: transparent;
      margin: 10px 0;
    }
    input[type=range]::-webkit-slider-runnable-track {
      height: 12px; border-radius: 6px;
      background: linear-gradient(to right, #4ade80, #facc15, #ef4444);
    }
    input[type=range]::-webkit-slider-thumb {
      -webkit-appearance: none; width: 36px; height: 36px;
      border-radius: 50%; background: #fff;
      border: 3px solid #4ade80; margin-top: -12px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.5);
      cursor: pointer;
    }
    input[type=range]::-moz-range-track {
      height: 12px; border-radius: 6px;
      background: linear-gradient(to right, #4ade80, #facc15, #ef4444);
    }
    input[type=range]::-moz-range-thumb {
      width: 36px; height: 36px; border-radius: 50%;
      background: #fff; border: 3px solid #4ade80; cursor: pointer;
    }
    .botones {
      display: grid; grid-template-columns: 1fr 1fr;
      gap: 12px; margin-top: 20px;
    }
    button {
      padding: 18px 10px; font-size: 1.1em; font-weight: bold;
      border: none; border-radius: 12px; cursor: pointer;
      color: #fff; transition: transform 0.1s, opacity 0.2s;
      user-select: none;
    }
    button:active { transform: scale(0.95); opacity: 0.85; }
    .btn-adelante { background: #22c55e; }
    .btn-atras    { background: #3b82f6; }
    .btn-parar    { background: #ef4444; grid-column: span 2; }
    .btn-parar-emergencia { background: #dc2626; grid-column: span 2; }
    .estado {
      margin-top: 16px; text-align: center;
      font-size: 0.9em; color: #94a3b8;
    }
    .indicador {
      display: inline-block; width: 10px; height: 10px;
      border-radius: 50%; margin-right: 6px;
      background: #64748b; vertical-align: middle;
    }
    .indicador.on { background: #4ade80; box-shadow: 0 0 8px #4ade80; }
  </style>
</head>
<body>
  <h1>🚂 Control de Tren</h1>
  <div class="panel">
    <div class="velocidad-label" id="velLabel">0%</div>

    <input type="range" id="slider" min="0" max="100" value="0" step="1">

    <div class="botones">
      <button class="btn-adelante" onclick="setDir(1)">▶ Adelante</button>
      <button class="btn-atras"    onclick="setDir(-1)">◀ Atrás</button>
      <button class="btn-parar"    onclick="parar()">■ Parar</button>
      <button class="btn-parar-emergencia" onclick="emergencia()">🛑 PARADA EMERGENCIA</button>
    </div>

    <div class="estado">
      <span class="indicador" id="ind"></span>
      <span id="estadoTxt">Desconectado</span>
    </div>
  </div>

<script>
  const slider   = document.getElementById('slider');
  const velLabel = document.getElementById('velLabel');
  const ind      = document.getElementById('ind');
  const estadoTxt= document.getElementById('estadoTxt');

  let direccionActual = 0;

  slider.addEventListener('input', () => {
    velLabel.textContent = slider.value + '%';
  });

  slider.addEventListener('change', enviarVelocidad);

  let throttleTimer = null;
  slider.addEventListener('input', () => {
    if (throttleTimer) return;
    throttleTimer = setTimeout(() => {
      enviarVelocidad();
      throttleTimer = null;
    }, 100);
  });

  function enviarVelocidad() {
    fetch('/velocidad?valor=' + slider.value)
      .then(r => r.text())
      .then(txt => { marcarConectado(true); })
      .catch(() => marcarConectado(false));
  }

  function setDir(d) {
    direccionActual = d;
    fetch('/direccion?valor=' + d)
      .then(r => r.text())
      .then(() => {
        marcarConectado(true);
        estadoTxt.textContent = d === 1 ? 'Adelante' : 'Atrás';
      })
      .catch(() => marcarConectado(false));
  }

  function parar() {
    slider.value = 0;
    velLabel.textContent = '0%';
    fetch('/parar')
      .then(r => r.text())
      .then(() => {
        marcarConectado(true);
        estadoTxt.textContent = 'Parado';
      })
      .catch(() => marcarConectado(false));
  }

  function emergencia() {
    slider.value = 0;
    velLabel.textContent = '0%';
    direccionActual = 0;
    fetch('/emergencia')
      .then(r => r.text())
      .then(() => {
        marcarConectado(true);
        estadoTxt.textContent = 'EMERGENCIA';
      })
      .catch(() => marcarConectado(false));
  }

  function marcarConectado(ok) {
    if (ok) {
      ind.classList.add('on');
    } else {
      ind.classList.remove('on');
      estadoTxt.textContent = 'Desconectado';
    }
  }

  setInterval(() => {
    fetch('/estado')
      .then(r => r.json())
      .then(d => {
        marcarConectado(true);
        estadoTxt.textContent = 'Vel: ' + d.vel + ' | Dir: ' + 
          (d.dir === 0 ? 'Parado' : (d.dir === 1 ? 'Adelante' : 'Atrás'));
      })
      .catch(() => marcarConectado(false));
  }, 2000);
</script>
</body>
</html>
)rawliteral";

#endif


Archivo ControlTren.ino:


/*
 * ============================================================
 *  CONTROL DE TREN A ESCALA N - Ibertren Cuco 020
 * ============================================================
 *
 *  NodeMCU + ESP12E Motor Shield (L293D)
 *
 *  Modo: Access Point WiFi
 *  Red:  TrenControl / tren1234
 *  URL:  http://192.168.4.1
 *
 *  Frecuencia PWM: 2.5 kHz (óptima para motor de escobillas
 *                  de escala N). Si tuvieras motor coreless,
 *                  cambiar a 20000 y poner disipador al L293D.
 *
 *  Página web en archivo separado: web_page.h
 * ============================================================
 */

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "web_page.h"

// ==================== CONFIGURACIÓN WIFI ====================
const char* SSID_AP     = "TrenControl";
const char* PASSWORD_AP = "tren1234";

// ==================== PINES DEL SHIELD ====================
#define PIN_PWM_A   5   // D1 -> GPIO5  (velocidad PWM, Motor A)
#define PIN_DIR_A   0   // D3 -> GPIO0  (dirección, Motor A)

// ==================== PARÁMETROS DE CONTROL ====================
const int   PWM_MAX          = 1023;
const int   VELOCIDAD_MAX    = 900;
const int   PASO_RAMPA       = 5;
const int   INTERVALO_RAMPA  = 30;
const int   FRECUENCIA_PWM   = 2500;   // 2.5 kHz, óptimo escala N

// ==================== ESTADO GLOBAL ====================
int  velocidadObjetivo = 0;
int  velocidadActual   = 0;
int  direccion         = 0;

unsigned long ultimaRampa = 0;

// ==================== SERVIDOR WEB ====================
ESP8266WebServer server(80);

// ============================================================
//  FUNCIONES AUXILIARES
// ============================================================
void aplicarDireccion(int dir) {
  if (dir == 1) {
    digitalWrite(PIN_DIR_A, HIGH);
  } else if (dir == -1) {
    digitalWrite(PIN_DIR_A, LOW);
  }
}

void aplicarVelocidad(int v) {
  if (v < 0) v = 0;
  if (v > VELOCIDAD_MAX) v = VELOCIDAD_MAX;
  analogWrite(PIN_PWM_A, v);
}

// ============================================================
//  HANDLERS DE LAS RUTAS WEB
// ============================================================
void handleRaiz() {
  server.send_P(200, "text/html", PAGINA_HTML);
}

void handleVelocidad() {
  if (server.hasArg("valor")) {
    int pct = server.arg("valor").toInt();
    if (pct < 0) pct = 0;
    if (pct > 100) pct = 100;
    velocidadObjetivo = map(pct, 0, 100, 0, VELOCIDAD_MAX);

    if (velocidadObjetivo > 0 && direccion == 0) {
      direccion = 1;
      aplicarDireccion(direccion);
    }
    server.send(200, "text/plain", "OK");
  } else {
    server.send(400, "text/plain", "Falta parametro valor");
  }
}

void handleDireccion() {
  if (server.hasArg("valor")) {
    int d = server.arg("valor").toInt();
    if (d < -1) d = -1;
    if (d > 1)  d = 1;
    direccion = d;
    aplicarDireccion(direccion);
    server.send(200, "text/plain", "OK");
  } else {
    server.send(400, "text/plain", "Falta parametro valor");
  }
}

void handleParar() {
  velocidadObjetivo = 0;
  server.send(200, "text/plain", "OK");
}

void handleEmergencia() {
  velocidadObjetivo = 0;
  velocidadActual   = 0;
  direccion         = 0;
  analogWrite(PIN_PWM_A, 0);
  server.send(200, "text/plain", "OK");
}

void handleEstado() {
  String json = "{\"vel\":" + String(velocidadActual) +
                ",\"dir\":" + String(direccion) + "}";
  server.send(200, "application/json", json);
}
  

// ============================================================
//  SETUP
// ============================================================
void setup() {
  Serial.begin(115200);
  Serial.println("\n\n=== Control de Tren Ibertren Cuco 020 ===");

  pinMode(PIN_PWM_A, OUTPUT);
  pinMode(PIN_DIR_A, OUTPUT);
  analogWriteRange(PWM_MAX);
  analogWriteFreq(FRECUENCIA_PWM);   // 2.5 kHz
  analogWrite(PIN_PWM_A, 0);
  digitalWrite(PIN_DIR_A, LOW);

  WiFi.softAP(SSID_AP, PASSWORD_AP);
  Serial.print("AP: ");
  Serial.println(SSID_AP);
  Serial.print("IP: ");
  Serial.println(WiFi.softAPIP());

  server.on("/",            handleRaiz);
  server.on("/velocidad",   handleVelocidad);
  server.on("/direccion",   handleDireccion);
  server.on("/parar",       handleParar);
  server.on("/emergencia",  handleEmergencia);
  server.on("/estado",      handleEstado);

  server.begin();
  Serial.println("Servidor listo en http://192.168.4.1");
}

// ============================================================
//  LOOP
// ============================================================
void loop() {
  server.handleClient();

  unsigned long ahora = millis();
  if (ahora - ultimaRampa >= INTERVALO_RAMPA) {
    ultimaRampa = ahora;

    if (velocidadActual < velocidadObjetivo) {
      velocidadActual += PASO_RAMPA;
      if (velocidadActual > velocidadObjetivo) velocidadActual = velocidadObjetivo;
      aplicarVelocidad(velocidadActual);
    } else if (velocidadActual > velocidadObjetivo) {
      velocidadActual -= PASO_RAMPA;
      if (velocidadActual < velocidadObjetivo) velocidadActual = velocidadObjetivo;
      aplicarVelocidad(velocidadActual);

      if (velocidadActual == 0 && direccion == 0) {
        aplicarDireccion(0);
      }
    }
  }
}

Detalles que marcan la diferencia

La rampa de aceleración. Fíjate en que el código no aplica directamente la velocidad que pides desde la web. En su lugar, guarda una velocidad objetivo y va acercando la velocidad actual hacia ella en pasos pequeños. Esto simula la inercia de un motor real y evita tirones bruscos. Puedes ajustar la suavidad cambiando PASO_RAMPA (más grande = más brusco) y INTERVALO_RAMPA (más grande = más lento).

La frecuencia PWM. El programa usa analogWriteFreq(2500), es decir, 2.5 kHz. Esta frecuencia es un buen equilibrio para el L293D: lo suficientemente alta para que el motor no zumbe, pero no tanto como para que el chip se caliente en exceso. Si oyes un pitido agudo, sube a 4000 Hz. Si el L293D se calienta mucho, baja a 1000 Hz.

El estado inicial seguro. Antes de crear el Access Point, el código configura los pines como salida y los deja en reposo (PWM a 0, dirección en LOW). Esto evita que el motor arranque solo al enchufar el NodeMCU, algo que puede pasar si los pines quedan flotando.

El auto-arranque de dirección. Si mueves el slider sin haber pulsado antes "Adelante" o "Atrás", el código asume que quieres ir hacia adelante. Es un pequeño detalle de usabilidad que evita que el motor se quede parado sin saber por qué.

Cómo usarlo desde el móvil

  1. Alimenta el NodeMCU (por USB o con la fuente externa).
  2. En el móvil, ve a Ajustes → WiFi y conéctate a la red MotorControl con la contraseña motor1234.
  3. Android avisará de que la red "no tiene acceso a Internet". Es normal: el ESP no tiene salida a Internet, solo sirve la web local. Elige "Mantener conexión".
  4. Abre Chrome (o cualquier navegador) y escribe 192.168.4.1.
  5. Ya tienes el panel de control en pantalla.

Si quieres, puedes añadir la página a la pantalla de inicio del móvil como si fuera una app. En Chrome: menú de tres puntos → "Añadir a pantalla de inicio". Así la abres con un solo toque.

¿Y si quiero controlarlo desde fuera de casa?

Este ejemplo usa modo Access Point, lo que significa que el alcance está limitado a la cobertura WiFi del ESP8266 (unos 20-30 metros en interior, más en exterior). Para controlarlo desde fuera de casa necesitarías:

  • Modo Station: conectar el ESP a tu router doméstico en lugar de crear su propia red. Tendrías que cambiar WiFi.softAP() por WiFi.begin() y usar la IP que te asigne el router.
  • Redirección de puertos: abrir el puerto 80 en tu router hacia la IP del ESP. Esto expone el control a Internet, con los riesgos de seguridad que conlleva.
  • Un broker MQTT: si quieres algo más robusto y seguro, MQTT es el camino. Pero eso ya es otro proyecto.

Para uso doméstico, el modo Access Point es más que suficiente. Y si tienes la maqueta en una habitación concreta, la cobertura del ESP llega de sobra a cualquier punto de esa habitación.

Reflexión final

Este tercer ejemplo muestra cómo un pequeño ESP12E Motor Shield, que a primera vista parece solo un driver de motores, se convierte en el corazón de una aplicación completa cuando le añades el WiFi del ESP8266. La combinación de hardware sencillo y software bien estructurado es lo que hace que estos proyectos sean tan satisfactorios.

Lo mejor de todo: no necesitas nada más que la placa, un motor y un navegador. Ni apps, ni cables, ni configuraciones complicadas. Enchufas, te conectas y controlas. Así debería ser siempre la electrónica.


Si te gusta el contenido o tienes alguna duda estare encantado de leer y contestar, Saludos a todos y todas

Otros articulos de la web que te pueden interesar:


Saludos a todas y todos!!!

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!!!