Caso práctico: Rover autoequilibrado con IMU en Nano 33 BLE

Caso práctico: Rover autoequilibrado con IMU en Nano 33 BLE — hero

Objetivo y caso de uso

Qué construirás: Un rover autoequilibrado utilizando un Arduino Nano 33 BLE Sense, un controlador TB6612FNG y un sensor AS5600 para la detección de posición.

Para qué sirve

  • Control de equilibrio en tiempo real para vehículos robóticos.
  • Aplicaciones en robótica educativa para enseñar principios de control y estabilidad.
  • Integración de sensores para mejorar la navegación autónoma en entornos complejos.
  • Prototipos de vehículos que requieren ajuste dinámico de posición.

Resultado esperado

  • Estabilidad del rover con un tiempo de respuesta de menos de 100 ms.
  • Latencia en la comunicación entre el IMU y el controlador de motor inferior a 50 ms.
  • Capacidad de mantener el equilibrio en superficies inclinadas de hasta 15 grados.
  • Medición de la posición angular con una precisión de ±1 grado utilizando el AS5600.

Público objetivo: Estudiantes y profesionales en robótica; Nivel: Avanzado

Arquitectura/flujo: Sensor IMU (LSM9DS1) -> Procesador (nRF52840) -> Controlador de motor (TB6612FNG) -> Actuadores (motores del rover).

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas (versiones probadas)

  • Windows 11 23H2, macOS 14.5 (Sonoma) o Ubuntu 22.04 LTS.
  • Git 2.44.0 o superior.
  • Python 3.11.x (probado con 3.11.9).
  • PlatformIO Core 6.1.15 (CLI). Nota: Usaremos PlatformIO para la placa Arduino Nano 33 BLE Sense (nRF52840, Mbed OS). No usaremos Arduino IDE/GUI.

Toolchain concreta (con versiones)

  • PlatformIO Core (pio) 6.1.15.
  • Plataforma: nordicnrf52 @ 10.3.1 (pinned).
  • Board ID: nano33ble (compatible con Arduino Nano 33 BLE y BLE Sense, MCU nRF52840).
  • Framework: arduino (Mbed OS para Nano 33 BLE Sense).
  • Paquete del compilador: toolchain-gccarmnoneeabi@1.90301.0 (GCC 9-2019-q4-major) fijado para reproducibilidad.
  • Librerías de proyecto (pinned):
  • Arduino_LSM9DS1@1.1.0 (IMU de Nano 33 BLE Sense Rev1).
  • AS5600@0.4.1 (librería de Rob Tillaart para encoder magnético).
  • ArduinoBLE@1.3.5 (opcional si luego quieres telemetría BLE; aquí la dejaremos como comentada).

Nota importante sobre IMU: Este caso práctico utiliza la IMU LSM9DS1 (Nano 33 BLE Sense Rev1). Si dispones de un Nano 33 BLE Sense Rev2 (LSM6DSOX), sustituye la librería por Arduino_LSM6DSOX y adapta las llamadas. Más adelante se indica la variante.

Materiales

  • 1 x Arduino Nano 33 BLE Sense (modelo con IMU LSM9DS1).
  • 1 x Driver de motor TB6612FNG (placa breakout doble canal A/B).
  • 1 x Sensor AS5600 (encoder magnético absoluto I2C, 12 bits, dirección 0x36).
  • 2 x Motores DC con caja reductora, 6–12 V, con ruedas (igualados).
  • 1 x Batería LiPo 2S (7.4 V) con interruptor.
  • 1 x Imán diametral para el AS5600 (6–8 mm), montado en eje de rueda o eje intermedio.
  • Cables de puente macho-macho y macho-hembra.
  • Porta-baterías o cinta de fijación.
  • Soporte temporal para pruebas (p. ej., varillas o un marco que impida caídas mientras ajustas el control).
  • 1 x Convertidor buck si necesitas derivar 5 V auxiliares (no requerido para la lógica: el Nano 33 BLE Sense funciona a 3.3 V y se alimenta por USB durante el desarrollo).
  • Elementos de seguridad: gafas, superficie acolchada para evitar daños al robot durante el ajuste.

Preparación y conexión

Consideraciones de alimentación

  • TB6612FNG:
  • VM (potencia motores): 6–12 V desde la batería (recomendado 2S LiPo).
  • VCC (lógica): 3.3 V desde el pin 3V3 del Nano 33 BLE Sense.
  • GND común: une GND del driver, GND del Arduino y GND de la batería.

  • Arduino Nano 33 BLE Sense: durante el desarrollo, aliméntalo via USB. En modo autónomo, puedes alimentar por VIN (5–21 V) si la batería lo permite, o usar un regulador DC/DC a 5 V/USB.

  • AS5600: se alimenta con 3.3 V. Asegúrate de que la placa del AS5600 soporte 3.3 V en VCC y que I2C sea 3.3 V.

Tabla de conexiones

La siguiente tabla asigna pines del Nano 33 BLE Sense a TB6612FNG y AS5600. Las líneas PWM deben ser pines con soporte PWM en el Nano 33 BLE.

Función Componente Pin en TB6612FNG / AS5600 Pin en Nano 33 BLE Sense Notas
Alimentación lógica TB6612FNG VCC 3V3 2.7–5.5 V; usamos 3.3 V del Nano
Tierra TB6612FNG GND GND GND común con Arduino y batería
Alimentación motores TB6612FNG VM Batería (+) 6–12 V (p. ej., 7.4 V LiPo)
Standby TB6612FNG STBY D8 Ponlo en HIGH para habilitar drivers
Motor A sentido 1 TB6612FNG AIN1 D4
Motor A sentido 2 TB6612FNG AIN2 D5
Motor A PWM TB6612FNG PWMA D9 PWM
Motor B sentido 1 TB6612FNG BIN1 D6
Motor B sentido 2 TB6612FNG BIN2 D7
Motor B PWM TB6612FNG PWMB D10 PWM
Motor A TB6612FNG AO1/AO2 Motor izquierdo Consolida sentido luego en software
Motor B TB6612FNG BO1/BO2 Motor derecho
I2C SDA AS5600 SDA SDA (A4) 3.3 V I2C
I2C SCL AS5600 SCL SCL (A5) 3.3 V I2C
Alimentación AS5600 VCC 3V3
Tierra AS5600 GND GND

Notas:
– Comprueba el mapping físico de SDA/SCL en tu placa; en Nano 33 BLE Sense suelen estar serigrafiados como SDA/SCL y corresponden a A4/A5.
– Si inviertes los cables del motor, solo tendrás que corregir “signos” en software (o viceversa).
– Montaje AS5600: el imán debe estar centrado con el chip, a ~2 mm de distancia. Fija mecánicamente para evitar variaciones.

Orientación del IMU

Para este proyecto, asume:
– La placa se monta verticalmente formando el “cuerpo” del rover.
– El eje de pitch (inclinación hacia adelante/atrás) es el eje X del IMU.
– USB mirando hacia la izquierda del rover y el conector de pines hacia arriba.
Si tu montaje difiere, ajusta el cálculo de ángulos (mapear ejes o cambiar signos).

Código completo

A continuación, el firmware en C++ (framework Arduino) que:
– Lee IMU (LSM9DS1) y filtra el ángulo de pitch con un filtro complementario.
– Lee ángulo del AS5600 para estimar velocidad de rueda.
– Ejecuta un control PID de equilibrio con término de velocidad de rueda como “damping” adicional.
– Comanda los motores vía TB6612FNG.

Archivo: src/main.cpp

#include <Arduino.h>
#include <Wire.h>
#include <AS5600.h>            // Rob Tillaart AS5600 library
#include <Arduino_LSM9DS1.h>   // IMU Arduino Nano 33 BLE Sense (Rev1)

// -------------------- Configuración de pines TB6612FNG --------------------
constexpr uint8_t PIN_STBY = 8;
constexpr uint8_t PIN_AIN1 = 4;
constexpr uint8_t PIN_AIN2 = 5;
constexpr uint8_t PIN_PWMA = 9;    // PWM
constexpr uint8_t PIN_BIN1 = 6;
constexpr uint8_t PIN_BIN2 = 7;
constexpr uint8_t PIN_PWMB = 10;   // PWM

// -------------------- Parámetros de control --------------------
static float loop_hz = 500.0f;     // Frecuencia de control (Hz)
static float dt = 1.0f / 500.0f;
static const float alpha = 0.98f;  // Filtro complementario

// PID del ángulo (pitch)
volatile float Kp = 24.0f;
volatile float Ki = 2.0f;
volatile float Kd = 0.35f;

// “Damping” adicional con velocidad de rueda estimada (AS5600)
volatile float Kv = 0.05f;

// Límites de salida
static const int16_t PWM_MAX = 255;    // Máx. 8 bits (PlatformIO mapea a 0-255)
static const float ANGLE_LIMIT_DEG = 20.0f; // Apagar si sobrepasa

// -------------------- Estado --------------------
AS5600 as5600;            // I2C addr 0x36 por defecto
float as5600_prev_angle = 0.0f;  // [rad], absoluto 0..2π
unsigned long as5600_prev_ms = 0;

// Offset y escala IMU
float gyro_bias_x = 0.0f, gyro_bias_y = 0.0f, gyro_bias_z = 0.0f; // [dps]
float pitch_deg = 0.0f;                 // Ángulo estimado [deg]
float pitch_rate_dps = 0.0f;            // Velocidad angular [deg/s]

// PID internals
float err_int = 0.0f;
float err_prev = 0.0f;

// Dirección de motores (puede ajustarse según cableado)
int motor_sign_left = +1;
int motor_sign_right = +1;

// -------------------- Utilidades TB6612FNG --------------------
void tb6612_init() {
  pinMode(PIN_STBY, OUTPUT);
  pinMode(PIN_AIN1, OUTPUT);
  pinMode(PIN_AIN2, OUTPUT);
  pinMode(PIN_PWMA, OUTPUT);
  pinMode(PIN_BIN1, OUTPUT);
  pinMode(PIN_BIN2, OUTPUT);
  pinMode(PIN_PWMB, OUTPUT);
  digitalWrite(PIN_STBY, HIGH); // Habilitar
}

void setMotorRaw(bool chA, int16_t pwm) {
  // pwm en rango [-255, 255], signo = sentido
  uint8_t pinIn1 = chA ? PIN_AIN1 : PIN_BIN1;
  uint8_t pinIn2 = chA ? PIN_AIN2 : PIN_BIN2;
  uint8_t pinPWM = chA ? PIN_PWMA : PIN_PWMB;

  int16_t val = constrain(pwm, -PWM_MAX, PWM_MAX);
  if (val > 0) {
    digitalWrite(pinIn1, HIGH);
    digitalWrite(pinIn2, LOW);
    analogWrite(pinPWM, val);
  } else if (val < 0) {
    digitalWrite(pinIn1, LOW);
    digitalWrite(pinIn2, HIGH);
    analogWrite(pinPWM, -val);
  } else {
    // freno libre: IN1=LOW, IN2=LOW (coast) o freno activo IN1=HIGH, IN2=HIGH
    digitalWrite(pinIn1, LOW);
    digitalWrite(pinIn2, LOW);
    analogWrite(pinPWM, 0);
  }
}

void setMotors(int16_t left, int16_t right) {
  setMotorRaw(true,  motor_sign_left * left);
  setMotorRaw(false, motor_sign_right * right);
}

// -------------------- IMU --------------------
bool imu_init_and_calibrate() {
  if (!IMU.begin()) {
    return false;
  }
  delay(50);

  // Intentar fijar tasa de muestreo razonable si está disponible
  // Nota: Arduino_LSM9DS1 no expone fácilmente ODR desde el wrapper Arduino;
  // trabajamos con lectura en bucle fijo y dt estable.
  // Calibración de offset giroscópico:
  const int N = 2000; // ~4 s @500 Hz
  gyro_bias_x = gyro_bias_y = gyro_bias_z = 0.0f;
  int count = 0;

  while (count < N) {
    float gx, gy, gz, ax, ay, az;
    bool ok = IMU.gyroscopeAvailable() && IMU.accelerationAvailable();
    if (ok) {
      IMU.readGyroscope(gx, gy, gz);        // dps
      IMU.readAcceleration(ax, ay, az);     // g
      gyro_bias_x += gx;
      gyro_bias_y += gy;
      gyro_bias_z += gz;
      count++;
    }
    delayMicroseconds(1000); // ~1 kHz loop en calibración
  }
  gyro_bias_x /= N; gyro_bias_y /= N; gyro_bias_z /= N;

  // Inicializa pitch con acelerómetro (suponiendo montaje vertical)
  float ax, ay, az;
  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(ax, ay, az);
    // Pitch ≈ atan2(-ax, sqrt(ay^2 + az^2)) en grados (depende de orientación)
    float pitch0 = atan2f(-ax, sqrtf(ay*ay + az*az)) * 180.0f / PI;
    pitch_deg = pitch0;
  }
  return true;
}

// Lee sensores y actualiza pitch
void imu_step() {
  float gx, gy, gz, ax, ay, az;
  if (IMU.gyroscopeAvailable()) {
    IMU.readGyroscope(gx, gy, gz);
    gx -= gyro_bias_x;
    gy -= gyro_bias_y;
    gz -= gyro_bias_z;
  } else {
    gx = gy = gz = 0.0f;
  }
  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(ax, ay, az);
  } else {
    ax = ay = 0.0f; az = 1.0f;
  }

  // Velocidad de pitch (deg/s): asumir eje de pitch = X
  pitch_rate_dps = gx;

  // Ángulo de pitch desde acelerómetro (grados)
  float pitch_acc_deg = atan2f(-ax, sqrtf(ay*ay + az*az)) * 180.0f / PI;

  // Filtro complementario
  pitch_deg = alpha * (pitch_deg + pitch_rate_dps * dt) + (1.0f - alpha) * pitch_acc_deg;
}

// -------------------- AS5600 --------------------
bool as5600_init() {
  Wire.begin();
  // Librería Rob Tillaart: setup básico
  if (!as5600.begin(AS5600_DEFAULT_ADDRESS)) {
    return false;
  }
  // Modo de potencia normal
  as5600.setPowerMode(AS5600_NORMAL);
  // Guardar ángulo inicial como referencia
  as5600_prev_angle = as5600.getRadians();
  as5600_prev_ms = millis();
  return true;
}

float as5600_wheel_speed_rads() {
  // Diferencia de ángulo con “unwrap” simple
  float angle = as5600.getRadians(); // 0..2π
  float da = angle - as5600_prev_angle;
  // unwrapping
  if (da > PI)  da -= 2.0f * PI;
  if (da < -PI) da += 2.0f * PI;
  unsigned long now = millis();
  float dt_s = (now - as5600_prev_ms) / 1000.0f;
  if (dt_s <= 0) dt_s = 1e-3f;

  float omega = da / dt_s; // rad/s

  as5600_prev_angle = angle;
  as5600_prev_ms = now;
  return omega;
}

// -------------------- Control --------------------
void safety_stop() {
  setMotors(0, 0);
  // Mantener STBY en alto, pero sin PWM
}

int16_t control_step(float pitch_setpoint_deg, float wheel_vel_rads) {
  float err = pitch_setpoint_deg - pitch_deg;
  err_int += err * dt;

  // Derivada sobre la medida (para reducir ruido)
  float err_der = -(pitch_rate_dps);

  // PID del ángulo
  float u = Kp * err + Ki * err_int + Kd * err_der;

  // Damping con velocidad de rueda (reduce oscilaciones)
  u -= Kv * wheel_vel_rads;

  // Anti-windup simple: limitar integral si saturamos
  float u_sat = constrain(u, -PWM_MAX, PWM_MAX);
  if (u != u_sat) {
    // retrocálculo proporcional
    err_int -= 0.1f * (u - u_sat) / max(1.0f, Ki);
  }
  return (int16_t)u_sat;
}

// -------------------- Setup y loop --------------------
void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 2000) { /* wait up to 2s */ }

  tb6612_init();
  setMotors(0, 0);

  if (!as5600_init()) {
    Serial.println("ERROR: AS5600 no encontrado (I2C 0x36). Revisa SDA/SCL/3V3/GND.");
    // seguimos, pero sin damping por rueda
  } else {
    Serial.println("AS5600 OK");
  }

  if (!imu_init_and_calibrate()) {
    Serial.println("ERROR: IMU no inicializada. Revisa Arduino_LSM9DS1 y alimentación.");
    while (true) { delay(1000); }
  }
  Serial.println("IMU calibrada. Iniciando control...");

  // Configurar la frecuencia de control
  dt = 1.0f / loop_hz;
}

void loop() {
  static unsigned long last_us = micros();
  unsigned long now_us = micros();
  float elapsed = (now_us - last_us) / 1000000.0f;
  if (elapsed < dt) {
    // esperar el siguiente tick
    delayMicroseconds(100);
    return;
  }
  last_us = now_us;

  // Leer sensores
  imu_step();
  float wheel_vel = as5600_prev_ms ? as5600_wheel_speed_rads() : 0.0f;

  // Seguridad por ángulo excesivo
  if (fabs(pitch_deg) > ANGLE_LIMIT_DEG) {
    safety_stop();
    // Indicar estado por serial
    static uint32_t t0 = 0;
    if (millis() - t0 > 200) {
      Serial.println("Fuera de rango. Coloca el rover vertical y reinicia.");
      t0 = millis();
    }
    return;
  }

  // Setpoint de pitch
  float pitch_sp = 0.0f; // vertical

  // Control
  int16_t u = control_step(pitch_sp, wheel_vel);

  // Reparto simétrico a ambos motores
  setMotors(u, u);

  // Telemetría básica
  static uint32_t last_print = 0;
  if (millis() - last_print > 50) { // 20 Hz
    Serial.print("pitch=");
    Serial.print(pitch_deg, 2);
    Serial.print(" dps=");
    Serial.print(pitch_rate_dps, 1);
    Serial.print(" wheel_radps=");
    Serial.print(wheel_vel, 2);
    Serial.print(" u=");
    Serial.println(u);
    last_print = millis();
  }
}

Explicación breve de partes clave

  • Filtro complementario (alpha=0.98): combina giroscopio (integración rápida) y acelerómetro (referencia de gravedad) para estimar pitch robusto y con baja deriva.
  • Calibración de giroscopio: promedia lecturas en reposo para eliminar bias; mejora notablemente el control.
  • Control PID: salida en unidades de PWM. El término derivativo usa la velocidad de pitch medida (sobre la medida) para mitigar ruido. El término Ki integra lentamente para corregir pequeñas desviaciones.
  • Damping por velocidad de rueda (Kv): reduce el “péndulo” aprovechando el AS5600; si no hay AS5600 disponible/funcional, Kv puede ponerse a 0.
  • Safety: si |pitch| > 20°, se desactiva PWM para evitar caídas violentas.
  • setMotors: encapsula la lógica de dirección y PWM para TB6612FNG por canal.

Variante para Nano 33 BLE Sense Rev2 (LSM6DSOX)

  • Sustituye en platformio.ini la librería Arduino_LSM9DS1 por Arduino_LSM6DSOX.
  • Cambia include y lectura de IMU:
  • #include <Arduino_LSM6DSOX.h>
  • Reemplaza IMU por IMU (misma API: IMU.begin(), IMU.gyroscopeAvailable(), IMU.accelerationAvailable(), etc.). La API Arduino es casi equivalente.

Compilación, carga y ejecución

Estructura mínima del proyecto

  • platformio.ini
  • src/main.cpp

Archivo: platformio.ini

[env:nano33ble]
platform = nordicnrf52@10.3.1
board = nano33ble
framework = arduino
platform_packages =
  toolchain-gccarmnoneeabi@1.90301.0
lib_deps =
  arduino-libraries/Arduino_LSM9DS1@1.1.0
  robtillaart/AS5600@0.4.1
;  arduino-libraries/ArduinoBLE@1.3.5
monitor_speed = 115200
monitor_filters = time, default
build_flags =
  -DCORE_DEBUG_LEVEL=0

Instalación de PlatformIO Core (si no lo tienes)

  • Windows/macOS/Linux con Python 3.11.x:
  • pipx recomendado:
    • pipx install platformio
  • o con pip:
    • python -m pip install --user platformio

Verifica la versión:

pio --version

Debe mostrar 6.1.15.

Inicializar proyecto y dependencias

  1. Crea directorio:
    mkdir imu-self-balancing-rover && cd imu-self-balancing-rover
  2. Inicializa proyecto:
    pio project init --board nano33ble
  3. Sustituye platformio.ini por el mostrado arriba.
  4. Copia el código a src/main.cpp.
  5. Instala dependencias explícitamente (opcional; PlatformIO las resuelve en el primer build):
    pio pkg install --library "arduino-libraries/Arduino_LSM9DS1@1.1.0"
    pio pkg install --library "robtillaart/AS5600@0.4.1"

Compilación

pio run

Carga (upload)

  • Conecta el Nano 33 BLE Sense por USB. En Windows aparecerá un puerto COM; en macOS/Linux /dev/ttyACM0 o similar.
  • En Linux, si recibes error de permisos en /dev/ttyACM0:
  • sudo usermod -aG dialout $USER
  • Cierra sesión y vuelve a entrar.
  • Carga el firmware:
    pio run -t upload --upload-port /dev/ttyACM0
    Sustituye el puerto si es necesario (Windows: COM7, p. ej.).

Monitor serie

pio device monitor -b 115200 --eol LF

Verás líneas tipo:

AS5600 OK
IMU calibrada. Iniciando control...
[12:34:56.789] pitch=0.45 dps=0.2 wheel_radps=0.01 u=5

Validación paso a paso

1) Validar IMU en reposo

  • Coloca el rover en un soporte vertical, quieto.
  • Conecta el monitor serie.
  • Observa:
  • “IMU calibrada. Iniciando control…”
  • pitch ≈ 0 ± 1.5° en reposo.
  • dps cercano a 0 (|dps| < 0.5).
  • Si no, revisa calibración (repetir reset con el rover completamente quieto).

2) Validar AS5600

  • Gira lentamente la rueda con el imán.
  • En telemetría, wheel_radps debe ser distinto de 0 y cambiar de signo según sentido.
  • Gira continuamente: se esperan valores típicos entre ±5 rad/s para ruedas lentas, más si giras rápido.

3) Validar drivers de motor (sin control)

  • Temporalmente, comenta la lógica de safety y control y fija un PWM bajo para probar sentidos (por ejemplo, en setup tras tb6612_init()):
  • setMotors(+60, +60); delay(1000); setMotors(-60, -60); delay(1000); setMotors(0,0);
  • Comprueba que ambos motores giran y que con el mismo signo giran hacia adelante. Si no, invierte motor_sign_left/right o intercambia cables.

4) Validación del bucle de control en soporte

  • Habilita el código original.
  • Sostén el rover vertical sobre un soporte que permita pequeñas oscilaciones sin caídas.
  • Al soltar suavemente, debe intentar mantenerse, aplicando correcciones (escucharás el zumbido PWM).
  • Observa telemetría: cuando se inclina hacia adelante (pitch positivo, por ejemplo), la salida u debe ir en el sentido que empuja hacia atrás para recuperar.

5) Ajuste inicial de PID

  • Si oscila poco y cae, aumenta Kp en pasos de +2 hasta que “reaccione” con rapidez pero sin excederse.
  • Si ves oscilación sostenida, aumenta Kd de 0.35 a 0.5–0.8 en pequeños pasos.
  • Ki modera el error estático; sube lentamente (2.0 → 3.0 → 4.0). Si ves “deriva” de salida o overshoot lento, reduce Ki.
  • Kv (damping por rueda): si el AS5600 reporta velocidad confiable, prueba subir Kv a 0.1–0.2 para amortiguar.

6) Validación en suelo

  • Usa una superficie lisa y despejada.
  • Enciende con el rover sujetado en vertical; suelta suavemente.
  • Debe mantenerse unos segundos; afina PID para incrementar tiempo de equilibrio.
  • Métrica: tiempo en equilibrio (>10 s), amplitud de oscilación (<±5°), deriva longitudinal aceptable.

7) Pruebas de seguridad

  • Inclina deliberadamente más de 20°. Debe cortar motores (“Fuera de rango…”).
  • Verifica que al volver a vertical y reiniciar, retoma el control.

Troubleshooting

1) No compila por librerías IMU
– Síntoma: error “Arduino_LSM9DS1.h not found”.
– Causa: librería no instalada o tienes un Nano 33 BLE Sense Rev2.
– Solución:
– Instala la librería: pio pkg install --library "arduino-libraries/Arduino_LSM9DS1@1.1.0".
– Si tu placa es Rev2, usa Arduino_LSM6DSOX y ajusta el include y lib_deps.

2) Error de puerto serie en Linux (permiso denegado)
– Síntoma: “Permission denied: /dev/ttyACM0”.
– Solución:
sudo usermod -aG dialout $USER y reinicia sesión.
– Verifica pertenencia al grupo: groups.

3) Sin lectura de AS5600 (siempre 0)
– Síntoma: “ERROR: AS5600 no encontrado”.
– Causas:
– SDA/SCL intercambiados o sin pull-ups (las placas Nano 33 BLE Sense ya llevan pull-ups).
– VCC del AS5600 a 5 V en una placa que no lo soporta.
– Imán mal centrado o demasiado lejos.
– Soluciones:
– Verifica conexiones según la tabla.
– Alimenta a 3.3 V, comparte GND.
– Ajusta la distancia del imán (1.5–3 mm) y céntralo.
– Prueba un escáner I2C para ver 0x36 presente.

4) Motores no giran
– Síntoma: PWM u ≠ 0 en serie, pero motores parados.
– Causas:
– STBY en LOW; VM sin tensión; GND no común.
– PWM en pines sin soporte o soldaduras flojas.
– Soluciones:
– Comprueba D8 = HIGH (STBY).
– Mide VM con multímetro (6–12 V).
– Verifica que D9 y D10 son PWM y tienen continuidad.

5) Robo “tiembla” y cae
– Síntoma: vibración fuerte y pérdida de equilibrio.
– Causas:
– Kp muy alto, Kd muy bajo, fricción o backlash mecánico.
– Soluciones:
– Reduce Kp, aumenta Kd.
– Incrementa Kv si AS5600 está bien montado.
– Revisa holguras mecánicas y aprieta sujecciones.

6) Deriva constante hacia adelante/atrás
– Síntoma: mantiene equilibrio pero se desplaza.
– Causas:
– Bias residual del giroscopio; offset de pitch no centrado; ruedas con radios distintos.
– Soluciones:
– Repite calibración: enciende sin tocar el robot, sobre un soporte estable.
– Añade corrección de offset de pitch (sumar un pequeño bias al setpoint, p. ej., +0.3°).
– Igualar ruedas y presión.

7) Lecturas IMU ruidosas
– Síntoma: pitch “salta” o telemetría errática.
– Causas:
– Vibraciones de motor, cables cercanos a IMU, dt inestable.
– Soluciones:
– Añade espuma antivibración bajo la placa.
– Asegura dt constante (este código ya fuerza un intervalo).
– Baja loop_hz a 250 Hz y prueba alpha=0.97.

8) Se resetea al acelerar
– Síntoma: reinicios cuando la demanda de motor sube.
– Causas:
– Caída de tensión; ruido EMI; masa mal distribuida.
– Soluciones:
– Añade condensadores en VM (100 µF + 1 µF cerámico).
– Cables de potencia trenzados y separados de señales.
– Usa un buck dedicado para VIN del Arduino si alimentas todo de la misma batería.

Mejoras/variantes

  • Segunda rueda con AS5600: añade otro AS5600 para estimar velocidad diferencial y mejorar control de trayectoria.
  • Telemetría BLE: habilita ArduinoBLE para monitorizar pitch, PWM, tuning Kp/Ki/Kd desde una app móvil.
  • Estimación avanzada de actitud: Madgwick/Mahony para mejorar frente a vibraciones, o un Kalman discreto.
  • Control cascada completo:
  • Lazo interno: pitch (rápido).
  • Lazo externo: velocidad/posición (lento) con encoders (AS5600 x2).
  • Modo “arranque asistido”: un algoritmo que detecte verticalidad, aplica rampas suaves de PWM.
  • Limitación de corriente: mide corriente del motor (sensor ACS o shunt + ADC) y modula PWM para proteger.
  • Modo “seguimiento”: añade un sensor de distancia (ToF o ultrasonidos) y controla desplazamiento manteniendo equilibrio.
  • Ahorro energético: reduce PWM cuando el robot está estable; duerme IMU si queda inclinado y sin intento de recuperación.

Checklist de verificación

  • [ ] Sistema operativo y Python 3.11.x instalados.
  • [ ] PlatformIO Core 6.1.15 verificado con pio –version.
  • [ ] Proyecto creado con board nano33ble y platform = nordicnrf52@10.3.1.
  • [ ] platformio.ini con toolchain-gccarmnoneeabi@1.90301.0 y lib_deps fijados.
  • [ ] Cableado conforme a la tabla (STBY→D8, PWMA→D9, PWMB→D10, etc.), GND común.
  • [ ] AS5600 alimentado a 3.3 V, SDA→A4, SCL→A5, imán centrado.
  • [ ] Compila sin errores: pio run.
  • [ ] Carga correcta: pio run -t upload –upload-port .
  • [ ] Monitor serie operativo a 115200 bps.
  • [ ] IMU calibrada con el robot quieto al encender.
  • [ ] Lecturas de pitch estables en reposo (±1.5°).
  • [ ] Motores probados en ambos sentidos con PWM bajo.
  • [ ] Control mantiene el equilibrio en soporte y luego en suelo.
  • [ ] Ajuste Kp/Ki/Kd/Kv documentado y valores anotados finales.
  • [ ] Prueba de seguridad: corte por |pitch| > 20° funciona.

Apéndice: comandos útiles de PlatformIO

  • Limpiar build:
    pio run -t clean
  • Reconocer puertos serie:
    pio device list
  • Monitor con timestamp y reset a DTR desactivado:
    pio device monitor -b 115200 --eol LF --rts=0 --dtr=0

Notas finales de coherencia hardware/software

  • Este caso práctico está diseñado específicamente para:
  • Arduino Nano 33 BLE Sense (nRF52840, Mbed; usamos IMU LSM9DS1).
  • Driver TB6612FNG para dos motores DC.
  • Encoder magnético AS5600 por I2C para estimar velocidad de una rueda.
  • Toda la toolchain, comandos y librerías están alineados con esa combinación.
  • Si migras a Nano 33 BLE Sense Rev2, ajusta únicamente la librería IMU y mantén el resto igual.

Con esto dispones de un rover autoequilibrado funcional, reproducible con versiones fijadas y preparado para iterar en control, sensorización y robustez mecánica.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es la versión mínima de Git requerida para el proyecto?




Pregunta 2: ¿Qué sistema operativo NO es compatible según los requisitos?




Pregunta 3: ¿Qué herramienta se utilizará para programar la placa Arduino?




Pregunta 4: ¿Cuál es la versión de PlatformIO Core que se utilizará?




Pregunta 5: ¿Qué modelo de Arduino se menciona en el artículo?




Pregunta 6: ¿Qué librería se debe usar para la IMU si se tiene un Nano 33 BLE Sense Rev2?




Pregunta 7: ¿Cuál es la dirección I2C del sensor AS5600?




Pregunta 8: ¿Qué tipo de batería se requiere para el proyecto?




Pregunta 9: ¿Cuál es la función del driver de motor TB6612FNG en el proyecto?




Pregunta 10: ¿Qué tipo de motores se utilizan en el proyecto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio