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
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.



