Termostato per incubatrice

Salve,

avrei bisogno di un termostato utilizzando arduino e DHT22.

La temperatura dovrebbe restare il più possibile vicino ai 37.7 °C +- 0.1°C

Il problema è che la resistenza non riscalda in maniera immediata, stessa cosa quando si raffredda, niente di nuovo, ma rendere stabile la temperatura è difficile.

Sono passato da oltre 1°C di differenza utilizzando un normale "if" a 0.8 °C utilizzando il codice sotto riportato, però sono ancora distante e non ho altre idee...

P.S: c'è anche l'umidificatore e quello con un semplice "if" è più che buono, non mi occorre maggiore precisione.

#include <DHT.h>
DHT dht;
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>
LiquidCrystal_I2C lcd(0x20,16,2);
unsigned long time;
unsigned long accesotem_time;
unsigned long inc_time;
unsigned long loop_time;
int acceso=1100;
int spento=2900;
int stato=0;
int tempprima;
int tempoloop;


#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 6
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);





void setup(){
dht.setup(2); // data pin 2 
lcd.init();
lcd.backlight();  
wdt_enable(WDTO_8S);
Serial.begin(9600);
time=millis();
accesotem_time=millis();
inc_time=millis();
loop_time=millis();


sensors.begin();


}
 
void loop(){
time=millis();
umidita();
temint();
wdt_reset();
loop_time=millis();
tempoloop=loop_time-time;
Serial.print(" ");
Serial.println(tempoloop);
}







void umidita(){
  
float humidity = dht.getHumidity();
lcd.setCursor(0,0);
  lcd.print("UM ");
  lcd.setCursor(3,0);
  lcd.print(humidity);
Serial.print("Umidita' ");
Serial.print(humidity);
if(humidity>62){
   digitalWrite (10,LOW);
   lcd.setCursor(0,1);
   lcd.print("U 0");
  }
     
if(humidity<61){
   digitalWrite (10,HIGH);
   lcd.setCursor(0,1);
   lcd.print("U 1");
  } 
}







void temint(){
  
 //sensors.requestTemperatures(); 
//float temp2 =   sensors.getTempCByIndex(0);
  
float temp2 = dht.getTemperature();
Serial.print(" temperatura ");
Serial.print(temp2);
lcd.setCursor(7,0);
lcd.print(" TE ");
lcd.setCursor(11,0);
lcd.print(temp2); 
Serial.print(" acceso ");
Serial.print(acceso);
Serial.print(" spento ");
Serial.print(spento);
 if(temp2<36.7){
  digitalWrite (7,HIGH);
  lcd.setCursor(5,1);
  lcd.print("T 1");
 }
 if(temp2>38.7){ 
  digitalWrite (7,LOW);
  lcd.setCursor(5,1);
  lcd.print("T 0");
 } 
 
 
 if(temp2>=36.7 && temp2<=38.7){  
    if(time>inc_time+30000){
    if(tempprima<temp2 && temp2>37.7){
      acceso -= 40;
      spento += 40;
    }
     if(tempprima=temp2 && temp2>37.7){
      acceso -= 40;
      spento += 40;
    }
    
     if(tempprima<temp2 && temp2<37.7){
      acceso += 40;
      spento -= 40;
    }
     if(tempprima=temp2 && temp2<37.7){
      acceso += 40;
      spento -= 40;
    }
    
    
  tempprima=temp2;  
  inc_time=millis(); 
  } 
 

 if(stato==1){
 
  if(time>accesotem_time+acceso){
    
    digitalWrite (7,LOW);
   lcd.setCursor(5,1);
  lcd.print("T 0");
    stato=0;
 accesotem_time=millis(); 
  } 
 }
  else {
 
  if(time>accesotem_time+spento){
    
    digitalWrite (7,HIGH);
  lcd.setCursor(5,1);
  lcd.print("T 1");
    stato=1;
 accesotem_time=millis(); 
} 
}
}
}

Forse perchè la resistenza che hai è troppo grossa se ti serve una precisione così estrema.
Dovresti considerare la temperatura esterna all'incubatrice, perchè la differenza tra le due è quella che influisce sula dispersione, un calcolo campione sulle tempistiche di on/of in base alla temp ext e quella interna.
Considerare anche più di una resistenza una molto piccola e una media che lavorano insieme quando necessario, quella piccola praticamente sempre accesa che si spegne per poco tempo oppure farla lavorare ad impulsi tramite scr, ambiente interno ventilato (circolazione d'aria) in modo da spargere lentamente e uniformemente la temperatura.

con il sistema di alimentazione on/off, della resistenza, non riuscirai mai a tenere la temperatura entro +/- 0,1°C
devi minimo alimentare il riscaldatore con il sistema pwm.
poi non so se è sufficiente.
stare entro quel deltaT non è facile.
ho fatto ai tempi sistemi di regolazione della temperatura che stava entro quel range.
il riscaldatore veniva alimentato in questo modo:
con piu' la temperatura si avvicinava al set impostato, il riscaldatore (resistenza) veniva alimentato ad impulsi via via sempre piu' brevi, man mano che l'errore tra set impostato e temperatura letta diminuiva.
usavo un integrato, del quale purtroppo non ricordo piu' la sigla, faceva tutto lui, in ingresso aveva il potenziometro per il set, il sensore di temperatura letta, ed in uscita un pilota per scr che alimentava in definitiva il riscaldatore a parzializzazione di fase.
una volta capito il concetto, con arduino, dovrebbe essere facile.
se cerchi in rete,sicuramente trovi qualche info.

ciao.

questa è la più classica applicazione del sistema PID.

il PWM un semplice on off, la frequenza di on-off è limitata dall'elettronica che pilota la resistenza, se un normale relè lo sfasci se lo piloti troppo spesso, se un relè a stato solido puoi divertirti, ma salvo non sia NON-zero crossing (che non sono semplicissimi da usare) il tuo limite resta a 50Hz

Grazie per il consiglio PID, ho provato, funziona quando la temperatura cresce, ma quando passa il limite 37.7, il relay resta sempre acceso, perché?

Ho un relay a stato solido, FOTEK SSR 25 DA, può andare?

#include <DHT.h>
DHT dht;
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>
LiquidCrystal_I2C lcd(0x20,16,2);
unsigned long time;
unsigned long accesotem_time;
unsigned long inc_time;
unsigned long loop_time;
int acceso=1100;
int spento=2900;
int stato=0;
int tempprima;
int tempoloop;


#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 6
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#include <PID_v1.h>
#define RelayPin 7

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

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

int WindowSize = 5000;
unsigned long windowStartTime;



void setup(){
dht.setup(2); // data pin 2 
lcd.init();
lcd.backlight();  
wdt_enable(WDTO_8S);
Serial.begin(9600);
time=millis();
accesotem_time=millis();
inc_time=millis();
loop_time=millis();
  windowStartTime = millis();
  
  //initialize the variables we're linked to
  Setpoint = 37.7;

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

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

sensors.begin();


}
 
void loop(){
time=millis();
//umidita();
//temint();
sensors.requestTemperatures();

Input = sensors.getTempCByIndex(0);
Serial.print(" temperatura ");
Serial.print(Input);
lcd.setCursor(7,0);
lcd.print(" TE ");
lcd.setCursor(11,0);
lcd.print(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(RelayPin,HIGH), lcd.setCursor(5,1), lcd.print("T 1");
  else digitalWrite(RelayPin,LOW), lcd.setCursor(5,1), lcd.print("T 0");;
Serial.print(" output ");
Serial.print(Output);



wdt_reset();
loop_time=millis();
tempoloop=loop_time-time;
Serial.print(" ");
Serial.println(tempoloop);
}

Il tuo SSR sembrerebbe zero crossing quindi come ha detto lesto fai attenzione, per pilotarlo correttamente per questa applicazione devi aggiungere un rilevatore di zero crossing (fotoaccoppiato) che porterai su un pin di arduino, di solito un interrupt esterno, e piloterai il SSR nella risposta all'interrupt del zero crossing , ogni semionda dura 10mSec e questo è il tempo minimo di ON per la tua resistenza, questo deve essere considerato nel PID che non deve avere un periodo troppo veloce altrimenti il SSR rimarrebbe sempre ON, (come appunto ti succede ora) quindi il tuo PID deve avere un periodo da 10mSec in sù, io ti consiglio di mettere il PID direttamente nella risposta all'interrupt del zero crossing.

Se per te questo discorso è troppo difficile , la strada più semplice è il pilotaggio in PWM della resistenza, devi pilotare la tua resistenza in DC mettendo un ponte e un condensatore da 400VDC e tramite un mosfet o IGBT e un fotoaccoppiatore al posto del tuo SSR pilotarlo appunto in PWM.

Che potenza ha la tua resistenza riscaldante?

icio, visto che sappiamo la Frequenza essere 50Hz, per quanto possa essere grezzo non è ACCETABILE aggiornare l'output senza zero crossing, ma ogni 10-100ms?

io farei un update ogni 100ms, tanto la resistenza in 100ms si scalda ben poco, anzi direi che anche un secondo va più che bene.

in oltre devi trovare valori di P I e D che vanno bene per la applicazione, io non ho idea se lo ha fatto o ha usato dei valori che erano codati da altri

opto per la soluzione di lesto, 1 secondo come tempo minimo credo sia sufficiente.

i valori di P I D erano quelli codati da altri, quindi sicuramente non sono adatti alla mia applicazione, però non capisco perché superato il valore impostato continua a rimanere alta l'uscita per il relay, se non ho capito male dovrebbe iniziare ancora prima di passare il valore a riscaldare meno o comunque stabilizzarsi.

posta il codice, magari stai facendo andare in windup l'integrale

Ecco il codice.

Può essere un problema che ogni loop dura quasi 1 secondo?

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>
LiquidCrystal_I2C lcd(0x20,16,2);

unsigned long time;
unsigned long loop_time;
int tempoloop;


#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 6
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#include <PID_v1.h>
#define RelayPin 7
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
int WindowSize = 5000;
unsigned long windowStartTime;



void setup(){
lcd.init();
lcd.backlight();  
wdt_enable(WDTO_8S);
Serial.begin(9600);
time=millis();
loop_time=millis();
 
  windowStartTime = millis();
  //initialize the variables we're linked to
  Setpoint = 37.7;
  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);
  //turn the PID on
  myPID.SetMode(AUTOMATIC);

sensors.begin();


}
 
void loop(){
time=millis();

sensors.requestTemperatures();

Input = sensors.getTempCByIndex(0);
Serial.print(" temperatura ");
Serial.print(Input);
lcd.setCursor(7,0);
lcd.print(" TE ");
lcd.setCursor(11,0);
lcd.print(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(RelayPin,HIGH), lcd.setCursor(5,1), lcd.print("T 1");
  else digitalWrite(RelayPin,LOW), lcd.setCursor(5,1), lcd.print("T 0");;
Serial.print(" output ");
Serial.print(Output);



wdt_reset();
loop_time=millis();
tempoloop=loop_time-time;
Serial.print(" ");
Serial.println(tempoloop);
}

lesto:
icio, visto che sappiamo la Frequenza essere 50Hz, per quanto possa essere grezzo non è ACCETABILE aggiornare l'output senza zero crossing, ma ogni 10-100ms?

io farei un update ogni 100ms, .....

Ok, qualsiasi tempo di ciclo superiore ai 10mSec va bene

allora, prima di tutto prima di caricare il codice sul forum per favore fai tools->auto format
perchè come è scritto ora è illeggibile e fa passare la voglia di leggerlo al primo sgurado, guardiamo il loop() formattato decentemete:

void loop() {
  time = millis();

  sensors.requestTemperatures();

  Input = sensors.getTempCByIndex(0);
  Serial.print(" temperatura ");
  Serial.print(Input);
  lcd.setCursor(7, 0);
  lcd.print(" TE ");
  lcd.setCursor(11, 0);
  lcd.print(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(RelayPin, HIGH), lcd.setCursor(5, 1), lcd.print("T 1");
  else digitalWrite(RelayPin, LOW), lcd.setCursor(5, 1), lcd.print("T 0");;
  Serial.print(" output ");
  Serial.print(Output);

  wdt_reset();
  loop_time = millis();
  tempoloop = loop_time - time;
  Serial.print(" ");
  Serial.println(tempoloop);
}

ora vorrei che mi spiegassi cosa cosa fai qui:

if (Output < millis() - windowStartTime){
    digitalWrite(RelayPin, HIGH);
    lcd.setCursor(5, 1);
    lcd.print("T 1");
  }else{
    digitalWrite(RelayPin, LOW);
    lcd.setCursor(5, 1);
    lcd.print("T 0");;
  }
  Serial.print(" output ");
  Serial.print(Output);

Ho utilizzato l'esempio presente nella libreria PIDv1 per poter utilizzare il relay, credo attivi il relay. di più non saprei

scusami ma if (Output < millis() - windowStartTime){ l'hai scritto te, altrimentimandami un link alla libreria/pagina che stavi usando che cerco di capirci qualocsa da lì. quella scritta così sai dirmi cosa fa?

ps. il "ho fatto copia-incolla" non lo devi dire davanti a me, perchè ti spello vivo e ti metto sotto sale. Se sono in buona. ]:smiley: ]:smiley:

ma stai scherzando io che faccio il copia incolla.... :*

comunque l'ho scaricato da questo sito Arduino Playground - PIDLibraryRelayOutputExample

In teoria dovrebbe gestire l'attivazione e disattivazione del relay, tieni presente che al momento utilizzo un normale non quello a stato solido,

/************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }

qui passati 5000 ms imposta il nuovo "windowsStartTime" che è la partenza per il nuovo intervallo di tempo in cui il relay dovrebbe essere acceso o spento per il tempo che verrà indicato sotto.

  if (Output < millis() - windowStartTime) digitalWrite(RelayPin, HIGH), lcd.setCursor(5, 1), lcd.print("T 1");
  else digitalWrite(RelayPin, LOW), lcd.setCursor(5, 1), lcd.print("T 0");;

Premetto che non so che valore possa avere "Output"

se l'output fosse 3000 ms in questo caso verifica se sono passati questi 3 secondi ed accende il relay altrimenti resta spento.

Ho visto ora ma non ho tempo per provarlo, può essere il problema che ci sono due ; consecutivi nel codice

 else digitalWrite(RelayPin, LOW), lcd.setCursor(5, 1), lcd.print("T 0");;

ok, allora mi sono il perso il pezzo in cui fai:

myPID.SetOutputLimits(0, WindowSize);

quindi in pratica il pid.compute() mette in output la durata del segnale ALTO in un PWM con periodo WindowSize.

quindi mi aspetto che all'inizio Output sia == a WindowSize, mano a mano che la temperatura si avvicina al valore desiderato (Setpoint = 37.7;) Output deve diminuire fino ad essere 0 (o quasi, potrebbe rimanere non zero per mantenere la temperatura x combattere le fughe di calore).

puoi alegare l'output della Seriale?

e poi fare una altro test in cui togli

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

e lasci solo

PID myPID(&Input, &Output, &Setpoint,2,0,0, DIRECT);

Questo la seriale con il codice originale

a me sembra ok. dal tuo codice la temperatura desiderata è di 37.7°
io vedo che output sale sempre di valore (ovvero la R rimane accesa perpiù tempo) fiono ad arrivare a 5000 (semrpe accesa) perchè NON risce a raggiungere i 37.7°, anzi, si nota bene che ad un certo punto a 28° la temperatura iniziaa SCENDERE, segno che qualcosa è andato storto ma non certo lato output di arduino.

secondo test

Purtroppo però non è così, se guardi ho inserito anche quando il relay è attivo (rele 1) e quando spento (rele 0), quando arriva a 5000 rimane sempre spento.

Provo ad invertire?

La seconda prova rimane sempre acceso.