Sintonizar PID con Arduino – Balancín simple

Sintonizar PID con Arduino – Balancín simple

Introducción

Aquí tenéis la segunda entrega sobre sintonización PID. En la primera entrega vimos un control de temperatura y en esta veremos un control de posición de un balancín simple.

Sistema empleado

Se trata de un balancín simple con inclinación en un solo eje, que consta de un motor de corriente continua con hélice y un giroscopio / acelerómetro que hará de sensor para calcular la inclinación.

La maqueta está hecha con caña de bambú y madera y consta de solo 2 componentes principales, el motor de corriente continua y el acelerómetro. El motor de corriente continua es de 5V pero necesita una fuente de alimentación externa ya que consume entre 750mA y 1,8A.

maqueta del balancín

El objetivo es mediante un controlador PID situar el balancín en el ángulo de inclinación deseado. Nos movemos en un rango de entre 30 y -30º.

Esquema de control

Componentes

* El microinterruptor nos sirve para conmutar entre Automático y Manual.

** En manual el potenciómetro altera la OP. En automático altera el SP.

El coste de los componentes es de aproximadamente 30-40€ sin contar la fuente de alimentación externa.

Esquema de conexiones

Conexiones del sistema

Código de Arduino

Notas:

  • Para evitar problemas y errores se han convertido a porcentaje todas las variables.
  • Para la conversión de rangos con decimales se ha implementado la función MAPF, ya que la función MAP nativa de Arduino devuelve simplemente un long integer.
/***************************************************************
* Desarrollado por Garikoitz Martínez [garikoitz.info] [06/2020]
* https://garikoitz.info/blog/?p=890
***************************************************************/
/***************************************************************
* Librerías
***************************************************************/
#include "I2Cdev.h"
#include "MPU6050.h"
#include <Wire.h>
#include <PID_v1.h>
/***************************************************************
* Variables
***************************************************************/
  //Hélice
  const int INA = 10; //Motor Forward
  const int INB = 11; //Motor Reverse
  const int Interruptor = 12;
  const int pot = 0;                                                                                //Potenciómetro en pin A0
  unsigned long previousMillis = 0;
  int Ts = 100; //Sample time 100ms
  //Giroscópio
  const int mpuAddress = 0x68;  //Puede ser 0x68 o 0x69
  MPU6050 mpu(mpuAddress);
  //Offsets de calibración
  #define MPU6050_ACCEL_OFFSET_X -2936
  #define MPU6050_ACCEL_OFFSET_Y 1412
  #define MPU6050_ACCEL_OFFSET_Z 962
  #define MPU6050_GYRO_OFFSET_X  58
  #define MPU6050_GYRO_OFFSET_Y  85
  #define MPU6050_GYRO_OFFSET_Z  15
  int ax, ay, az;
  int gx, gy, gz;
  float ang_x, ang_y;
  float ang_x_prev, ang_y_prev;
  long tiempo_prev;
  float dt;
  //Control PID
  boolean modo = false; //false=auto true=manual
  float tmp = 0;
  float tmp_ant = 0;
  float OP = 0;
  double SetpointP, InputP, OutputP;
  double Kp=6, Ki=3, Kd=2;
  PID myPID(&InputP, &OutputP, &SetpointP, Kp, Ki, Kd,P_ON_E, DIRECT);    //PI-D
  //PID myPID(&InputP, &OutputP, &SetpointP, Kp, Ki, Kd,P_ON_M, DIRECT);  //I-PD
/***************************************************************
* SETUP
***************************************************************/
void setup() 
{ 
  //Hélice
  pinMode(INA,OUTPUT); 
  pinMode(INB,OUTPUT); 
  pinMode(Interruptor, INPUT);
  //PID 
  myPID.SetSampleTime(50);        //PID calcula cada 50ms. Por defecto es cada 100ms.
  myPID.SetOutputLimits(0, 100);
  myPID.SetMode(MANUAL);
  //Giroscópio
  mpu.initialize();
  //Calibración
  mpu.setXAccelOffset(MPU6050_ACCEL_OFFSET_X);
  mpu.setYAccelOffset(MPU6050_ACCEL_OFFSET_Y);
  mpu.setZAccelOffset(MPU6050_ACCEL_OFFSET_Z);
  mpu.setXGyroOffset(MPU6050_GYRO_OFFSET_X);
  mpu.setYGyroOffset(MPU6050_GYRO_OFFSET_Y);
  mpu.setZGyroOffset(MPU6050_GYRO_OFFSET_Z);
  Serial.println(mpu.testConnection() ? F("IMU iniciado correctamente") : F("Error al iniciar IMU"));
  Serial.begin(9600);
}
/***************************************************************
* BUCLE PRINCIPAL
***************************************************************/
void loop() 
{
  if (millis() - previousMillis > Ts) 
  {
    previousMillis = millis();
    if (digitalRead(12) == HIGH){
      modo=true;//Manual
      myPID.SetMode(MANUAL);
    }else{
      digitalWrite(INB ,LOW);
      analogWrite(INA,OP);   //Forward
      modo=false;//Automático
      myPID.SetMode(AUTOMATIC);
    }
    mpu.getAcceleration(&ax, &ay, &az);
    mpu.getRotation(&gx, &gy, &gz);
    //Calcular los ángulos con acelerometro
    double accel_ang_x=atan(ay/sqrt(pow(ax,2) + pow(az,2)))*(180.0/3.14);
    double accel_ang_y=atan(-ax/sqrt(pow(ay,2) + pow(az,2)))*(180.0/3.14);
    dt = (millis()-tiempo_prev)/1000.0;
    tiempo_prev=millis();
    //Ángulo de rotación y filtro complementario 
    ang_x = 0.98*(ang_x_prev+(gx/131)*dt) + 0.02*accel_ang_x;
    ang_x_prev=ang_x;
    //
    /***************************************************************
    * Modo MANUAL - Potenciómetro ---> PWM Motor CC
    ***************************************************************/
    if (modo==true){
      SetpointP=0;
      //Leo el sensor
      InputP = mapf(ang_x, -30, 30, 0, 100);          //Filtro complementario
      //Leo el potenciómetro
      //Evitar saltos del 0.85% al leer el potenciómetro
      tmp = analogRead(pot);
      tmp = mapf(tmp, 0, 1023, 0, 100);
      if(abs(tmp-tmp_ant)>0.85){
        OP = tmp;
      }
      tmp_ant=OP;
      digitalWrite(INB ,LOW);
      analogWrite(INA,OP/0.392156);   //FORWARD 0-100 a 0-255 para PWM
      /***************************************************************
      * DEBUG PUERTO SERIE (Para Arduino COM Plotter)
      ***************************************************************/
      Serial.print("#");             //Char inicio
      Serial.print(SetpointP,0);     //SP
      Serial.write(" ");             //Char separador
      Serial.print(InputP,3);        //PV
      Serial.write(" ");             //Char separador
      Serial.print(OP,0);            //OP
      Serial.println();
    }
    /***************************************************************
    * Modo AUTOMÁTICO - PID
    ***************************************************************/
    if (modo==false){
      tmp = analogRead(pot);
      tmp = mapf(tmp, 0, 1023, 0, 100);
      //Evitar saltos del 0.85% al leer el potenciómetro
      if(abs(tmp-tmp_ant)>0.85){
        SetpointP = tmp;
      }
      tmp_ant=SetpointP;
      InputP = mapf(ang_x, -30, 30, 0, 100);
      myPID.Compute();
      digitalWrite(INB ,LOW);
      analogWrite(INA,OutputP/0.392156); //FORWARD 0-100 a 0-255 para PWM
      /***************************************************************
      * DEBUG PUERTO SERIE (Para Arduino COM Plotter)
      ***************************************************************/
      Serial.print("#");                //Char inicio
      Serial.print(SetpointP,0);        //SP
      Serial.write(" ");                //Char separador
      Serial.print(InputP,3);           //PV
      Serial.write(" ");                //Char separador
      Serial.print(OutputP,0);          //OP
      Serial.println();
    }
  }
}
/***************************************************************
* FUNCIONES
***************************************************************/
//Función MAP adaptada a punto flotante.
double mapf(double val, double in_min, double in_max, double out_min, double out_max) {
    return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Identificación del sistema

Adecuando la señal del sensor

Del sensor recibimos 6 medidas, 3 aceleraciones y 3 velocidades angulares. Nosotros nos centraremos en las aceleraciones y en concreto en el ángulo theta del eje X. No me explayo más ya que este sensor da para una entrada del blog completa, os dejo en referencias unos artículos para que juzguéis vosotros mismos e indaguéis todas las posibilidades.

Cálculo del ángulo Theta del eje x

Las medidas resultantes del sensor son muy oscilantes y además debemos tener en cuenta el «drift» generado al integrar la velocidad angular con respecto del tiempo para hallar el ángulo deseado. La forma más sencilla de minimizar el drift es con un filtro complementario y la más elegante con un filtro de Kalman. El filtro complementario combina el ángulo calculado por el giroscopio con el ángulo calculado por el acelerómetro pudiendo dar más peso a uno o a otro dependiendo de nuestras necesidades. El filtro de Kalman es mucho más complejo pero a cambio el resultado es bastante mejor. En la imagen inferior vemos que ambos filtros hacen bien su trabajo pero que el filtro de Kalman está afectando a la respuesta del sistema por lo que habría que retocar los parámetros hasta encontrar los idóneos.

Señal RAW VS Filtro complementario VS Filtro de Kalman

Hay que tener cierta precaución al utilizar filtros de cualquier tipo si lo que te planteas es hacer control con la señal filtrada. El filtro nos da una señal «limpia» pero a cambio el tiempo de respuesta es alterado. En resumen, cada caso requiere una reflexión que afectará a la sintonía de control.

Por sencillez implementaré un filtro complementario con la ecuación 1 y con los parámetros α=0.98 y β=0.02.

\begin{align}
& X = \alpha \left ( \left (X-1  \right )+\omega_{giroscopio} \ast dt  \right )+\beta\left ( \measuredangle \hspace{0.1cm} acelerometro \right )\\
& \alpha +\beta =1
\end{align}

Recopilación de datos en lazo abierto

Con el interruptor en modo manual realizamos una serie de escalones para ver como reacciona el sensor con la ayuda de ACP.

Step Test

Introduciendo los datos en ACP de un escalón representativo obtenemos las constantes del sistema que nos ayudarán a simular y a ajustar nuestro controlador.

Constantes del sistema obtenidas con ACP

Facilidad de control

El cociente entre T0 y Tp nos sugiere la facilidad de control de un sistema. En este caso estamos en la zona fácil ya que 1/1,5=0,66.

Facilidad de control

Ajuste y simulación en lazo cerrado

En la siguiente tabla tenemos las sintonías calculadas por ACP. ACP nos da Ti y Td pero la librería de arduino utiliza Ki y Kd por lo que hay que realizar la siguiente conversión:

\begin{align}
& K_{i}=\frac{K_{c}}{T_{i}} \hspace{0.2cm};\hspace{0.2cm} K_{d}={K_{c}}\times {T_{d}}
\end{align}
Sintonías calculadas por ACP

Resultados

A continuación veamos la respuesta de unas cuantas sintonías. El tiempo de muestreo es de 100ms.

CC-25 (PI)

Como se aprecia en la imagen, un pequeño cambio en el punto de consigna hace que entre en oscilación continua. Posiblemente en esta maqueta, un sobre pasamiento del 25% con una estrategia PI no sea adecuado.

Respuesta real
Simulación en lazo cerrado

CC-25 (PI-D)

Al incluir el término derivativo ganamos en estabilidad. Ya no entra en oscilación continua aunque variemos el punto de consigna >10%.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 10%
Simulación en lazo cerrado

CC-10 (PI)

En esta ocasión con un sobrepasamiento del 10% la estrategia PI consigue mantenerse estable aunque la respuesta no es igual a la simulación.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 5%
Simulación en lazo cerrado

CC-10 (PI-D)

De nuevo al incorporar el término derivativo se gana en estabilidad.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 11%
Simulación en lazo cerrado

CC-10 (I-P)

Al utilizar un algoritmo I-P la respuesta del sistema se vuelve similar a una sintonía lambda o cualquier otra que utilice el parámetro «Tf». Esto es debido a que el algoritmo utiliza la PV en lugar del error en la constante proporcional suavizando la respuesta considerablemente.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 9%
Simulación en lazo cerrado

CC-10 (I-PD)

La respuesta es similar a la respuesta I-P pero al incluir el término derivativo, la salida se ha vuelto más oscilante.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 13%
Simulación en lazo cerrado

IAE-SP (PI)

Inicialmente aunque con más oscilaciones de las esperadas el control se mantiene estable, pero finalmente entra en oscilación continua tras una perturbación no medida.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 7%
Simulación en lazo cerrado

IAE-SP (PI-D)

El término derivativo da estabilidad incluso con cambios en el punto de consigna del 15%.

Respuesta real
Respuesta real – Detalle de un cambio en el SP del 10%
Simulación en lazo cerrado

LAMBDA (PI)

Con este método podemos ajustar mediante el parámetro Tf cuando queremos que la PV se iguale al SP. En este caso se ha utilizado un Tf = 5 por lo que la PV igualará al SP en 5 unidades de tiempo.

Respuesta real

Haciendo un cálculo rápido tenemos que la PV y el SP se deberían igualar en el segundo 5×5+67,7 = 92,7. Como vemos, en la respuesta real hay un ligero sobrepasamiento inicial igualándose unos segundos más tarde, aunque dado que existe ruido en la PV se puede considerar se asemeja bastante con la simulación.

Respuesta real – Detalle de un cambio en el SP del 8%
Simulación en lazo cerrado

Conclusiones

Maqueta sencilla y control relativamente sencillo. Una vez identificado, todo depende de la respuesta que estemos buscando. Si buscamos una respuesta rápida nos decantaremos por métodos clásicos o métodos que busquen minimizar el índice de error. Si lo que buscamos es una respuesta suave podemos decantarnos por cualquier método afectado por el parámetro «Tf» o usar un algoritmo I-P / I-PD para suavizar la respuesta del sistema. Así mismo, si nos empeñamos en utilizar un algoritmo PI nos vemos obligados a recurrir a sintonías que utilizan el parámetro «Tf» o bien usar un algoritmo I-P.

Casi todos los métodos obtienen una respuesta adecuada y bastante fiel a la simulación y me reitero, la simulación debe ser similar a la respuesta real, de lo contrario algo falla.

Balancín simple con sintonía CC-10

Descargas

Referencias

Enlaces

Libros y publicaciones

[1] Aidan O’Dwyer. Handbook of PI and PID controller tuning rules, 3rd edition, Imperial College Press. ISBN: 978-1-84816-242-6

[2] Karl J. Astrom, Tore Hagglund. Control PID avanzado, Pearson, ISBN: 978-84-8322-511-0

[3] Daniel Chuck. Los sistemas de primer orden y los controladores PID, edición 2012. [Link] [Link2]

[4] J.G. Ziegler, N.B. Nichols. Optimum Settings For Automatic Controllers, 1942 edition, American Society of Mechanical Engineers. [Link]

[5] G.H. Cohen, G.A. Coon. Theoretical Consideration of Retarded Control, 1953 edition, American Society of Mechanical Engineers. [Link] [Link2]

[6] Daniel E. Rivera. Internal Model Control: A Comprehensive View, 1999 edition, College of Engineering and Applied Sciences. [Link]

[7] R. Vilanova, A. Visioli. PID Control in the Third Millennium. Chapter 5, The SIMC Method for smooth PID Controller Tuning, Springer. ISBN: 978-1-4471-2424-5.

[8] Chriss Grimholt, Sigurd Skogestad. The improved SIMC method for PI controller tuning, IFAC-conference PID’12, Brescia, Italy, March 2012. [Link]

[9] Guillermo J. Silva, Aniruddha Datta, S.P. Bhattacharyya. PID Controllers for Time-Delay Systems. Chapter 10, Analysis of Some PID Tunning Techniques. Birkhäuser. ISBN: 0-8176-4266-8

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.