PIDControl – Librería de control para Arduino

PIDControl – Librería de control para Arduino

Introducción

PIDControl nace de una mezcla bastante habitual entre necesidad, curiosidad y esa mala costumbre de mirar algo que ya funciona y pensar que seguramente se puede complicar un poco más.

La idea inicial era relativamente sencilla. Quería disponer de una librería PID para Arduino con la que pudiera trabajar de una forma parecida a como estoy acostumbrado en control de procesos. Manual y automático, seguimiento de PV, transferencia bumpless, anti-windup, diferentes estructuras de PID, acción directa o inversa y poco más. El problema es que, una vez empiezas a tirar del hilo, descubres que el PID es casi la parte fácil. Antes hay que medir una señal que normalmente viene con ruido, picos y alguna lectura que parece enviada directamente desde otra dimensión. Después hay que entender cómo responde el proceso, identificarlo, obtener una sintonía razonable y, solo entonces, dejar que el controlador haga su trabajo sin intentar destruir aquello que pretendíamos controlar.

Mientras desarrollaba los artículos anteriores sobre identificación y sintonización me di cuenta de otra cosa que terminó siendo una de las principales motivaciones del proyecto. Encontraba librerías PID, encontraba filtros, encontraba ejemplos de autotuning y encontraba implementaciones aisladas de métodos de identificación, pero no encontraba una solución que cubriera todas las etapas del control de un proceso dentro de una misma librería y con una filosofía coherente.

Así que ese terminó siendo el objetivo de PIDControl. No crear simplemente otra implementación de la ecuación PID, porque de esas ya tenemos suficientes para sobrevivir varias vidas, sino intentar recorrer el camino completo.

La intención tampoco es convertir un Arduino en una plataforma de control industrial. La idea es disponer de una herramienta con la que podamos recorrer todo el proceso de forma ordenada, entender qué estamos haciendo en cada etapa y, sobre todo, evitar el método tradicional de introducir tres constantes encontradas en Internet, pulsar AUTO y esperar que la termodinámica tenga un buen día.

En esta entrada no voy a documentar cada clase, cada método y cada parámetro de la librería. Para eso está la documentación completa del proyecto. Lo que quiero es recorrer el flujo principal con ejemplos prácticos. Veremos cómo tratar la señal, cómo identificar el proceso, cómo obtener una sintonía y cómo terminar controlándolo. Porque al final el objetivo no es tener un PID. El objetivo es controlar algo sin convertir el experimento en una investigación posterior de la compañía de seguros.

Instalación

La librería puede instalarse manualmente desde el archivo ZIP del proyecto o directamente desde el gestor incluido en Arduino IDE.

Instalación manual desde el archivo ZIP

La forma más sencilla durante el desarrollo o cuando queremos instalar una versión concreta consiste en descargar el archivo desde aquí o desde Github.

En el menú seleccionamos

Sketch
  -> Include Library
     -> Add .ZIP Library

Seleccionamos el ZIP descargado y Arduino copiará automáticamente la librería en su carpeta de bibliotecas. Después de la instalación podremos utilizarla simplemente incluyendo los módulos que necesitemos.

#include <PIDControl.h>
#include <PIDControlFilters.h>

Si además queremos utilizar identificación o sintonización podemos añadir

#include <StepTest.h>
#include <RelayTest.h>
#include <PIDTuning.h>

No es necesario incluir todos los módulos en todos los proyectos. Si únicamente queremos utilizar el controlador PID podemos incluir solo PIDControl.h.

Instalación desde el gestor de librerías del IDE

Cuando PIDControl esté disponible en el repositorio de librerías de Arduino, la instalación será todavía más sencilla. Abrimos el gestor desde

Sketch
  -> Include Library
     -> Manage Libraries

Buscamos PIDControl, seleccionamos la versión que queramos instalar y pulsamos Instalar. Arduino se encargará de descargar la librería y dejarla disponible para todos nuestros proyectos. Esta será la opción más cómoda para la mayoría de usuarios porque también permite recibir las nuevas versiones desde el propio IDE sin tener que descargar manualmente cada ZIP.

Tratamiento de la señal

Para los ejemplos de esta entrada voy a utilizar una placa TCLab montada sobre un Arduino Uno R4 WiFi. No hay nada especialmente mágico en esta combinación. Un Arduino Uno R3, un Leonardo o cualquier otra placa compatible con el formato habitual de pines de Arduino puede servir perfectamente siempre que adaptemos, si fuera necesario, la lectura analógica y la generación de la salida.

Utilizo TCLab simplemente porque la tengo disponible y porque resulta especialmente cómoda para este tipo de pruebas. Tenemos una variable de proceso fácil de entender, la temperatura, una fuente de calor cuya potencia podemos modificar y una dinámica suficientemente lenta como para poder ver con claridad qué está haciendo el controlador sin necesitar un laboratorio entero alrededor.

Tampoco es necesario disponer de una TCLab para seguir los ejemplos. Un montaje casero con una sonda de temperatura y algún elemento capaz de aportar calor puede cumplir exactamente la misma función. Podemos utilizar una resistencia calefactora, un transistor de potencia, una pequeña lámpara o cualquier otro sistema cuya potencia podamos controlar desde Arduino mediante PWM o una etapa de potencia adecuada. Lo importante no es el hardware concreto, sino disponer de una variable que podamos medir y de una salida que produzca una respuesta observable sobre ella.

En nuestro caso trabajaremos principalmente con la temperatura T1 como PV y con la potencia aplicada al primer calefactor Q1 como OP. El segundo sensor de temperatura T2 nos servirá también para observar cómo evoluciona térmicamente el conjunto, aunque no intervenga directamente en el lazo de control principal.

Antes de pensar en identificar el proceso o en calcular una sintonía conviene resolver algo bastante menos glamuroso, pero mucho más importante de lo que parece. La señal que entregamos al controlador tiene que ser mínimamente fiable.

Una lectura analógica nunca es completamente perfecta. Podemos encontrarnos ruido, pequeñas variaciones de ADC, interferencias, algún valor aislado incorrecto o simplemente una señal que cambia unas pocas centésimas continuamente aunque físicamente el proceso apenas se haya movido. Si alimentamos directamente al controlador con todo eso, el PID hará exactamente lo que le hemos pedido. Reaccionará también a cosas que probablemente no nos interesan.

PIDControl incluye varios filtros para tratar estos casos antes de que la señal llegue al controlador.

Filtro de media móvil

La media móvil toma las últimas muestras y calcula su valor medio.

MovingAverageFilter<10> filtro;

double PV = filtro.Update(PVRaw);

Es probablemente el filtro más sencillo de entender y funciona bien cuando tenemos ruido aleatorio aproximadamente simétrico alrededor del valor real.

El precio que pagamos es retardo. Cuantas más muestras incluyamos en la ventana, más suave será la señal, pero también más tardará el filtro en reflejar un cambio real del proceso.

Por eso aumentar el número de muestras hasta que la gráfica quede perfectamente recta no es necesariamente una buena idea. Una señal preciosa que llega varios segundos tarde sigue siendo una mala señal para controlar.

Filtro de mediana

El filtro de mediana resulta especialmente útil cuando el problema no es tanto el ruido continuo sino los picos aislados.

40,02
40,04
57,81
40,03
40,05

La temperatura difícilmente ha saltado diecisiete grados durante una décima de segundo para volver inmediatamente después a su valor anterior. Lo más probable es que esa muestra sea simplemente incorrecta.

MedianFilter<5> filtro;

double PV = filtro.Update(PVRaw);

Con un filtro de mediana ordenamos las muestras y nos quedamos con el valor central. En el ejemplo anterior, el 57,81 prácticamente desaparece del problema.

Para señales procedentes de sensores analógicos es uno de mis filtros favoritos como primera barrera. No intenta suavizar todo indiscriminadamente, sino eliminar aquellas muestras que claramente no encajan con las demás.

Filtro paso bajo

Después de eliminar posibles picos todavía podemos tener pequeñas oscilaciones continuas. Para este caso resulta útil un filtro paso bajo de primer orden.

LowPassFilter filtro(1.0);

double PV = filtro.Update(PVRaw, dt);

El parámetro 1.0 representa su constante de tiempo en segundos. Cuanto mayor sea esta constante de tiempo, más suavizada estará la señal y más lenta será también su respuesta.

PIDControl utiliza la expresión:

α=1eΔt/τα=1−e^{−Δt/τ}

y actualiza la salida mediante:

PVf(k)=PVf(k1)+α[PV(k)PVf(k1)]PV_{f}​(k)=PV_{f}(k−1)+α[PV(k)−PV_{f}(k−1)]

Esto tiene una ventaja importante frente a utilizar simplemente un factor arbitrario entre cero y uno, ya que estamos definiendo el comportamiento del filtro mediante una magnitud física, su constante de tiempo.

Combinar mediana y paso bajo

Puede ser interesante usar una combinación de ambos filtros. Primero eliminamos los posibles picos mediante una mediana de cinco muestras y posteriormente suavizamos el ruido restante con un paso bajo.

MedianFilter<5> pvMedian;
LowPassFilter pvLowPass(1.0);

double pvMedianValue = pvMedian.Update(PVRaw);
double PV = pvLowPass.Update(pvMedianValue, dt);

La mediana se ocupa principalmente de las muestras anómalas y el paso bajo del ruido continuo.

Filtro complementario

PIDControl también incluye un filtro complementario, aunque este tiene un propósito diferente.

No está pensado simplemente para suavizar una única señal. Su utilidad aparece cuando tenemos dos estimaciones diferentes de la misma variable y cada una tiene ventajas en una zona distinta.

Un ejemplo clásico sería combinar un giroscopio, muy bueno detectando cambios rápidos pero que acumula deriva, con un acelerómetro, que proporciona una referencia absoluta más lenta pero estable. Un maqueta típica en la que se usa es en un balancín como el que implementé hace unos años, aunque cualquier cosa que queramos controlar que lleve un giroscopio necesita al menos un filtro complementario.

ComplementaryFilter filtro(0.98);

double valor = filtro.Update(
    estimacionRapida,
    referenciaLenta
);

En nuestro ejemplo térmico no tenemos dos medidas independientes de T1 con características complementarias, por lo que no tendría demasiado sentido utilizarlo. Tener una herramienta disponible no obliga a utilizarla. Algo que también conviene recordar cuando empezamos a añadir filtros al código como si estuviéramos coleccionando Pokémon.

Qué filtro elegir

Como regla práctica, podemos pensar en algo parecido a esto.

Problema observadoFiltro que probaría primero
Pequeño ruido aleatorioMedia móvil
Picos aisladosMediana
Ruido continuo en señal analógicaPaso bajo
Picos más ruido continuoMediana + paso bajo
Dos estimaciones complementariasComplementario

No existe una configuración universal. Dependerá del sensor, del tiempo de muestreo y, sobre todo, de la velocidad del proceso que queremos controlar.

En procesos lentos como una temperatura podemos permitirnos cierto filtrado sin afectar demasiado al comportamiento global. En un sistema muy rápido, utilizar un filtro excesivamente lento puede convertirse en otro problema más.

Esto es especialmente importante cuando queremos identificar el proceso. El algoritmo de identificación no sabe que hemos colocado un filtro delante de la PV. Para él, ese retardo adicional forma parte del proceso observado. Si filtramos demasiado, podemos terminar identificando un tiempo muerto mayor del que realmente tiene la planta.

Por tanto, el objetivo no consiste en conseguir la curva más bonita posible. Consiste en eliminar suficiente ruido como para que la señal sea útil sin ocultar la dinámica que queremos estudiar.

Una vez que tenemos una PV razonablemente limpia podemos pasar al siguiente problema. Averiguar cómo responde realmente nuestro proceso cuando modificamos la salida. Ahí es donde entran los métodos de identificación de PIDControl.

Identificar el proceso

Una vez que tenemos una señal razonablemente limpia llega el siguiente problema. Necesitamos averiguar cómo responde el proceso cuando modificamos la salida.

Podríamos empezar asignando valores a KcK_c, KiK_i y KdK_d, poner el controlador en automático y comprobar qué sucede. Es un método perfectamente válido si nuestro objetivo es convertir la puesta en marcha en una actividad de riesgo.

La alternativa consiste en identificar primero el proceso.

PIDControl incorpora dos métodos distintos para hacerlo:

  • StepTest identifica el proceso a partir de uno o varios escalones de salida
  • RelayTest provoca una oscilación controlada alrededor de un punto de trabajo

Los dos persiguen información diferente y, por tanto, no son métodos intercambiables.

Identificación mediante StepTest

El ensayo de escalón es probablemente el método más intuitivo. Partimos de un proceso estabilizado y modificamos la salida una cantidad conocida. Después observamos cuánto cambia la PV y cuánto tarda en hacerlo. En nuestro caso podemos comenzar, por ejemplo, con la TCLab estabilizada al 20 % de potencia, es decir, ponemos la OP al 20% y esperamos a que estabilice.

MAN 20

A continuación lanzamos el ensayo:

STEP(10,2,UP,POS,0.3,60,0.10,0.4,30,0,60,15,45,0,80,0,100)

A primera vista parece la contraseña del WiFi del vecino, pero realmente solo estamos indicando las condiciones del ensayo.

En este ejemplo estamos pidiendo:

Escalon de OP          10 %
Numero de escalones    2
Primer escalon         UP
Ganancia esperada      positiva
Banda estable          +/-0.3 C
Tiempo estable         60 s
Pendiente maxima       0.10 C/min
Respuesta minima       0.4 C
Tiempo maximo          30 min
Limites OP             0..60 %
Limites PV             15..45 C
Rango completo PV      0..80 C
Rango completo OP      0..100 %

La librería espera primero a que el proceso esté estable. Después aplica el escalón, observa la respuesta, espera de nuevo el estacionario y repite el proceso si hemos solicitado más de un salto.

Todo se realiza de forma no bloqueante, por lo que el loop() de Arduino continúa ejecutándose normalmente. El objetivo es obtener un modelo de primer orden con tiempo muerto o FOPDT.

G(s)=KpeT0sTps+1G(s)=\frac{K_{p}​e^{−T_{0}​s}​}{T_{p​}s+1}

Los tres parámetros principales son Kp que representa la ganancia del proceso, T0 que representa el tiempo muerto y Tp que representa la constante de tiempo.

Imaginad que en una de las pruebas realizadas con TCLab hemos obtenido aproximadamente:

Kp = 0.431
T0 = 27.5 s
Tp = 147.2 s

Esto ya nos dice bastante sobre el proceso. Sabemos cuánto cambia aproximadamente la temperatura por cada incremento de salida, cuánto tarda en comenzar a apreciarse la respuesta y con qué velocidad evoluciona después.

La librería también puede calcular la ganancia normalizada cuando conocemos los rangos completos de PV y OP.

Esto resulta útil para comparar procesos expresados en unidades distintas, aunque para calcular posteriormente la sintonía de nuestro proceso utilizaremos normalmente la ganancia en unidades de ingeniería.

¿Por qué uso varios escalones?

Podríamos identificar el proceso utilizando un único cambio de salida, pero realizar varios nos permite comprobar si el comportamiento es razonablemente repetible.

Step1: 20 % -> 30 %
Step2: 30 % -> 20 %

En un sistema perfectamente lineal deberían ser muy parecidos, pero en el mundo real probablemente no lo sean.

En TCLab, por ejemplo, la transferencia de calor cambia con la temperatura y con las pérdidas hacia el ambiente. El proceso que vemos alrededor del 30 % de potencia no tiene por qué comportarse exactamente igual que alrededor del 60 %. Esto no significa que la identificación haya fallado. Significa que acabamos de descubrir que la física sigue negándose a leer nuestros modelos matemáticos.

Identificación mediante RelayTest

El segundo método incluido en PIDControl utiliza un ensayo de relé. En lugar de aplicar un escalón y esperar a que el proceso alcance un nuevo estacionario, hacemos oscilar la salida alrededor de un punto de trabajo. Supongamos que tenemos el proceso estabilizado aproximadamente con 40%.

MAN 40

Podemos lanzar directamente:

RELAY(10,0.5,3,CURRENT,15,20,60,15,48)

donde:

Amplitud del rele      10 %
Histeresis             0.5 C
Ciclos validos         3
Referencia             PV actual
Tiempo maximo          15 min
Limites OP             20..60 %
Limites PV             15..48 C

Como el bias inicial es del 40 % y la amplitud es del 10 %, la salida irá alternando entre 50% y 30%.

Cuando la PV supera la referencia más la histéresis, la salida cambia de estado. Cuando cae por debajo de la referencia menos la histéresis, vuelve al estado contrario. El resultado es una oscilación controlada alrededor del punto de funcionamiento.

De esa oscilación obtenemos principalmente dos parámetros, Ku que es la ganancia última y Tu que es el periodo último.

Con estos parámetros podemos calcular un número más limitado de sintonías PI y PID que con el método anterior en lazo abierto, ya que la identificación realizada también es más limitada.

StepTest o RelayTest

Los dos métodos sirven para identificar el proceso, pero la información que proporcionan es distinta.

MétodoResultadoCaracterística
StepTestKpK_p, T0T_0, TpT_pObtiene un modelo dinámico FOPDT
RelayTestKuK_u, TuT_uObtiene las condiciones de oscilación última

StepTest resulta especialmente interesante cuando queremos disponer de un modelo del proceso y utilizar métodos de sintonización basados en él.

RelayTest evita tener que esperar varios estados estacionarios y nos permite obtener directamente los parámetros necesarios para métodos basados en KuK_u y TuT_u.

La elección dependerá del proceso y de cuánto podamos perturbarlo. En ambos casos conviene realizar los ensayos alrededor de una zona de operación representativa. Identificar un proceso a 25 °C para después trabajar permanentemente a 75 °C puede producir resultados bastante menos emocionantes de lo que esperamos.

Dicho esto, siempre que el proceso nos lo permita conviene realizar un StepTest, ya que esto nos brinda una gama más amplia de sintonías y lo más importante, simularlas en lazo cerrado.

Recuperar una identificación anterior

Hay una situación especialmente irritante durante una prueba larga. Esperas varios minutos a que termine la identificación, obtienes unos resultados estupendos y entonces consigues borrar la ventana del puerto serie. Por eso PIDControl conserva en memoria el último resultado válido de cada ensayo.

Podemos recuperarlos con

IDENT		// Recupera los dos métodos     
IDENT STEP	// Recupera solo Kp, T0 y Tp
IDENT RELAY	// Recupera Ku y Tu

Los resultados permanecen en memoria aunque limpiemos el monitor serie. Naturalmente desaparecen si reiniciamos o apagamos Arduino.

Sintonizar el controlador

Una vez identificado el proceso ya tenemos la información necesaria para calcular una primera sintonía razonable. La idea de PIDControl es que el usuario no tenga que terminar un ensayo, copiar resultados, buscar una tabla en Internet y empezar a hacer cuentas por separado.

La librería utiliza el resultado de la identificación y ofrece directamente una propuesta PI y otra PID. No las aplica automáticamente. Primero las calcula, las muestra y deja que decidamos cuál queremos probar. Me parece una forma bastante más sensata de trabajar que terminar un ensayo y entregar inmediatamente el control a la primera combinación de parámetros que aparezca. El método de sintonización utilizado depende de cómo hayamos identificado el proceso.

Sintonización después de StepTest

Cuando utilizamos StepTest obtenemos un modelo del proceso basado en su ganancia, su tiempo muerto y su velocidad de respuesta. A partir de ese modelo PIDControl calcula dos alternativas.

La primera es una sintonización IMC / Lambda PI. Es una buena opción cuando queremos un controlador sencillo y robusto, especialmente en procesos lentos como temperatura, nivel o muchas aplicaciones habituales de control de procesos. La segunda es una Lambda PID. Utiliza el mismo modelo identificado, pero añade acción derivativa.

En ambos casos podemos decidir cuánto queremos exigir al controlador mediante tres niveles de respuesta, AGGRESSIVE , NORMAL y ROBUST. AGGRESSIVE busca una respuesta más rápida, ROBUST sacrifica velocidad a cambio de una respuesta más conservadora y NORMAL queda entre ambas. También podemos introducir manualmente el valor Lambda si queremos controlar directamente ese compromiso.

Mi primera elección en un proceso nuevo sería normalmente comenzar con la PI y utilizar la PID únicamente si realmente necesitamos mejorar la respuesta y disponemos de una señal suficientemente limpia. Añadir derivada porque existe la opción suele ser una forma bastante eficaz de crear un problema que antes no teníamos.

Sintonización después de RelayTest

RelayTest proporciona otro tipo de información. En este caso obtenemos Ku y Tu, que describen el comportamiento del proceso alrededor de una oscilación mantenida. A partir de estos valores PIDControl calcula dos propuestas mediante Tyreus-Luyben, una es una sintonización PI y la otra una PID.

He elegido Tyreus-Luyben como método integrado porque proporciona normalmente una respuesta menos agresiva que los ajustes clásicos de Ziegler-Nichols y me parece un punto de partida más razonable para una librería que pretende utilizarse sobre procesos reales y no únicamente para conseguir una gráfica espectacular durante treinta segundos.

Elegir entre PI y PID

Que la librería pueda calcular ambas alternativas no significa que debamos utilizar siempre la PID. En muchos procesos un PI es más que suficiente. Tiene menos parámetros, suele ser más fácil de ajustar y es menos sensible al ruido. Añadir derivada empieza a tener más interés cuando la dinámica está bien identificada, la medida es razonablemente limpia y necesitamos mejorar la respuesta utilizando también la velocidad de cambio de la PV.

Como criterio inicial podemos pensar así:

SituaciónPrimera opción
Proceso térmico o lentoPI
Señal con bastante ruidoPI
Proceso sencillo y establePI
Señal limpia y dinámica bien conocidaPI o PID
Necesitamos una respuesta más anticipativaPID

Aplicar la sintonización

PIDControl trabaja con Kc, Ki y Kd, por lo que las sintonizaciones calculadas pueden aplicarse directamente al controlador. Desde la consola utilizamos:

TUNE Kc Ki Kd       // por ejemplo TUNE 4.448 0.0112 0

El comando anterior aplicaría una sintonización PI porque el término derivativo es cero. Si posteriormente queremos probar la propuesta PID simplemente introducimos los valores correspondientes que haya calculado la librería.

En resumen:

IDENT
Muestra todas las identificaciones almacenadas

IDENT STEP
Muestra Kp, T0 y Tp junto con las sintonías
IMC / Lambda PI y Lambda PID

IDENT RELAY
Muestra Ku y Tu junto con las sintonías
Tyreus-Luyben PI y PID

TUNE Kc Ki Kd
Aplica una sintonización al controlador

STATUS
Muestra la configuración y estado actual

Con esto ya hemos recorrido una buena parte de la cadena. Primero hemos tratado la señal, después hemos identificado el proceso y finalmente hemos obtenido una sintonización PI o PID.

El siguiente paso es comprobar si todo ese trabajo sirve para algo útil. Ya podemos pasar al control en MAN y AUTO y ver cómo responde el proceso cuando dejamos que PIDControl tome las decisiones.

Configurar el controlador

Una vez tratada la señal, identificado el proceso y obtenida una sintonización, ya podemos configurar el controlador. Buena parte de esta configuración se realiza en el propio momento en que declaramos el objeto PIDControl, aunque la librería incluye unos valores por defecto pensados para cubrir una configuración habitual y reducir al mínimo el código necesario.

La declaración más corta necesita únicamente las variables de proceso y los tres parámetros de sintonización.

PIDControl pid(&PV, &OP, &SP, Kc, Ki, Kd);
PIDControl pid(&PV, &OP, &SP, 4.45, 0.15, 0.0);

Con esta declaración la librería utiliza automáticamente sus valores por defecto. El controlador se crea en modo MAN, con estructura PI_D, acción REVERSE, PV Tracking activado, límites de salida entre 0 y 100 % y un tiempo de muestreo de 100 ms. La salida manual inicial toma el valor que tenga OP en el momento de crear el controlador. Kc, Ki y Kd no tienen valores por defecto, ya que debemos proporcionarlos nosotros al construir el objeto.

ParámetroValor por defecto
ModoMAN
EstructuraPI_D
AcciónREVERSE
PV TrackingON
OP mínima0
OP máxima100
Tiempo de muestreo100 ms

Para muchos procesos esta configuración puede utilizarse directamente. En nuestro ejemplo con TCLab coincide además con la configuración que queremos utilizar, por lo que la declaración corta es suficiente.

La librería permite también utilizar la declaración completa. Puede resultar útil cuando queremos dejar explícita la configuración en el código o cuando necesitamos utilizar una estructura, una acción o un comportamiento de seguimiento diferentes de los valores por defecto.

PIDControl pid(&PV, &OP, &SP, Kc, Ki, Kd, PIDType::PI_D, PIDAction::REVERSE, true);
PIDControl pid(&PV, &OP, &SP, 4.45, 0.15, 0.0, PIDType::PI_D, PIDAction::REVERSE, true);

Acción de control

Antes de poner un controlador en automático debemos comprobar siempre que la acción de control es correcta. La forma más sencilla de elegirla consiste en preguntarnos qué debe hacer la OP cuando aumenta la PV. En nuestro proceso térmico, si aumenta la temperatura queremos reducir la potencia del calefactor, por lo que necesitamos acción REVERSE. Este es precisamente el valor por defecto de PIDControl, por lo que en nuestro ejemplo no necesitamos configurar nada adicional. Si trabajáramos con un proceso en el que al aumentar la PV también debiera aumentar la OP utilizaríamos DIRECT.

La acción puede modificarse en cualquier momento desde código o desde la consola.

ACTION REVERSE
ACTION DIRECT

Aunque REVERSE sea el valor por defecto, conviene entender bien este parámetro antes de pasar a AUTO. Elegir una acción incorrecta convierte la realimentación negativa que pretendíamos conseguir en realimentación positiva, algo que suele conseguir que el experimento deje de ser aburrido bastante rápido.

Estructura del controlador

PIDControl permite trabajar con las estructuras PID, PI_D e I_PD. La estructura utilizada por defecto es PI_D, que también es la que vamos a emplear en TCLab.

En PI_D las acciones proporcional e integral trabajan sobre el error mientras que la derivativa trabaja sobre la PV. Esto evita el salto de la acción derivativa cuando modificamos el SP.

Podemos cambiar la estructura posteriormente si lo necesitamos.

STRUCT PID
STRUCT PI_D
STRUCT I_PD

PV Tracking

El PV Tracking también está activado por defecto. Mientras el controlador permanece en manual, el SP interno sigue a la PV mientras se conserva por separado la consigna solicitada por el usuario. Esto facilita posteriormente el paso de MAN a AUTO porque el controlador comienza desde una situación coherente con el estado actual del proceso y puede realizar una transferencia sin un salto brusco de salida. Por tanto, con la declaración corta tampoco necesitamos activar expresamente esta función. Si queremos modificar su comportamiento podemos hacerlo mediante:

TRACK ON
TRACK OFF

Límites de la salida

Los límites de salida son por defecto de 0 a 100 %, que ya encajan con nuestro ejemplo. Solo sería necesario modificarlos si el actuador o el proceso requieren otro rango.

OUTLIMITS 0 100

PIDControl incorpora además anti-windup. Cuando la OP alcanza alguno de estos límites, la integral no continúa creciendo en una dirección que intentaría llevar todavía más lejos una salida que ya está saturada.

Tiempo de muestreo

El tiempo de muestreo por defecto es de 100 ms, que también utilizaremos en nuestras pruebas con TCLab. De nuevo, este comando no es necesario mientras queramos conservar el valor por defecto. Podemos modificarlo cuando la dinámica del proceso o la adquisición de datos requieran otro periodo de ejecución.

TS 100

Puesta en marcha

Llegados a este punto ya tenemos el controlador configurado y podemos ponerlo a trabajar. También conviene aclarar que PIDControl puede utilizarse directamente desde aquí, sin necesidad de pasar previamente por filtros, StepTest, RelayTest ni métodos de sintonización. Si ya conocemos unos valores razonables de Kc, Ki y Kd, podemos cargarlos y comenzar a controlar el proceso. Todo lo anterior no es obligatorio para utilizar la librería, aunque merece la pena comprenderlo porque nos permite trabajar con una señal mejor tratada, conocer cómo responde realmente el proceso y obtener una sintonización con algo más de fundamento que el tradicional método de prueba, error y esperanza.

La puesta en marcha debería comenzar siempre en modo manual. De esta forma podemos llevar el proceso hasta una zona de operación razonable y comprobar que la relación entre PV y OP es la esperada antes de entregar el control al PID. En nuestro ejemplo con TCLab podemos comenzar fijando la potencia al 40 %

MAN 40

Esperamos a que la temperatura se encuentre cerca de una zona estable y cargamos la sintonización que queramos utilizar. Si los valores ya se proporcionaron al declarar el objeto no sería necesario hacerlo de nuevo, pero durante las pruebas resulta muy cómodo utilizar TUNE para cambiar de sintonización sin recompilar el programa.

TUNE 4.45 0.15 0

Con Kd igual a cero estaremos trabajando como un controlador PI. Una vez comprobado que la acción de control, la estructura y los límites son correctos podemos realizar el paso a automático.

AUTO

Como el PV Tracking está activado por defecto, el SP interno ha estado siguiendo a la PV mientras permanecíamos en manual. PIDControl utiliza esta situación para realizar una transferencia bumpless e intentar mantener la OP existente en el momento del cambio, evitando que el simple paso de MAN a AUTO provoque un salto innecesario en la salida.

A partir de ese momento podemos modificar la consigna.

SP 50

También podemos realizar el paso a automático y establecer directamente el nuevo SP mediante una única orden.

AUTO 50

El controlador comenzará entonces a modificar la OP para llevar la PV hacia la consigna. En nuestro proceso térmico aumentará la potencia mientras la temperatura esté por debajo del SP y la irá reduciendo a medida que se aproxime al objetivo. Si posteriormente cambiamos la consigna, el controlador volverá a ajustar la salida de acuerdo con la nueva situación.

Este es el mínimo necesario para comenzar a utilizar PIDControl. Todo lo anterior nos ayuda a llegar hasta aquí con una señal mejor tratada, una identificación del proceso y una sintonización calculada, pero la librería no obliga a seguir ese recorrido completo. Se puede empezar directamente por el control y profundizar después, aunque normalmente entender lo que ocurre antes de pulsar AUTO suele salir bastante más barato que intentar entenderlo después.

Código de ejemplo

#include <PIDControl.h>
#include <PIDControlFilters.h>

using PIDControlFilters::MedianFilter;
using PIDControlFilters::LowPassFilter;

// ------------------------------------------------------------
// Hardware TCLab
// ------------------------------------------------------------

const uint8_t PIN_T1 = A0;
const uint8_t PIN_Q1 = 3;

// Arduino UNO R4 + TCLab
const uint8_t ADC_BITS = 14;
const double ADC_REFERENCE_V = 3.300;

// TCLab limita Q1 a PWM = 200 cuando OP = 100 %
const uint8_t Q1_PWM_MAX = 200;

// ------------------------------------------------------------
// PID
// ------------------------------------------------------------

double SP = 40.0;
double PV = 0.0;
double OP = 0.0;

double Kc = 4.45;
double Ki = 0.15;
double Kd = 0.0;

// PI-D, REVERSE y PV Tracking activado
PIDControl pid(
    &PV, &OP, &SP,
    Kc, Ki, Kd,
    PIDType::PI_D,
    PIDAction::REVERSE,
    true
);

// ------------------------------------------------------------
// Filtrado
// ------------------------------------------------------------

MedianFilter<5> medianFilter;
LowPassFilter lowPassFilter(1.0);

const unsigned long Ts_ms = 100;
unsigned long previousTime = 0;

// ------------------------------------------------------------
// Lectura de temperatura
// ------------------------------------------------------------

double ReadTemperatureC()
{
    // Se descarta la primera conversion
    analogRead(PIN_T1);

    const int adc = analogRead(PIN_T1);

    const double adcMax =
        (double)((1UL << ADC_BITS) - 1UL);

    const double voltage =
        adc * ADC_REFERENCE_V / adcMax;

    // Sensor TMP36 de TCLab
    return (voltage - 0.5) * 100.0;
}

// ------------------------------------------------------------
// Salida calefactor
// ------------------------------------------------------------

void WriteHeater(double percent)
{
    if (percent < 0.0)
        percent = 0.0;

    if (percent > 100.0)
        percent = 100.0;

    const int pwm =
        (int)(percent * Q1_PWM_MAX / 100.0);

    analogWrite(PIN_Q1, pwm);
}

// ------------------------------------------------------------
// Setup
// ------------------------------------------------------------

void setup()
{
    Serial.begin(115200);

    pinMode(PIN_Q1, OUTPUT);
    analogWrite(PIN_Q1, 0);

    analogReference(AR_EXTERNAL);
    analogReadResolution(ADC_BITS);

    pid.SetOutputLimits(0.0, 100.0);
    pid.SetSampleTime(Ts_ms);

    // Empezamos en manual
    pid.SetManualOutput(30.0);
    pid.SetMode(PIDMode::MAN);

    previousTime = millis();
}

// ------------------------------------------------------------
// Loop
// ------------------------------------------------------------

void loop()
{
    const unsigned long now = millis();

    if ((unsigned long)(now - previousTime) < Ts_ms)
        return;

    const double dt =
        (now - previousTime) / 1000.0;

    previousTime = now;

    // 1. Leer temperatura
    const double PVRaw =
        ReadTemperatureC();

    // 2. Eliminar picos
    const double PVMedian =
        medianFilter.Update(PVRaw);

    // 3. Suavizar ruido
    PV =
        lowPassFilter.Update(PVMedian, dt);

    // 4. Calcular PID
    pid.Compute();

    // 5. Aplicar salida
    WriteHeater(OP);

    // 6. Monitorizar
    Serial.print("SP=");
    Serial.print(SP, 2);

    Serial.print(" PVRaw=");
    Serial.print(PVRaw, 2);

    Serial.print(" PV=");
    Serial.print(PV, 2);

    Serial.print(" OP=");
    Serial.println(OP, 2);
}

Enlaces

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Información básica sobre protección de datos
Responsable Garikoitz Martínez Moreno +info...
Finalidad Gestionar y moderar tus comentarios. +info...
Legitimación Consentimiento del interesado. +info...
Destinatarios Automattic Inc., EEUU para filtrar el spam. +info...
Derechos Acceder, rectificar y cancelar los datos, así como otros derechos. +info...
Información adicional Puedes consultar la información adicional y detallada sobre protección de datos en nuestra página de política de privacidad.