Go Down

Topic: Come cuocere le cose da cuocere (Read 346 times) previous topic - next topic

Standardoil

Ovvero, come ti comando un forno per scaldare qualcosa alle temperature e per i tempi previsti
mi riallaccio ad una dicussione che ho visto qui da poco, una persona che aveva da riscaldare delle cose rispettando dei tempi

come mio solito ho pensato a un (paio di)  Array per conservare tempi e temperature
una cosa così, insomma
Code: [Select]

// di Nelson "StandardOil"
// Idea da sviluppare:
// termostato a passi
// senza strutture, facile da capire
// non bloccante, si possono fare altre cose


#define PASSI 3   // il numero di passi da eseguire
int soglia[PASSI] = {15, 20, 10}; // le temperature da raggiungere passo a passo
int tempo[PASSI] = {5, 10, 20}; // il tempo (in minuti) di mantenimento della temperatura prima del prossimo passo

#define AVVIO 2 // il pin del led di macchina in ciclo
#define FERMO 3 // il pin del led di ciclo terminato
#define CALDO 4 // il pin del riscaldatore, ma anche del led di riscaldatore acceso


int temperatura; // il valore attuale della temperatura (letto da una funzione a parte)
byte passo;      // il gradino attuale
bool mantiene;   // indica se abbiamo già raggiunto la soglia oppure no, se si siamo in mantenimento

int timer; // per il timer


void setup(void)
{
    pinMode(AVVIO, OUTPUT);
    pinMode(FERMO, OUTPUT);
    pinMode(CALDO, OUTPUT);
    // accendo il led di macchina in ciclo
    digitalWrite(AVVIO, 1);
}

void loop(void)
{
    if (!(millis() / 1000)) // dovrebbe essere 60000 per i minuti
    {
        // passato un minuto, vado ad aggiornare i termostati
        mantienitemperatura();
    }
}


void mantienitemperatura(void)
{
    // siccome il tutto non è bloccante Arduino potrebbe fare altro
    // se lo facesse (non bloccante pure lui) dobbiamo prevedere il caso che
    // il forno avesse terminato, ma le altre operazioni no
    if (passo < PASSI)
    {
        // mantiene la temperatura imposta dall'array di temperature
        // richiamta una volta al minuto
        // gestisce anche i timer
        // acquisisco la temperatura (dipende dal tipo di sonda)
        temperatura = leggitemperatura();

        if (mantiene)
        {
            // siamo in mantenimento
            // per prima cosa controllo la temperatura e accendo/spengo il riscaldatore
            digitalWrite(CALDO, temperatura < soglia[passo]);
            // devo gestire il timer
            // ma uso uno sporco trucco
            // conto che un giro di loop valga un minuto
            timer--; // e tolgo uno dal conto dei minuti

            // se il tempo è scaduto
            if (timer <= 0)
            {
                // primo: tolgo il flag di mantenimento
                mantiene = 0;
                //secondo: avanti un passo
                passo++;

                // terzo, se avessi superato i passi programmati, fermo il programma
                if (passo >= PASSI)
                {
                    // spengo il riscaldatore
                    digitalWrite(CALDO, 0);
                    // spengo il led di macchina in ciclo
                    digitalWrite(AVVIO, 0);
                    // accendo il ledi di ciclo terminato
                    digitalWrite(FERMO, 1);
                }
            }
        }
        else
        {
            // siamo in salita
            if (temperatura >= soglia[passo])
            {
                // appena raggiunta
                // spengo il riscaldatore
                digitalWrite(CALDO, LOW);
                // passo in mantenimento
                mantiene = 1;
                // memorizzo il tempo
                timer = tempo[passo];
            }
            else
            {
                // non ancora raggiunta
                // mi assicuro che il riscaldatore sia acceso
                digitalWrite(CALDO, HIGH);
            }
        }
    }
}

int leggitemperatura(void)
{
    // funzione fittizia, che fa finta di leggere la temperatura, in realtà fà una stima un po' farlocca
    static int interna = 0;

    if (digitalRead(CALDO))
    {
        interna = interna + 2;
    }
    else
    {
        interna = interna - 1;
    }

    return interna;
}



prende un (paio di) array di tempi e temperature,
poi ogni minuto (non è bloccante) vede se siamo già arrivati alla temperatura, se si, mantenendo la temperatura, aspetta il tempo previsto, e passa al passo succcessivo


Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

Standardoil

#1
May 11, 2019, 11:21 pm Last Edit: May 12, 2019, 08:12 am by Standardoil
nel frattempo però ho pensato che un allarme di temperatura non rispettata non sarebbe male, e che la gestione pensata dalla persona che in origine aveva il problema non è ottimale
sembrerebbe meglio prescrivere invece una temperatura ed un tempo per raggiungerla
che così abbiamo anche la gestione di rampe di riscaldamento (e nei limiti del naturale raffreddamente, anche rampe di raffrreddamento)
naturalmente per mantenere una temperatura basta dare una seconda volta la stessa temperatura con un tempo di salita che diventa tempo di mantenimento
ecco quindi la versione 2
Code: [Select]

// di Nelson "StandardOil"
// Idea da sviluppare:
// termostato a passi
// versione 2, con rampe controllate
// non bloccante, si possono fare altre cose

typedef struct
{
    int grado; // valore di temperatura da raggiungere
    int tempo; // tempo in minuti per raggiungere la temperatura richiesta
} Passo;


Passo passi[] = {{15, 15}, {15, 40}, {25, 15}, {25, 40}};
// 4 passi, arriva a 15 gradi in 15 minuti
// arriva (rimane) a 15 gradi per 40 minuti
// arriva a 25 in 15 minuti
// arriva (rimane) a 25 per 40 minuti
#define PASSI sizeof(passi)/sizeof(passi[0])
#define MINUTO 60000UL // per contare i minuti


#define AVVIO 2 // il pin del led di macchina in ciclo
#define CALDO 3 // il pin del riscaldatore, ma anche del led di riscaldatore acceso
#define ALARM 4 // il pin che indica se il forno non è riuscito a tenere la temperatura
#define DELTA 2 // il massimo errore accettabile per ALARM


int temperatura; // il valore attuale della temperatura (letto da una funzione a parte)
byte passo;      // il gradino attuale
unsigned long int avvio = millis(); // il tempo di start del forno, serve per calcolare tutti gli altri
int target ; //la temperatura attualmente richiesta

void setup(void)
{
    pinMode(AVVIO, OUTPUT);
    pinMode(CALDO, OUTPUT);
    pinMode(ALARM, OUTPUT);
    // accendo il led di macchina in ciclo
    digitalWrite(AVVIO, 1);
}

void loop(void)
{
    if (!(millis() % 1000)) // una volta ogni quanto serve
    {
        mantienitemperatura();
    }
}


void mantienitemperatura(void)
{
    temperatura = leggitemperatura();
    int target = 0;
    int partenza = 0;
    int setpoint = 0;
    float quota = 0;
    int passo = PASSI; //così se non trova sappiamo che ha sbordato
    unsigned long int tempotrascorso = millis() - avvio; // ok si parte a contare da quando si è avviato il forno

    // calcolo il passo nel quale mi trovo
    for (byte i = 0; i < PASSI; i++)
    {
        // o abbiamo superato il passo
        if (tempotrascorso > MINUTO * passi[i].tempo)
        {
            passo = i; // memorizzo il passo in corso
            // si superato
            // ricalcolo il tempo trascorso
            tempotrascorso = tempotrascorso - MINUTO * passi[i].tempo;
        }
        else
        {
            // no non superato
            quota = tempotrascorso / MINUTO * passi[i].tempo;
            break;
        }

        // la percentuale di tempo raggiunta
        // la temperatura target
        target = passi[i].grado;
        // la temperatura di partenza
        partenza = passi[i - 1].grado * i > 0;
        setpoint = partenza + quota * (target - partenza);
        digitalWrite(CALDO, (temperatura < setpoint));

        if (passo)
        {
            // primo passo non guardo gli allarmi
            if (temperatura < setpoint - DELTA)
            {
                // allarme sottotemperatura
                digitalWrite(ALARM, 1);
            }
        }
    }

    // siccome il tutto non è bloccante Arduino potrebbe fare altro
    // se lo facesse (non bloccante pure lui) dobbiamo prevedere il caso che
    // il forno avesse terminato, ma le altre operazioni no
    if (passo < PASSI)
    {
        // forno non ha terminato
    }
    else
    {
        // forno ha terminato
        digitalWrite(CALDO, 0);
        // spengo il riscaldatore
        digitalWrite(AVVIO, 0);
        // Spengo il led di forno avviato
    }
}

int leggitemperatura(void)
{
    // funzione fittizia, che fa finta di leggere la temperatura, in realtà fà una stima un po' farlocca
    static int interna = 0;

    if (digitalRead(CALDO))
    {
        interna = interna + 2;
    }
    else
    {
        interna = interna - 1;
    }

    return interna;
}



Edit, la notte porta consiglio, tolto bug


questa purtroppo non la ho potuta provare, nel pomeriggio non avevo Arduino (pestifero nipote è passato)
se me la provate voi, mi fate favore
come mio solito (adesso) fa uso di strutture ed array delle stesse
non è bloccante, nemmeno dopo aver terminato il ciclo termico
segnala forno avviato/ciclo terminato
segnala riscaldatore acceso/spento
gestisce rampe di riscladamento (ovvio che la velocità massima programmabile deve essere inferiore a quella raggiungibile dal forno
segnala con un allarme fisso se la temperatura rimane sotto di 2 gradi dopo il primo passo (nel primo passo non avrebbe significato, potrebbe essere una temperatura qualunque, all'accensione, anche forno gelato)


credo sia ben commentata, casomai chiedete
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

Standardoil

è necessario riconoscere i propri errori
la seconda versione postata è una immonda fetenzia, mi vergogno ad aver scritto una così grande collezione di errori
fate come se non la avessi messa, grazie
questo semmai è la conferma di quello che dico spesso:
cavalli malati vanno abbattuti
non avrei dovuto tentare di modificare un programma funzionante
ma ri-scrivere ex-novo
ci avrei messo meno e avrei ottenuto una cosa della quale essere soddisfatti
che rimanga qui, a memoria di errori e come esempio per i posteri (cattivo esempio: quello da NON imitare)
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

zoomx

(e nei limiti del naturale raffreddamente, anche rampe di raffrreddamento)
Comandare una ventola?
Per adesso sono in black out cerebrale, non sono in grado di avere idee.....

Standardoil

Arriveremo
Devo vendicarsi della figuraccia....
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

zoomx

Invece dei passi io userei una macchina a stati finiti con 3 soli stati, riscaldamento, raffreddamento e mantenimento.
Oppure non ho capito il primo sketch.

Standardoil

#6
May 13, 2019, 10:56 pm Last Edit: May 13, 2019, 11:09 pm by Standardoil
Fatto, ma adesso ho Montalbano
Domani posto
Con ventola, start stop, spegnimento, allarme, rampe , non bloccante, citrosodina, pane e nutella...
No, solo fino a non bloccante , citrosodina e nutella le ho dimenticate
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

Standardoil

Come promesso ecco il termstato a rampa V2
Code: [Select]

// di Nelson "StandardOil"
// Idea da sviluppare:
// Supertermostato a rampa

// i/o
#define CALDO   2   // riscaldatore
#define FREDDO  3   // ventola raffreddamento
#define AVVIO   4   // Segnale di forno avviato
#define STOP    5   // Segnale di ciclo terminato
#define ALARM   6   // segnale di temperatura non rispettata
#define STORICO 7   // Storico allarmi
#define GO      8   // pulsante di avvio ciclo forno

// struttura della programmazione del forno
typedef struct
{
    float setpoint; // temperatura richiesta a fine rampa
    int passi; // tempo in passi elementari per raggiungerla
} Rampa;

Rampa punti[] = {{25, 15}, {25, 15}, {120, 15}, {0, 5}}; // ultimo ciclo sempre di stabilizzazione
#define PUNTI sizeof(punti)/sizeof(punti[0])

// capacità del forno
#define SALITA 1.5
#define DISCESA 0.8
// servono per conoscere i limiti di tempo
#define PASSO 1500UL // passo di programmazione del forno in millisecondi, occhio che le capacità sono espressi in gradi al passo
#define DELTA 3 // massima differenza per allarme
#define ISTERESI 1 // isteresi del controllo

bool go = 0; // forno avviato opure no
int allarme; //  0 no allarme, -1 sottotemperatura, +1 sovratemperatura
unsigned long int tempostart; // tempo all'istante di avvio



void setup(void)
{
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(CALDO, OUTPUT);
    pinMode(FREDDO, OUTPUT);
    pinMode(AVVIO, OUTPUT);
    pinMode(STOP, OUTPUT);
    pinMode(ALARM, OUTPUT);
    pinMode(STORICO, OUTPUT);
    pinMode(GO, INPUT);
}

void loop(void)
{
    static unsigned long int tempotermo = 0; // timer per il termostato
    static unsigned long int tempoled = 0; // timer per il led

    // una volta al minuto, solo a forno avviato
    if (go && millis() - tempotermo > PASSO)
    {
        // passo alla funzione termostato
        termostato();
        tempotermo = millis();
    }

    // per dimostrare che comunque il termostato non è bloccante

    if (millis() - tempoled > 1000)
    {
        // lampeggio il led
        digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
        tempoled = millis();
    }

    // controllo pulsante
    if (digitalRead(GO))
    {
        go = 1;
        digitalWrite(AVVIO, 1);
        digitalWrite(STOP, 0);
        digitalWrite(STORICO, 0);
        tempostart = millis();
        allarme = 0;
    }

    // lampeggio dell'allarme
    if (allarme)
    {
        if (allarme < 0)
        {
            if (!(millis() % 100))
            {
                digitalWrite(ALARM, !digitalRead(ALARM));
            }
        }
        else
        {
            digitalWrite(ALARM, 1);
        }
    }
    else
    {
        digitalWrite(ALARM, 0);
    }
}


void termostato(void)
{
    unsigned long int tempotrascorso = millis() - tempostart;
    // vediamo a che punto siamo arrivati
    byte punto = PUNTI;

    for (byte i = 0; i < PUNTI; i++)
    {
        if (tempotrascorso > PASSO * punti[i].passi)
        {
            // punto superato
            tempotrascorso = tempotrascorso - PASSO * punti[i].passi;
        }
        else
        {
            // punto ancora da superare
            punto = i;
            // esco dal for
            break;
        }
    }

    if (punto != PUNTI)
    {
        // forno non ha ancora finito
        // leggo la temperatura
        float temperatura = leggitemperatura();
        // calcolo la rampa
        float inizio = punti[punto - 1].setpoint;

        // la temperatura della rampa precedente
        if (!punto)
        {
            inizio = temperatura;
            // il primo passo non guardo la rampa di inizio
        }

        float rampa = (float)tempotrascorso / (float)(PASSO * punti[punto].passi);
        float setpoint = inizio + (punti[punto].setpoint - inizio) * rampa;
        digitalWrite(CALDO, temperatura < setpoint - ISTERESI);
        digitalWrite(FREDDO, temperatura > setpoint + ISTERESI);
        allarme = 0;

        if (temperatura < (setpoint - DELTA))
        {
            allarme = 1;
            digitalWrite(STORICO, 1);
        }

        if (temperatura > (setpoint + DELTA))
        {
            allarme = -1;
            digitalWrite(STORICO, 1);
        }
    }
    else
    {
        // Forno ha finito
        go = 0;
        digitalWrite(AVVIO, 0);
        digitalWrite(STOP, 1);
        digitalWrite(CALDO, 0);
        digitalWrite(FREDDO, 1);
       
        allarme = 0;
    }
}

float leggitemperatura(void)
{
    static float stima = 15.1; // per compensare il primo gradino

    // dovrebbe leggere la temperatura, in realtà mi limito a stimarla
    if (digitalRead(CALDO))
    {
        // riscaldatore ON
        // faccio finta che salga xxx  al passo
        return stima = stima + SALITA;
    }

    if (digitalRead(FREDDO))
    {
        // ventola ON
        // faccio finta che scenda xx  al passo
        return stima = stima - DISCESA;
    }

    // nessuna azione, si raffredda di 1 decimo al minuto
    return stima = stima - 0.1;
}





Le spiegazioni:
una matrice di temperature da raggiungere e di tempi concessi per raggiungerle
punto per punto, calcola la rampa rampa di temperatura, e la usa come setpoint
poi, se siamo sotto il setpoint accende il riscaldatore, se siamo sopra la ventola
poi, se siamo sotto oltre l'errore concesso accende due allarmi (uno storico e un istantaneo)
se siamo sopra oltre l'errore concesso anche
la differenza è che in un caso l'istantaneo lampeggia nell'altro no
al termine dell'ultimo passo spegne tutto salvo l'allarme storico (per sapere se il ciclo comunque ha avuto problemi)
nel frattempo NON è bloccante e la loop puo' fare altro
in particolare lampeggia il pin13, per dimostrare che non è bloccante
e fa eventulamente lampeggiare il led di allarme istantaneo
per ogni domanda io sono qui
per zoomx, le spiegazioni sul primo programma a dopo....
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

Standardoil

Ok in ritardo ma ci riesco
per Zoomx, le spiegazioni

il primo programma viene dritto da una cosa simile postata da uno che chiedeva aiuto
faceva alcuni "gradini" alzando il set-point diu un termostato, e poi aspettava che la temperatura arrivasse
arrivata avrebbe dovuto aspettare un tempo pre-impostato prima del gradino successivo
Siccome usava tensione di rete è stato giustament (s)troncato
vabbe', mi son detto, a me queste sfide piacciono, faccio qualcosa lo stesso
di fatto il mio è una macchina a stati, N stati "doppi": salita e attesa (mantenimento) per poi passari a nuova salita e nuovo mantenimento, in origine il raffreddamento non era previsto
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

zoomx

Io avrei separato gli stati in vere e proprie funzioni separate in modo da poterle sostituire con algoritmi diversi senza alterare la struttura generale.
Ad esempio riscaldamento e mantenimento possono essere effettuati usando soglie, soglie con isteresi, PID, controlli fuzzy.
Il raffreddamento è con ventole ma potrebbe prevedere anche di aprire delle valvole per fare entrare aria da fuori e cacciare via quella calda.

Oppure invece di scaldare aria si potrebbe trattare di una bagnomaria per la cioccolata (o la cottura lenta) dove andrebbero comandate pompe e valvole.

Standardoil

#10
May 15, 2019, 09:30 am Last Edit: May 15, 2019, 11:49 am by Standardoil
Accendere una ventola, o aprire una valvola di fluido freddo, o avviare un ciclo frigorigeno
Sempre un contatto è...
Semmai PID, e comando proporzionale, sarebbero stati interessanti, ma ci sono già librerie dedicate
Lo scopo era "limitato e immediato" ,
e anche inutile visto che la cv conversazione era nel frattempo stata chiusa
Però come mio solito lascio qui, se a qualcuno servisse da ispirazione...
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

Go Up