You dont have javascript enabled! Please enable it!

Caso práctico: Control PID de motor DC AS5600 + TB6612 ESP32

Caso práctico: Control PID de motor DC AS5600 + TB6612 ESP32 — hero

Objetivo y caso de uso

Qué construirás: Un sistema de control de velocidad PID para un motor DC utilizando un ESP32 DevKitC, un controlador TB6612FNG y un sensor AS5600 para la retroalimentación de posición.

Para qué sirve

  • Control preciso de la velocidad de un motor DC en aplicaciones robóticas.
  • Regulación de la posición en sistemas de automatización industrial.
  • Implementación de sistemas de control en vehículos eléctricos pequeños.
  • Optimización de procesos en impresoras 3D para mantener la velocidad constante.

Resultado esperado

  • Latencia de control inferior a 50 ms en la respuesta del motor.
  • Estabilidad del sistema con un error de seguimiento menor al 5% en condiciones de carga variable.
  • Capacidad de manejar hasta 2 A de corriente sin sobrecalentamiento en el TB6612FNG.
  • Frecuencia de actualización de datos del sensor AS5600 de 100 Hz para un control preciso.

Público objetivo: Ingenieros y desarrolladores de sistemas embebidos; Nivel: Avanzado

Arquitectura/flujo: Sensor AS5600 → ESP32 DevKitC → Controlador TB6612FNG → Motor DC

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas

  • Windows 11 23H2 / Ubuntu 22.04.4 LTS / macOS 14 (Sonoma). Se recomienda 64 bits.
  • Python 3.11.x (solo si usas CLI de PlatformIO o scripts auxiliares).
  • Git 2.44+ (opcional, para versionado del proyecto).
  • Drivers USB del ESP32 DevKitC (según el chip USB-serie del módulo):
  • CP210x (Silicon Labs) v10.1.10 (Windows) / integrado en macOS.
  • CH340 (WCH) v3.6 (Windows) si tu DevKitC fuese variante con CH34x.

Toolchain exacta (PlatformIO)

  • PlatformIO Core: 6.1.16
  • VSCode: 1.95.x con extensión PlatformIO IDE 3.3.x (recomendado para entorno gráfico).
  • Plataforma ESP32 en PlatformIO:
  • platform-espressif32: 6.5.0
  • framework-arduinoespressif32: 3.20014.0 (Arduino-ESP32 2.0.14)
  • toolchain-xtensa32: 2.80400.0 (xtensa-esp32-elf-gcc 8.4.0)
  • tool-esptoolpy: 1.40501.0 (esptool.py 4.5.1)

Verificación rápida de versiones:
– PlatformIO Core:
– pio –version → Debe mostrar 6.1.16
– Paquetes (una vez inicializado el proyecto):
– pio pkg list -e esp32dev → Debe listar versiones fijadas arriba

Nota: fijaremos estas versiones explícitamente en platformio.ini para máxima reproducibilidad.

Materiales

  • 1x ESP32 DevKitC (módulo ESP32-WROOM-32).
  • 1x Driver dual H-Bridge TB6612FNG (usaremos el canal A).
  • 1x Encoder magnético AS5600 (módulo I2C, dirección 0x36).
  • 1x Motor DC con imán en el eje para el AS5600.
  • 1x Fuente de alimentación para motor: 6–12 V DC, 2 A (según el motor).
  • Jumpers Dupont macho-macho.
  • 2x Resistencias de 4.7 kΩ (pull-ups I2C a 3.3 V).
  • Protoboard (opcional, recomendado).
  • 1x Cable USB micro/USB-C (según tu DevKitC).
  • Cables para conectar el motor a AO1/AO2 del TB6612FNG.

Observaciones:
– Todo el control de velocidad (pid-dc-motor-encoder-velocity) se implementa con ESP32 DevKitC + TB6612FNG + AS5600; no se requieren otros sensores.
– Mantén la masa común: GND de ESP32, TB6612FNG y AS5600 deben estar unidos.

Preparación y conexión

Recomendaciones generales

  • AS5600 en modo I2C a 3.3 V. No mezclar 5 V en el bus I2C del ESP32.
  • El TB6612FNG requiere dos alimentaciones:
  • VM: alimentación del motor (6–12 V típico).
  • VCC: lógica del driver (3.3 V del ESP32).
  • Conecta STBY del TB6612FNG a un GPIO del ESP32 (habilitaremos/inhabilitaremos el puente H por software).
  • PWM a 20 kHz mediante LEDC del ESP32 para minimizar ruido audible.
  • SDA/SCL con pull-ups a 3.3 V de 4.7 kΩ si tu módulo AS5600 no los incluye.

Tabla de conexiones

Componente Pin Conectar a ESP32 DevKitC GPIO/Alimentación Notas
TB6612FNG VM Fuente motor + 6–12 V según motor
TB6612FNG VCC 3.3 V 3V3 Lógica a 3.3 V
TB6612FNG GND GND GND Masa común con ESP32 y AS5600
TB6612FNG STBY GPIO33 GPIO33 Alto para habilitar
TB6612FNG AIN1 GPIO26 GPIO26 Dirección A
TB6612FNG AIN2 GPIO27 GPIO27 Dirección A
TB6612FNG PWMA GPIO25 GPIO25 (LEDC) PWM velocidad A
TB6612FNG AO1 Terminal motor 1 Salida a motor
TB6612FNG AO2 Terminal motor 2 Salida a motor
AS5600 (I2C) VCC 3.3 V 3V3 No usar 5 V
AS5600 (I2C) GND GND GND Masa común
AS5600 (I2C) SDA SDA GPIO21 Pull-up 4.7 kΩ a 3.3 V si hace falta
AS5600 (I2C) SCL SCL GPIO22 Pull-up 4.7 kΩ a 3.3 V si hace falta

Notas adicionales:
– Si tu módulo AS5600 incluye resistencias pull-up integradas, no añadiras externas.
– Asegúrate de polaridad correcta en VM y VCC. No mezclar VM (motor) con 3.3 V (lógica).

Código completo

A continuación se presenta un proyecto funcional con:
– platformio.ini con versiones exactas de la toolchain.
– src/main.cpp con un bucle de control de velocidad a 1 kHz usando FreeRTOS, lectura del AS5600 por I2C, PID de velocidad y modulación PWM con LEDC hacia el TB6612FNG.
– Interfaz serie para ajuste online de setpoint y ganancias PID.

platformio.ini

; Proyecto PlatformIO para ESP32 DevKitC + TB6612FNG + AS5600
; Toolchain EXACTA fijada para reproducibilidad

[env:esp32dev]
platform = espressif32@6.5.0
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 460800
monitor_filters = time

; Fijamos paquetes exactos:
platform_packages =
  tool-esptoolpy@1.40501.0
  toolchain-xtensa32@2.80400.0
  framework-arduinoespressif32@3.20014.0

build_flags =
  -DCORE_DEBUG_LEVEL=1
  -DARDUINO_USB_CDC_ON_BOOT=0

; Puerto serie (opcional; en Windows puede ser COMx)
; upload_port = /dev/ttyUSB0
; monitor_port = /dev/ttyUSB0

src/main.cpp

#include <Arduino.h>
#include <Wire.h>

// ===================== Hardware mapping (ESP32 DevKitC + TB6612FNG + AS5600) =====================
static constexpr uint8_t PIN_TB_STBY = 33;
static constexpr uint8_t PIN_TB_PWMA = 25; // LEDC PWM
static constexpr uint8_t PIN_TB_AIN1 = 26;
static constexpr uint8_t PIN_TB_AIN2 = 27;

static constexpr uint8_t PIN_I2C_SDA = 21;
static constexpr uint8_t PIN_I2C_SCL = 22;

static constexpr uint8_t AS5600_ADDR = 0x36;
static constexpr uint8_t AS5600_REG_ANGLE_H = 0x0E; // High/Low bytes 12-bit angle
static constexpr uint8_t AS5600_REG_ANGLE_L = 0x0F;
static constexpr uint8_t AS5600_REG_STATUS  = 0x0B; // Magnet detection

// ===================== PWM (LEDC) =====================
static constexpr uint32_t PWM_FREQ_HZ = 20000; // 20 kHz
static constexpr uint8_t  PWM_CHANNEL = 0;
static constexpr uint8_t  PWM_TIMER_BITS = 12; // duty 0..4095
static constexpr uint16_t PWM_MAX_DUTY = (1u << PWM_TIMER_BITS) - 1;

// ===================== Control rates =====================
static constexpr uint32_t CTRL_HZ = 1000;     // 1 kHz control loop
static constexpr float    CTRL_DT = 1.0f / CTRL_HZ;

// ===================== PID =====================
struct PID {
  float kp{0.8f};
  float ki{25.0f};
  float kd{0.000f};
  float i{0.0f};
  float prevErr{0.0f};
  float outMin{-1.0f};
  float outMax{+1.0f};
  float awTau{0.1f}; // anti-windup back-calculation factor
} pid;

// Setpoint en RPM (interfaz usuario). Internamente trabajaremos en rad/s
volatile float g_setpoint_rpm = 180.0f;

// Filtro simple de velocidad (exponencial)
float vel_alpha = 0.2f; // 0..1, mayor = más rápido, menos filtrado

// Datos compartidos para telemetría
volatile float g_meas_rpm = 0.0f;
volatile float g_cmd_u = 0.0f; // salida normalizada -1..+1
volatile float g_error_rpm = 0.0f;
volatile bool  g_magnet_ok = false;

// ===================== Utilidades AS5600 =====================
bool i2cReadBytes(uint8_t dev, uint8_t reg, uint8_t *buf, size_t len) {
  Wire.beginTransmission(dev);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return false; // repeated start
  size_t n = Wire.requestFrom((int)dev, (int)len);
  if (n != len) return false;
  for (size_t i = 0; i < len; ++i) {
    buf[i] = Wire.read();
  }
  return true;
}

uint16_t as5600ReadAngle12() {
  uint8_t buf[2] = {0};
  if (!i2cReadBytes(AS5600_ADDR, AS5600_REG_ANGLE_H, buf, 2)) {
    return 0;
  }
  uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1];
  return (raw & 0x0FFF); // 12-bit
}

bool as5600MagnetDetected() {
  uint8_t s = 0;
  if (!i2cReadBytes(AS5600_ADDR, AS5600_REG_STATUS, &s, 1)) return false;
  // STATUS bits: MD=Magnet detected(5), ML=too weak(3), MH=too strong(4)
  bool md = s & (1 << 5);
  bool tooWeak = s & (1 << 3);
  bool tooStrong = s & (1 << 4);
  // Consideramos OK si MD y no ML/MH
  return md && !tooWeak && !tooStrong;
}

// ===================== Conversión ángulo/velocidad =====================
// 4096 ticks por vuelta; velocidad en RPM a partir de delta_tics / dt.
static constexpr float TICKS_PER_REV = 4096.0f;
static constexpr float TWO_PI = 6.283185307179586f;

float ticksPerSecToRPS(float tps) {
  return tps / TICKS_PER_REV; // rev/s
}
float rpsToRPM(float rps) {
  return rps * 60.0f;
}
float rpmToRPS(float rpm) {
  return rpm / 60.0f;
}

// ===================== TB6612FNG Motor Driver =====================
void motorStandby(bool enable) {
  digitalWrite(PIN_TB_STBY, enable ? HIGH : LOW);
}

void motorDrive(float u) {
  // u en [-1, +1]
  float uu = constrain(u, -1.0f, +1.0f);
  int duty = (int)(fabsf(uu) * PWM_MAX_DUTY);

  // Compensación de zona muerta (~8 %) para arrancar motores pequeños
  const int dead_zone = (int)(0.08f * PWM_MAX_DUTY);
  if (duty > 0) duty = max(duty, dead_zone);

  if (uu >= 0.0f) {
    digitalWrite(PIN_TB_AIN1, HIGH);
    digitalWrite(PIN_TB_AIN2, LOW);
  } else {
    digitalWrite(PIN_TB_AIN1, LOW);
    digitalWrite(PIN_TB_AIN2, HIGH);
  }
  ledcWrite(PWM_CHANNEL, duty);
}

// ===================== PID compute =====================
float pidCompute(PID &c, float set_rps, float meas_rps, float dt) {
  float err = set_rps - meas_rps;
  // Proporcional
  float p = c.kp * err;
  // Derivativa (sobre error)
  float d = c.kd * (err - c.prevErr) / dt;
  // Integrativa con anti-windup por back-calculation
  float u_unsat = p + c.i + d;
  float u_sat = constrain(u_unsat, c.outMin, c.outMax);
  float aw = c.awTau * (u_sat - u_unsat); // si satura, corrige integrador
  c.i += c.ki * err * dt + aw;
  c.i = constrain(c.i, c.outMin, c.outMax);
  c.prevErr = err;
  return constrain(p + c.i + d, c.outMin, c.outMax);
}

// ===================== Tarea de control (1 kHz) =====================
TaskHandle_t g_ctrlTask = nullptr;

void controlTask(void *arg) {
  // Estado para unwrap de ángulo
  int32_t accumTicks = 0;
  uint16_t prev = as5600ReadAngle12();
  accumTicks = prev;
  uint32_t last_us = micros();

  // Filtro de velocidad
  float vel_rps_filt = 0.0f;

  // Programación periódica
  TickType_t lastWake = xTaskGetTickCount();

  for (;;) {
    vTaskDelayUntil(&lastWake, pdMS_TO_TICKS(1)); // 1 ms

    bool magnetOk = as5600MagnetDetected();
    g_magnet_ok = magnetOk;

    uint16_t now = as5600ReadAngle12();
    // unwrap
    int16_t delta = (int16_t)now - (int16_t)prev;
    if (delta > 2048) delta -= 4096;
    if (delta < -2048) delta += 4096;
    accumTicks += delta;
    prev = now;

    uint32_t now_us = micros();
    float dt = (now_us - last_us) / 1e6f;
    if (dt < 1e-6f) dt = CTRL_DT; // fallback
    last_us = now_us;

    // Velocidad en ticks/seg
    float tps = (float)delta / dt;
    float rps = ticksPerSecToRPS(tps);
    // Filtro exponencial
    vel_rps_filt = vel_alpha * rps + (1.0f - vel_alpha) * vel_rps_filt;

    // Setpoint y PID
    float set_rps = rpmToRPS(g_setpoint_rpm);
    // Seguridad si no hay imán correcto: poner setpoint 0 y salida 0
    float u = 0.0f;
    if (magnetOk) {
      u = pidCompute(pid, set_rps, vel_rps_filt, dt);
    } else {
      pid.i = 0.0f; pid.prevErr = 0.0f; u = 0.0f;
    }
    g_cmd_u = u;

    // Aplicar al motor
    motorDrive(u);

    // Telemetría compartida (no crítica, precisión best-effort)
    g_meas_rpm = rpsToRPM(vel_rps_filt);
    g_error_rpm = g_setpoint_rpm - g_meas_rpm;
  }
}

// ===================== Interfaz serie simple =====================
void printHelp() {
  Serial.println(F("# Comandos:"));
  Serial.println(F("#   sp <rpm>        : fija setpoint (RPM)"));
  Serial.println(F("#   kp <val>        : Kp"));
  Serial.println(F("#   ki <val>        : Ki"));
  Serial.println(F("#   kd <val>        : Kd"));
  Serial.println(F("#   alpha <0..1>    : filtro velocidad (exponencial)"));
  Serial.println(F("#   stop            : setpoint = 0"));
  Serial.println(F("#   status          : imprime estado/gains"));
  Serial.println(F("# Formato log: t_ms, set_rpm, meas_rpm, err_rpm, u, magnet_ok"));
}

void printStatus() {
  Serial.printf("# Kp=%.5f Ki=%.5f Kd=%.5f alpha=%.3f SP=%.2f RPM\r\n",
                pid.kp, pid.ki, pid.kd, vel_alpha, g_setpoint_rpm);
}

void handleSerial() {
  if (!Serial.available()) return;
  String line = Serial.readStringUntil('\n');
  line.trim();
  if (line.length() == 0) return;

  if (line.startsWith("sp ")) {
    float v = line.substring(3).toFloat();
    g_setpoint_rpm = v;
    Serial.printf("# SP=%.2f RPM\r\n", g_setpoint_rpm);
  } else if (line.startsWith("kp ")) {
    pid.kp = line.substring(3).toFloat();
    printStatus();
  } else if (line.startsWith("ki ")) {
    pid.ki = line.substring(3).toFloat();
    printStatus();
  } else if (line.startsWith("kd ")) {
    pid.kd = line.substring(3).toFloat();
    printStatus();
  } else if (line.startsWith("alpha ")) {
    vel_alpha = constrain(line.substring(6).toFloat(), 0.0f, 1.0f);
    printStatus();
  } else if (line == "stop") {
    g_setpoint_rpm = 0.0f;
    Serial.println("# SP=0");
  } else if (line == "status") {
    printStatus();
  } else if (line == "help" || line == "?") {
    printHelp();
  } else {
    Serial.println("# Comando no reconocido. Escribe 'help'.");
  }
}

// ===================== Setup/Loop =====================
void setup() {
  pinMode(PIN_TB_STBY, OUTPUT);
  pinMode(PIN_TB_AIN1, OUTPUT);
  pinMode(PIN_TB_AIN2, OUTPUT);

  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000); // I2C @ 400 kHz

  Serial.begin(115200);
  delay(200);
  Serial.println("# ESP32 DevKitC + TB6612FNG + AS5600 - PID velocity control");
  Serial.println("# Toolchain: PlatformIO Core 6.1.16, espressif32 6.5.0, arduino-esp32 2.0.14");
  printHelp();

  // Configurar PWM (LEDC)
  ledcSetup(PWM_CHANNEL, PWM_FREQ_HZ, PWM_TIMER_BITS);
  ledcAttachPin(PIN_TB_PWMA, PWM_CHANNEL);

  // Motor standby inicialmente deshabilitado
  motorStandby(false);
  delay(50);
  motorStandby(true);

  // Crear tarea de control en core 1 con prioridad media-alta
  xTaskCreatePinnedToCore(controlTask, "ctrl", 4096, nullptr, 3, &g_ctrlTask, 1);
}

void loop() {
  static uint32_t lastPrint = 0;
  handleSerial();

  uint32_t now = millis();
  if (now - lastPrint >= 20) { // ~50 Hz de logging
    lastPrint = now;
    // Log sencillo: t_ms, set_rpm, meas_rpm, err_rpm, u, magnet_ok
    Serial.printf("%lu, %.2f, %.2f, %.2f, %.3f, %d\r\n",
                  (unsigned long)now,
                  g_setpoint_rpm,
                  g_meas_rpm,
                  g_error_rpm,
                  g_cmd_u,
                  g_magnet_ok ? 1 : 0);
  }
}

Script opcional para graficar (local)

Si te interesa visualizar la respuesta en tiempo real (no es obligatorio), puedes usar este script Python (requiere pyserial y matplotlib). Ajusta el puerto serie.

# tools/plot_pid.py
import serial, time
import matplotlib.pyplot as plt
from collections import deque

PORT = "/dev/ttyUSB0"   # o COMx en Windows
BAUD = 115200

ser = serial.Serial(PORT, BAUD, timeout=0.1)
time.sleep(2)

buf_t, buf_sp, buf_meas = deque(maxlen=1000), deque(maxlen=1000), deque(maxlen=1000)
plt.ion()
fig, ax = plt.subplots()
ln_sp, = ax.plot([], [], label='Setpoint RPM')
ln_me, = ax.plot([], [], label='Medida RPM')
ax.set_xlabel('t (s)')
ax.set_ylabel('RPM')
ax.legend()
ax.grid(True)

t0 = time.time()
while True:
    try:
        line = ser.readline().decode(errors='ignore').strip()
        if ',' in line and not line.startswith('#'):
            parts = [p.strip() for p in line.split(',')]
            if len(parts) >= 3:
                t_ms = float(parts[0])
                sp = float(parts[1])
                me = float(parts[2])
                buf_t.append((t_ms/1000.0) - (t0))
                buf_sp.append(sp)
                buf_meas.append(me)
                ln_sp.set_data(list(buf_t), list(buf_sp))
                ln_me.set_data(list(buf_t), list(buf_meas))
                ax.relim(); ax.autoscale_view()
                plt.pause(0.01)
    except KeyboardInterrupt:
        break

Compilación, grabación y ejecución

Inicialización del proyecto

  • Crea el proyecto con PlatformIO (en una carpeta vacía):
  • pio project init –board esp32dev
  • Sustituye el contenido de platformio.ini por el proporcionado arriba (fija versiones).
  • Crea src/main.cpp con el código proporcionado.
  • Opcional: añade tools/plot_pid.py si usarás la gráfica.

Comandos exactos

  • Compilar:
  • pio run
  • Subir firmware al ESP32 DevKitC:
  • pio run -t upload
  • Abrir monitor serie (115200 baudios):
  • pio device monitor -b 115200
  • Todo encadenado:
  • pio run -t upload && pio device monitor -b 115200

Verificación de paquetes y versiones instaladas para este entorno:
– pio pkg list -e esp32dev

Ejemplo de salida esperada (resumen):
– framework-arduinoespressif32 3.20014.0
– toolchain-xtensa32 2.80400.0
– tool-esptoolpy 1.40501.0
– platform-espressif32 6.5.0

Validación paso a paso

1) Comprobación eléctrica previa

  • Con motor desconectado (AO1/AO2 sin motor), enciende el sistema con VM presente y ESP32 por USB.
  • Monitor serie a 115200. Debes ver:
  • Línea de bienvenida con “ESP32 DevKitC + TB6612FNG + AS5600 – PID velocity control”.
  • Toolchain listada.
  • Ayuda con comandos.

Si no hay errores, conecta el motor a AO1/AO2 del TB6612FNG y coloca el imán del AS5600 alineado con el eje (distancia y centrado adecuados).

2) Estado del imán (AS5600)

  • Observa el último campo del log periódico: “magnet_ok”.
  • 1: AS5600 detecta imán en rango válido.
  • 0: demasido débil/fuerte o no detectado.
  • Si aparece 0:
  • Reajusta la distancia entre sensor y magneto.
  • Asegura alimentación 3.3 V adecuada y pull-ups I2C.
  • Consulta Troubleshooting.

3) Primer giro con setpoint bajo

  • Introduce por monitor serie:
  • sp 60
  • Observa:
  • El motor debe empezar a girar suavemente.
  • Log aproximado:
    • t_ms, set_rpm, meas_rpm, err_rpm, u, magnet_ok
    • 1234, 60.00, 5.00, 55.00, 0.25, 1
    • … (debe converger hacia meas_rpm ≈ 60)

Si hay un leve retardo de arranque, es normal: la compensación de zona muerta se encarga de vencer el rozamiento.

4) Escalón a velocidad media

  • Cambia a:
  • sp 180
  • Esperado:
  • Aumento de “u” en el log, un pico de error y convergencia en menos de 1–2 s (dependiendo del motor).
  • Overshoot razonable si Kp alto (p.ej., +5–15 %). Si es excesivo, reduce Kp o aumenta D.
  • Estabilidad:
  • El valor meas_rpm debe mantenerse cercano al setpoint bajo cargas leves.
  • Aplica una carga con el dedo (con cuidado) y observa cómo el PID compensa elevando “u”.

5) Ajuste de ganancias PID

  • Si el sistema:
  • Oscila: reduce Kp o aumenta Kd ligeramente (p.ej., kd 0.0005, luego 0.001).
  • Es lento: aumenta Kp gradualmente (0.8 → 1.2 → 1.5) y luego sube Ki (25 → 35).
  • Tiene mucho error estacionario: aumenta Ki en pasos pequeños (±5) con cuidado.
  • Recuerda:
  • Demasiada Ki provoca oscilaciones y saturación (u pega en ±1).
  • Usa kd con moderación si el ruido de velocidad es elevado. Aumenta alpha (p.ej., alpha 0.3) para filtrar más rápido.

6) Validación cuantitativa

  • Paso de 0 → 120 RPM → 240 RPM, mide:
  • Tiempo de establecimiento: < 1.5 s (objetivo).
  • Overshoot: < 10–15 %.
  • Error en régimen: < 3–5 % sin carga.
  • Log representativo:
  • En reposo: “u” ~ 0, meas_rpm ~ 0.
  • A 120 RPM: error se acerca a 0, “u” se estabiliza.
  • Con carga transitoria: “u” sube y luego regresa a su valor precedente al retirar la carga.

7) Prueba de parada controlada

  • stop
  • Esperado:
  • setpoint = 0.
  • El motor desacelera hasta detenerse, con “u” → 0.
  • Sin imán (magnet_ok = 0), el control corta salida por seguridad y resetea integrador.

8) Opcional: gráfica

  • Ejecuta:
  • python3 tools/plot_pid.py
  • Cambia setpoints y observa en tiempo real setpoint/medida.

Troubleshooting

1) Motor no gira
– Verifica STBY del TB6612FNG está en alto (GPIO33 configurado y en HIGH).
– Comprueba VM (motor supply) presente y GND común con ESP32.
– Revisa AIN1/AIN2 invertidos o desconectados. Invierte los cables del motor si gira al revés.
– Aumenta setpoint (p.ej., sp 120) para vencer rozamiento si el motor es duro.

2) I2C no responde (velocidad medida 0 y magnet_ok = 0)
– Chequea pull-ups a 3.3 V en SDA/SCL (4.7 kΩ), o confirma que el módulo AS5600 ya los trae.
– Verifica el cableado SDA→GPIO21, SCL→GPIO22.
– Asegura VCC del AS5600 a 3.3 V (no 5 V).
– Revisa que la dirección 0x36 es correcta (evitar módulos clon con otra address rara).

3) Lecturas de velocidad erráticas (picos o ruido excesivo)
– Aumenta alpha (p.ej., alpha 0.3–0.5) para filtrar la velocidad.
– Reduce kd a 0 si no necesitas término derivativo.
– Verifica alineación y distancia del imán; evita vibraciones mecánicas.

4) Sobreoscilación persistente
– Reduce kp y/o ki. Empieza con kd = 0 y añade derivativa en incrementos pequeños (0.0005, 0.001).
– Confirma que la frecuencia de control (1 kHz) es estable (no satures el puerto serie con logs; ya está a ~50 Hz).

5) Ruido audible/pérdidas de par a bajas velocidades
– Asegura PWM a 20 kHz (LEDC configurado como en el código): build limpio y verificación en runtime.
– Ajusta dead_zone (8 %) si tu motor requiere más duty para arrancar.

6) Reseteos del ESP32 al iniciar el motor
– La fuente de motor produce caídas de VM/GND: usa una fuente estable y separa el 5V/3.3V de lógica del ESP32.
– Mantén GND común y añade condensadores de bulk (p.ej., 470–1000 µF) cerca del TB6612FNG.

7) Velocidad se invierte con setpoints positivos
– Invierte AIN1/AIN2 en el código (o invierte el cableado del motor).
– Verifica la lógica de motorDrive(u): u>=0 debe corresponder al sentido que consideras “positivo”.

8) No se puede flashear (puerto no aparece)
– Instala driver CP210x/CH34x correcto y usa un cable USB de datos.
– Selecciona el puerto correcto en platformio.ini (upload_port/monitor_port).
– Pulsa BOOT/EN del DevKitC si fuese necesario (algunas placas requieren pulsos manuales).

Mejoras/variantes

  • Sustituir LEDC por MCPWM del ESP32 para PWM de alta resolución y sincronización avanzada (no imprescindible para TB6612FNG).
  • Añadir feedforward (u_ff = kff * set_rps) para reducir esfuerzo del integrador y mejorar respuesta ante cargas.
  • Almacenamiento de parámetros PID en NVS (no volátil) para mantener ajustes tras reinicios.
  • Estimación más robusta de velocidad:
  • Promediado móvil de delta ticks con ventana deslizante.
  • Filtro de mediana para spikes.
  • Filtro de Kalman si el entorno es muy ruidoso.
  • Limitador de rampa del setpoint (slew rate) para evitar peticiones bruscas al motor.
  • Supervisión de corriente (añadiendo un shunt + ADC) para protección térmica o de sobrecarga.
  • Cambio de objetivo: control cascada posición-velocidad (outer loop de posición con inner loop de velocidad).
  • Integración con ROS2/micro-ROS para telemetría remota y control superior.

Checklist de verificación

  • [ ] Toolchain instalada: PlatformIO Core 6.1.16, espressif32 6.5.0, arduino-esp32 2.0.14 (3.20014.0).
  • [ ] Drivers USB instalados (CP210x/CH34x) y puerto serie visible.
  • [ ] platformio.ini idéntico al del tutorial (versiones fijadas).
  • [ ] Cableado revisado:
  • [ ] TB6612FNG VM a fuente motor, VCC a 3.3 V, GND común.
  • [ ] STBY a GPIO33, AIN1→GPIO26, AIN2→GPIO27, PWMA→GPIO25.
  • [ ] AS5600 SDA→GPIO21, SCL→GPIO22, VCC 3.3 V y GND; pull-ups presentes.
  • [ ] Subida de firmware sin errores (pio run -t upload).
  • [ ] Monitor serie a 115200 con mensajes de inicio y “help”.
  • [ ] magnet_ok = 1 en reposo (AS5600 detecta imán correctamente).
  • [ ] sp 60 hace girar el motor estable cerca de 60 RPM.
  • [ ] sp 180 responde con subida de velocidad y estabiliza sin oscilaciones severas.
  • [ ] stop detiene el motor y “u” → 0.
  • [ ] Ajuste de Kp/Ki/Kd probado para optimizar respuesta de tu motor.

Con este caso práctico de nivel avanzado, has implementado un control PID de velocidad completo basado en sensores magnéticos absolutos (AS5600) y un puente H TB6612FNG, ejecutado sobre un ESP32 DevKitC con una toolchain totalmente especificada para reproducibilidad. El pipeline de medición (I2C 400 kHz, unwrapping de 12 bits), estimación de velocidad filtrada, lazo de control a 1 kHz y modulación PWM a 20 kHz permite un control fino y silencioso de motores DC en aplicaciones embebidas avanzadas.

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: ¿Qué sistema operativo se recomienda para utilizar con el ESP32 DevKitC?




Pregunta 2: ¿Cuál es la versión mínima de Git recomendada?




Pregunta 3: ¿Qué driver USB es necesario si el módulo tiene un chip CP210x?




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




Pregunta 5: ¿Qué tipo de motor se menciona en el artículo?




Pregunta 6: ¿Qué resistencia se utiliza como pull-up para el I2C?




Pregunta 7: ¿Qué fuente de alimentación se recomienda para el motor?




Pregunta 8: ¿Cuál es la dirección I2C del módulo AS5600?




Pregunta 9: ¿Qué extensión se recomienda para usar con VSCode?




Pregunta 10: ¿Cuál es la versión del framework-arduinoespressif32 recomendada?




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 to Top