2026 Season Archives - The Borikengineers (FTC Team #20380) https://borikengineers.com/category/2026-season/ FTC Team #20380: The Borikengineers Sat, 21 Feb 2026 02:28:58 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 207332593 Programador de Servos Wi-Fi: De la Necesidad a la Solución de Campo https://borikengineers.com/programador-de-servos-wi-fi-de-la-necesidad-a-la-solucion-de-campo/ https://borikengineers.com/programador-de-servos-wi-fi-de-la-necesidad-a-la-solucion-de-campo/#respond Sat, 21 Feb 2026 02:20:13 +0000 https://borikengineers.com/?p=4121 En el taller de Borikengineers, este proyecto nació de una necesidad absoluta. Durante el desarrollo y las pruebas en robótica competitiva, cambiar la configuración de un servo de modo continuo a modo posicional (o viceversa) suele ser un proceso tedioso. Necesitábamos una herramienta de campo que fuera rápida, confiable y que no dependiera de estar […]

The post Programador de Servos Wi-Fi: De la Necesidad a la Solución de Campo appeared first on The Borikengineers (FTC Team #20380).

]]>
En el taller de Borikengineers, este proyecto nació de una necesidad absoluta. Durante el desarrollo y las pruebas en robótica competitiva, cambiar la configuración de un servo de modo continuo a modo posicional (o viceversa) suele ser un proceso tedioso. Necesitábamos una herramienta de campo que fuera rápida, confiable y que no dependiera de estar conectados a una computadora portátil en medio de los pits.

Así nació nuestro Programador de Servos Wi-Fi, impulsado por un ESP8266 (NodeMCU V3) y controlable completamente desde un teléfono móvil sin necesidad de internet.

Dando Honor a Quien Honor Merece: La Comunidad de Chief Delphi

Antes de hablar de la interfaz y la conectividad, es vital reconocer de dónde provino la “magia” de este proyecto. La ingeniería inversa y el trabajo pesado para descubrir las secuencias de bytes hexadecimales exactas necesarias para flashear la memoria EEPROM de estos servos no fue obra nuestra.

Ese mérito pertenece a la increíble comunidad de Chief Delphi. Encontramos un código base funcional en este hilo del foro: Reprogramming Dual Mode Servo Using Arduino. El usuario Asaf Meizner, alumni del equipo Makers Assemble 5951, creó el script original para enviar los comandos a través de Arduino. Nosotros tomamos esa excelente base y la llevamos al siguiente nivel para crear una herramienta de grado de campo.

Nuestra aportació: Una Interfaz Inalámbrica y Elegante

El script original requería enviar comandos a través del Monitor Serie de una computadora. Nosotros decidimos empaquetar esa lógica dentro de un servidor web autónomo alojado en un NodeMCU.

Las características de nuestra versión incluyen:

  • Punto de Acceso Independiente: El NodeMCU crea su propia red Wi-Fi (Borik-Servo-Tool). Te conectas desde tu teléfono, abres el navegador y la herramienta está lista.
  • Interfaz Móvil Nativa: Utilizando HTML, CSS y JavaScript alojados directamente en la memoria del microcontrolador, diseñamos una interfaz que se siente como una aplicación móvil profesional. Cuenta con botones táctiles con animaciones, notificaciones emergentes (Toasts) y bloqueos de seguridad para evitar dobles clics accidentales durante el flasheo.
  • Banco de Pruebas Integrado: No solo flasheamos el servo, sino que añadimos una sección de “Test Bench” para enviar señales PWM de 1000µs, 1500µs y 2000µs inmediatamente después de la programación para validar el movimiento.
  • Lógica a Prueba de Fallos: Implementamos un sistema de control de pines dinámico, alternando entre OUTPUT e INPUT_PULLUP para la comunicación UART (RX/TX), junto con comandos de “Signal Wash” para asegurar que el motor no se mueva erráticamente al terminar de programarse.

El Hardware

El circuito es engañosamente simple, utilizando una configuración UART Half-Duplex:

  1. Un NodeMCU V3 (ESP8266).
  2. El pin D1 (RX) escucha las respuestas del servo.
  3. El pin D2 (TX) envía los comandos de programación y la señal PWM.
  4. Una resistencia de 470Ω conectada entre D2 y el cable de señal del servo para proteger el microcontrolador de colisiones de datos.

Componentes Eléctricos

  1. Microcontroller: ESP8266 NodeMCU V3.
  2. Resistencia (Importante): 470Ω a 1kΩ (1/4 Watt).
    • Localización: Entre el pin D2 y el cable de señal del servo.
    • Propósito: Sirve para proteger el NodeMCU cuando el servo responde a la señal que se le envia. Ya que la señal del servo es de 5V, mientras que el NodeMCU espera un voltaje de 3.3V. La resistencia ayuda a mantener la corriente en un nivel tolerable.
  • Fuente de poder
    • Para servos pequeños puedes usar el pin VIN si estas conectado a USB.
    • Para servos grandes de alto torque necesitas una fuente de poder externa o un paquete de baterias de 5V a 6V y asegurarte de conectar el negativo de la bateria al GND del NodeMCU.

Instrucciones de Alambrado

  1. Union de señales
    • Toma el cable blanco o naranja del servo.
    • Conectalo directamente al pin D1 del NodeMCU.
    • Conecta el mismo cable a una resistencia de 470Ω.
    • Conecta el otro lado del resitor al pin D2 del NodeMCU.
  2. Poder
    • Conecta el cable rojo del servo al positivo de una fuente externa de poder de 5V o a pin VIN del NodeMCU.
    • Conecta el cable negro del servo al pin GND del NodeMCU
    • Si usas una fuente de poder externa, conecta el negativo al GND del NodeMCU.

Aqui te dejo un diagrama de la conexión:

     NODE MCU V3 (ESP8266)                          SERVO MOTOR
   +-----------------------+                    +-----------------+
   |                       |                    |                 |
   |                   GND |--------------------| Negro / Marrón  |
   |                       |                    |                 |
   |              VIN (5V) |--------------------| Rojo (VCC)      |
   |                       |                    |                 |
   |             D1 (GPIO5)|--------------+     |                 |
   |             (RX Input)|              |     |                 |
   |                       |              +-----| Blanco / Naranja|
   |             D2 (GPIO4)|----[470Ω]----^     | (Señal)         |
   |             (TX/PWM)  |   Resistencia      |                 |
   +-----------------------+                    +-----------------+

El Código Completo: Construye tu Propio Programador

Para aquellos que quieran replicar esta herramienta en sus propios talleres, aquí compartimos el código fuente completo para el NodeMCU V3. Hemos empaquetado toda la interfaz web, el manejo de Wi-Fi y la lógica de programación en un solo archivo .ino para facilitar su implementación.

Necesitas instalar el compilador de Arduino. Una vez instalado, ve a File>Preferences, y en Additional Board Manager URLs pon esta direccion:

https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json

Luego abres Tool>Board>Boards Manager, buscas “ESP8266” e instalas el que se ve abajo. Eso te permitira compilar y subir el codigo al NodeMCU.

Este es el código. Tambien puedes conseguirlo en nuestro repositorio en Github.

/* ============================================================================
 * Project: Borikengineers Servo Programmer & Test Bench
 * Author:  Borikengineers FTC Team 20380
 * Origin:  Aguadilla, Puerto Rico
 *
 * Description: 
 * This code configures an ESP8266 to host a local web server network 
 * ("Borik-Servo-Tool") to program and test servos via a mobile-friendly UI. 
 *
 * License:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * ============================================================================ */

#include <ESP8266WiFi.h>      // Core Wi-Fi library for ESP8266
#include <ESP8266WebServer.h> // Library to host the local web server
#include <SoftwareSerial.h>   // Allows serial communication on custom pins
#include <Servo.h>            // Standard library to generate PWM signals for servos

// Define hardware pins used for communicating with the servo
#define RX_PIN D1
#define TX_PIN D2 

// Initialize objects
SoftwareSerial mySerial(RX_PIN, TX_PIN); // Serial connection for programming the servo
Servo myServo;                           // PWM connection for testing the servo
ESP8266WebServer server(80);             // Web server running on standard HTTP port 80

// State flag to prevent concurrent requests from crashing the serial sequence
bool isBusy = false;

// Helper function to clear the incoming serial buffer
// This ensures old responses don't interfere with new commands
void flushMySerial() {
  while (mySerial.available()) { mySerial.read(); }
}

// Configures the servo to operate in Positional Mode (Standard 0-180 degree operation)
void sendServoModeSequence() {
  if(isBusy) return; // Block execution if another operation is running
  isBusy = true;
  
  // Stop sending PWM signals before attempting serial communication on the same line
  myServo.detach();
  delay(500);

  // Command 1: Initialization / Wake-up sequence
  pinMode(TX_PIN, OUTPUT);
  byte s1[] = {0xFF,0xFF,0xFE,0x02,0x01,0xFE};
  mySerial.write(s1, sizeof(s1));
  pinMode(TX_PIN, INPUT_PULLUP); // Switch to input to allow the servo to pull the line (half-duplex simulation)
  delay(10); flushMySerial();

  // Command 2: Main configuration payload for Positional Mode
  pinMode(TX_PIN, OUTPUT);
  byte s4s[] = {0xFF,0xFF,0x01,0x2F,0x03,0x06,0x02,0x1E,0x00,0x05,0x00,0x0F,0x00,0x2D,0x00,0x00,0x00,0x00,0x0F,0x03,0xFC,0x00,0x00,0x00,0x00,0x41,0x03,0xC5,0x00,0x00,0x01,0xFF,0x01,0x00,0x02,0x09,0xC4,0x01,0xF4,0x03,0xE8,0x00,0x01,0x00,0x00,0x00,0x00,0x03,0xE8,0x00,0xB2};
  mySerial.write(s4s, sizeof(s4s));
  pinMode(TX_PIN, INPUT_PULLUP);
  delay(10); flushMySerial();

  // Command 3: Finalize / Save to EEPROM
  pinMode(TX_PIN, OUTPUT);
  byte s5[] = {0xFF,0xFF,0x01,0x04,0x03,0x34,0x01,0xC2};
  mySerial.write(s5, sizeof(s5));
  pinMode(TX_PIN, INPUT_PULLUP);
  delay(10); flushMySerial();
  
  isBusy = false; // Release the lock
}

// Configures the servo to operate in Continuous Rotation Mode (360 degree wheel mode)
void sendContinuousModeSequence() {
  if(isBusy) return;
  isBusy = true;
  
  myServo.detach();
  delay(500);

  // Command 1: Initialization / Wake-up sequence
  pinMode(TX_PIN, OUTPUT);
  byte s1[] = {0xFF,0xFF,0xFE,0x02,0x01,0xFE};
  mySerial.write(s1, sizeof(s1));
  pinMode(TX_PIN, INPUT_PULLUP);
  delay(10); flushMySerial();

  // Command 2: Main configuration payload for Continuous Mode
  pinMode(TX_PIN, OUTPUT);
  byte s4c[] = {0xFF,0xFF,0x01,0x2F,0x03,0x06,0x32,0x14,0x00,0x05,0x00,0x0A,0x00,0x0A,0x00,0x1E,0x00,0x00,0x00,0x03,0xFF,0x01,0x00,0x00,0x00,0x41,0x03,0xC5,0x01,0xF4,0x01,0xFF,0x00,0x00,0x02,0x06,0x0E,0x05,0xAA,0x03,0xE8,0x00,0x14,0x00,0x00,0x00,0x00,0x03,0xE8,0x00,0x99};
  mySerial.write(s4c, sizeof(s4c));
  pinMode(TX_PIN, INPUT_PULLUP);
  delay(10); flushMySerial();

  // Command 3: Finalize / Save to EEPROM
  pinMode(TX_PIN, OUTPUT);
  byte s5[] = {0xFF,0xFF,0x01,0x04,0x03,0x34,0x01,0xC2};
  mySerial.write(s5, sizeof(s5));
  pinMode(TX_PIN, INPUT_PULLUP);
  delay(10); flushMySerial();
  
  isBusy = false;
}

// Generates the HTML, CSS, and JS for the web interface
String getHTML() {
  // Capture URL arguments to display toast notifications (e.g., /?msg=Success)
  String msg = server.arg("msg");
  msg.replace("+", " ");
  String displayMode = (msg == "") ? "none" : "block";

  // Build the HTML page structure
  String html = "<!DOCTYPE html><html><head>";
  html += "<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'>";
  html += "<title>Borikengineers Tool</title>";
  
  // Inject CSS styling
  html += "<style>";
  html += ":root { --bg: #f2f2f7; --card: #ffffff; --primary: #003366; --accent: #ff8c00; --text: #1c1c1e; } ";
  html += "* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } ";
  html += "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 0; } ";
  html += ".header { background: linear-gradient(135deg, var(--primary), #001f3f); padding: 35px 20px 25px; text-align: center; border-bottom: 4px solid var(--accent); border-radius: 0 0 24px 24px; box-shadow: 0 10px 25px rgba(0,51,102,0.15); } ";
  html += ".header h1 { margin: 0; color: white; font-size: 24px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase; } ";
  html += ".header p { margin: 6px 0 0; color: rgba(255,255,255,0.8); font-size: 13px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; } ";
  html += ".toast { display: " + displayMode + "; background: #34c759; color: white; font-weight: 600; text-align: center; padding: 14px; margin: -10px 20px 20px; border-radius: 14px; box-shadow: 0 8px 20px rgba(52,199,89,0.3); animation: slideDown 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); font-size: 15px; position: relative; z-index: 10; } ";
  html += "@keyframes slideDown { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } } ";
  html += ".container { padding: 20px; max-width: 500px; margin: 0 auto; } ";
  html += ".card { background: var(--card); border-radius: 20px; padding: 24px; margin-bottom: 20px; box-shadow: 0 4px 24px rgba(0,0,0,0.06); } ";
  html += ".card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; border-bottom: 2px solid #f0f0f5; padding-bottom: 12px; } ";
  html += "h3 { margin: 0; color: #8e8e93; font-size: 13px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; } ";
  html += "#working { display: none; color: var(--accent); font-size: 13px; font-weight: 700; } ";
  html += ".spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255,140,0,0.3); border-radius: 50%; border-top-color: var(--accent); animation: spin 0.8s linear infinite; margin-right: 6px; vertical-align: middle; } ";
  html += "@keyframes spin { to { transform: rotate(360deg); } } ";
  html += ".btn { display: block; width: 100%; padding: 18px; border: none; border-radius: 16px; font-size: 16px; font-weight: 700; color: white; text-align: center; text-decoration: none; cursor: pointer; transition: transform 0.1s ease, box-shadow 0.1s ease; margin-bottom: 14px; } ";
  html += ".btn:active { transform: scale(0.96); box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important; } ";
  html += ".btn-orange { background: linear-gradient(135deg, #ff9500, #ff8c00); box-shadow: 0 8px 20px rgba(255,140,0,0.3); } ";
  html += ".btn-blue { background: linear-gradient(135deg, #007aff, #0056b3); box-shadow: 0 8px 20px rgba(0,122,255,0.3); } ";
  html += ".btn-green { background: linear-gradient(135deg, #34c759, #28a745); box-shadow: 0 8px 20px rgba(40,167,69,0.3); } ";
  html += ".btn-red { background: linear-gradient(135deg, #ff3b30, #dc3545); box-shadow: 0 8px 20px rgba(220,53,69,0.3); } ";
  html += ".btn-gray { background: linear-gradient(135deg, #8e8e93, #6c757d); box-shadow: 0 8px 20px rgba(108,117,125,0.3); margin-bottom: 0; } ";
  html += ".grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; } ";
  html += ".grid .btn { margin-bottom: 0; } ";
  html += ".disabled { opacity: 0.6; pointer-events: none; transform: none !important; box-shadow: none !important; } ";
  html += ".footer { text-align: center; font-size: 12px; color: #aeaeb2; font-weight: 500; padding: 10px 0 30px; } ";
  html += "</style>";

  // Inject JavaScript for UI interactions
  html += "<script>";
  html += "function disable(m){";
  html += "  document.querySelectorAll('.btn').forEach(b => b.classList.add('disabled'));"; // Disable buttons to prevent duplicate requests
  html += "  let w = document.getElementById('working');";
  html += "  w.style.display = 'block';";
  html += "  w.innerHTML = '<span class=\"spinner\"></span> ' + m;"; // Show loading spinner
  html += "}";
  html += "</script>";

  // Build the HTML body elements and route links
  html += "</head><body>";
  html += "<div class='header'><h1>Borikengineers</h1><p>Servo Programmer</p></div>";
  html += "<div class='container'>";
  html += "<div class='toast'>" + msg + "</div>"; // Display success messages here
  html += "<div class='card'>";
  html += "<div class='card-header'><h3>Program Mode</h3><div id='working'></div></div>";
  html += "<a href='/set?m=1' onclick=\"disable('Flashing Continuous...')\" class='btn btn-orange'>FLASH CONTINUOUS</a>";
  html += "<a href='/set?m=2' onclick=\"disable('Flashing Positional...')\" class='btn btn-blue'>FLASH POSITIONAL</a>";
  html += "</div>";
  html += "<div class='card'>";
  html += "<div class='card-header' style='margin-bottom:18px; border:none; padding:0;'><h3>Test Bench</h3></div>";
  html += "<div class='grid'>";
  html += "<a href='/move?v=2000' class='btn btn-green'>FWD / 180&deg;</a>"; // 2000 microseconds = max forward
  html += "<a href='/move?v=1000' class='btn btn-red'>REV / 0&deg;</a>";    // 1000 microseconds = max reverse
  html += "</div>";
  html += "<a href='/move?v=1500' class='btn btn-gray'>STOP / 90&deg;</a>";  // 1500 microseconds = center/stop
  html += "</div>";
  html += "<div class='footer'>Borik Engineers | Field Tool v3.0</div>";
  html += "</div></body></html>";
  return html;
}

void setup() {
  // Serial setup for debugging
  Serial.begin(115200);
  
  // Software Serial setup for servo communication (Servos often use specific baud rates)
  mySerial.begin(76800);
  
  // Default TX pin to input with pullup to idle high
  pinMode(TX_PIN, INPUT_PULLUP);

  // Set up the ESP8266 as an Access Point
  WiFi.softAP("Borik-Servo-Tool", "12345678");

  // Route: Main Page
  server.on("/", [](){ server.send(200, "text/html", getHTML()); });
  
  // Route: Handle programming requests
  server.on("/set", [](){
    if(isBusy) return;
    String mode = server.arg("m");
    String responseMsg = "";
    
    // Check which mode the user selected
    if(mode == "1") {
      sendContinuousModeSequence();
      responseMsg = "Continuous+Mode+Activated";
    } else {
      sendServoModeSequence();
      responseMsg = "Positional+Mode+Activated";
    }
    
    // Redirect back to the main page with a success message
    server.sendHeader("Location", "/?msg=" + responseMsg);
    server.send(303);
  });

  // Route: Handle PWM test movements
  server.on("/move", [](){
    if(isBusy) return;
    
    // Read the requested pulse width (in microseconds)
    int pulse = server.arg("v").toInt();
    
    // Re-attach the servo to the TX pin for standard PWM generation
    myServo.attach(TX_PIN);
    myServo.writeMicroseconds(pulse);
    
    // Redirect back to the main page
    server.sendHeader("Location", "/");
    server.send(303);
  });

  // Start the web server listener
  server.begin();
}

void loop() { 
  // Continuously listen for incoming HTTP requests
  server.handleClient(); 
}

Como usar la herramienta

Una vez compilas el codigo y lo subes al NodeMCU el programa se inicia y se crea el punto de acceso WiFi llamado Borik-Servo-Tool. El password es: 12345678. El nombre y password se puede cambiar en el código cambiando esta linea:

WiFi.softAP("Borik-Servo-Tool", "12345678");

Una vez conectado al punto de acceso, abres un navegador de internet y entras la siguiente direccion: 192.168.4.1

Al entrar veras esta pantalla

Servo Programmer
Servo Programmer

El programa es bien simple. La parte de arriba (Program Mode) te permite programar el servo en modo de rotación continua o modo posicional. La parte de abajo (Test Bench) te permite mover el servo para probarlo.

Conclusión

Este es un ejemplo perfecto de cómo el código abierto y la colaboración en línea impulsan la ingeniería hacia adelante. Tomamos una solución brillante de la comunidad de Chief Delphi y le aplicamos nuestra experiencia en interfaces web y sistemas integrados para crear una herramienta indispensable para nuestro equipo.

Esperamos que esta herramienta le sirva a otros equipos para ser más eficientes durante las competencias. Un enorme agradecimiento nuevamente a la comunidad de Chief Delphi por hacer el trabajo pesado de ingeniería inversa. Si integras este código en tu taller, ¡déjanos un comentario o etiquétanos en redes sociales!

The post Programador de Servos Wi-Fi: De la Necesidad a la Solución de Campo appeared first on The Borikengineers (FTC Team #20380).

]]>
https://borikengineers.com/programador-de-servos-wi-fi-de-la-necesidad-a-la-solucion-de-campo/feed/ 0 4121