Problema con librería PID con relay

Hola a todos, estoy con problemas al utilizar la librería PID con salida a relay.
El proyecto es un sistema de control de calefacción de un vehículo el cual tiene una válvula ON/OFF que deja circular el agua. La temperatura es obtenida por un sensor DS18B20, la temperatura de referencia se modifica mediante 2 interrupciones y cuando se modifica se almacena en la eeprom para que no se pierda el valor cuando se apague el vehículo.
Todo eso trabaja bien, el problema es que el relay siempre queda encendido y solo en un ciclo después de muchos se apaga y se enciende al tiro. he revisado y no logro encontrar el problema. les dejo el código.

Muchas gracias por su tiempo y ayuda!!

/********************************************************


#include <PID_v1.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>  // F Malpartida's NewLiquidCrystal library
#include <EEPROM.h>

/*-----( Declare Constants )-----*/
#define I2C_ADDR    0x27  // Direccion I2C para PCF8574A que es el que lleva nuestra placa diseñada por MJKDZ
//definimos las constantes para esta placa

#define  LED_OFF  0
#define  LED_ON 1

//mjkdz i2c LCD board
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// Pin donde se conecta el bus 1-Wire
const int pinDatosDQ = 9;

// Instancia a las clases OneWire y DallasTemperature
OneWire oneWireObjeto(pinDatosDQ);
DallasTemperature sensorDS18B20(&oneWireObjeto);

float temp;

int calpin = 13;


volatile int tref;
volatile long t0 = 0;
boolean flag = false;

// necesario para guardar t en eeprom
int Direccion = 0;



//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp = 2, Ki = 0.1, Kd = 0.05;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;
int relay;

void setup()
{
  // put your setup code here, to run once:
  // Iniciamos la comunicación serie
  Serial.begin(9600);


  // Iniciamos el bus 1-Wire
  sensorDS18B20.begin();
  pinMode(calpin, OUTPUT);


  lcd.begin (16, 2);
  lcd.setBacklight(LED_ON);
  lcd.clear();

  tref = EEPROM.read(Direccion);

  delay(100);

  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt( 0, distemp, LOW);
  attachInterrupt( 1, aumtemp, LOW);

  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = tref;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  delay(1500);
}

void loop()
{

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("T SET");
  lcd.setCursor(6, 0);
  lcd.print(tref);

  lcd.setCursor(0, 1);
  lcd.print("TEMP");
  lcd.setCursor(6, 1);
  lcd.print(temp);
  if (relay == 1) {
    lcd.setCursor(12, 0);
    lcd.print("ON ");
  } else {
    lcd.setCursor(12, 0);
    lcd.print("OFF");
  }

  Serial.print("Temp refe:    ");
  Serial.println(tref);
  Serial.print("Temp medida:  ");
  Serial.println(temp);
  Serial.print("Flag:         ");
  Serial.println(flag);
  Serial.print("Relay:    ");
  Serial.println(relay);
  Serial.println("otro ciclo");
  Serial.println("");
  if (flag == true) {
    EEPROM.write(Direccion, tref);
    flag = false;
    delay(10);
  }

  sensorDS18B20.requestTemperatures();
  temp = (sensorDS18B20.getTempCByIndex(0));
  delay(100);


  Input = temp;
  myPID.Compute();

  /************************************************
     turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (Output < millis() - windowStartTime) {
    digitalWrite(calpin, HIGH);
    relay = 1;
  }
  else {
    digitalWrite(calpin, LOW);
    relay = 0;

  }

}

void distemp()
{
  if ( millis() > t0  + 200)
  { tref-- ;
    flag = true;
    t0 = millis();
  }

}
void aumtemp()
{
  if ( millis() > t0  + 200)
  { tref++ ;
    flag = true;
    t0 = millis();
  }

}

Las variables (windowStartTime, WindowSize y t0) para contener millis() deben ser del tipo unsignet long.

Que modelo de Arduino estas usando ? recuerda que double = float

Un PID y un relay? Como es eso?

El ejemplo esta en la librería PID de Brett Beauregard. Uso esa libreria para PID normales y funciona bien, es raro que el ejemplo de Relay Output no me funcione.

Saludos!

/********************************************************
 * PID RelayOutput Example
 * Same as basic example, except that this time, the output
 * is going to a digital pin which (we presume) is controlling
 * a relay.  the pid is designed to Output an analog value,
 * but the relay can only be On/Off.
 *
 *   to connect them together we use "time proportioning
 * control"  it's essentially a really slow version of PWM.
 * first we decide on a window size (5000mS say.) we then
 * set the pid to adjust its output between 0 and that window
 * size.  lastly, we add some logic that translates the PID
 * output into "Relay On Time" with the remainder of the
 * window being "Relay Off Time"
 ********************************************************/

#include <PID_v1.h>

#define PIN_INPUT 0
#define RELAY_PIN 6

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;

void setup()
{
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(PIN_INPUT);
  myPID.Compute();

  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH);
  else digitalWrite(RELAY_PIN, LOW);

}

Kike_GL:
Las variables (windowStartTime, WindowSize y t0) para contener millis() deben ser del tipo unsignet long.

Que modelo de Arduino estas usando ? recuerda que double = float

Hola, Arduino nano. No creo que ese sea el problema, no comprendo bien el funcionamiento de un PID con salida a relay.

Saludos y gracias!

Ahhh ahora comprendo. Usa la salida digital como PWM creando un control variable no ON/OFF.
Esa salida debe existar una valvula con control proporcional PWM.
Si dispones de dicha válvula que varía su apertura/cierre con un control PWM no hay problema y todo ese código es el apropiado pero sino, estas en problemas.

thomasn24:
no comprendo bien el funcionamiento de un PID con salida a relay.

Imagino que ya sabes que es un PID. Bueno lo normal es que dada una entrada (Input) y un parámetro objetivo (Setpoint), se calcule una salida (Output) en base a cálculos matemáticos que luego se ponderan con las ganancias (kp, ki, kd) según que tan agresivo quieras el PID. Dicha salida luego la usas para corregir físicamente la "planta" (en este caso la temperatura de la cabina del vehiculo) de manera directa o inversa.

Como un rele no puede responder a las distintas magnitudes de la salida se hace una conversión a la que se llama ciclo de trabajo (Duty clycle), donde la salida es convertida en un % del ciclo donde el rele esta apagado el resto del ciclo esta prendido. En tu código veo que tu ciclo es WindowSize = 5000.

thomasn24:
El proyecto es un sistema de control de calefacción de un vehículo el cual tiene una válvula ON/OFF que deja circular el agua.

Entonces imagino que con la valvula estas controlando la circulación de agua de radiador (que tipicamente esta a 90°C) de modo que el sensor DS18B20 imagino estará en la cabina.

Aqui esta lo complicado y es que si la temperatura dada por el DS18B20 es mucho menor que el Setpoint (=tref, la cual nunca estableciste) y estableciste el PID en modo "directo" la salida (Output) sera 0 y el pín calpin (9) se pone en HIGH.

PD: No logro entender en que momento asignas el valor de tref.

Unas recomendaciones adicionales.

Imagino que tienes dos pulsadores entre GND y pin 2 y 3 respectivamente. Usa el flanco de subida (RISING) que es cuando sueltas el boton que controla mejor porque solo hay uno por pulsacion.

//Interrupciones
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
attachInterrupt(0, distemp, RISING);
attachInterrupt(1, aumtemp, RISING);

Con esto ya no necesitas controlar el tiempo de la presión del pulsador:

void distemp(){
   tref-- ;
   flag = true;
}
void aumtemp(){
   tref++ ;
   flag = true;
}

Hay otro tema que me preocupa y es que no se si Setpoint se entera del cambio en tref. Yo mejor me aseguraria asi:

//Cambio en tref (interrupcion)
if (flag) {
   Setpoint = tref;
   EEPROM.write(Direccion, tref);
   flag = false;
   delay(10);
}

PD: Este es un proyecto real o solo un ejercicio para entender la libreria PID ? En otras palabras ya probaste el método simple con un if() y no te funciono ?

jajajaja justo ese es el problema, la válvula es on/off sin modulación. Por eso la idea de tener un PID con salida a un relay que se active por intervalos largos de tiempo (tipo PWM pero con un periodo de 4 minutos por ejemplo).

Lo que tienes en ese código no tiene nada que ver con un PID.
Un rele no sirve para esa tarea. Debe ser una salida MOSFET o TBJ y controlar una válvula proporcional.
Asi que eso no sirve!! Simple

Hola, si comprendo el PID normal, a este mismo vehículo le instale un control de velocidad crucero y afortunadamente opera bastante bien, y en este caso creía comprender también el tema de la ventana de tiempo para el duty cicle para el relay pero no me funciona.
La tref se cambia mediante las interrupciones y un pulsador de 3 posiciones con retorno, eso funciona ok.
Donde esta el error bien indicas es en la actualización del Setpoint, lo puse en setup y no en el loop, entonces cuando modificaba la temperatura de referencia no se actualizaba el Setpoint. Voy a corregir esto y probar ademas necesito aumentar la ventana de 5000 a unos 50000. Espero que ese haya sido el problema!! He leído mucho de PID con salidas a relay y no hay muchos aciertos.
Voy a probar y les cuento! Gracias!!!

surbyte:
Lo que tienes en ese código no tiene nada que ver con un PID.
Un rele no sirve para esa tarea. Debe ser una salida MOSFET o TBJ y controlar una válvula proporcional.
Asi que eso no sirve!! Simple

Pero si se trabaja con un por ejemplo Output de 0-100 y se pasa ese valor a un duty cycle pero de un periodo muy grande por ejemplo si Output es 50% y el periodo es 1 minuto que el relay activo durante 30 segundos y off durante los siguientes 30, si Output es 100% que este encendido todo el periodo, y que el sistema evalúe cambiar los parametro de los pulsos solo una vez que haya transcurrido un periodo, creo que se lograría manejar una potencia variable en un determinado tiempo similar a lo que haria un PWM. En el fondo es PWM con frecuencia de 0,00X Hz (a lo que voy frecuencia muy baja).

Espero se entienda,

Saludos!

Lo probe y sigue con lo mismo, siempre ON no importa que temperatura sea la deseada como tref. :confused: voy a tratar de escribir una parte nueva de activacion de relay. Les cuento!

Encontre el problema, esta en la parte donde se compara el Output con el tiempo de ese momento menos el comienzo de esa ventana de tiempo, es decir en ese pulso es especifico. tiene que ser asi
if (Output > millis() - windowStartTime). Ahora funciona, voy a probar con un periodo de tiempo mas grande quizas 3 minutos para no abusar del relay.

Gracias!

Estas seguro que necesitas un PID actuando de forma directa ?

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

Me refiero a que si la temperatura esta por debajo del Setpoint (error negativo) tu necesitas meter calor para corregir (aumentar parte positiva del ciclo)

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);

Aunque quizas sea lo mismo que cambiar < por el > en el if(), no estoy muy seguro.

Si, es Direct. Probé el sistema y funciona bien lo que si hay que configurar las contantes Kp Ki y kd con valores extremamente altos, ya que la diferencia de temperatura probablemente nunca de un valor mayor a 20 (Tdeseada - Trequerida) y el Output va de 0 a 180000 en mi caso ya que le di una ventana de 3 minutos.
Bueno pero funciona!

Saludos y gracias!