Control PID de posición con librería para Arduino

En esta página presento un control PID para posicionar un motor y está basado en el algoritmo publicado por Brett Beauregard.

Es un Arduino Nano, pero es 100% compatible con Arduino UNO.

Antes de ponerlo en marcha y otras cuestiones:

  • Si al poner en marcha el Arduino y al mover el eje el motor gira sin parar, significa que la polaridad del motor está invertida por tanto has de enrocar (intercambiar) las dos señales del PWM que va al puente en H.

  • Los puentes en H suelen tener dos patillas para el control del sentido del motor y una patilla "Enable" o también llamada "PWM". El programa que presento aquí mete la señal de PWM directamente por las dos salidas de control de sentido, por tanto el "Enable" o "PWM" que tienen muchos puentes en H hay que ponerlas a 1 lógico, es decir a 3,3V ó 5V, según la lógica de alimentación que use tu puente en H. Esa patilla (Enable o PWM) nunca la has de poner a 12V porque estropearía tu puente en H. Si nunca has usado un puente en H, primero trata de mover un motor directamente en ambos sentidos y cuando ya conozcas el funcionamiento entonces ya estarás listo para conectarlo a cualquier Hardware. En el enlace del vídeo (de "El Profe García") explica que para controlar velocidad hay que quitar el jumper del enable, sin embargo con el programa que presento eso no lo has de hacer, porque como decía arriba el control de velocidad lo hago directamente con los pines de control de sentido de giro. ¿Por qué lo hago así? Porque de esta manera en vez de usar tres cables uso sólo dos.

  • Es necesario que los dos pines que van al puente en H (pines D5 y D6 del Arduino Uno o Nano) sean esos pines y no otros, porque están configurados como PWM y a máxima frecuencia (cerca de 60KHz). Recuerda que no todos los pines son PWM y que ciertos pines los maneja registros internos muy concretos que en el programa ya están preconfigurados. Es decir, que si decides cambiar la salida a otros pines no te va a funcionar. Por tanto, al menos la primera vez, cíñete al proyecto tal como lo presento y si luego quieres hacer modificaciones, al menos sabrás que antes de hacer las modificaciones todo funcionaba bien.

  • Lo mismo sucede con las entradas del encoder, han de ser los pines D2 y D3 y no otros, porque están configurados como interrupción externa. Si decides cambiar a otros pines tampoco te funcionará.

  • Podría suceder que el encoder que tu utilices tenga mucha resolución y/o que el motor sea especialmente veloz; el propio Arduino ha de contar los pulsos y si el encoder tiene mucha resolución podría perderlos porque no le daría tiempo a contarlos. Si este es tu caso y sólo quieres probar el control PID que propongo en esta página, has de bajar la velocidad del motor alimentándolo con una tensión menor. Yo he utilizado un encoder de 334 ppr con un motor de 12V y no he tenido problemas.

  • El puente en H puede ser cualquiera que se adapte al motor que vayas a utilizar. Para los que no tienen mucha experiencia en puentes en H decirles que es importante que tenga protección para cuando los dos niveles de entrada están en "alto", y ha de llevar los 4 diodos de protección para eliminar las corrientes inversas que producen las bobinas del motor. Yo utilizo puentes en H que compro en Internet y a día de hoy son muy económicos y están preparados para controlar motores y bobinas.

  • En este enlace puedes ver la configuración del patillaje de algunos modelos de encoders ópticos de media/alta resolución.

  • Desde el terminal serie podemos poner cifras enteras y decimales para modificar las constantes PID, pero si haces una modificación de esas constantes desde dentro del programa (no desde el terminal) has de utilizar siempre la notación en punto flotante porque dichas constantes son del tipo "double" y si no le pones el punto decimal, en algunos casos puede interpretarlo como entero y eso puede provocar errores en los cálculos.

Si has venido directamente a esta página, en Control PID con Librería tienes toda la información sobre la comunicación PC-Arduino, manejo óptimo del motor, una introducción para encontrar las constantes PID óptimas y otras cuestiones. En el ZIP de descarga también tienes unas instrucciones con todas estas cuestiones.

Sintonización del Controlador PID.

En el código tengo puesto las constantes PID que son óptimas para mi caso. En el tuyo es probable que el motor sea diferente y/o tengas un encoder con otra resolución. Estos cambios hace que mis constantes PID puedan ser diferentes a las que tú necesitas, entonces hay que re-sintonizar dichas constantes.

Se trata de ir probando valores para las constantes KP, KI, KD y tiempo de muestreo.

  • Control proporcional: Este es el control más importante, el que realmente mueve el motor la mayor parte del recorrido. Imagina que KP=1, KI=0 y KD=0 (al poner 0 anulamos ese tipo de control). Ahora imagina que el motor está en la posición 0 y queremos llevarlo a la posición 23000. El control proporcional tendrá casi todo recorrido el valor de 255 (el valor máximo del PWM) hasta que finalmente llegamos a la posición 23000-255, es decir a posición 22745, a partir de ese momento el valor del PWM irá bajando en proporción a la posición en la que se va acercando al punto designado. Si aumentamos el valor de KP, haríamos que se acercase un poco más, pero esto tiene un límite. Si el motor está libre de carga, por regla general, lo que sucede es que por la propia inercia del movimiento tiende a pasarse de la posición designada y se produce oscilaciones cada vez más lenta (si KP es lo suficientemente bajo, de lo contrario no pararía de hacerlo), y aún así no se pone en el lugar que le pedíamos, sino un poco más allá o más acá de la posición designada.

  • Control derivativo (o diferencial): La velocidad es una derivada, por eso se llama así. El control derivativo lo que hace es restar velocidad al motor en la medida que se acerca al punto designado. Este tipo de control elimina la oscilación que mencioné anteriormente en el control proporcional y también lo hará con el control integral. Un valor demasiado alto en la constante derivativa (KD) produce comportamientos especialmente ruidosos u oscilaciones arbitrarias. El control derivativo es muy sensible a los cambios porque trata de darle al motor la velocidad de llegada correcta.

  • Control Integral: El control proporcional "intenta" llegar a la meta, el derivativo ajusta la velocidad, por tanto evita las oscilaciones, pues el control integral es la parte más sensible de este asunto: Cuando el motor se acerca a la meta, en el mejor de los casos, lo que hace es que si ve que todavía falta un poco para llegar al punto designado eleva el valor del PWM, hasta que consigue hacer que el motor se mueva lo suficiente. Una manera de experimentar esto es poner un valor muy pequeño a la constante integral (KI), si mueves el eje del motor notarás en tus dedos que hace un esfuerzo progresivo (cada vez más fuerte) para posicionarse en el mismo punto que en el que estaba. Un valor alto en KI sucede lo mismo que con KP y KD, el eje del motor oscila exageradamente.

  • Tiempo de muestreo: Esta parte es también muy importante. Es la unidad de tiempo que le dice al Arduino cada cuánto tiempo ha de realizar los cálculos. No actúa sobre el control proporcional, pero sí lo hace sobre el control integral y derivativo por razones matemáticamente obvias. El tiempo de muestreo es más importante de lo que parece y en la medida que se modifica este parámetro observarás que mejora o empeora el comportamiento del motor.

En resumen:

KP = Es la que realmente hace mover al motor la mayor parte del recorrido.

KI = Incrementa, en tiempos discretos, el valor del PWM, y lo hace en proporción a la desviación.

KD = Ajusta la velocidad de llegada y eso tiene el efecto añadido de eliminar las oscilaciones que pueda provocar el control proporcional e integral.

SampleTime = Acelera o disminuye los efectos del control integral y derivativo.

El control de posición de motores con control PID no es sencillo, pero a medida que acumules experiencia sabrás qué valores has de poner en esas constantes, hasta dejarlo perfecto. Mi consejo es comenzar con KI=0 (para anular el control integral), y poner KP=1 y KD=1. Entonces haces mover al motor hacia un lado y hacia otro, y si ves que el motor se pasa, aumentas KD, hasta que finalmente deje de hacerlo. Aumentar la constante KP nos daría mucha velocidad en el posicionamiento, pero si te excedes se pondrá a oscilar. Una vez que tengas todo esto de manera aceptable nos falta un último empuje y de eso se encarga el control integral. Para la constante integral has de comenzar con valores muy pequeños y poco a poco ir elevando esa cifra hasta conseguir el ajuste perfecto.

¿Cuándo un controlador puede ser sólo P ó PI ó PD?

Según sea la naturaleza de tu proyecto te puedes encontrar que haciendo funcionar sólo la parte proporcional (P) ya te funciona estupendamente, o sólo la parte proporcional-integral (PI), o sólo la parte proporcional-derivativa (PD).

Control P: Cuando hay un amplio margen de error, existe mucha resolución (cuanto más mejor) y existe esfuerzo continuado, este controlador puede ir bien.

Control PI: Cuando la exactitud sí es crítica, en cualquier resolución y existe un esfuerzo continuado, este controlador puede ir bien.

Control PD: La exactitud aquí depende del tipo de proyecto, en cualquier resolución, con fuertes inercias, este controlador puede ir bien.

Como ya sabrás hay muchos tipos de aplicaciones de controladores PID, bien sea para posicionar un motor, controlar la velocidad, mantener una temperatura, manejar galvos, etc. Cada uno, dependiendo de la naturaleza del proyecto te puedes encontrar que no siempre es necesario un controlador PID completo.

Comunicación a través del terminal serie.

Cuando estés en el terminal serie no importará si la letra que escribimos es en mayúscula o minúscula, dentro del programa se convertirá en mayúscula.

Posición relativa (sumar una cantidad a la posición en la que está el motor):

Desde el terminal serie, las letras que hacen mover el motor son Q-W, A-S, Z-X, 1-2. Cada par es derecha o izquierda, por ejemplo, Q es izquierda y W es derecha, así con el resto. Escribes una de esas letras y al pulsar enter (o dándole a "Enviar"), el motor se moverá cierto número de pasos. Q y W es una distancia corta, A y S una distancia media, Z y X una distancia más larga y finalmente la tecla 1 y 2 es una distancia aún más larga. Si necesitas una distancia relativa muy larga puedes poner por ejemplo "11111" y pulsar enter, aunque lo mejor para esto es usar posiciones absolutas.

Posición absoluta: Escribes 'G' junto a un valor numérico y le das a enter, por ejemplo: "G23000" e irá a esa posición.

Modificar las constantes PID y tiempo de muestreo:

Ponemos 'P' y el valor de la constante proporcional.

Ponemos 'I' y el valor de la constante integral.

Ponemos 'D' y el valor de la constante derivativa.

Ponemos 'T' y el valor en milisegundos del tiempo de muestreo.

Por ejemplo, en el terminal serie ponemos: "D20" (y pulsamos enter), cambiará la constante KD a 20. Se puede utilizar decimales, por ejemplo "D21.35". Nótese que el decimal es un punto, no una coma, esto es importante. También se puede poner varias a la vez y han de ir obligatoriamente separadas por un espacio.

Ejemplos:

P1 D22.1 (y pulsamos enter)

P1.5 D20.3 T14 (y pulsamos enter)

Arduino te responderá con los datos de las constantes PID y tiempo de muestreo que hay en ese momento.

Si pulsas la letra "K" y le damos a enter, también te saldrá los valores de las constantes PID y tiempo de muestreo. Esto nos sirve para consultarlas en cualquier momento, porque cuando se hace pruebas, el terminal se llena de números y es fácil olvidar cómo estaban esos parámetros.

Métete dentro del programa y modifica la cantidad de distancia que ha de recorrer a tu gusto, añadir teclas con otras distancias, otras funciones, etc.

Control óptimo del motor.

Al principio de este proyecto usaba "if" por separados para manejar el motor. Pero me di cuenta rápido que hacerlo de esa manera, aunque el motor estuviera parado, seguía circulando pequeñas corrientes por sus bobinas. Optimicé esta parte hasta dejarlo perfecto, creando if-else encadenados y jerárquicos.

Observa la comparación: tenemos igual que, mayor que y menor que (este último a través del else). Al principio tenía puesto en la comparación de "igual que" (primer if) la variable "Out", pero tenía el efecto que comento más arriba, porque al parecer no siempre consigue hacerse cero. Entonces pensé en otra variable que lo pudiera conseguir, y efectivamente, la variable "error" cumple ese cometido.

"Out" tiene dos funciones: si es mayor de cero irá en un sentido y si es menor de cero irá en sentido contrario, y el valor que contiene esa variable es el PWM determinado a la posición o circunstancia que tenga el motor.

La estructura IF-ELSE encadenada es importante, sirve para sólo determinarse por una condición. Si esta parte no estuviera optimizada podríamos pensar que el control PID no hace lo que debe porque el motor sufre inconsistencias de programación en esta parte del programa. Con estas mejoras se consigue que el motor tenga un funcionamiento optimizado y sin consumos residuales.

Hago encender el led que hay en el pin 13 del Arduino cuando el motor llega a la meta. Esto tiene utilidad si un día vas a controlar varios motores, o simplemente quieres avisar con una señal a otro controlador de que ya está posicionado. El led se apaga cuando recibe una nueva posición y no se volverá a encender hasta llegar a la nueva menta.