01 · Punto de partida
Qué vamos a construir
El montaje reúne piezas conocidas: el motor de 12 V del tema 30 (3 Ω, Kt = Ke = 0,03), con un encoder de 300 PPR en el eje (1200 cuentas por vuelta en x4) leído con el módulo del tema 26, y un puente H TB6612FNG que recibe el PWM. El objetivo: que el motor mantenga una velocidad pedida (por ejemplo 2000 RPM) aunque cambien la carga o la batería.
Lo construimos en pasos, y en cada uno medimos qué mejora y qué se rompe:
- Lazo abierto: un PWM calculado de antemano. Rápido y simple, pero no corrige nada.
- Lazo cerrado proporcional: el PWM es proporcional al error de velocidad. Corrige, pero deja un error permanente.
- Con acción integral: el error permanente desaparece, pero aparece un riesgo (el windup) que hay que prevenir.
- Con protecciones: qué hace el lazo si el encoder falla.
Es una introducción práctica al control: el tema 42 formaliza el control proporcional y el 43 el PID completo, con sus términos y su ajuste. Acá lo usamos tal como un programador lo necesita en un robot: el mínimo que funciona y las trampas que hay que conocer.
02 · Paso 1
Arquitectura y tiempos
El diseño es el de los talleres anteriores, con una tarea más:
| Tarea | Dónde | Ritmo |
|---|---|---|
| Decodificar el encoder | Interrupción | En cada flanco (tema 26) |
| Medir velocidad, calcular el control, actualizar el PWM | Lazo con millis() | Cada 20 ms (50 Hz) |
| Recibir el objetivo y reportar | Lazo principal | Sin bloquear (tema 41) |
El período del control (20 ms) es una decisión de diseño con dos consecuencias. La medición tiene un escalón de 60 / (1200 × 0,02) = 2,5 RPM (tema 25). Y el retardo del lazo es de al menos un período: lo que el programa corrige llega 20 ms después. El motor tiene una constante de tiempo mecánica de unos 65 ms, así que el período es unas tres veces más rápido que la dinámica a controlar: la proporción mínima razonable. Con un motor más veloz o inercias menores, el lazo debería ser más rápido.
Dos reglas de diseño del lazo: el período debe ser constante (el cálculo de la velocidad usa dt fijo) y el cálculo debe terminar bien antes del siguiente instante (nada de delay() ni Serial.print largos dentro del lazo).
03 · Paso 2
Medir la velocidad
Cada 20 ms se leen las cuentas y se calcula RPM = (Δcuentas / CPR) / dt × 60, como en el taller 26. Lo importante para el control es qué le pasa a la medición:
- Está cuantizada en escalones de 2,5 RPM: sobre 2000 RPM es el 0,125 %, despreciable. A 30 RPM sería el 8 %, y el control sería «nervioso».
- Llega con retardo: es el promedio de los últimos 20 ms, por lo que equivale a la velocidad de hace 10 ms.
- Un filtro (tema 5) la suaviza pero suma más retardo. A 2000 RPM con 2,5 RPM de escalón no hace falta filtro; a bajas velocidades, sí.
Una regla: el error del lazo no puede ser menor que el escalón de la medición. Pedir 2000 RPM ± 1 con un escalón de 2,5 RPM es imposible, y el control oscilaría buscando una precisión que el sensor no tiene.
04 · Paso 3
El lazo abierto y sus límites
Con el modelo del tema 30, la velocidad en vacío con ciclo u es aproximadamente 3696 × u RPM. Para 2000 RPM: u = 2000 / 3696 = 0,541, es decir, un 54 % de PWM. Este PWM de prealimentación (feedforward) da la velocidad exacta si el modelo es perfecto y nada cambia. Pero:
- Con una carga de 0,03 N·m el motor necesita 1,07 A y un 79 % de ciclo. Con el 54 % fijo, la velocidad cae a 1076 RPM, es decir, 46 % menos.
- Con la batería a 10,5 V en lugar de 12 V, el mismo ciclo da 1750 RPM (12,5 % menos).
- La temperatura, el desgaste y la fricción cambian la relación todo el tiempo.
El lazo abierto es válido cuando el modelo es fiable y no hay perturbaciones (un ventilador, un LED). Cuando la velocidad importa, hay que medirla. El valor de prealimentación igualmente se conserva: aprovecharlo hace que el controlador solo tenga que corregir lo que el modelo no sabe.
05 · Paso 4
Control proporcional
La idea más sencilla: el PWM es proporcional al error de velocidad.
Si la velocidad es baja, el error es grande y el PWM sube; si se acerca al objetivo, el error y el PWM bajan. El controlador funciona como un resorte que tira hacia el objetivo. La ganancia Kp se mide en ciclo por RPM, y conviene pensar en la ganancia del lazo: Kp multiplicado por cuántas RPM da el motor por unidad de ciclo (3696). Con Kp = 0,001 la ganancia del lazo es 3,7; con 0,002, 7,4.
Dos límites inevitables del control proporcional:
- Error permanente. Para sostener 2000 RPM hace falta un ciclo de 0,54 y, como
u = Kp × e, tiene que haber un error que lo genere. Con Kp = 0,001 el motor se estabiliza en 1574 RPM (−21 %) y, al aplicar la carga, en 1377 RPM. Una ganancia mayor reduce el error, nunca lo elimina. - Inestabilidad. Con ganancias altas, el retardo del lazo hace que la corrección llegue tarde y en el sentido equivocado. Con Kp = 0,0015 todavía es estable; con Kp = 0,002 (ganancia del lazo 7,4) la velocidad no se estabiliza y queda oscilando unos 440 RPM pico a pico, entre ~1400 y ~2100.
06 · Paso 5
Acción integral y anti-windup
Para eliminar el error permanente se suma un término que acumula el error en el tiempo: mientras haya error, la integral crece y empuja el PWM, hasta que el error es cero. El control PI es:
La integral «recuerda» el ciclo necesario: cuando la carga aumenta, el error crece, la integral sube y el PWM se acomoda sin intervención. Con Kp = 0,001 y Ki = 0,01 el motor llega a 2000 RPM con error cero antes y después de la carga.
El peligro es el windup (acumulación excesiva). Cuando el arranque es de golpe con el objetivo lejos, el PWM se satura en el máximo mientras el motor acelera, y la integral sigue acumulando error aunque el PWM ya no pueda crecer. Cuando por fin se alcanza el objetivo, la integral es enorme y empuja a la velocidad mucho más allá del objetivo, que tarda en desaparecer. La defensa, el anti-windup, es no integrar mientras la salida está saturada (salvo que el error ayude a salir de la saturación):
float KP = 0.001, KI = 0.01;
float integral = 0;
float calcularControl(float error, float dt) {
float sinLimitar = KP * error + KI * integral;
float u = constrain(sinLimitar, 0.0, 1.0);
// anti-windup: se integra solo si no está saturado o si el error ayuda a salir
if (sinLimitar == u || (sinLimitar > 1.0 && error < 0) || (sinLimitar < 0.0 && error > 0)) {
integral += error * dt;
}
return constrain(KP * error + KI * integral, 0.0, 1.0);
}
Con Kp = 0,0005 y Ki = 0,01, el sobrepico es del 6,4 % con anti-windup y del 24 % sin él. El PID completo (con derivada, filtrado y ajuste sistemático) está en el tema 43.
07 · Paso 6
El programa completo
// ... ISR del encoder, leerEncoder(), mover(), compensarZona() de los temas 26 y 30 ...
const float CPR = 1200.0; // 300 PPR x4
const uint16_t T_CONTROL = 20; // ms
const float DT = T_CONTROL / 1000.0;
float objetivoRpm = 2000;
int32_t posPrev = 0;
uint32_t tControl = 0;
uint16_t msSinMovimiento = 0;
bool falla = false;
void loop() {
uint32_t ahora = millis();
if (ahora - tControl >= T_CONTROL) {
tControl += T_CONTROL;
int32_t pos; uint16_t err;
leerEncoder(pos, err);
int32_t delta = pos - posPrev;
posPrev = pos;
float medida = (delta / CPR) / DT * 60.0;
float u = falla ? 0.0 : calcularControl(objetivoRpm - medida, DT);
// protección: orden alta y ni una cuenta del encoder
if (u > 0.95 && delta == 0) msSinMovimiento += T_CONTROL; else msSinMovimiento = 0;
if (msSinMovimiento >= 200) { falla = true; u = 0; }
mover(compensarZona((int16_t)(u * 255)));
}
// aquí se atiende el puerto serie, sin bloquear, para cambiar objetivoRpm
}
La protección del final es esencial: si el encoder se desconecta, el programa «ve» una velocidad cero, el error es máximo, y el control sube el PWM al tope; el motor sigue girando a la velocidad máxima sin que nadie se entere.
08 · Paso 7
Verificar
- Escalón de objetivo. Pedí 2000 RPM desde el reposo y registrá la velocidad por el puerto serie. Anotá el tiempo de subida al 90 %, el sobrepico y el valor final.
- Perturbación de carga. Frená el eje con un dedo (con cuidado) o aplicá una carga: la velocidad debe caer poco y recuperarse; con acción integral, volver al objetivo.
- Batería baja. Alimentá con 10 V y comprobá que el lazo mantiene la velocidad hasta que el PWM llega al 100 %.
- Distintos objetivos. Probá 500, 1000 y 2000 RPM: a velocidades bajas el escalón de la medición se nota más.
- Prueba de estabilidad. Subí la ganancia hasta que oscile y bajala a la mitad: ese es un margen razonable.
- Falla del encoder. Desconectá un canal con el motor en marcha: el motor debe detenerse en menos de 300 ms.
- Reposo. Con objetivo cero, el motor debe quedarse quieto (sin zumbar ni «cazar» por la zona muerta).
09 · Paso 8
Fallas y robustez
| Falla | Síntoma | Defensa |
|---|---|---|
| Encoder desconectado | Medida cero, PWM al tope, motor fuera de control | Detección de «orden alta y cero cuentas»; corte de potencia |
| Eje bloqueado | Mismo síntoma, corriente máxima | La misma detección; límite de corriente (tema 30) |
| Ganancia excesiva | Oscilación o zumbido | Reducir Kp; filtrar la medición |
| Windup | Gran sobrepico tras una saturación | Anti-windup |
| Zona muerta | El motor no arranca con objetivos bajos | Compensar (tema 30); la integral ayuda |
| Batería que se agota | El PWM llega al 100 % y la velocidad cae | Detectar la saturación sostenida y avisar |
| Retardos del lazo | Inestabilidad con ganancias razonables | Período constante, medición más rápida |
Un lazo cerrado cambia la naturaleza de las fallas: sin él, un sensor roto daba una lectura equivocada; con él, un sensor roto puede hacer que el actuador se desboque. Por eso se prevé siempre una respuesta segura (tema 44): un valor máximo y un tiempo máximo de saturación, la detección de «sin movimiento» y un estado de reposo.
10 · Analogía con programación
Cinco estrategias frente a la misma perturbación
Simulemos el motor del tema 30 con un encoder de 1200 cuentas por vuelta, un control cada 20 ms y la medición cuantizada. El objetivo es 2000 RPM, y a los 1,5 s se aplica una carga de 0,03 N·m. Se comparan: lazo abierto, proporcional, PI con dos juegos de ganancias, y PI sin anti-windup.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
R, KT, KE, B, J, L = 3.0, 0.03, 0.03, 1e-5, 2e-5, 0.002 # motor del tema 30
VBAT, CPR = 12.0, 1200 # batería y cuentas por vuelta (300 PPR x4)
RPM = 60 / (2 * math.pi)
DT, T_CONTROL, OBJETIVO = 1e-4, 0.02, 2000.0 # paso de simulación, período del lazo, RPM pedidas
W_LIBRE = 3696.5 # RPM en vacío con 12 V
def simular(modo, kp=0.0, ki=0.0, anti_windup=True, total=3.0, t_carga=1.5, carga=0.03):
i = w = fase = 0.0
previo = 0
u = integral = medida = 0.0
filas = []
por_control = round(T_CONTROL / DT)
for k in range(round(total / DT)):
t = k * DT
if k % por_control == 0: # un paso del controlador cada 20 ms
cuentas = math.floor(fase)
medida = (cuentas - previo) / CPR / T_CONTROL * 60
previo = cuentas
e = OBJETIVO - medida
if modo == "abierto":
u = OBJETIVO / W_LIBRE
elif modo == "P":
u = min(1.0, max(0.0, kp * e))
else:
sin_limitar = kp * e + ki * integral
saturada = min(1.0, max(0.0, sin_limitar))
if (not anti_windup) or sin_limitar == saturada or (sin_limitar > 1 and e < 0) or (sin_limitar < 0 and e > 0):
integral += e * T_CONTROL
u = min(1.0, max(0.0, kp * e + ki * integral))
par = carga if t >= t_carga else 0.0
di = (VBAT * u - R * i - KE * w) / L
dw = (KT * i - B * w - par) / J
i += di * DT
w += dw * DT
if w < 0:
w = 0.0
fase += w / (2 * math.pi) * CPR * DT
filas.append((t, w * RPM))
return filas
def metricas(filas, t_carga=1.5):
t90 = next((t for t, w in filas if w >= 0.9 * OBJETIVO), None)
sobrepico = (max(w for t, w in filas if t < t_carga) / OBJETIVO - 1) * 100
antes = [w for t, w in filas if 1.2 <= t < t_carga]
final = [w for t, w in filas if t >= 2.7]
minimo = min(w for t, w in filas if t >= t_carga)
reposo = None
if abs(filas[-1][1] - OBJETIVO) <= 0.02 * OBJETIVO: # solo si termina dentro de ±2 %
fuera = [t for t, w in filas if t >= t_carga and abs(w - OBJETIVO) > 0.02 * OBJETIVO]
reposo = (fuera[-1] - t_carga + DT) if fuera else 0.0
return t90, sobrepico, sum(antes) / len(antes), minimo, sum(final) / len(final), max(final) - min(final), reposo
casos = (("lazo abierto", "abierto", 0, 0, True),
("P Kp=0,001", "P", 0.001, 0, True),
("P Kp=0,002", "P", 0.002, 0, True),
("PI Kp=0,001 Ki=0,01", "PI", 0.001, 0.01, True),
("PI Kp=0,0005 Ki=0,01", "PI", 0.0005, 0.01, True),
("PI igual, sin anti-windup", "PI", 0.0005, 0.01, False))
print("estrategia 90 % sobrepico antes carga mínimo final oscila reposo ±2 %")
for nombre, modo, kp, ki, aw in casos:
t90, sp, antes, minimo, final, osc, reposo = metricas(simular(modo, kp, ki, aw))
reposo_txt = "—" if reposo is None else f"{reposo:.3f} s"
print(f"{nombre:25s} {t90:.3f} s {sp:7.1f} % {antes:9.0f} {minimo:6.0f} {final:5.0f} {osc:5.0f} {reposo_txt}")
Resultado: el lazo abierto llega a 2000 RPM sin sobrepico en 0,148 s, pero con la carga cae a 1076 RPM y no se recupera. El proporcional con Kp = 0,001 sube en 0,049 s sin sobrepasar, se estabiliza en 1574 RPM (error permanente de −21 %) y termina en 1377 tras la carga; con Kp = 0,002 el lazo oscila unos 438 RPM pico a pico y nunca se estabiliza. El PI con Kp = 0,001 y Ki = 0,01 sube en 0,045 s con solo 1,3 % de sobrepico, mantiene 2000 RPM antes de la carga, cae hasta 1723 y vuelve al ±2 % en 0,227 s; con Kp = 0,0005 sobrepasa 6,4 % y se recupera en 0,117 s. El mismo PI sin anti-windup llega a 24 % de sobrepico.
11 · Exploración interactiva
El banco de pruebas del lazo de velocidad
El primer instrumento simula los 3 segundos completos del programa de Python con controles para elegir la estrategia, las ganancias, el objetivo, la carga, la batería y una falla del encoder. El segundo calcula, con las fórmulas del modelo, el ciclo necesario, el error permanente esperado de un control proporcional y la ganancia del lazo. Las simulaciones usan el mismo motor del tema 30.
Instrumento 1: simulación del lazo
Instrumento 2: ciclo necesario y ganancia del lazo
Recorrido guiado
- Con «Lazo abierto», 2000 RPM, carga 0,03 y batería de 12 V: llega a 2000 RPM sin sobrepico y, a los 1,5 s, cae a ~1076 RPM y se queda ahí. Bajá la batería a 10,5 V: ni siquiera llega al objetivo.
- Pasá a «Proporcional» con Kp = 0,001: sube sin sobrepasar, pero se estabiliza en 1574 RPM (error permanente de −21 %) y cae a 1377 con la carga. Subí Kp a 0,0015: el error baja (1695 RPM) pero ya aparece sobrepico.
- Con Kp = 0,002 el sistema ya no se estabiliza y oscila alrededor de 1800 RPM; con 0,01 salta entre ~1300 y ~2400 RPM. Es la inestabilidad por ganancia excesiva con el retardo del lazo.
- Pasá a «PI con anti-windup», Kp = 0,001 y Ki = 0,01: 2000 RPM exactos antes y después de la carga, con 1,3 % de sobrepico. La caída por la carga llega a 1723 y se recupera en ~0,2 s.
- Bajá Kp a 0,0005: más sobrepico (6,4 %) pero recuperación más rápida. Cambiá a «PI sin anti-windup» con los mismos valores: el sobrepico se va a 24 %. Esa es la acumulación de la integral durante la saturación.
- Con objetivo 3000 RPM y batería de 8 V, el motor no llega: el PWM se satura en el 100 % y ninguna ganancia puede arreglarlo. El lazo no crea energía.
- Activá la falla del encoder «sin protección»: la medición cae a cero, el PWM llega al tope y el motor se dispara por encima del objetivo. «Con protección»: a los 200 ms el programa corta el motor.
- En el instrumento 2, mirá el ciclo necesario (79 % con la carga, 54 % sin ella) y cómo el error permanente de un P disminuye al subir Kp mientras la ganancia del lazo crece hacia la zona de oscilación.
Desafío: la cinta transportadora
Una cinta debe moverse a velocidad constante con cargas variables (cajas que se apoyan y se retiran). Con el motor del ejemplo, ¿qué esperarías de un control proporcional puro frente a uno PI? Con el P, cada caja produce una caída permanente de velocidad proporcional al peso: la cinta queda más lenta mientras haya carga. Con el PI, la velocidad vuelve al valor pedido después de una pequeña caída transitoria. Si además se retira la caja de golpe, el PI sin anti-windup puede pasarse un instante, y por eso conviene el límite de la integral. Y si la cinta se atasca, el control debe detectarlo y detener el motor.
12 · Método de trabajo
Lo aprendido, para cualquier lazo de control
- Modelá lo que podás (ciclo necesario, constantes de tiempo) y usalo como prealimentación.
- Elegí el período del lazo al menos 3 a 5 veces más rápido que la dinámica del sistema, y mantenelo constante.
- Empezá con un proporcional chico, y subilo hasta que aparezca oscilación; trabajá con la mitad.
- Agregá integral para eliminar el error permanente, con anti-windup desde el primer día.
- No pidas más precisión que la del sensor (un escalón de 2,5 RPM es el piso del error).
- Prevé la saturación: el lazo no puede dar más que el 100 % del actuador.
- Protegé contra la falla del sensor, porque el lazo cerrado amplifica sus consecuencias.
- Registrá y mirá los datos: la gráfica de velocidad frente al tiempo cuenta más que cualquier cálculo.
Si oscila, bajá Kp o Ki; si es lento, subí Kp; si queda con error, falta integral; si se pasa mucho, windup o Ki excesiva; si se dispara, falta la protección del sensor.
13 · Comprensión
Confusiones frecuentes
«Con mucha ganancia el control es más preciso»
Hasta cierto punto. Con retardo en el lazo, una ganancia excesiva hace que el sistema oscile o se inestabilice.
«Un control proporcional puede dejar el error en cero»
Solo si el ciclo necesario es cero. En general deja un error permanente que se reduce con la ganancia pero no desaparece; hace falta integral.
«La integral siempre ayuda»
Sin anti-windup, acumula error durante la saturación y produce grandes sobrepicos.
«El lazo cerrado hace innecesaria la prealimentación»
La prealimentación hace que el controlador solo corrija lo que el modelo desconoce, y mejora la respuesta inicial.
«Si el sensor falla, el lazo se queda parado»
Al contrario: ve velocidad cero, el error es máximo y manda el actuador al tope. Se necesita una detección de falla.
«El lazo puede dar más velocidad que la del motor»
El PWM se satura en el 100 %. Si el objetivo supera lo que el motor entrega con esa batería, el error queda para siempre.
14 · Práctica guiada
Predecí antes de comprobar
1. Ciclo de prealimentación
El motor da 3696 RPM en vacío con 12 V. ¿Qué ciclo aproximado necesitás para 1500 RPM? ¿Y con la batería a 10 V?
Ver solución
A 12 V: u = 1500 / 3696 = 0,41. Con 10 V la velocidad en vacío es 3696 × 10/12 = 3080 RPM, y u = 1500 / 3080 = 0,49.
2. Escalón de la medición
Con el encoder de 1200 cuentas por vuelta y un lazo de 20 ms, ¿qué precisión relativa tiene la medición a 100 RPM y a 2000 RPM?
Ver solución
El escalón es 60 / (1200 × 0,02) = 2,5 RPM: 2,5 % a 100 RPM y 0,125 % a 2000 RPM.
3. Error permanente
Un control proporcional con Kp = 0,002 debe mantener un ciclo de 0,6. Despreciando otras cosas, ¿qué error de velocidad permanece?
Ver solución
u = Kp × e → e = 0,6 / 0,002 = 300 RPM. Duplicar Kp lo reduce a 150 RPM, sin llegar a cero.
4. Integral
Con Ki = 0,01 y un error constante de 100 RPM, ¿cuánto aporta la integral al PWM después de 0,5 s?
Ver solución
∫ e dt = 100 × 0,5 = 50 RPM·s; aporte = 0,01 × 50 = 0,5 de ciclo. La integral sube mientras haya error.
5. Windup
El PWM está saturado al 100 % durante 0,2 s con un error medio de 1000 RPM. Si no hay anti-windup, ¿cuánto acumula la integral y qué ocurre con Ki = 0,01?
Ver solución
∫ e dt = 1000 × 0,2 = 200 RPM·s; aporte = 0,01 × 200 = 2,0 de ciclo, el doble de lo que el PWM puede dar. Al llegar al objetivo, el error se hace cero, pero la integral sigue empujando hasta que el error negativo la descargue: sobrepico grande.
6. Falla del sensor
El encoder de un ventilador se desconecta con el lazo funcionando. ¿Qué hace el motor y qué protección agregás?
Ver solución
La medida pasa a cero, el error es máximo y el control lleva el PWM al 100 %: el motor gira a máxima velocidad. Se agrega la detección «PWM alto y cero cuentas durante ~200 ms» y se corta la potencia, con aviso de falla.
15 · Síntesis
Ideas para recordar
- El lazo cerrado mide, compara con el objetivo y corrige; el período debe ser constante y 3 a 5 veces más rápido que la dinámica del sistema.
- El lazo abierto (prealimentación) da el valor correcto solo sin perturbaciones; con carga, el motor pierde casi la mitad de la velocidad.
- El control proporcional corrige pero deja un error permanente; una ganancia alta con retardo produce oscilación.
- La acción integral elimina el error permanente; sin anti-windup produce un gran sobrepico tras una saturación.
- El anti-windup consiste en no integrar mientras la salida está saturada.
- La medición cuantizada (2,5 RPM de escalón) fija el piso del error; no se debe pedir más precisión que la del sensor.
- El lazo no crea energía: si el actuador está saturado, el objetivo no se alcanza.
- Un lazo cerrado amplifica la falla del sensor: hay que detectar «orden alta y cero cuentas» y cortar la potencia.
¿Listo para el tema 32?
Antes de avanzar, explicá por qué un control proporcional deja un error con carga, qué es el windup y cómo se evita, y qué le pasa a un lazo de velocidad si se desconecta el encoder. Si podés justificar las tres respuestas, ya sabés cerrar un lazo de control.
En el próximo tema pasamos a los servomotores: señal de 50 Hz, ancho de pulso, torque, límites y servos de rotación continua.