miglior approccio per gestione isteresi in ambito termoregolazione

ciao a tutti,

dunque sono alle battute finali con la realizzazione di uno cronotermostato da ambiente con gestione via telegram attraverso connessione wi-fi.

Hardware: esp8266 + rtc ds3231 + relè songle 5V + sonda DS18B20.

Preciso che la sonda al momento è configurata con risoluzione a 10 bit, quindi le letture sono a step di 0.25°C per cui ottengo letture del tipo: 20.25 / 20.50 /20.75 / 21 ecc ecc.

float t_set;     // temperatura set point da raggiungere 
float t_h=0.2;  // isteresi 
t_read =         // temperatura letta dal sensore


// metodo utilizzato con isteresi superiore e inferiore dello stesso valore 

        if (t_read < t_set - t_h) {
          digitalWrite(rele, HIGH);
        } else if (t_read > t_set + t_h) {
          digitalWrite(rele, LOW);
        }

purtroppo però, anche così, quando la sonda è in quella fascia in cui ad esempio legge 20.25 / 20.50 il relè comincia a scattare di continuo finchè la temperatura non si stabilizza. Esistono modo più efficaci di gestire la faccenda rispetto a come lo abbia fatto io? voi come fareste?

Grazie

t_h o t_th? :slight_smile:

Piuttosto: come fa a compilarlo se t_h non esiste?...

errore di battitura, corretto....

Io ho fatto così:

relayEngaged = (relayEngaged ? !(currentTempRead >= tempToReach + tempThresoldUp) : currentTempRead <= tempToReach - tempThresoldDown);

All'inizio avevo una sola soglia sia per la parte "gradi in salita" che per la parte "gradi in discesa" poi ho separato in que differenti soglie per gestirmi meglio l'impianto radiante.
In pratica se il relé è attivato controllo se la temperatura attuale è maggiore o uguale a quella desiderata + la soglia d'intervento (0.5 gradi nel mio caso) se si negando il tutto disattivo il relé.
Se invece il relé è disattivo controllo se la temperatura attuale è minore o uguale a quella desiderata meno la soglia d'intervento se è minore allora attivo il relé.
Quindi se voglio 20° quando la temperatura scende a 19.5° il relé si attiva e resta tale finché la temperatura non raggiunge i 20.5° e poi il tutto si ripete

E' esattamente la stessa cosa... :slight_smile:
P.s.: quella che tu chiami "soglia di intervento" in realtà è metà di quella che si chiama isteresi.

Datman:
E' esattamente la stessa cosa... :slight_smile:
P.s.: quella che tu chiami "soglia di intervento" in realtà si chiama isteresi.

Direi proprio di no

non riesco a seguire la sintassi della tua riga, è scritta "molto stretta"

relayEngaged è una digital read?

No è lo stato del relé con cui fare la digitalWrite, te le scrivo più esteso in pseudo linguaggio che magari risulta più chiaro:

bool impianto_acceso; //Per me è una variabile globale
if(impianto_acceso)
{
  impianto_acceso = !(temperatura_attuale >= temperatura_desiderata + soglia);
}
else
{
 impianto_acceso = temperatura_attuale <= temperatura_desiderata - soglia;
}

digitalWrite(PIN_RELE, impianto_acceso);

adesso è più chiaro. Devo provare assolutamente il tuo codice...

Intanto ti chiedo anche: ma per far funzionare al meglio il tutto, per la sonda ds18b20, è meglio settare una certa risoluzione tra tutte quelle disponibili?

Evita le variabili float per almeno tre ragioni:

  1. Sono molto ingombranti
  2. Rallentano i calcoli
  3. Se fai un confronto con un intero apparentemente uguale, quasi sicuramente sono diversi. Per le approssimazioni, un valore che dovrebbe essere 25, ad esempio, può venire 24,999999: facendo un if(A==B) è evidente che il risultato sarà FALSE!

Utilizza, quindi, variabili intere, moltiplicando i valori per una potenza di dieci per considerare i decimali che ti interessano. Nel tuo caso, hai 20,25°C - 20,50°C - 20,75°C, quindi moltiplichi per 100 usando una variabile int e ottieni: 2025 - 2050 - 2075, che confronterai con valori analoghi.

droidprova:
adesso è più chiaro. Devo provare assolutamente il tuo codice...

Intanto ti chiedo anche: ma per far funzionare al meglio il tutto, per la sonda ds18b20, è meglio settare una certa risoluzione tra tutte quelle disponibili?

La risoluzione della sonda è da settare in base a ciò che ti aspetti di ottenere, mi spiego meglio, per me non ha molto senso settarla a risoluzioni di 0,00001 gradi (è un esempio) per rilevare una temperatura ambientale per comandare i termosifoni così come settara con la precisione di 10 gradi per rilevare la temperatura di cottura dell'uovo.
Io ho impostato una risoluzione di 0,1 gradi (in molti prodotti commerciali le sonde e i cronotermostati sono passati dal mezzo grado al decimo negli ultimi anni).
Quello che secondo me è importante è non basarsi su un unica lettura ma eseguendone alcune a distanza di tempo e andando a fare la media di queste. E qui si aprono molteplici possibilità di rilevazione/calcolo sul forum ne abbiamo parlato in un topic di un acquario io e docdoc se non erro, ad esempio io ho adottato 12 letture e scarto la più alta e la più bassa e sulle restanti faccio la media, docdoc suggeriva di inserire i valori tra quelli utilizzabili se quello rilevato non fosse "troppo" discostante dal precedente e poi fare la media dei campioni validi. Ma di metodi ve ne sono molteplici a te scegliere quale adottare, comunque tutti concordano su una cosa una media è meglio di una lettura secca

fabpolli:
Io ho fatto così:
relayEngaged = (relayEngaged ? !(currentTempRead >= tempToReach + tempThresoldUp) : currentTempRead <= tempToReach - tempThresoldDown);

Ehm... :slight_smile: Scusa ma se il codice è per qualcuno non espertissimo di linguaggio C perché dovete complicargli la vita? Quella istruzione (tra l'altro manca la "h" in "threshold" :wink: ) sarebbe equivalente a:

if ( relayEngaged ) {
  if ( currentTempRead >= tempToReach + tempThresoldUp )
    relayEngaged = false;
  // l'else è inutile perché reimposta relayEngaged a true
}
else {
  if ( currentTempRead <= tempToReach - tempThresoldDown)
    relayEngaged = true;
  // l'else è inutile perché reimposta relayEngaged a false
}

Se poi vogliamo ragionare sulla logica, ti dico che testare relayEngaged è anche inutile, se la temperatura supera tempToReach+tempThresoldUp si disattiva, se scende sotto a tempToReach-tempThresoldDown si attiva, nel mezzo non deve cambiare stato:

  if ( currentTempRead >= tempToReach + tempThresoldUp ) {
    relayEngaged = false;
  }
  if ( currentTempRead <= tempToReach - tempThresoldDown) {
    relayEngaged = true;
  }

docdoc:
Ehm... :slight_smile: Scusa ma se il codice è per qualcuno non espertissimo di linguaggio C perché dovete complicargli la vita? Quella istruzione (tra l'altro manca la "h" in "threshold" :wink: ) sarebbe equivalente a:

Non sapevo il suo livello, infatti poco dopo ho messo il codice in formato esteso, la prima cosa è stato un copia incolla di un mio codice non è che ho voluto scrivergli la cosa da zero in modo complicato.
per l'h :-[ :-[ :-[ errore di battitura perpetuato dal copiaincolla vado a vergognarmi 5 minuti e torno :slight_smile:

La logica alla fine, correggimi se sbaglio, sia nel mio caso che nel tuo ad ogni giro valuta due if quindi non è che si discosta molto. Sulle performance tra la mia e la tua manco mi sono posto il problema perché sono convinto che il mio cronotermostato che legge la temperatura ogni minuto e dopo 12 letture decide se accendere o spegnere il relé tra un operazione e l'altra si annoia tantissimo :slight_smile:

Che nessuno si vergogni ;D , altrimenti io che dovrei fare, sotterrarmi ....

x docdoc, il tuo ultimo codice è simile al mio postato ad inizio topic tranne che per else presente (nel mio). Vero?

x fabpolli: quindi il tuo crono legge la temperatura ogni minuto? Già potrebbe bastare questo per rendere il tutto più stabile, io per ora mandavo in lettura la sonda ad ogni ciclo.

droidprova:
x fabpolli: quindi il tuo crono legge la temperatura ogni minuto? Già potrebbe bastare questo per rendere il tutto più stabile, io per ora mandavo in lettura la sonda ad ogni ciclo.

Si io ho deciso per il minuto, ma se il sistema funziona bene anche leggendo ogni secondo il relé non deve scattare di continuo.
Io dopo molti studi e applicando complessi algoritmi predittivi (in latino si traduce ad cazzum) considerando l'inerzia termica di un ambiente da riscaldare sono contento di agire sul relé a intervalli di 12 minuti (faccio 12 letture una ogni minuto, ne scarto due (massima e minima rilevata) e faccio la media).
Ho parametrizzato praticamente tutto per non dover mettere mano al codice di continuo ma non è bastato...
Per la cronaca sono operativi da quest'anno (due mesi circa) e rispetto al codice in esecuzione ho già dovuto apportare modifiche derivanti dalla "prova sul campo" ovvero suddivisione della soglia in due valori distinti rispetto al valore uguale che ho ora, gestione del setup delle perferiche che in caso di blocco non impedisca l'avvio (per quanto possibile), mi spiego meglio perché potrebbe (credo) essere fonte di ragionamenti:
I miei crono sono collegati tra loro ad un certo punto l'adattatore ha deciso di abbandonarmi e per colpa di questo (collegato alle seriale hardware) il crono non si avviava più, rimpiazzato il componente che si era danneggiato tutto ok, nella nuova versione se l'inizializzazione di un componente blocca l'avvio per più di N volte il componente verrà disattivato, perché ho pensato così perché alla fine voglio un oggetto che "alla peggio" non mi lasci al freddo quindi non va la seriale per dire agli altri il mio stato peccato salto e vado avanti, non va l'RTC per applicare le temperature in base alla programmazione... peccato setto una temperatura standard e la mantengo, non va il display vabbé non so cosa sta succedendo ma non rientro al freddo e così via. Come dicevo alla peggio leggo la tempratura e agisco sul relé finché questi due componenti funzionano se non posso interrogarlo/pilotarlo da remoto, vedere il display ecc. ecc. chissene lo sistemerò ma il suo vero scopo sarà preservato.

Secondo me il problema può dipendere dal fatto che magari i valori che leggi hanno oscillazioni più ampie rispetto al valore di isteresi impostato.
Riesci a verificarlo? L'isteresi dovrebbe creare un canale tra le soglie più ampio rispetto a quello del rumore di fondo sommato alla lettura. Occhio che fai anche arrotondamenti che potrebbero aumentare l'ampiezza del rumore.

fabpolli:
Io dopo molti studi e applicando complessi algoritmi predittivi (in latino si traduce ad cazzum) considerando l'inerzia termica di un ambiente da riscaldare sono contento di agire sul relé a intervalli di 12 minuti (faccio 12 letture una ogni minuto, ne scarto due (massima e minima rilevata) e faccio la media).

Interessante..! Questo lo ottieni mettendo un ciclo for dentro un if che verifica un millis()?

Ho tutto sotto millis() :slight_smile:
Di solito strutturo il programma in modo che nel loop vi siano praticamente solo chiamate a funzioni e poco altro, ogni funzione si coccupa di un compito specifico e nellla stragrande maggioranza dei casi c'è di mezzo millis per determinare se fare o non fare le operazioni.
Nel caso specifico ogni intervallo (60000 nel mio caso ma è un parametro modificabile runtime) leggo la sonda e inserisco il valore letto in un array quando il numero di letture è uguale al numero massimo di letture impostato (anch'esso un parametro modificabile runtime) scansiono l'array per cercare il valore minimo rilevato, lo scansiono una seconda volta per cercare il valore massimo rilevato (escludendo l'indice del valore minimo) e infine una terza scansione per sommare tutte le altre temperature lette e poi fare una media di esse e metterla nella variabile del codice che ti ho incollato al mio primo post (currentTempRead) in modo che altre funzioni ne facciano ciò che devono, es. il relé la verifica per sapere se attivarsi, la funzione del display controlla il suo valore con una variabile di appoggio che contiene il vecchio valore letto per sapere se deve aggiornare il display, ecc.

Perfetto! Ti ringrazio per l'imbeccata, cerco di mettere in codice l'idea, che te ne pare di questo:?

int numero_letture = 0;
float somma = 0;
float t_read;
unsigned long tempo = 0;

.......


if ((millis() - tempo) > 5000) {                   // intervallo lettura iniziale ogni 5 secondi 
  somma = somma + sensors.getTempCByIndex(0);
  numero_letture ++;
  tempo = millis();
  }


if (numero_letture >= 7){
  t_read = somma/numero_letture;                  // ottengo la media delle letture e passo la variabile t_read alla funzione che fa il confronto con la t_set
  Serial.println(t_read);
  numero_letture = 0;
  somma = 0;
  }

fabpolli:
Di solito strutturo il programma in modo che nel loop vi siano praticamente solo chiamate a funzioni e poco altro, ogni funzione si coccupa di un compito specifico e nellla stragrande maggioranza dei casi c'è di mezzo millis per determinare se fare o non fare le operazioni.

già, pure io, e penso quasi tutti qui dentro.
Eliminando del tutto o limitando al minimo sulla soglia dell'estinzione i delay().
Che frulli per le mie cose sto processore invece di fare continue pause caffè ripetendo di continuo NOP NOP NOP...

EDIT:
Poi, se non è troppo complicato per te, io userei un array per salvare le letture, usato in modo circolare, cioè ad ogni lettura scrivi su una posizione differente e quando arrivi all'ultimo indice ricominci dal primo.
In questo modo ad ogni lettura hai sempre in memoria le ultime X letture e puoi fare la media per verificare la soglia.
Magari diradi le letture ma validi la soglia sempre dopo ogni lettura con la media delle ultime X che hai sempre disponibili grazie all'array usato in modo circolare.
Devi solo stare attendo al primo giro, quando non hai ancora fatto un giro completo di letture e quindi l'array non è completamente pieno, ma potresti anche risolvere inizializzando tutto l'array con la prima lettura, così alla prima lettura fa la media di se stessa.