Encuentra tu pasión a solo 4,17 US$/mes | Aprende Robótica, IA e IOT.

Cómo construir un robot omnidireccional con ESP32

Construir un robot omnidireccional con ESP32 es uno de los proyectos más interesantes dentro de la robótica. No solo permite aprender sobre movimiento avanzado con ruedas Mecanum, sino también sobre control remoto mediante WiFi.

En este tutorial vas a ver cómo desarrollar el robot paso a paso, desde el ensamblaje del chasis hasta las conexiones electrónicas. La idea es que entiendas cómo cada parte del sistema trabaja en conjunto para lograr un movimiento en todas las direcciones.

Además, vamos a llevar este proyecto un nivel más allá. El robot será controlado mediante una interfaz web utilizando WiFi, lo que te permitirá manejarlo desde cualquier navegador sin necesidad de aplicaciones externas.

Materiales

Para construir este robot omnidireccional con ESP32 se requieren los siguientes componentes electrónicos, mecánicos y de alimentación.

  • 1 ESP32 DevKit V1 30 pines
  • 1 Shield de expansión para ESP32 de 30 pines
  • 2 Driver L298N
  • 4 Motores reductores DC 3–6V (amarillos)
  • 4 Ruedas Mecanum (2 derechas y 2 izquierdas)
  • 1 Switch de palanca
  • 1 Pack de cables jumper macho-hembra (40 unidades)
  • 1 Pack de cables jumper hembra-hembra (40 unidades)
  • 2 metros de alambre de timbre
  • 4 Tornillos M3x10
  • 4 Tornillos M3x15
  • 2 Tornillos M3x20
  • 8 Tornillos M3x30
  • 1 metro de termoencogible de 3 mm
  • 2 Baterías 18650
  • 1 Porta baterías para 2×18650

Montaje del chasis

Para iniciar el montaje del robot omnidireccional con ESP32, vamos a utilizar nuestro chasis diseñado para corte láser, ya que está optimizado específicamente para este proyecto.

Este chasis ya incluye perforaciones listas para motores, drivers, ESP32 y batería. Esto hace que el montaje sea más rápido, preciso y sin necesidad de mediciones adicionales.

Además, su diseño tipo tanque mejora la estabilidad y distribución del peso, y le da un acabado más atractivo y profesional. 

Si no puedes adquirir el chasis, puedes utilizar una base alternativa.

La opción más común es una plancha de MDF o triplex de 3 mm de espesor con dimensiones de 20 cm por 12 cm. Esta opción funciona correctamente, pero requiere mayor trabajo de medición y perforación manual.

Para realizar el montaje utilizando nuestro chasis, reproduce el video tutorial en el segundo 32 y sigue los pasos para ensamblar el robot.

Este video corresponde a una versión anterior del chasis, por lo que hemos agregado indicaciones adicionales para que puedas identificar fácilmente las modificaciones realizadas y evitar problemas durante el montaje. 

Orientación del chasis

La primera modificación que debes considerar es la siguiente:

Antes de comenzar el montaje, es importante identificar la orientación correcta del chasis y tomar como referencia las nuevas indicaciones para evitar errores durante la instalación.

  • En la parte frontal de robot existe una ranura central
  • En lado derecho tiene igual una ranura pero más pequeña

Sistema de baterías y mejoras del diseño

El chasis incluye otra actualización en el soporte para baterías 18650, diseñado para fijarlas mediante presión. Esta mejora no aparece en el video, ya que corresponde a la versión anterior.

🧠 Aprende IA con Arduino

Explora nuestro eBook con proyectos guiados paso a paso para aprender inteligencia artificial con Arduino desde cero, en casa o en el aula.

Realiza el ajuste con cuidado y presiona suavemente para evitar cualquier accidente. Este sistema permite un encaje más firme y seguro de las baterías.

También el diseño permite ampliaciones sin modificar la estructura principal.

  • Parte frontal: espacio para sensor ultrasónico
  • Laterales: puntos para LEDs o indicadores

Diagrama de conexiones

Llegó el momento de revisar las conexiones del robot. Sigue exactamente el esquema que se muestra en la imagen.

La orientación de los componentes es la misma tanto en la imagen como en el chasis. Es decir, lo que ves en el diagrama es prácticamente un reflejo directo de cómo debe quedar armado físicamente.

Además, el video tutorial también funciona como complemento para ayudarte a armar correctamente las conexiones. Junto con la imagen tendrás todo lo necesario para completar el ensamblaje sin complicaciones. 

Programación

Para la programación del robot, puedes utilizar cualquier versión del Arduino IDE, ya sea Arduino IDE 1 o Arduino IDE 2, sin ningún problema.

Sin embargo, es importante que la versión del soporte de la ESP32 sea la 2.0.8, ya que esto garantiza la compatibilidad correcta con el código del proyecto.

				
					#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <vector>


const char* ssid = "OmniRobot";
const char* password = "12345678";


WebServer server(80);


#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define UP_LEFT 5
#define UP_RIGHT 6
#define DOWN_LEFT 7
#define DOWN_RIGHT 8
#define TURN_LEFT 9
#define TURN_RIGHT 10
#define STOP 0


#define FRONT_RIGHT_MOTOR 0
#define BACK_RIGHT_MOTOR 1
#define FRONT_LEFT_MOTOR 2
#define BACK_LEFT_MOTOR 3


#define FORWARD 1
#define BACKWARD -1


// Canales PWM para cada motor
#define FR_PWM_CH 0
#define BR_PWM_CH 1
#define FL_PWM_CH 2
#define BL_PWM_CH 3


// Estructura para pines del L298N (IN1, IN2, ENA/ENB)
struct MOTOR_PINS {
  int pinIN1;      // Control de dirección 1
  int pinIN2;      // Control de dirección 2
  int pinEnable;   // Pin Enable (ENA o ENB) - PWM para velocidad
  int pwmChannel;  // Canal PWM
};


// Configuración de pines para 4 motores con L298N
// Ajusta estos pines según tu conexión física
std::vector<MOTOR_PINS> motorPins = {
  {17, 16, 4, FR_PWM_CH},  // Motor Frontal Derecho
  {27, 26, 13, BR_PWM_CH},  // Motor Trasero Derecho
  {19, 18, 21, FL_PWM_CH},  // Motor Frontal Izquierdo
  {25, 33, 32, BL_PWM_CH},  // Motor Trasero Izquierdo
};


int motorSpeed = 0;


const int freq = 10000;
const int resolution = 8;


const char* movementNames[] = {
  "Detenido",
  "Adelante",
  "Atrás",
  "Izquierda",
  "Derecha",
  "Adelante Izquierda",
  "Adelante Derecha",
  "Atrás Izquierda",
  "Atrás Derecha",
  "Rotar Izquierda",
  "Rotar Derecha"
};


volatile int currentMovement = STOP;
volatile int currentSpeed = 0;


void rotateMotor(int motorNumber, int motorDirection, int speed) {
  if (motorNumber < 0 || motorNumber >= motorPins.size()) {
    return;
  }


  MOTOR_PINS motor = motorPins[motorNumber];


  if (motorDirection == FORWARD) {
    digitalWrite(motor.pinIN1, HIGH);
    digitalWrite(motor.pinIN2, LOW);
    ledcWrite(motor.pwmChannel, speed);
  } else if (motorDirection == BACKWARD) {
    digitalWrite(motor.pinIN1, LOW);
    digitalWrite(motor.pinIN2, HIGH);
    ledcWrite(motor.pwmChannel, speed);
  } else {
    // STOP
    digitalWrite(motor.pinIN1, LOW);
    digitalWrite(motor.pinIN2, LOW);
    ledcWrite(motor.pwmChannel, 0);
  }
}


void processCarMovement(int inputValue, int speed) {
  currentMovement = inputValue;
  motorSpeed = speed;
 
  switch(inputValue) {
    case UP:
      rotateMotor(FRONT_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, FORWARD, speed);
      break;
    case DOWN:
      rotateMotor(FRONT_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, BACKWARD, speed);
      break;
    case LEFT:
      rotateMotor(FRONT_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, FORWARD, speed);
      break;
    case RIGHT:
      rotateMotor(FRONT_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, BACKWARD, speed);
      break;
    case UP_LEFT:
      rotateMotor(FRONT_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, STOP, 0);
      rotateMotor(FRONT_LEFT_MOTOR, STOP, 0);
      rotateMotor(BACK_LEFT_MOTOR, FORWARD, speed);
      break;
    case UP_RIGHT:
      rotateMotor(FRONT_RIGHT_MOTOR, STOP, 0);
      rotateMotor(BACK_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, STOP, 0);
      break;
    case DOWN_LEFT:
      rotateMotor(FRONT_RIGHT_MOTOR, STOP, 0);
      rotateMotor(BACK_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, STOP, 0);
      break;
    case DOWN_RIGHT:
      rotateMotor(FRONT_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, STOP, 0);
      rotateMotor(FRONT_LEFT_MOTOR, STOP, 0);
      rotateMotor(BACK_LEFT_MOTOR, BACKWARD, speed);
      break;
    case TURN_LEFT:
      rotateMotor(FRONT_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, FORWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, BACKWARD, speed);
      break;
    case TURN_RIGHT:
      rotateMotor(FRONT_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(BACK_RIGHT_MOTOR, BACKWARD, speed);
      rotateMotor(FRONT_LEFT_MOTOR, FORWARD, speed);
      rotateMotor(BACK_LEFT_MOTOR, FORWARD, speed);
      break;
    case STOP:
    default:
      rotateMotor(FRONT_RIGHT_MOTOR, STOP, 0);
      rotateMotor(BACK_RIGHT_MOTOR, STOP, 0);
      rotateMotor(FRONT_LEFT_MOTOR, STOP, 0);
      rotateMotor(BACK_LEFT_MOTOR, STOP, 0);
      break;
  }
}


void setUpPWM() {
  for (size_t i = 0; i < motorPins.size(); i++) {
    // Configurar pines de dirección como salidas digitales
    pinMode(motorPins[i].pinIN1, OUTPUT);
    pinMode(motorPins[i].pinIN2, OUTPUT);
   
    // Configurar pin Enable con PWM
    ledcSetup(motorPins[i].pwmChannel, freq, resolution);
    ledcAttachPin(motorPins[i].pinEnable, motorPins[i].pwmChannel);
   
    // Inicializar motores detenidos
    digitalWrite(motorPins[i].pinIN1, LOW);
    digitalWrite(motorPins[i].pinIN2, LOW);
    ledcWrite(motorPins[i].pwmChannel, 0);
  }
}


void handleRoot() {
  String html = "<!DOCTYPE html><html><head><title>OmniRobot</title>";
  html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  html += "<meta charset='UTF-8'>";
  html += "<style>";
  html += "body{font-family: Arial, sans-serif; text-align: center; background-color: #f0f0f0; user-select: none;}";
  html += ".container{margin-top: 20px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 300px; margin: 20px auto;}";
  html += ".button{padding: 20px; font-size: 24px; background-color: #4CAF50; color: white; border: none; cursor: pointer; border-radius: 8px; transition: transform 0.1s; display: flex; align-items: center; justify-content: center; user-select: none;}";
  html += ".button.stop{background-color: #f44336;}";
  html += ".button:active { transform: scale(0.95); }";
  html += "#status-bar{margin: 20px auto; padding: 10px; font-size: 18px; color: #333;}";
  html += "input[type='range']{width: 100%;}";
  html += "</style></head><body>";
  html += "<h1>Control OmniRobot</h1>";
  html += "<p id='status-bar'>Estado del robot: Detenido</p>";
  html += "<div class='container'>";
  html += "<button class='button' onclick='sendMove(5)'>&#x2196;</button>";
  html += "<button class='button' onclick='sendMove(1)'>&#x2191;</button>";
  html += "<button class='button' onclick='sendMove(6)'>&#x2197;</button>";
  html += "<button class='button' onclick='sendMove(3)'>&#x2190;</button>";
  html += "<button class='button stop' onclick='sendMove(0)'>&#x23F9;</button>";
  html += "<button class='button' onclick='sendMove(4)'>&#x2192;</button>";
  html += "<button class='button' onclick='sendMove(7)'>&#x2199;</button>";
  html += "<button class='button' onclick='sendMove(2)'>&#x2193;</button>";
  html += "<button class='button' onclick='sendMove(8)'>&#x2198;</button>";
  html += "</div>";
  html += "<div class='container'>";
  html += "<button class='button' onclick='sendMove(9)'>&#x21BA;</button>";
  html += "<button class='button' onclick='sendMove(10)'>&#x21BB;</button>";
  html += "</div>";
  html += "<h2>Velocidad</h2>";
  html += "<input type='range' id='speedSlider' min='0' max='255' value='" + String(motorSpeed) + "' oninput='sendSpeed(this.value)'>";
  html += "<p>Valor de velocidad: <span id='speedValue'>" + String(motorSpeed) + "</span></p>";
  html += "<script>";
  html += "const movementNames = ['Detenido', 'Adelante', 'Atrás', 'Izquierda', 'Derecha', 'Adelante Izquierda', 'Adelante Derecha', 'Atrás Izquierda', 'Atrás Derecha', 'Rotar Izquierda', 'Rotar Derecha'];";
  html += "const speedValueSpan = document.getElementById('speedValue');";
  html += "const statusBar = document.getElementById('status-bar');";
  html += "let currentMovement = " + String(currentMovement) + ";";
  html += "let currentSpeed = " + String(motorSpeed) + ";";
  html += "function sendMove(move) {";
  html += "  currentMovement = move;";
  html += "  let url = '/move?dir=' + move + '&speed=' + currentSpeed;";
  html += "  fetch(url).then(response => { if (response.ok) { updateStatus(move); } });";
  html += "}";
  html += "function sendSpeed(speed) {";
  html += "  currentSpeed = speed;";
  html += "  speedValueSpan.textContent = speed;";
  html += "  let url = '/move?dir=' + currentMovement + '&speed=' + speed;";
  html += "  fetch(url);";
  html += "}";
  html += "function updateStatus(move) {";
  html += "  statusBar.textContent = 'Estado del robot: ' + movementNames[move];";
  html += "}";
  html += "</script>";
  html += "</body></html>";
  server.send(200, "text/html", html);
}


void handleMove() {
  if (server.hasArg("dir")) {
    int dir = server.arg("dir").toInt();
    int speed = motorSpeed;
    if (server.hasArg("speed")) {
        speed = server.arg("speed").toInt();
    }
    processCarMovement(dir, speed);
    server.send(200, "text/plain", "OK");
  } else {
    server.send(400, "text/plain", "Error: Missing arguments");
  }
}


void setup(void) {
  Serial.begin(115200);
  setUpPWM();
 
  WiFi.softAP(ssid, password);
  IPAddress IP = WiFi.softAPIP();
  Serial.print("Access Point listo! IP: ");
  Serial.println(IP);


  server.on("/", handleRoot);
  server.on("/move", handleMove);
  server.begin();
  Serial.println("Servidor HTTP iniciado");
}


void loop() {
  server.handleClient();
}

				
			

Solo debes copiarlo, pegarlo en el Arduino IDE y cargarlo directamente en la ESP32.

Pruebas experimentales

Paso 1: Conexión Wi-Fi

  • Activa el Wi-Fi desde tu teléfono, computadora o cualquier dispositivo con navegador web.
  • Busca la red OmniRobot y conéctate usando la contraseña 12345678.

Paso 2: Acceso a la interfaz

  • Abre un navegador y escribe la dirección IP: 192.168.4.1.
  • En este punto debería cargarse la interfaz de control del robot.

Paso 3: Ajuste de velocidad

  • Ubica el control deslizante (slider) y colócalo en un valor inicial de 200.

Paso 4: Pruebas de movimiento

  • Empieza a controlar el robot y realiza los movimientos básicos.
  • Puedes ir variando la velocidad poco a poco hasta encontrar el punto óptimo de funcionamiento y la velocidad máxima estable del sistema.

🧠 Aprende IA con Arduino

Explora nuestro eBook con proyectos guiados paso a paso para aprender inteligencia artificial con Arduino desde cero, en casa o en el aula.

Ingrese a su cuenta