Estrategias de control con Arduino

Estrategias de control con Arduino

Introducción

El proyecto elegido para llevar a cabo con Arduino y en el que desarrollaremos diferentes tipos de controles,  consiste en dos depósitos de agua de 13 litros de capacidad interconectados mediante una bomba. El depósito 1 se considera el principal y por lo tanto se implementará un control de nivel mediante PID y un control de temperatura del agua mediante un sistema ON/OFF con histéresis.

Figura 1: Esquema general de la planta
Figura 1: Esquema general de la planta

Control ON/OFF con histéresis

Para el control ON/OFF con histéresis, tal como se muestra en la figura 1, controlaremos la temperatura del depósito 1 mediante la sonda de temperatura DS18B20 que actuará de sensor y el calentador que hará las veces de actuador.

En la figura 2 se puede apreciar el diagrama de flujo del control ON/OFF con histéresis. Se trata de un control tradicional en lazo cerrado por lo que el actuador entra y sale repetidas veces hasta situarse en la temperatura de referencia.

Figura 2: Diagrama de flujo del control ON/OFF con histéresis
Figura 2: Diagrama de flujo del control ON/OFF con histéresis

Para minimizar el tiempo de funcionamiento del actuador y así alargar su vida útil, estableceremos una banda muerta o de tolerancia de modo que se amplíe la diferencia entre el tiempo de encendido y apagado (histéresis). El valor de la banda muerta dependerá de cada sistema y de su análisis.

PID

Para el control PID, tal y como muestra la figura 1, utilizaremos el sensor ultrasónico para medir el nivel y la bomba como actuador. El objetivo es tratar de controlar el nivel del depósito 1.

El diagrama de flujo de la planta es el siguiente:

Figura 3: Diagrama de flujo del control PID
Figura 3: Diagrama de flujo del control PID

Control por eventos

El control por eventos implica que es un evento el que hace que muestreamos una señal y no se haga periódicamente como es más habitual. Obviamente el tiempo no desaparece nunca del cálculo, ya que necesitamos tener una referencia a la hora de detectar eventos y muestrear señales.

Figura 4: Diagrama de flujo del control por eventos en PID
Figura 4: Diagrama de flujo del control por eventos en PID

Para ello generaremos una serie de funciones que nos ayudaran en que situación está nuestra planta y si debemos lanzar un evento o podemos esperar al siguiente.

Componentes utilizados

Arduino Uno R3

La placa elegida para el desarrollo del proyecto es Arduino Uno R3. Esta placa dispone de 13 entradas/salidas digitales, de las cuales 6 se pueden utilizar como PWM,  y 5 entradas analógicas. Dispone de conectividad suficiente para pequeños proyectos aunque con ciertos componentes como el display LCD se hace necesario la utilización del bus I2C para economizar entradas y salidas.

Figura 5: Arduino UNO R3.
Figura 5: Arduino UNO R3.

Sensor HC-SR04

Como sensor de nivel utilizaremos un sensor ultrasónico de bajo costo. Además de su reducido precio, ofrece otras ventajas ya que no le afectan ni la luz solar ni materiales oscuros. Dispone de dos transductores, básicamente un micrófono y un altavoz que son los encargados de enviar y recibir la señal. El cálculo de la distancia se basa en la velocidad del sonido en el aire, 340 m/s a 20ºC, teniendo en cuenta que por cada grado centígrado de aumento la velocidad aumenta 0,6 m/s. Las características principales se describen a continuación:

Figura 6: Sensor HC-SR04.
Figura 6: Sensor HC-SR04.
  • Pines utilizados: 2 (TRIGGER, ECHO) + 2 (alimentación).
  • Rango de medición entre 20 y 4000mm (±3mm).
  • Ángulo efectivo de medición: <15º.
  • Consumo en reposo: <2mA
  • Consumo en funcionamiento: 15mA.
Figura 7: Esquema de conexión HC-SR04.
Figura 7: Esquema de conexión HC-SR04.

Sonda DS18B20

Se trata de una sonda impermeable con una resolución configurable de entre 9 y 12 bits y un rango de medición de -55 a 125ºC (±0,5ºC). Funciona bajo la interfaz 1-Wire que requiere un solo hilo para su conexión, llevando cada sensor un numero identificativo de 64 bits grabado internamente para su distinción.

Figura 8: Sonda DS18B20.
Figura 8: Sonda DS18B20.
Figura 9: Esquema de conexión DS18B20.
Figura 9: Esquema de conexión DS18B20.

GDM2004D – Display 20×4

Con el fin de visualizar el estado de los diferentes sensores, utilizaremos un display de 20 caracteres por 4 columnas. Nativamente el display necesita la conexión de 6 pines digitales para su funcionamiento.

Figura 10: Display GDM2004D.
Figura 10: Display GDM2004D.

Con el fin de simplificar y economizar la conexión con Arduino, usaremos un adaptador I2C que nos reduce los pines necesarios a 2.

Figura 11: Adaptador LCD - I2C
Figura 11: Adaptador LCD – I2C
Figura 12: Esquema de conexión adaptador I2C. Fuente: https://www.robotics.org.za/image/data/Display/I2C_display/LCD2004_03_arduino.jpg

Micro bomba de agua

Utilizaremos una micro bomba (55x35mm) de 12V sin escobillas, sumergible (clase IP68) y capaz de proporcionar 240 L/H. El consumo nominal es de 400mA (5W). Debido a que trabaja a 12V y con el fin de poder regularla en un rango aceptable para nuestro proyecto, utilizaremos un transistor NPN para su conexión con arduino.

Figura 13: Micro bomba.
Figura 14: Esquema de conexión de la micro bomba.

Calentador

Se trata de un calentador parcialmente sumergible con un rango de trabajo de 20 a 34ºC. Funciona a 220V por lo que su accionamiento desde arduino se hará a través de un relé.

Figura 15: Calentador
Figura 16: Esquema de conexión del calentador mediante relé.

Modelo matemático

A continuación se desarrollará un modelo matemático pragmático que nos servirá sobre todo para desarrollar el apartado de control por eventos.

Figura 17: Esquema del depósito
  • Datos
    • a = 0,18m, b = 0,4m, c = 0,18m
    • Qe = entre 3,05*10-5 m³/s y 4,71*10-5 m³/s
    • Qs = 3,33×10-5m³/s

Conocidas las medidas del prisma rectangular obtenemos el volumen.

(1)

El volumen real lo podemos obtener en todo momento sabiendo la altura actual del nivel del depósito.

(2)

Conocido el volumen en cada instante podemos obtener el tiempo de llenado o vaciado. Para el tiempo de vaciado (tv) usamos el volumen calculado previamente. Para el tiempo de llenado (tl) necesitamos el volumen que nos queda por llenar.

(3)
(4)

Desarrollo del proyecto

Arduino proporciona un máximo de 40 mA por salida, debido a eso utilizaremos una fuente de alimentación externa de 5 y 12V para garantizar el buen funcionamiento de todos los equipos. En total usaremos entre entradas y salidas 13 pines, 5 analógicos y 8 digitales.

Figura 18: Relación de pines utilizados.

El panel de mando es físico como se puede apreciar en la figura 19. En la parte izquierda están situados los controles pertenecientes a la parte automática y a la derecha los manuales. En el display tenemos toda la información necesaria para el control PID y ON/OFF con histéresis. También se dispone de un modo manual para pruebas y ajustes.

Figura 19: Panel de mando
Figura 20: Depósito 1
Figura 20: Depósito 1

La purga se realiza mediante una bomba estrangulada en la impulsión, debido a que por sus características no admite regulación mediante PWM. La bomba de purga se puede apreciar en la figura 20.

Figura 21: Depósito 2

La bomba principal (bomba B1) se muestra en la figura 21. Esta bomba admite regulación PWM pero tiene un rango muerto entre 0 y 5,5V (0 – 117 PWM), algo que se tendrá en cuenta al ajustar los parámetros del PID y la purga.

Control ON/OFF con histéresis

Figura 22: Sistema ON/OFF con histéresis.

Para este sistema desarrollaremos un control clásico de temperatura. Al tratarse de un calentador de 220V, se actuará mediante un relé, que para evitar que no esté entrando y saliendo de forma continua, se establecerá una banda muerta. Tras analizar el comportamiento del sistema, se establece una tolerancia de 0,5ºC como se puede observar en la figura 23.

Figura 23: Gráfica del control ON/OFF con histéresis.

El rango útil del calentador está entre 20 y 30ºC y leeremos la temperatura mediante un sensor de temperatura situado en el depósito 1. Para simplificar, el setpoint de la temperatura será un número entero.

Código Arduino resumido

Leemos el setpoint introducido mediante el potenciómetro y comprobamos que la diferencia con la temperatura real no es mayor o igual a medio grado centígrado. Con el fin de obtener una respuesta más rápida en la temperatura, y puesto que el calentador y la sonda se encuentran en depósitos separados, el nivel está variando entre 65 y 100mm de forma continua, de este modo se garantiza el movimiento de flujo de agua entre depósitos.

A continuación se muestra el código principal del control. Para ver el código completo saltar al apartado 7.

//SetPoint regulable Temperatura
float sett=analogRead(A2);
SPtemp=map(sett,0,1023,30,15);
//Calculo el error
ErrorTemp = SPtemp-temp;
//Banda muerta de 0.5ºC
if(ErrorTemp<=0.5){ 
   digitalWrite(13, LOW);
}else{
   digitalWrite(13, HIGH);
}
…
// Nivel entre 65 y 100mm de forma continua
      	if (bypassPID==true){//No hago caso al setpoint
        if (mediamm>=100){
          analogWrite(3, 0);
        }
        if (mediamm<=65){
          analogWrite(3, 255);
        }
      	}else{
        analogWrite(3, control);
}
…

Muestreo de datos con Matlab

Aun favoreciendo el movimiento del agua entre depósitos, la respuesta de la temperatura es bastante lenta como muestra la figura 24.

Figura 24: Datos de temperatura recogidos con Matlab

Control PID

Lectura recomendada: Introducción al algoritmo PID y su implementación en Arduino

La implementación del PID se hará sobre un control de nivel simple. El nivel lo obtendremos en milímetros mediante un sensor de ultrasonidos y en función del setpoint de referencia actuaremos sobre una micro bomba de corriente contínua aprovechando una salida PWM de arduino. La microbomba es de 12V de modo que utilizaremos un transistor NPN para hacer funcionar a la bomba, ya que arduino no es capaz de aportar ese voltaje. En resumen, transmitiremos la señal PWM (0-5V) a la base del transistor y este dejerá pasar entre colector y emisor los 12V de forma proporcional a la señal transmitida por la base. El rango útil de trabajo de la bomba está entre 5,5 y 12V (117 – 255 PWM).

Figura 25: Sistema PID.

Código Arduino resumido

Solamente se muestra el código parcial del PID, para ver el código completo de arduino saltar al apartado 7.

El PID es un sistema de control robusto y eficiente, pero requiere de ciertas precauciones a la hora de implementarlo. A continuación se detallan las características principales incluidas.

  • Tiempo de cálculo. El PID funciona mejor cuanto menos tiempo pase entre cálculos, por ello mismo se ha establecido el tiempo de cálculo en 10ms.
  • Filtro: La variable más importante en el control, es el nivel, por esto mismo, se utiliza la media de las últimas cinco medidas a modo de filtro.
  • Paso de automático a manual. Con el fin de economizar potencia de cálculo, cuando estemos en modo manual simplemente dejaremos de calcular los valores del PID.
  • Anti Windup. Cuando se llega a saturar el actuador, la salida del controlador deja de corresponderse con la acción aplicada al proceso. Para ello, limitamos la acción integral entre un mínimo y un máximo con el fin de no llegar a la situación mencionada anteriormente.
…
float Kp = 25;
float Kd = 10; 
float Ki = 1;
float I; // Valor Integral
float D; // Valor Derivativo
//AUTOMÁTICO----------------------------------------------------
if (modo==false){
    //SetPoint regulable NIVEL
    float set=analogRead(A1);
    SetPoint=map(set,0,1023,120,50);
    // PID
    // Error-----------------------------------------------------
    ErrorAnt=Error;
    Error= SetPoint- mediamm;
    // Derivada--------------------------------------------------
    D=Error-ErrorAnt;
    // Limito Integral (anti-windup)
    int ok = 3; //damos por bueno -> banda muerta de 3mm
    int nook = 10; //10mm de diferencia, dejo actuar
    if(abs(I)>ok && abs(I)<nook){
       I=I+mediamm*Ki;
    }else {
       I=0;
    }
    //
    control=Kp*Error+Kd*D+I;
    //Límites del controlador
    if(control<=0){
        control=0;
    } 
    if(control>=255){
        control=255;
    } 
    if(control<=165 && control>=50){
        	 control=175;
    }
    // Actuo sobre la bomba
      analogWrite(3, control);
}
    …

Muestreo de datos con Matlab

Mediante un script en Matlab (ver apartado 10.2),recogeremos los datos que envía Arduino por el puerto serie (USB). El objetivo es recoger los datos suficientes para poder analizar posteriormente nuestro sistema y optimizar los datos de control.

Muestreamos el nivel, el setpoint del nivel y la salida pwm de la bomba. Las muestras se toman cada 100ms.

Figura 26: Datos muestreados antes del ajuste de constantes.

En la figura 26 vemos diferentes cambios de nivel sin optimizar los parámetros del PID.

Figura 27: Datos muestreados con constantes ajustadas.

En la figura 27 podemos ver los diferentes cambios de nivel que hemos muestreado. Partimos de 69mm y terminamos en 100mm. Vemos que la bomba se encuentra en el rango de acción especificado (165 – 255) y que la PV sigue al SP de forma correcta.

Ajuste manual del PID

Según la tabla 1 podemos sintonizar nuestro PID mediante prueba error. Nuestro sistema es sencillo, por lo que la sintonización no debería suponer mucho problema. Las pruebas se realizan en lazo cerrado empezando por un control proporcional (P) para pasar a un control PI y finalmente un PID.

Tabla 1: Efecto de los parámetros de control

Empezamos anulando los parámetros  Ki y Kd y poniendo un valor de 100 en Kp para ver la respuesta que tiene el sistema. La respuesta es muy brusca por lo que vamos bajando sucesivamente el valor hasta que el valor que nos satisface es 25. A continuación hacemos lo propio con el parámetro Ki. En este caso llegamos a la conclusión de que necesitamos un valor bajo para que la respuesta sea adecuada, ya que valores superiores a 1 hacen que la respuesta sea demasiado anticipativa. Finalmente buscamos el valor para Kd. Los valores obtenidos manualmente se indican en la tabla 2.

Tabla 2: Valores obtenidos de forma manual

Con estos parámetros obtenemos una respuesta adecuada. Se puede ver un ejemplo del funcionamiento del proceso en el siguiente video:

Ajuste del PID con Matlab

Como hemos visto anteriormente, el ajuste de las constantes del PID se realizó mediante prueba y error. Para optimizar estos valores, haremos uso de Matlab, de la herramienta de identificación de sistemas (System Identification Tool) y de la herramienta PID Tunning.

Lanzamos la herramienta de identificación de sistemas mediante el comando IDENT e importamos nuestros datos.

Figura 28: System Identification Tool – Importar datos

Los datos que vamos a importar están en el dominio del tiempo y las variables que nos interesan son la bomba (PWM) y el nivel (mm), el tiempo de muestreo (Sampling interval) es de 0.1 segundos.

Figura 29: Datos importados

A continuación procesamos el modelo y lo exportamos al espacio de trabajo como “P1”.

Figura 30: System Identification Tool – Procesado

Una vez que tenemos nuestro modelo en el espacio de trabajo abrimos PID tunning e importamos el modelo. PID tunning nos brinda la oportunidad de ajustar los parámetros del control PID mediante visualización de la respuesta escalón del proceso analizado. Podemos ver la respuesta escalón y los parámetros obtenidos en las figuras 31 y 32.

Figura 31: PID tunning. Respuesta escalón.
Figura 32: PID tunning. Parámetros obtenidos.

Finalmente obtenemos los valores de las constantes para la respuesta elegida. El objetivo es minimizar el tiempo de respuesta y el sobreimpulso. Podemos ver una comparativa de los parámetros de control en la tabla 3.

Tabla 3: Comparativa de los valores obtenidos

Control por eventos

Para desarrollar el control por eventos, usaremos el modelo matemático desarrollado anteriormente. La idea es analizar tanto el control ON/OFF con histéresis como el PID y optimizar los consumos.

En el caso del control ON/OFF con histéresis, con los datos muestreados podemos afirmar que para subir un grado centígrado necesitamos aproximadamente 250 segundos. El sistema disipa temperatura aproximadamente a un ritmo de 1,5ºC/h por lo que el calentador actuará 3 veces por hora durante 750 segundos, para mantenerse en la temperatura de referencia. En este caso en concreto, la mejor opción para este sistema de control sería economizar el consumo durmiendo a Arduino y despertarle mediante interrupción. De este modo solamente despertaría cuando fuera necesario para activar el calentador. Si nuestro sistema de control contara con más relés, y estos se activaran de forma asíncrona, desarrollar un control por eventos si sería beneficioso, ya que la naturaleza del sistema nos impediría dormir el microcontrolador. En nuestro control de temperatura, no hay lugar a dudas, implementar el control por eventos es malgastar tiempo y recursos, ya que, por su simplicidad  podemos economizar consumos al máximo durmiendo el microcontrolador. Tras el análisis del sistema y los consumos obtenidos, podemos afirmar que utilizando la estrategia de dormir el microcontrolador, estamos ahorrando 30mA con respecto al estado de reposo, esto es un 70% de consumo menos.

Tabla 4: Consumo control ON/OFF con histéresis.

El control PID, al contrario que el caso anterior, es más efectivo cuanto menor sea el muestreo, pero una alta tasa de muestreo implica mayor comunicación con el actuador, mayor consumo y menor vida útil de los elementos que componen el sistema. Una de las maneras en que economiza consumos el control por eventos es minimizando la comunicación con el actuador. Para minimizar la acción del actuador necesitamos muestrear muy rápido y tener nuestro sistema modelado. Supongamos que inicialmente nuestro tiempo de muestreo era de 1 segundo, para implementar en control por eventos, establecemos el tiempo de muestreo en 100ms, de modo que ahora muestreamos 10 veces más que antes pero eso no significa que el actuador trabaje 10 veces más, sino que disponemos de más datos para decidir si debe actuar o no. Para que el control sea eficiente debemos además, de establecer ciertos límites como un tiempo máximo en el que el actuador llevará a cabo una acción obligada, y el error máximo que nos podemos permitir para que el sistema siga siendo estable y nos dé tiempo a actuar. Para establecer un tiempo máximo de actuación, deberemos analizar el sistema en detalle. En función del tiempo máximo, estableceremos un error máximo adecuado.

Figura 33: Gráfica de muestreo rápido.

El caudal de entrada (Qe) lo hemos obtenido de forma experimental midiendo el tiempo que tarda en llenar un vaso de 250ml a diferentes voltajes.

Figura 34: Relación PWM y L/min de la bomba B1.

A continuación, hallamos el tiempo de llenado o vaciado que usaremos como tiempo máximo que no debemos sobrepasar. Para ello usaremos las ecuaciones planteadas en el modelo matemático.

Teniendo en cuenta que:

  • a = 0,18m, b = 0,4m, c = 0,18m
  • Qe = entre 3,05×10-5 m³/s y 4,71×10-5 m³/s
  • Qs = 3,33×10-5m³/s
Tabla 5: Tabla de tiempos.

Obtenida la tabla de tiempos, podemos establecer el tiempo máximo en 30 segundos sin perder la estabilidad del sistema.

Finalmente estableceremos un error máximo entre el nivel actual y el setpoint de 10mm, de este modo economizaremos actuaciones pero controlando el sistema en todo momento.

A continuación veamos el código PID original y el PID basado en eventos.

Tras el cálculo del error, establecemos una condición de actuación solamente si el error es mayor o igual a 10mm y si por el contrario llegamos a los 300 muestreos. Recordemos que el muestreo se realiza cada 100ms, por lo que 30000ms / 100ms = 300. Si cualquiera de las opciones se da, calculamos el control y reiniciamos el contador de muestreo.

Tabla 6: Consumos control PID.

Cada vez que no se ejecuta la acción de control, estamos ahorrando entre 290mA con la bomba principal parada, a 13 mA en régimen de control, dependiendo del consumo puntual de los elementos del sistema. También hay que tener en cuenta que cada vez que no ejecutamos una acción estamos preservando la vida útil de ciertos elementos del sistema, lo que supone un ahorro a largo plazo.

Código Arduino

//===============================================================
// Librerías
//===============================================================
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>                 
#include <LiquidCrystal_I2C.h>   
//===============================================================
// Variables globales & Constantes
//===============================================================
#define ONE_WIRE_BUS 2
//                             
OneWire oneWire(ONE_WIRE_BUS); 
DallasTemperature sensores(&oneWire);
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);//Direccion de LCD
//
DeviceAddress S1 = {0x28, 0xFF, 0xAA, 0x58, 0x2D, 0x04, 0x00, 0xBD};  //Tª Tanque
unsigned long time = 0; // tiempo de ejecucion del ultimo ciclo
int period = 50; // Periodo de muestreo en ms
int periodo = 1000; // Periodo de muestreo en ms
boolean debug = false;//false=matlab
boolean modo = false;//false=auto true=manual
boolean bypassPID = false;//FALSE=PID normal TRUE=nivel 65mm-100mm
String modos = "";
float temp;
int Trig_pin, Echo_pin; //Trigger = pin 9 | ECHO = pin 8  
int n=5; // valores para media
int v[5];
unsigned long mm;
int mediamm;
unsigned long lastmm;
int senal;
//
float Kp = 25;//19.45;
float Kd = 0.1;//0.0058;
float Ki = 0.3;//0.1;
float I; // Valor Integral
float D; // Valor Derivativo
int Error;
int ErrorAnt;
float control;
unsigned long SetPoint;
int SPtemp;
float ErrorTemp;
//===============================================================
// SETUP
//===============================================================
void setup(void)
{
  Serial.begin(9600); //Abrimos la comunicación por serial
  sensores.begin();   //Iniciamos sensor Tª
  lcd.begin(20,4);    //Indicamos tipo de LCD  
  //lcd.backlight();
  Ultrasonido(9,8);   //Trigger = pin 9 | ECHO = pin 
  mm = microsecondsToMeasure(duracion());
  v[0] =mm;
  v[1] =mm;
  v[2] =mm; 
  v[3] =mm; 
  v[4] =mm;       
}
//===============================================================
// BUCLE PRINCIPAL
//===============================================================
void loop(void)
{
    //Nivel
    mm = microsecondsToMeasure(duracion());
    // Calculo la media del NIVEL
      for (int i=0; i<n-1; i++){ // Utilizamos array
        v[i] =v[i+1];
      }
      v[n-1]= mm;//(mm - lastmm);
      mediamm=0;
      for (int i=0; i<n; i++){ // Calculamos la media
        mediamm = mediamm+ v[i];
      }
      mediamm = mediamm/n;
      mediamm = 170-mediamm;//Nivel en parte superior
    //Temperatura
    sensores.setResolution(S1, 9);//0,5ºC resolución
    sensores.requestTemperatures();
    Mostrar_Datos(S1);
  if (digitalRead(12) == HIGH){
    modo=true;
    modos="MANUAL";
  }else{
      modo=false;
      modos="AUTOMATICO";
   }
   //Purga
   //if (digitalRead(7) == HIGH && mediamm <=120){
   // analogWrite(5, 255);
   //}else{
   // analogWrite(5, 0);
   //}
 //MODO MANUAL-------------------------------------------------
 //-------------------------------------------------------------      
  if (modo==true){
       //Bomba en MANUAL
      int bomba=0;
      float niv=analogRead(A0);
      bomba=map(niv,0,1023,0,255);
      analogWrite(3, bomba);
      //Temperatura en MANUAL
         //Relé calentador
      if (digitalRead(7) == HIGH){
       digitalWrite(13,HIGH);
       }else{
       digitalWrite(13,LOW);
       }
      //LCD en MANUAL
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("====== ");
      lcd.print(modos);
      lcd.print(" ======");
      //
      lcd.setCursor(0,1);
      lcd.print("Temperatura ");             
      lcd.print(temp);
      lcd.print(" \337C");
      //
      lcd.setCursor(0,2);
      lcd.print("Bomba ");
      lcd.print(bomba*0.0470588);
      lcd.print(" V");
      //
      lcd.setCursor(0,3);
      lcd.print("Nivel ");
      lcd.print(mediamm);
      lcd.print(" mm");
      //
      }
  //AUTOMÁTICO----------------------------------------------------
  if (modo==false){
    // MODO AUTOMÁTICO---------------------------------------------
    //------------------------------------------------------------- 
    //SetPoint regulable NIVEL
    float set=analogRead(A1);
    SetPoint=map(set,0,1023,50,120);
    //-----------------------------
    // PID
    // Error-----------------------------------------------------
      ErrorAnt=Error;
      Error=SetPoint-mediamm;
      //if(abs(Error)<=3){ //Banda muerta de 5mm
      //  Error=0;
      //} 
    // Derivada--------------------------------------------------
      D=Error-ErrorAnt;
    // Limito Integral
      int ok = 3; //damos por bueno -> banda muerta de 5mm
      int nook = 10; //8mm de diferencia, dejo actuar
      if(abs(Error)>ok && abs(Error)<nook){
        I=I+mediamm*Ki;
      } 
      else {
        I=0;
      }
    //
      control=Kp*Error+Kd*D+I;
      if(control<=0){
        control=0;
        senal=0;
      } 
      if(control>=255){
        control=255;
        senal=255;
      } 
      if(control<=165 && control>=50){
        senal=175;
        control=175;
      } else{
        senal=control;
      }
      // Actuo sobre la bomba
      if (bypassPID==true){//No hago caso al setpoint
        if (mediamm>=100){
          analogWrite(3, 0);
        }
        if (mediamm<=65){
          analogWrite(3, 255);
        }
      }else{
        analogWrite(3, control);
      }
    //
  //ON-OFF con histéresis------------------------
  //SetPoint regulable Temperatura
  float sett=analogRead(A2);
  SPtemp=map(sett,0,1023,30,15);
  //Calculo el error
  ErrorTemp = SPtemp-temp;
  //Banda muerta de 0.5ºC
  if(ErrorTemp<=0.5){ 
     digitalWrite(13, LOW);
    }else{
     digitalWrite(13, HIGH);
  }
    //LCD en AUTO
     lcd.clear();
     lcd.setCursor(0,0);
    lcd.print("==== ");
    lcd.print(modos);
    lcd.print(" ====");
    //
    lcd.setCursor(0,1);
    lcd.print("Nivel ");
    lcd.print(mediamm);
    lcd.print(" mm");
    lcd.print(" SP ");
    lcd.print(SetPoint);
    //
    lcd.setCursor(0,2);
    lcd.print("Bomba ");
    //control*=0,3921;
    lcd.print(control*0.3921);
    lcd.print(" %  ");
    lcd.print(senal);
    //
    lcd.setCursor(0,3);
    lcd.print("Temp ");             
    lcd.print(temp);
    lcd.print(" \337C");
    lcd.print(" SP ");
    lcd.print(SPtemp);
  }
  //MATLAB-------------------------------------------------
  if(debug==false){
  //(Nivel,SetPoint,control,temp,sptemp)
  //Serial.print(mediamm);
  //Serial.print(",");
  //Serial.print(SetPoint);
  //Serial.print(",");
  //Serial.print(control);
  //Serial.print(",");
    Serial.print(temp);
    Serial.print(",");
    Serial.print(SPtemp);
    Serial.print(",");
    //Serial.print(ErrorTemp);
  }else{
    //TEMPERATURA
    //Serial.print("T ");
    //Serial.print(temp);
    //Serial.print(" SP ");
    //Serial.print(SPtemp);
    //Serial.print(" Error");
    //Serial.print(ErrorTemp);
    //Serial.println("");
    //NIVEL
    //Serial.print("Nivel: ");
    //Serial.print(mediamm);
    //Serial.print(" SP: ");
    //Serial.print(SetPoint);
    //Serial.print(" Error: ");
    //Serial.print(Error);
    //Serial.print(" ErrorAnt: ");
    //Serial.print(ErrorAnt);
    //Serial.print(" Control: ");
    //Serial.print(control);
    //Serial.print(" Kp*Error: ");
    //Serial.print(Kp*Error);
    //Serial.print(" D: ");
    //Serial.print(D);
    //Serial.print(" I: ");
    //Serial.print(I);
    //Serial.println(""); 
  }
  delay (100);//Dura 0.1 segundos
 }
//===============================================================
// Subroutinas
//===============================================================
void Ultrasonido(int TP, int EP)
{
   pinMode(TP,OUTPUT);
   pinMode(EP,INPUT);
   Trig_pin=TP;
   Echo_pin=EP;
}
unsigned long duracion(void)
{
  digitalWrite(Trig_pin, LOW);
  delayMicroseconds(2);
  digitalWrite(Trig_pin, HIGH);
  delayMicroseconds(10);
  digitalWrite(Trig_pin, LOW); 
  return pulseIn(Echo_pin,HIGH);
}
unsigned long microsecondsToMeasure(unsigned long microseconds)
{
  //return microseconds * 0.017;  //(cm) Distancia = 340m/s * 10^2cm/m * 1s/10^6us * tiempo_us
  return microseconds * 0.17;  //(mm)Distancia = 340m/s * 10^3mm/m * * 1s/10^6us * tiempo_us
}

//Creamos una funcion para mostrar la direccion de los sensores
void Mostrar_Direccion(DeviceAddress direccion)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    if (direccion[i] < 16)
      //Serial.print("0");
    Serial.print(direccion[i], HEX);
  }
}

//Funcion que muestra la temperatura en grados centigrados del sensor
void Mostrar_Temperatura(DeviceAddress direccion)
{
  float tempC = sensores.getTempC(direccion);
  temp = tempC;
}

//Funcion que muestra la resolucion del sensor de dicha direccion. Las resoluciones posibles pueden ser:
//Resolucion a 9 bits 0.50 ºC
//Resolucion a 10 bits 0.25 ºC
//Resolucion a 11 bits 0.125 ºC
//Resolucion a 12 bits 0.0625 ºC
void Mostrar_Resolucion(DeviceAddress direccion)
{
  //Serial.print("Resolucion: ");
  //Serial.print(sensores.getResolution(direccion));
  //Serial.println();   
}

//Funcion que muestra los datos del sensor de dicha direccion
void Mostrar_Datos(DeviceAddress direccion)
{
  //Serial.print("Direccion del dispositivo: ");
  //Mostrar_Direccion(direccion);
  //Serial.print(" ");
  Mostrar_Temperatura(direccion);
}

Código Matlab lectura puerto serie del nivel

function Matlab_Arduino(muestras)

close all;
clc;
%Variables
global nivel;
global nivelsp;
global bomba;
global tiempo;
nivel=[];
nivelsp=[];
bomba=[];
delay=0.01; % delay de void loop en segundos
tiempo=[];

%Inicializo el puerto serial
delete(instrfind({'Port'},{'COM3'}));
puerto_serial=serial('COM3');
puerto_serial.BaudRate=9600;
warning('off','MATLAB:serial:fscanf:unsuccessfulRead');

%Abro el puerto serial
fopen(puerto_serial); 

%Declaro un contador del número de muestras ya tomadas
contador=1;

%Creo una ventana para la gráfica
%figure('Name','Comunicación serie')
figure
ax1 = subplot(2,1,1); % subplot superior
ax2 = subplot(2,1,2); % subplot inferior
grid off;
hold on;
 
%Bucle de toma de muestras
while (contador<=muestras)
        %axis tight
        %d           decimal numbers                         
        %e, %f, %g   floating-point numbers
        puertoserie=fscanf(puerto_serial,'%f,%f,%f')';
        nivel(contador)=puertoserie(1);
        nivelsp(contador)=puertoserie(2);
        bomba(contador)=puertoserie(3);
        plot(ax1,contador,nivel(contador),'X',contador,nivelsp(contador),'*');
        ylim(ax1,[0 150]); 
        xlim(ax1,[contador-5 contador+5]);
        title(ax1,'Nivel real (mm) y SetPoint (mm)')
        ylabel(ax1,'Niveles (mm)')
        xlabel(ax1,'Tiempo (s)')
        %
        plot(ax2,contador,bomba(contador),'X');
        ylim(ax2,[0 260]); 
        xlim(ax2,[contador-5 contador+5]);
        title(ax2,'PWM Bomba B1')
        ylabel(ax2,'PWM (0-255)')
        xlabel(ax2,'Tiempo (s)')
        drawnow
        contador=contador+1;
        pause(1);
end
%Auto-generamos la variable tiempo
for i=1:length(nivel)
    tiempo(i)=i*delay;
end
%Guardo las variables en el espacio de trabajo
assignin('base', 'Nivel', nivel);
assignin('base', 'NivelSP', nivelsp);
assignin('base', 'Bomba', bomba);
assignin('base', 'tiempo', tiempo);
putvar(nivel);
putvar(nivelsp);
putvar(bomba);
putvar(tiempo);
%Cierro y limpio 
fclose(puerto_serial); 
delete(puerto_serial);

end

Código Matlab lectura puerto serie de la temperatura

function Datos_temperatura(muestras)

close all;
clc;
%Variables
global temp;
global tempsp;
global tiempo;
temp=[];
tempsp=[];
delay=1; % delay de void loop en segundos
tiempo=[];

%Inicializo el puerto serial
delete(instrfind({'Port'},{'COM3'}));
puerto_serial=serial('COM3');
puerto_serial.BaudRate=9600;
warning('off','MATLAB:serial:fscanf:unsuccessfulRead');

%Abro el puerto serial
fopen(puerto_serial); 

%Declaro un contador del número de muestras ya tomadas
contador=1;

%Creo una ventana para la gráfica
%figure('Name','Comunicación serie')
figure
grid off;
hold on;
 
%Bucle de toma de muestras
while (contador<=muestras)
%for contador=1:length(muestras)
        %axis tight
        %d           decimal numbers                         
        %e, %f, %g   floating-point numbers
        puertoserie=fscanf(puerto_serial,'%f,%d');
        temp(contador)=puertoserie(1);
        tempsp(contador)=puertoserie(2);
        plot(contador,temp(contador),'X',contador,tempsp(contador),'*');
        ylim([10 30]); 
        xlim([0 contador+2]);
        title('Temperatura (ºC) y SetPoint (ºC)')
        ylabel('Temperaturas (ºC)')
        xlabel('Tiempo (s)')
        drawnow
        contador=contador+1;
        pause(0.01);
end
%Auto-generamos la variable tiempo
for i=1:length(Temperatura)
    tiempo(i)=i*delay;
end
%Guardo las variables en el espacio de trabajo
assignin('base', 'Temperatura', temp);
assignin('base', 'TemperaturaSP', tempsp);
assignin('base', 'tiempo', tiempo);
putvar(temp);
putvar(tempsp);
putvar(tiempo);
%Cierro y limpio
fclose(puerto_serial); 
delete(puerto_serial);

end

Galería de imágenes

Bibliografía

[1]    Arduino. Arduino board UNO. Especificaciones técnicas. [en línea]. Italia, 2016. Disponible en: https://goo.gl/31jbDl

[2]    Farnell Components SL. BC549C. Transistor NPN. Especificaciones técnicas. [en línea]. España, 2016. Disponible en: http://goo.gl/hB8KzL

[3]    Friends-of-Fritzing foundation. Fritzing. Software de diseño de esquemas electrónicos [en línea]. Alemania, 2016. Disponible en: http://goo.gl/3hta7i

[4] HC-SR04. Sensor ultrasonidos. Especificaciones técnicas. [en línea]. 2016. Disponible en: http://goo.gl/5jUiYM

[5]    Karl-Erik Arzén. A simple event-based PID controller. [en línea]. 14th IFAC World Congress Beijing, China, 1999. Disponible en: http://goo.gl/y0zzGx

[6]    Katsuhiko Ogata. Ingeniería de control moderna. 5ª edición. Editorial Pearson, 2010. ISBN: 978-84-8322-660-5

[7]    Massimo Banzi, David Cuartielles, Tom Igoe, Gianluca Martino, and David Mellis. Arduino IDE. Software de programación de Arduino  [en línea]. Italia, 2016. Disponible en: https://goo.gl/OibmBq

[8]    Massimo Banzi, David Cuartielles, Tom Igoe, Gianluca Martino, and David Mellis. Arduino. Referencia de programación de Arduino [en línea]. Italia, 2016. Disponible en: https://goo.gl/PhfOHk

[9]    MathWorks INC. Documentación técnica de Matlab [en línea]. España, 2016. Disponible en: http://goo.gl/CGNZyc

[10] Maxim integrated INC. DS18B20. Sonda de temperatura. Especificaciones técnicas. [en línea]. California, 2016. Disponible en: https://goo.gl/Mc9pjV

[11] S.Dormido. Educación en automática. Nuevos retos [en línea]. Madrid, 2016. Disponible en: http://goo.gl/1vj64j

[12] Xiamen Ocular Optics CO Ltd. GDM2004D. Display LCD. Especificaciones técnicas. [en línea]. China, 2016. Disponible en: https://goo.gl/4E87Ch

6 comentarios en «Estrategias de control con Arduino»

    1. En el sistema planteado, el calentador está regulado al máximo (34°C) y se activa y desactiva mediante un relé. De este modo se puede jugar con la temperatura encendiendo unos segundos o minutos para conseguir diferentes temperaturas. Eso sí, una vez caliente a cierta temperatura el agua, el enfriamiento es lento.

  1. Excelente trabajo, muy bien explicado, estoy realizando un proyecto con control PID y la verdad este ejemplo me está orientando mucho.

Responder a Garikoitz Martínez Moreno Cancelar la 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.