Cómo construir un LiDAR 3D casero con el VL53L0X: mapea tu entorno con sensores láser de tiempo de vuelo

En esta práctica aprenderás a construir tu propio LiDAR 3D casero usando cuatro sensores láser de tiempo de vuelo VL53L0X montados en abanico vertical sobre una base giratoria, conectados por I2C a una tarjeta STM32 Black Pill. En lugar de depender de un LiDAR comercial de cientos de dólares, dejarás que cuatro sensores económicos —combinados con el giro de un motor a pasos— reconstruyan la forma de una habitación. Para comprobar que todo funciona, llevarás esas distancias a la computadora y verás, en tiempo real, cómo se forma una nube de puntos 3D que imita exactamente el entorno que rodea al sensor.

Introducción

¿Alguna vez te has preguntado cómo una aspiradora robot recorre tu sala esquivando las patas de las sillas, o cómo un dron mantiene su altura sobre el suelo, incluso a oscuras y sin usar una cámara? La respuesta, en muchos casos, es un LiDAR: un sensor que dispara luz, mide cuánto tarda en rebotar, y con eso sabe a qué distancia está cada objeto a su alrededor.

El problema es que los LiDAR rotativos comerciales suelen costar cientos de dólares, y las alternativas económicas —como los sensores ultrasónicos— entregan un haz demasiado ancho e impreciso para dibujar la forma real de una habitación. Aquí es donde entra el VL53L0X: un sensor láser de distancia por tiempo de vuelo (ToF), diminuto y de bajo costo, que mide con gran precisión y se comunica por I2C. La idea de este proyecto es sencilla pero poderosa: montar cuatro de estos sensores sobre una base giratoria para construir tu propio escáner 3D casero.

En este artículo aprenderás qué es el VL53L0X, cómo funciona la medición por tiempo de vuelo, el truco imprescindible para usar varios sensores en el mismo bus I2C mediante el pin XSHUT, cómo se estructura un escáner rotativo de 4 sensores, y cómo visualizar el resultado en tiempo real como una nube de puntos 3D de tu entorno.

¿Qué es el VL53L0X?

El VL53L0X es un pequeño módulo (de apenas 25 × 10 × 3 mm) construido alrededor del chip del mismo nombre de STMicroelectronics. Es un sensor de distancia por tiempo de vuelo que integra, en un solo encapsulado, un emisor láser de 940 nm tipo VCSEL (infrarrojo invisible al ojo humano), un detector SPAD (Single Photon Avalanche Diode) y la electrónica interna FlightSense, que realiza el cálculo de la distancia por sí misma. Se conecta a tu microcontrolador por I2C y mide distancias en un rango aproximado de 50 mm a 2000 mm, con un campo de visión de 25°.

¿Cómo funciona un sensor de tiempo de vuelo (ToF)?

El tiempo de vuelo (Time-of-Flight) es el corazón de este sensor. El VL53L0X emite un pulso de luz láser, y mide el tiempo exacto que tarda ese pulso en viajar hasta el objeto y regresar reflejado. Como la velocidad de la luz es una constante conocida, con ese tiempo el chip calcula directamente la distancia. A diferencia de un sensor infrarrojo sencillo —que intenta estimar la distancia según la intensidad de la luz reflejada—, el ToF mide tiempo, no brillo. Eso significa que entrega lecturas exactas y estables sin importar el color o la reflectividad de la superficie, e incluso funciona en total oscuridad. Es la misma tecnología que usan los teléfonos para el enfoque automático de la cámara.

Características principales:

  • Chip VL53L0X de STMicroelectronics: sensor de distancia láser ToF de alta precisión.
  • Óptica: emisor láser VCSEL de 940 nm (infrarrojo invisible, Clase 1, seguro), detector SPAD y motor de cálculo FlightSense integrados. Rango: 50 a 2000 mm (modo estándar hasta ~1.2 m, modo ampliado hasta 2 m). Campo de visión de 25°.
  • Comunicación: I2C estándar a 400 kHz, compatible con Arduino, ESP32, STM32, Raspberry Pi y MicroPython.
  • Control: pines XSHUT (apagado, activo en bajo) y GPIO1 (interrupción por dato listo), clave para usar varios sensores a la vez.
  • Alimentación: 3 a 5 VDC, 10 mA típicos (40 mA máx).

Materiales

Puedes encontrar todo el material en la siguiente lista.

Diagrama de Conexión

Requerimientos previos

El firmware del escáner se encargará de inicializar los cuatro VL53L0X con el truco de XSHUT y direcciones, controlar el motor a pasos para recorrer el azimut, leer las cuatro distancias en cada paso y empaquetarlas para enviarlas a la computadora por USB.

Código

Lectura de los 4 sesnores

Sube el siguiente código a tu placa de desarrollo Black Pill

 
#include <AccelStepper.h>
#include <Wire.h>
#include <VL53L0X.h>


const int   NUM_SENSORES = 4;       
const float ANGULO_TOTAL = 90.0;
const long  VELOCIDAD    = 700;     


AccelStepper motor(AccelStepper::HALF4WIRE, PA4, PA6, PA5, PA7);

#define PIN_SDA PB7
#define PIN_SCL PB6

const uint8_t XSHUT[4] = { PB12, PB13, PB14, PB15 };
const uint8_t ADDR[4]  = { 0x30, 0x31, 0x32, 0x33 };
const float   ELEV[4]  = { 37.5, 12.5, -12.5, -37.5 };

VL53L0X sensor[4];

const long PASOS_VUELTA = 4096;
const long PASOS_RANGO  = (long)(PASOS_VUELTA * (ANGULO_TOTAL / 360.0));

const uint16_t DIST_MAX_VALIDA = 1500;


inline bool datoListo(int i) {
  return (sensor[i].readReg(VL53L0X::RESULT_INTERRUPT_STATUS) & 0x07) != 0;
}
inline uint16_t leerRango(int i) {
  uint16_t r = sensor[i].readReg16Bit(VL53L0X::RESULT_RANGE_STATUS + 10);
  sensor[i].writeReg(VL53L0X::SYSTEM_INTERRUPT_CLEAR, 0x01);
  return r;
}

void setup() {
  Serial.begin(115200);
  uint32_t t0 = millis();
  while (!Serial && millis() - t0 < 3000) {}
  delay(300);

  Wire.setSDA(PIN_SDA);
  Wire.setSCL(PIN_SCL);
  Wire.begin();
  Wire.setClock(400000);

  Serial.print("# Inicializando ");
  Serial.print(NUM_SENSORES);
  Serial.println(" sensor(es)...");

  for (int i = 0; i < NUM_SENSORES; i++) {
    pinMode(XSHUT[i], OUTPUT);
    digitalWrite(XSHUT[i], LOW);
  }
  delay(50);

  for (int i = 0; i < NUM_SENSORES; i++) {
    digitalWrite(XSHUT[i], HIGH);
    delay(50);

    sensor[i].setTimeout(500);
    if (!sensor[i].init()) {
      Serial.print("Fallo al iniciar sensor ");
      Serial.println(i);
      while (1) { delay(1000); Serial.println("... detenido, revisa cableado"); }
    }
    sensor[i].setAddress(ADDR[i]);
    sensor[i].setSignalRateLimit(0.25);
    sensor[i].setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
    sensor[i].setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);

 
    sensor[i].setMeasurementTimingBudget(20000);

    
    sensor[i].startContinuous(0);  

    Serial.print("# Sensor "); Serial.print(i); Serial.println(" OK (continuo)");
  }

  Serial.println("# Todos OK. Barrido continuo iniciado.");


  motor.setMaxSpeed(1000);
  motor.setSpeed(VELOCIDAD);       
}

void loop() {
 
  motor.runSpeed();

  long pos = motor.currentPosition();
  if (pos >= PASOS_RANGO) { motor.setSpeed(-VELOCIDAD); }
  if (pos <= 0)           { motor.setSpeed(+VELOCIDAD); }

 
  for (int i = 0; i < NUM_SENSORES; i++) {
    if (datoListo(i)) {
      uint16_t d = leerRango(i);
      if (d == 0 || d > DIST_MAX_VALIDA) d = 0;   // 0 = sin retorno (la interfaz lo borra)

      float az = constrain(pos, 0, PASOS_RANGO) * (ANGULO_TOTAL / (float)PASOS_RANGO);

      Serial.print(az, 1);        Serial.print(',');
      Serial.print(ELEV[i], 1);   Serial.print(',');
      Serial.println(d);
    }
  }
}

Cómo funciona el firmware.

Inicialización. El setup() resuelve el problema de las direcciones I2C: apaga los cuatro sensores con XSHUT, los enciende uno por uno y le asigna a cada uno su dirección propia (0x30 a 0x33). Ya con eso, los cuatro conviven en el mismo bus sin pisarse. Además los deja en modo continuo (startContinuous), es decir, midiendo sin parar por su cuenta.

El barrido. Aquí está el truco del proyecto. La forma intuitiva sería: mover el motor, detenerlo, leer los sensores, repetir. Pero eso amarra la velocidad del sensor a la del motor, y el escaneo sale lento y con pocos puntos. La solución es desacoplarlos: en el loop() nada bloquea.

  • motor.runSpeed() gira el motor a velocidad constante, un paso a la vez, sin detenerse a esperar a nadie. Al llegar al límite del rango invierte el signo de la velocidad y regresa.
  • datoListo(i) pregunta a un sensor si ya tiene medición lista. Si no la tiene, el código sigue de largo; si la tiene, se lee al vuelo.

Así el motor gira a su ritmo y cada sensor entrega sus mediciones al suyo, sin que nadie frene a nadie. El dato que sale. Cuando un sensor entrega una lectura, se le pregunta al motor su posición en ese preciso instante y se convierte a grados de azimut. La elevación ya la conocemos: es fija por sensor, definida en el arreglo ELEV[]. Cada medición se envía por el puerto serie como tres números: azimut, elevación, distancia -> 45.0,12.5,830

Visualiza el mapa en tiempo real: nube de puntos

Leer números en el monitor serial no dice gran cosa; lo valioso es verlos ubicados en el espacio. Para eso construimos un visor web en 3D con Three.js, que recibe las mediciones y las dibuja en vivo como una nube de puntos que va reconstruyendo la forma del entorno.

Se conecta sin instalar nada. El visor es un único archivo HTML que se abre directamente en Chrome o Edge. Usa la Web Serial API, una función del navegador que permite hablar con el puerto USB de la placa: presionas Conectar, eliges el puerto COM de tu STM32 y el navegador empieza a recibir los datos al instante. Esto simplifica mucho el montaje, porque no hace falta ningún servidor intermedio ni instalar librerías: el STM32 escribe por serial y el navegador lee directo. La única condición es usar un navegador basado en Chromium, ya que Firefox y Safari aún no soportan Web Serial.

De tres números a un punto en el espacio. Por cada línea que llega, el visor convierte las coordenadas esféricas que envía el sensor a coordenadas cartesianas:

  • x = d * cos(elevación) * cos(azimut)
  • y = d * sin(elevación)
  • z = d * cos(elevación) * sin(azimut)

El sensor queda en el origen y cada medición se coloca como un punto flotando en el espacio, a la distancia y dirección que le corresponden. Al acumular miles de estos puntos, la forma de la habitación empieza a emerger sola.

Qué se ve en pantalla.

Cada detección se dibuja como un círculo cuyo color y tamaño dependen de la distancia: los objetos cercanos aparecen grandes y en tonos cálidos, y los lejanos, pequeños y en tonos fríos. Así la profundidad se lee de un vistazo, sin necesidad de rotar la escena. En el centro se modeló una torreta cilíndrica que representa el cabezal físico, con los cuatro emisores en sus ángulos de elevación reales; la torreta gira en tiempo real siguiendo el azimut que reporta el firmware, de modo que en pantalla ves exactamente hacia dónde está apuntando tu hardware en ese instante. Desde el centro del cilindro salen los rayos hacia cada punto detectado, lo que deja claro de dónde nace cada medición.

Persistencia. Un control permite definir cuántos segundos vive cada punto antes de desvanecerse. Con un valor bajo, el mapa reacciona al instante y los objetos que se retiran desaparecen enseguida, ideal para observar movimiento; con un valor alto, los puntos se acumulan y la nube se hace más densa, ideal para levantar el plano de un cuarto quieto. La escena se rota arrastrando el mouse y se acerca con la rueda.

Conclusiones

El VL53L0X demuestra que no necesitas un LIDAR de miles de pesos para darle “visión espacial” a tus proyectos. Con sensores ToF económicos, un motor a pasos y algo de ingenio, puedes reconstruir tu entorno en 3D y entender, de principio a fin, cómo se construye un mapa a partir de simples lecturas de distancia. Tanto si estás armando un robot móvil, un dron o un sistema de aviónica, este sensor es una excelente puerta de entrada al mapeo y la percepción espacial.
Para conseguir el VL53L0X y otros sensores de la más alta calidad, los podrás encontrar en el siguiente link: UNIT Electronics.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *