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

No hay comentarios :