← Índice del curso

Robótica y Automatización · Tema 18

Control de velocidad y posición con encoders

Hasta ahora ordenábamos al motor y confiábamos en que obedeciera. Con un encoder podemos medir lo que hizo, compararlo con lo pedido y corregir la orden. Vamos a cerrar el lazo de velocidad, a apoyar encima un lazo de posición y a ver dónde falla cada uno.

01 · Punto de partida

De ordenar a verificar

En el tema 13 variamos el PWM de un motor DC y comprobamos que la velocidad dependía de la carga, la batería y la fricción. En el tema 14 vimos que un paso a paso en lazo abierto no sabe si perdió pasos. En el tema 9 aprendimos a contar pulsos de un encoder en cuadratura. Este tema une esas piezas.

Cerrar el lazo significa usar la medición para decidir la siguiente orden: medir, comparar con la referencia y corregir. La diferencia entre lo que se quiere y lo que se mide es el error.

Este tema introduce lo mínimo para que el lazo funcione. El tratamiento general de la respuesta de un sistema, del control PID y de su ajuste ocupa los temas 41 a 44; aquí usamos solo proporcional e integral con ganancias de ejemplo.

02 · La estructura

Anatomía del lazo cerrado

Un lazo de velocidad con encoder tiene un controlador que corre a período fijo. En cada ciclo mide, calcula el error y entrega una orden al driver. El motor responde, el encoder cuenta y el ciclo siguiente parte de la nueva medición.

Lazo cerrado de velocidad con encoder La referencia y la velocidad medida se restan para obtener el error. El controlador calcula una orden de PWM que va al driver y al motor. El encoder mide el giro y su conteo se convierte en velocidad, que vuelve al comparador. Referencia−+errorControladoravance + P + IDriverPWM y sentidoMotor y cargareductor, ruedaEncodercuentasVelocidadΔcuentas / Δt

Esta realimentación tiene un precio: el lazo puede oscilar o volverse inestable si la corrección es demasiado agresiva respecto del retardo del sistema. Un lazo abierto nunca oscila por sí solo; uno cerrado sí. Por eso las ganancias se eligen con cuidado y se prueban con prudencia.

También hay dos relojes. El período de control (por ejemplo, 20 ms) debe ser mucho menor que la constante de tiempo del motor, y se mide con el reloj, no con el número de vueltas de loop() (tema 16).

03 · La medición

Medir la velocidad con el encoder

El método más simple cuenta pulsos en una ventana de tiempo fija. Si CPR son las cuentas por vuelta del eje de salida y se mide en un período Δt:

ω [rev/s] = (cuentask − cuentask−1) / (CPR · Δt)

Esta cuenta tiene una limitación de resolución: la velocidad medida cambia en saltos de 1/(CPR·Δt). Con CPR = 1440 y Δt = 20 ms, cada cuenta equivale a unas 0,035 rev/s (2,1 RPM). Con un encoder de 48 cuentas por vuelta, la misma ventana da pasos de más de 1 rev/s: la medición es inservible para un lazo fino.

Hay tres salidas conocidas: aumentar la resolución del encoder, alargar la ventana (a costa de un lazo más lento) o medir el tiempo entre pulsos en lugar de contarlos. Cada una cambia el comportamiento del lazo, así que conviene decidirlo antes de ajustar ganancias.

Recordá, como en el tema 9, que la medición pertenece al eje donde está el encoder. Si el encoder va en el motor y hay un reductor, se convierte con la relación. Si la rueda patina, el encoder mide el giro, no el avance del robot.

04 · Un primer lazo

Lazo de velocidad: avance directo y corrección

La orden de PWM suma dos partes. La primera es un avance directo (feedforward): una estimación de cuánto PWM hace falta para la velocidad pedida, tomada de la calibración del tema 13. La segunda es la corrección proporcional al error:

u = Kff · ωref + Kp · (ωref − ω)

El avance hace casi todo el trabajo si la calibración es buena, y la corrección absorbe lo que el modelo no sabe. Solo con corrección proporcional, un motor con ganancia G (velocidad por unidad de PWM) y una pérdida constante d por carga queda con un error permanente:

erégimen = d / (1 + Kp · G)

Subir Kp reduce ese error pero no lo anula, y una ganancia demasiado alta amplifica la cuantización de la medición y el retardo. Un proporcional puro tiene así un límite de buena puntería.

Con G = 0,02 rev/s por unidad de PWM, Kp = 20 y una pérdida de 0,5 rev/s, el error es 0,5 / (1 + 0,4) ≈ 0,36 rev/s. Sin lazo (Kp = 0) sería 0,5 rev/s.

05 · Corregir lo que queda

El término integral y la saturación

Para anular el error permanente se suma un término que acumula el error en el tiempo. Mientras quede error, la integral sigue creciendo y empuja la orden hasta que el error desaparece:

u = Kff · ωref + Kp · e + Ki · ∫e dt,   Ik = Ik−1 + e · Δt

El costo es una respuesta más lenta y la posibilidad de sobreimpulso. Y aparece un riesgo específico: la saturación. El PWM no puede superar ±255. Si el error persiste porque el motor ya está al máximo, la integral sigue creciendo sin efecto (windup), y al cambiar la situación tarda en descargarse: el motor sigue empujando de más.

La defensa más simple es limitar la integral para que su aporte nunca supere el rango de salida: |I| ≤ 255/Ki. Existen estrategias más finas (integrar solo si no hay saturación), que veremos con el PID completo.

Otra defensa es no pedir lo imposible: la referencia pasa por una rampa de aceleración, como en el tema 17. Un escalón de velocidad exige una orden brusca, genera un error grande y satura el controlador. La rampa mantiene el error pequeño y la orden dentro del rango.

06 · Un lazo sobre otro

Lazo de posición en cascada

Para llevar el eje a una posición, la cuenta del encoder se integra en una posición medida. Un lazo externo calcula el error de posición y produce, en lugar de un PWM, una velocidad de referencia que alimenta el lazo interno de velocidad:

ωref = sat( Kpos · (θobjetivo − θ),  ±ωmáx )

Es un control en cascada: el lazo interno es rápido y se ocupa del motor; el externo es más lento y se ocupa del destino. La saturación en ωmáx hace que, lejos del objetivo, el movimiento tenga crucero; cerca, la velocidad cae en proporción al error y el robot frena de forma natural.

La zona lineal termina cuando el error es ωmáx/Kpos. Con Kpos = 3 1/s y ωmáx = 2 rev/s, empieza a frenar a unos 0,67 vueltas del destino. Si esa distancia es menor que la que el motor necesita para frenar (tema 17), habrá sobreimpulso: o se baja Kpos, o se baja ωmáx, o se acompaña con un perfil de velocidad.

La referencia que ve el lazo interno también pasa por el limitador de aceleración. Así, aun con un escalón de objetivo, el lazo interno recibe una orden de velocidad que cambia a un ritmo razonable. Poner un limitador dentro de un lazo cambia su dinámica; una ganancia de posición alta puede producir oscilaciones sostenidas, como mostrará el laboratorio.

07 · Lo que el lazo no arregla

Dónde sigue fallando

  • Medición equivocada. Un encoder mal acoplado, con cuentas perdidas o con el sentido invertido hace que el lazo corrija en la dirección incorrecta. Si el signo está mal, la realimentación se vuelve positiva y el motor se dispara. Comprobá el signo antes de cerrar el lazo.
  • Holgura entre encoder y carga. El encoder mide el motor, no la carga. Con engranajes flojos o correas, la posición real de la carga puede diferir.
  • Patinaje. Medir la rueda no confirma el avance del chasis (temas 9 y 17). La odometría del tema 27 trata esos errores.
  • Resolución y retardo. Una medición gruesa o un ciclo lento limitan qué tan fuerte puede corregirse.
  • Límites del actuador. Si el motor no alcanza la velocidad pedida, el controlador satura y no puede cumplir. El lazo no crea potencia.
  • Pérdida de energía o fallo del sensor. Si el encoder deja de contar, el controlador interpretará que el motor está parado y aumentará la orden. Conviene vigilar que, con orden alta sostenida, haya movimiento medido.

La última es una condición de seguridad típica: un lazo cerrado que pierde su sensor puede ser más peligroso que uno abierto, porque empuja cada vez más fuerte sin saber qué ocurre. Se lo previene con límites de salida, tiempos máximos y detección de ausencia de movimiento.

08 · Código para la placa

Control de velocidad y posición en Arduino

Supuestos que debés verificar en tu montaje: UNO R3; encoder en cuadratura con canal A en D2 (interrupción externa) y canal B en D4; un driver con entradas PWM (D5) y sentido (D7); y 1440 cuentas por vuelta del eje de salida con la decodificación de este código (cambio de A, es decir, el doble de pulsos de un canal). Si tu encoder o tu reductor dan otra cifra, ajustá CUENTAS_VUELTA. Probalo con el eje libre y sin carga que pueda dañarse.

Comandos por el monitor serie: 1 y 2 piden 1 o 2 rev/s; N, −1 rev/s; P añade una vuelta al objetivo de posición; 0 frena. Cualquier movimiento pasa por una rampa de aceleración de 8 rev/s² y por un lazo de velocidad con avance, P e I. Si el sentido o el signo de las cuentas resultan invertidos, corregilo en el cableado o en SIGNO_ENCODER antes de probar el lazo.

const byte PIN_A = 2, PIN_B = 4, PIN_PWM = 5, PIN_DIR = 7;
const float CUENTAS_VUELTA = 1440.0;
const int SIGNO_ENCODER = 1;              // -1 si el conteo sale invertido
const unsigned long TS_MS = 20;
const float K_FF = 40.0, K_P = 20.0, K_I = 60.0;     // ganancias de ejemplo
const float K_POS = 3.0, V_MAX_POS = 2.0;            // lazo de posición
const float ACEL = 8.0;                              // rev/s² de la referencia
const float I_MAX = 255.0 / K_I;

volatile long cuentas = 0;
long previas = 0, objetivoCuentas = 0;
float refRampa = 0, refVel = 0, integral = 0;
bool modoPosicion = false;
unsigned long ultimo = 0;
byte contador = 0;

void contarA() {
  const bool a = digitalRead(PIN_A), b = digitalRead(PIN_B);
  cuentas += (a == b) ? -SIGNO_ENCODER : SIGNO_ENCODER;
}

long leerCuentas() {
  noInterrupts();
  const long c = cuentas;
  interrupts();
  return c;
}

void aplicar(float u) {
  digitalWrite(PIN_DIR, u >= 0 ? HIGH : LOW);
  analogWrite(PIN_PWM, (int)(fabs(u) + 0.5));
}

void setup() {
  pinMode(PIN_A, INPUT_PULLUP);
  pinMode(PIN_B, INPUT_PULLUP);
  pinMode(PIN_DIR, OUTPUT);
  pinMode(PIN_PWM, OUTPUT);
  aplicar(0);
  attachInterrupt(digitalPinToInterrupt(PIN_A), contarA, CHANGE);
  Serial.begin(115200);
  Serial.println("1/2: rev/s; N: -1 rev/s; P: +1 vuelta; 0: frenar");
  ultimo = millis();
}

void loop() {
  if (Serial.available() > 0) {
    const char cmd = Serial.read();
    if (cmd == '1' || cmd == '2' || cmd == 'N' || cmd == '0') {
      modoPosicion = false;
      refVel = (cmd == '1') ? 1.0 : (cmd == '2') ? 2.0 : (cmd == 'N') ? -1.0 : 0.0;
    } else if (cmd == 'P') {
      if (!modoPosicion) objetivoCuentas = leerCuentas();
      objetivoCuentas += (long)CUENTAS_VUELTA;
      modoPosicion = true;
    }
  }

  const unsigned long ahora = millis();
  if (ahora - ultimo < TS_MS) return;
  const float dt = (ahora - ultimo) / 1000.0;
  ultimo = ahora;

  const long c = leerCuentas();
  const float vel = (c - previas) / (CUENTAS_VUELTA * dt);   // rev/s
  previas = c;

  if (modoPosicion) {
    const float errPos = (objetivoCuentas - c) / CUENTAS_VUELTA;
    refVel = constrain(K_POS * errPos, -V_MAX_POS, V_MAX_POS);
  }
  const float paso = ACEL * dt;
  refRampa += constrain(refVel - refRampa, -paso, paso);

  const float err = refRampa - vel;
  integral = constrain(integral + err * dt, -I_MAX, I_MAX);
  const float u = constrain(K_FF * refRampa + K_P * err + K_I * integral, -255.0, 255.0);
  aplicar(u);

  if (++contador >= 5) {
    contador = 0;
    Serial.print(refRampa); Serial.print(' ');
    Serial.print(vel); Serial.print(' ');
    Serial.print((float)c / CUENTAS_VUELTA); Serial.print(' ');
    Serial.println(u);
  }
}

La salida imprime cuatro columnas cada 100 ms: velocidad de referencia, velocidad medida, posición en vueltas y orden de PWM. Abrí el Serial Plotter para ver el lazo trabajando.

Las ganancias son un punto de partida para ensayar, no valores para tu motor. Empezá con K_P y K_I bajos, subí K_P hasta que la respuesta sea rápida sin vibrar y recién entonces agregá la integral. Si el motor zumba o se dispara, cortá la alimentación del motor y revisá el signo del encoder.

Dos limitaciones: analogWrite() en D5 usa la frecuencia PWM por defecto del UNO, que puede ser audible según el driver (tema 13), y el programa no detecta ausencia de movimiento ni límites de recorrido. Un sistema real necesita ambos.

La parada con 0 no es una parada de emergencia: la referencia baja con la rampa de aceleración y el lazo frena activamente. Para emergencias hace falta una vía independiente (tema 4).

09 · Exploración interactiva

Laboratorios: lazo de velocidad y lazo de posición

1. Velocidad: abierto, P o PI frente a una perturbación

Motor ficticio de primer orden (constante de tiempo 0,15 s, 5 rev/s máximas) con avance directo calibrado al 80 %. La referencia se activa a los 0,1 s; a los 2 s aparece una pérdida por carga. El período de control es de 20 ms y la velocidad se mide por cuentas.

Velocidad del motor en el tiempoLa línea continua es la velocidad real simulada del motor; la punteada es la referencia con su rampa de aceleración. A los 2 segundos aparece una pérdida por carga. El eje horizontal cubre 4 segundos.— rev/s04 sperturbaciónLínea continua: velocidad del motorPunteada: referencia con rampa
Error antes de la perturbación—
Error al final (con carga)—
Sobreimpulso—
Tiempo de establecimiento (±5 %)—

Elegí el tipo de control y las ganancias.

En lazo abierto con 2 rev/s, el motor queda 0,43 rev/s por debajo (calibración imperfecta) y 0,93 rev/s con la carga. Con proporcional (Kp = 20) esos errores bajan a 0,31 y 0,67 rev/s. Con PI (20 y 60) el primer error es de unas 0,02 rev/s y se establece en unos 0,6 s; tras la perturbación la integral va recuperando y el error se acerca a cero (0,09 rev/s al cabo de 2 s). Con Ki = 150 y Kp = 60 se recupera más rápido, a cambio de un sobreimpulso de menos del 1 %.

Cambiá a un encoder de 48 cuentas: la velocidad medida sube y baja en saltos de más de 1 rev/s por ciclo. El controlador reacciona a ese ruido de cuantización; en el motor real se oiría y se vería. Es el efecto de resolución de la sección 3.

2. Posición en cascada sobre el lazo de velocidad

El lazo interno es el PI del laboratorio anterior (Kp = 20, Ki = 60). El externo convierte el error de posición en una velocidad de referencia, limitada, y la rampa de aceleración sigue activa.

Posición del eje en el tiempoLa línea continua es la posición simulada del eje. La línea horizontal punteada es el objetivo. El eje horizontal cubre 5 segundos.— vueltas05 sLínea continua: posiciónPunteada: objetivo
Sobreimpulso—
Tiempo hasta ±0,01 vueltas—
Velocidad pico—
Error final—

Modificá objetivo y ganancias.

Con Kpos = 3 y 2 rev/s, una vuelta llega en unos 1,6 s con 1,4 % de sobreimpulso. Con Kpos = 5 el sobreimpulso sube a 5,5 %. Con 15 o más el eje oscila y no se establece en la ventana: el lazo externo pide más de lo que el interno puede seguir. Probá bajar la velocidad máxima a 0,5 rev/s: el movimiento es más lento (unos 2,2 s) pero casi sin sobreimpulso.

El modelo es ideal: no tiene zona muerta, holgura ni fricción seca, y el motor es de primer orden. Un motor real puede quedar oscilando en pequeño alrededor del objetivo (hunting) o detenerse con un error residual por zona muerta.

10 · Método y diagnóstico

Pruebas y diagnóstico

  1. Verificá el encoder primero: girá el eje a mano y comprobá que el conteo crece en el sentido esperado y que una vuelta da el número de cuentas calculado.
  2. Probá el lazo abierto con el avance directo y registrá velocidad contra orden para calibrar K_FF.
  3. Cerrá el lazo solo con K_P pequeño y subilo de a poco, con la rueda en el aire o el eje libre.
  4. Agregá K_I y comprobá que no hay sobreimpulso fuerte ni oscilación sostenida.
  5. Aplicá perturbaciones controladas (frenar la rueda con la mano, cambiar la tensión) y observá cómo se recupera.
  6. Forzá saturación (referencia muy alta) y comprobá que al volver a una referencia normal el motor se recupera sin demora.
  7. Recién entonces cerrá el lazo de posición, con velocidad máxima baja, y subí de a poco.
«El motor se dispara al cerrar el lazo»

Lo más probable es el signo del encoder invertido: la realimentación positiva aumenta la orden cuando el motor ya gira. Cortá la alimentación y corregí el signo.

«Vibra o zumba con una ganancia alta»

La ganancia amplifica el ruido de cuantización y el retardo. Bajá K_P, aumentá la resolución o alargá la ventana de medición.

«Con integral, el motor tarda en reaccionar tras una saturación»

Es windup. Verificá que la integral esté limitada y que la referencia pase por una rampa.

«Llega cerca del objetivo y se queda temblando»

Puede ser zona muerta, holgura o ganancia de posición demasiado alta. Probá con un valor menor y una tolerancia de llegada (una banda alrededor del objetivo donde se apaga el motor).

«El encoder dice que llegó, así que el robot llegó»

Solo si el encoder mide lo que importa. Una rueda que patina o una holgura entre motor y carga hacen que la medición no refleje el movimiento del robot.

«Con lazo cerrado ya no hace falta calibrar el avance directo»

Hace falta menos precisión, pero el avance reduce el trabajo del lazo y su error inicial. Una mala calibración empuja la corrección al límite.

11 · Práctica guiada

Predecí antes de comprobar

1. Resolución de velocidad

Con ventana de 20 ms, ¿qué resolución de velocidad dan 1440 y 48 cuentas por vuelta? ¿Qué ventana necesitás con 48 cuentas para resolver 0,05 rev/s?

Ver solución

1/(1440·0,02) ≈ 0,035 rev/s y 1/(48·0,02) ≈ 1,04 rev/s. Para 0,05 rev/s con 48 cuentas: Δt = 1/(48·0,05) ≈ 0,42 s, una ventana muy lenta para un lazo de velocidad.

2. Error con proporcional

Un motor tiene G = 0,02 rev/s por unidad de PWM, avance perfecto y Kp = 20. Aparece una pérdida constante de 0,5 rev/s. ¿Cuál es el error en régimen? ¿Y con Kp = 60?

Ver solución

e = d/(1+KpG). Con 20: 0,5/1,4 ≈ 0,36 rev/s. Con 60: 0,5/2,2 ≈ 0,23 rev/s. Mejora, pero no desaparece. La integral sí lo anula si el actuador alcanza.

3. Límite de la integral

Con Ki = 60, ¿qué valor máximo de la integral conviene permitir para que su aporte no pase de 255 de PWM? ¿Qué pasa si el error es de 0,5 rev/s durante 2 s sin ese límite?

Ver solución

Imáx = 255/60 = 4,25 rev (error acumulado). Sin límite, I = 0,5·2 = 1 rev en este caso aún no es excesivo; pero con un error sostenido de varios segundos se supera, y al corregirse la causa la integral tarda en descargarse.

4. Lazo de posición

Con Kpos = 3 1/s y ωmáx = 2 rev/s, ¿qué velocidad se pide con un error de 0,5 vueltas y con uno de 1 vuelta? ¿A qué distancia del destino empieza a frenar?

Ver solución

0,5 vueltas: 3·0,5 = 1,5 rev/s. 1 vuelta: 3, saturada a 2 rev/s. Empieza a frenar con error 2/3 ≈ 0,67 vueltas.

5. Conversión de unidades

Un encoder da 12 pulsos por canal y vuelta de motor, se cuenta con decodificación x4 y el reductor es 30:1. La rueda mide 6,5 cm de diámetro. ¿Cuántas cuentas por vuelta de rueda? ¿Cuántas para avanzar 50 cm?

Ver solución

12·4·30 = 1440 cuentas por vuelta de rueda. Perímetro: π·6,5 ≈ 20,42 cm. Para 50 cm: 50/20,42·1440 ≈ 3526 cuentas (sin deslizamiento).

6. Diagnóstico

El eje llega al objetivo, se pasa un poco y oscila alrededor. Nombrá tres causas posibles y qué probarías primero.

Ver solución

Ganancia de posición alta para la velocidad que logra el lazo interno, retardo o resolución gruesa, y holgura o zona muerta. Primero bajar Kpos o la velocidad máxima; luego revisar resolución y mecánica.

12 · Síntesis

Ideas para recordar

  • Cerrar el lazo es medir, comparar con la referencia y corregir; el error es referencia menos medición.
  • La velocidad se estima con cuentas por ventana de tiempo; su resolución es 1/(CPR·Δt).
  • El avance directo hace el grueso del trabajo; el proporcional reduce el error y deja uno residual.
  • La integral anula el error permanente, pero exige limitar la saturación para evitar windup.
  • Una rampa en la referencia mantiene el error pequeño y la orden dentro del rango.
  • El lazo de posición en cascada genera la velocidad de referencia para el lazo interno, con límite de velocidad.
  • El lazo cerrado puede oscilar o dispararse: signo del encoder, ganancias y límites se prueban con prudencia.
  • Medir el motor no es medir el robot: holgura, patinaje y fallos del sensor siguen siendo problemas.

¿Listo para el tema 19?

Antes de avanzar, definí cómo vas a verificar el signo y las cuentas por vuelta de tu encoder, qué ganancias de partida usarías y qué señal te haría cortar la prueba. Pensá qué cambia si ahora hay dos motores que deben moverse de forma coordinada.

En el próximo tema estudiamos el movimiento coordinado de varios ejes: sincronizar motores y secuencias de movimiento sin bloquear el programa.