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:
pipxrecomendado: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
- Crea directorio:
mkdir imu-self-balancing-rover && cd imu-self-balancing-rover - Inicializa proyecto:
pio project init --board nano33ble - Sustituye platformio.ini por el mostrado arriba.
- Copia el código a src/main.cpp.
- 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
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.




