[C Arduino] Array 2D di Struct

di il
10 risposte

[C Arduino] Array 2D di Struct

Ho una necessità banale, ovvero gestire un Array Statico Bidimensionale di Struct, se è la scelta giusta…

Questa struttura rappresenta un punto in una Curva nel tempo di Temperatura ed Umidità, ogni curva ha 3 Punti o 3 Fasi di funzionamento di una Cella a T e H controllate, quindi ho il Tempo di stasi SpTemperatura e SpUmidità, che poi passo ad un PID, ci sono le rampe di variazione in realtà per le quali calcolo il coefficiente di incremento per raccordare da tsp(n)-tsp(n+1) in circa 5minuti, i tempi di stasi sono almeno 2 ore(7200000ms),  ma questo è un'altro discorso.

Ora dovrei creare 3(n) Curve predefinite fisse quindi pensavo di usare un un array 2D e fare una cosa simile… esempio solo per testare:

constexpr uint8_t NUM_FASI = 3;          // Numero di Passi del profilo
constexpr uint8_t NUM_CURVE = 3;          // Numero di profili

enum eCurves {CURVA1, CURVA2, CURVA3};

struct sCurva{
  uint32_t 		duration;
  float 	  	tsp;
  float		  	hsp;
};
sCurva Curve[NUM_CURVE ][NUM_FASI]
    {{{7200000, 28.0,68.0},{28800000, 26.0,68.0},{14400000, 25.5,65.0}},
     {{14400000, 28.0,68.0},{7200000, 26.0,68.0},{28800000, 25.5,65.0}},
     {{28800000, 28.0,68.0},{14400000, 26.0,68.0},{7200000, 25.5,65.0}}};

void setup() {
  uint8_t i,J;
  Serial.begin(9600);
  for (int i = 0; i < NUM_CURVE; i++) {
    for (int j = 0; j < NUM_FASI; j++) {
      Serial.print(String(Curve[i][j].duration) + ",");
      Serial.print(String(Curve[i][j].tsp) + ",");
      Serial.print(String(Curve[i][j].hsp));
      Serial.println(";");
    }
  }
}  

void loop() {
}

Funzionare funziona… io poi non devo ciclare l'Array esterno ma solo usarlo dopo aver sceltro la Curva1/2/3, devo però ciclare solo l'array interno per far seguire la curva ai PID nel Loop ciclico intercettando quando i millis>Dutata di fase.

Vi chiedo se quanto scritto come codice è migliorabile come logica, non ho pensato ad usare i Pointer(per la poca dimestichezza), non so se porterebbe vantaggi o meno…

Se sono sulla strada giusta procedo, altrimenti correggo, sono in tempo…

Grazie

10 Risposte

  • Re: [C Arduino] Array 2D di Struct

    Ciao Alex,

    per quanto riguarda il codice posso dirti poco o nulla (anche se a sensazione mi sembra un approccio sensato, casualmente è lo stesso approccio che utilizzo per gestire le fasi delle ricette nei plc ).

    In merito all'applicazione, se ho ben compreso si tratta di una ipotetica curva di cottura, io non vedo alternative a quanto hai ipotizzato. Ti avrei suggerito di prestare attenzione al cambio di setpoint per evitare pendolazioni del sistema ma a quanto pare tu hai già previsto un avvicinamento graduale al nuovo setpoint di 5 minuti.

    Non so esattamente cosa ti mette a disposizione Arduino come PID, ma trattandosi di regolazione di temperatura (che in genere è un processo lento, almeno nei processi industriali che io conosco) probabilmente puoi disattivare l'azione derivativa …

  • Re: [C Arduino] Array 2D di Struct

    Mi soffermo solo sul dubbio dei puntatori… nel codice attuale non vedo perchè usarli e non porterebbero nessun vantaggio.

    Forse nel resto del codice, nel loop, ma dipende da cosa farai…  

  • Re: [C Arduino] Array 2D di Struct

    Ciao Max.

    In prima cosa è una camera di Lievitazione/FermaBiga, posso andare da 4°C a 30°C, quindi ho la parte refrigerante con compressore e riscaldante elettrica, ed un piccolo generatore di umidità relativa per ora ad ultrasuoni ma non è igienico al massimo... penso di farlo con Resistenza elettrica in acqua ma vedrò…

    Il mio problema è oggettivamente il codice perchè non lo conosco bene ed ho sempre l'impressione di ragionare come con il Vb6.

    Le curve sono differenziate in quanto a seconda del tipo di lievitazione ci sono cicli specifici, la Biga ha 18°C fissi 18h circa…

    Oggi sta funzionando tutto senza Curve, a mano, e con un sistema di regolazione ON/OFF con Isteresi, funziona discretamente bene, ma siccome mi piace pasticciare e giocare, sto provando Offline un PID, arduino ha librerie che fanno già queste cose non mi sto a complicare nulla ed approfitto del già fatto.

    Il PID genera un uscita scalata in valore 0÷AmpiezzaFinestra che corrisponde alla finestra temporale di analisi, 5 secondi, quindi 0÷5000, di fatto se l'uscita è 2500 tengo ON per 2500ms e OFF per il resto quindi 50%, in sostanza modulo il DutyCicle di Attacca stacca in una finestra a tempo definito, leggendo varie logiche sembra quella più semplice ed adatta, nel mio caso mi evito tutta la gestione dello ZeroCrossing per la parzializzazione della potenza, che ovviamente potrebbe essere ancora meglio ma eccessivamente complicato per ora e forse non ne vale la pena.

    Nel mio caso i volumi sono ridotti, parliamo di 150L di volume, ma proporzionalmente anche le potenze in gioco sono basse, quindi le costanti di tempo sono da tarare, tuttavia nonostante le temperature siano basse non posso fare gradini di 2°C sul SP in quanto oltre ad innescare pendolazioni eccessive, che non mi posso permettere, ci sarebbe anche la banda che discrimina lo switch tra Riscaldamento/Raffreddamento che è con Isteresi di -1.5/+1.5 sul Sp, quindi se ho 

    Sp=28 e t=28 quando abbasso il SP=26 si attiva la refrigerazione, che vorrei evitare, infatti se raccordo il SP con gradiente questa cosa non avviene e tutto resta più stabile…

  • Re: [C Arduino] Array 2D di Struct

    03/04/2024 - oregon ha scritto:


    Mi soffermo solo sul dubbio dei puntatori… nel codice attuale non vedo perchè usarli e non porterebbero nessun vantaggio.

    Forse nel resto del codice, nel loop, ma dipende da cosa farai…  

    Ciao Antonio, io non li ho usati perchè di base nel VB6 non li si usa, leggevo tuttavia che il loro utilizzo alleggerisce l'utilizzo della memoria e velocizza il codice…?
    Può essere corretto quello che penso di aver capito dalla lettura di un testo…?

    Ovviamente non mi interessa la gestione delle modifiche dei valori, quanto proprio all'esecuzione nella LOOP, in cui uso un abbondante  elenco di Variabili “publiche” per poter copiare i valori e gestire poi Soglie/Rampe ecc…

  • Re: [C Arduino] Array 2D di Struct

    In VB6/VBA il concetto di puntatore non esiste o è molto sfumato anche perché non si accededirettamente alla memoria (a parte con le API).

    In certe occasioni i puntatori possono sveltire il codice ma dipende molto dalle situazioni. Non per un singolo accesso sicuramente m in loop pesanti e bisogna anche vedere se il compilatore, alla fine, non ottimizza tanto da usarli senza che si sappia.

    Quando scriverai quella parte di codice vediamo di utilizzare i puntatori e capiamo, con dei test, se vale la pena.

  • Re: [C Arduino] Array 2D di Struct

    03/04/2024 - @Alex ha scritto:

    Ciao Max.

    In prima cosa è una camera di Lievitazione/FermaBiga, posso andare da 4°C a 30°C, quindi ho la parte refrigerante con compressore e riscaldante elettrica, ed un piccolo generatore di umidità relativa per ora ad ultrasuoni ma non è igienico al massimo... penso di farlo con Resistenza elettrica in acqua ma vedrò…

    Mi sa che ti stai cercando un passatempo per quando andrai in pensione :)

    03/04/2024 - @Alex ha scritto:

    Il PID genera un uscita scalata in valore 0÷AmpiezzaFinestra che corrisponde alla finestra temporale di analisi, 5 secondi, quindi 0÷5000, di fatto se l'uscita è 2500 tengo ON per 2500ms e OFF per il resto quindi 50%, in sostanza modulo il DutyCicle di Attacca stacca in una finestra a tempo definito, leggendo varie logiche sembra quella più semplice ed adatta, nel mio caso mi evito tutta la gestione dello ZeroCrossing per la parzializzazione della potenza, che ovviamente potrebbe essere ancora meglio ma eccessivamente complicato per ora e forse non ne vale la pena.

    Per quanto riguarda pilotare la resistenza di riscaldamento ti consiglio di utilizzare un relè a stato solido, un relè canonico verrebbe sollecitato troppo. Se indichi la potenza (o meglio gli A) guardo se ho qualcosa di risulta da farti avere …

    Per il compressore non sono pratico ma temo che non sia possibile gestire ON/OFF a piacimento come per la resistenza. Probabilmente avrai un tempo di ON minimo seguito da un tempo di OFF minimo (e forse questi tempi sono superiori al secondo). Se così fosse dovrai tenerne conto nel PID.

    03/04/2024 - @Alex ha scritto:

    Nel mio caso i volumi sono ridotti, parliamo di 150L di volume, ma proporzionalmente anche le potenze in gioco sono basse, quindi le costanti di tempo sono da tarare, tuttavia nonostante le temperature siano basse non posso fare gradini di 2°C sul SP in quanto oltre ad innescare pendolazioni eccessive, che non mi posso permettere, ci sarebbe anche la banda che discrimina lo switch tra Riscaldamento/Raffreddamento che è con Isteresi di -1.5/+1.5 sul Sp, quindi se ho 

    Sp=28 e t=28 quando abbasso il SP=26 si attiva la refrigerazione, che vorrei evitare, infatti se raccordo il SP con gradiente questa cosa non avviene e tutto resta più stabile…

    Viste le caratteristiche del processo non hai alternative alla rampa del setpoint.

    Piuttosto ti consiglio di capire se la misura di temperatura è corretta magari prevedendo un doppio sensore (così utilizzi il valore medio). Potrebbe essere utile, nel caso non ci fosse, anche un sistema di ventilazione/ricircolo dell'aria all'interno della camera, in modo da evitare la probabile stratificazione dell'aria. Magari avrebbe anche senso la misura di temperatura all'interno dell'impasto (questioni di igiene permettendo).

  • Re: [C Arduino] Array 2D di Struct

    03/04/2024 - max.riservo ha scritto:

    Mi sa che ti stai cercando un passatempo per quando andrai in pensione :)

    Seee campa cavallo… quando andro in pensione sarò inagibile mentalmente da tanto mi manca ;-)

    Scherzi a parte, sono da parecchio appassionato di “lievitati” e mi sono intestato a fare una camera “casalinga”, ora già in funzione ma come detto solo manuale, poi se metti la voglia di giocare con Arduino con la voglia di giocare con la scatola… è finita mi pare di essere tornato ragazzino…, per la gioia di mia moglie.

    Per quanto riguarda pilotare la resistenza di riscaldamento ti consiglio di utilizzare un relè a stato solido, un relè canonico verrebbe sollecitato troppo. Se indichi la potenza (o meglio gli A) guardo se ho qualcosa di risulta da farti avere …

    Si ovviamente uso perforza SSR DC-AC pilotati a 5Vcc sono abbastanza di uso comune, avrei usato un Triac 20anni fa ma ora questi sono più comodi con disaccoppiamento ottico.

    Per il compressore non sono pratico ma temo che non sia possibile gestire ON/OFF a piacimento come per la resistenza. Probabilmente avrai un tempo di ON minimo seguito da un tempo di OFF minimo (e forse questi tempi sono superiori al secondo). Se così fosse dovrai tenerne conto nel PID.

    Si questa cosa in effetti mi era scappata, ora effettivamente ho un ON-OFF con isteresi anche sul compressore e funziona bene e riflettendo non credo che il PID sul cmpressore abbia un senso… probabilmente manterrò la regolazione attuale.

    Viste le caratteristiche del processo non hai alternative alla rampa del setpoint.

    Piuttosto ti consiglio di capire se la misura di temperatura è corretta magari prevedendo un doppio sensore (così utilizzi il valore medio). Potrebbe essere utile, nel caso non ci fosse, anche un sistema di ventilazione/ricircolo dell'aria all'interno della camera, in modo da evitare la probabile stratificazione dell'aria. Magari avrebbe anche senso la misura di temperatura all'interno dell'impasto (questioni di igiene permettendo).

    Certo Max oggi ho 2 PT100 con convertitore (0÷50°C ? 0÷5vcc) + 2 DHT22 incrociate, ovvero 1PT100 in alto a Dx ed un DHT22 in altro a Sx, una PT100 a mentà a Sx e l'altro DHt22 a metà a Dx

     in sostanza ho 4 misure di T° + 2 di Umidità, le PT100 sono veloci il DHT22 legge a 2sec, ed al PID che gira a 200ms vanno meglio le PT100, su cui faccio media.

    Per la ventilazione ho 2 ventoline piccole, con mandata in basso sul corpo riscaldante ed aspirezione canalizzata aumentano la circolazione proprio perchè il piatto sotto tenderebbe a stratificare nei primi 10cm.

    Attualmente con la regolazione ad isteresi hanno circa 0.5÷0.8°C di dt in fase riscaldante iniziale, poi si stabilizzano a 0.2÷0.4°C di differenza.
    Come display ho un Nextion 7" funziona bene ha ottime potenzialità grafiche e consente di avere gli eventi in callback sullo sketch.

    L'unica cosa che non ho installato è una ventola di estrazione, in quanto l'ambiente (cantinetta del vino) è con ottimo isolamento termico e volevo evitare dispersioni, che con una ventola di estrazione sarebbero ovvie, ma soprattutto perchè non mi fido a fare altri buchi nella struttura, se prendo la serpentina della parte refrigerante butto la cantina…! 

    Mi fa piacere quindi leggere che abbiamo avuto una linea abbastanza simile sul progetto ;-)

    Ora peccato che ho la testa più veloce della mia competenza sul linguaggio [C], ma mi trasferisco una settimana a Roma da Antonio vedrai che per mandarmi via velocemente mi aiuta ;-)

  • Re: [C Arduino] Array 2D di Struct

    03/04/2024 - oregon ha scritto:


    In VB6/VBA il concetto di puntatore non esiste o è molto sfumato anche perché non si accededirettamente alla memoria (a parte con le API).

    Si esatto non è proprio parte del linguaggio.

    In certe occasioni i puntatori possono sveltire il codice ma dipende molto dalle situazioni. Non per un singolo accesso sicuramente m in loop pesanti e bisogna anche vedere se il compilatore, alla fine, non ottimizza tanto da usarli senza che si sappia.

    Quando scriverai quella parte di codice vediamo di utilizzare i puntatori e capiamo, con dei test, se vale la pena.

    Ok intanto ci lavoro sistemando un po il grosso, poi pubblico qualche cosa, anche se usando qualche LIB esterna poi risulta un poco complicato…

    Grazie

  • Re: [C Arduino] Array 2D di Struct

    Buongiono a tutti, ho buttato giù un codice stringato ma funzionante al similatore.

    Premetto che ho dovuto eliminare il PID in quanto su suggerimento di MAX gestire con PID il Compressore frigorifero non è il massimo e siccome non volevo differenziare riscaldamento e raffrescamento, anche perchè pur con il sistema ON-OFF funzionano discretamente, per ora lascio tutto in modalità Termostato con Isteresi.

    Ho 2 riscaldamenti uno Ausiliario per basse temperature, da una spinta in più, poi a 2°C dal Sp lo stacco ed uso solo il principale.

    Include 2 Lib esterne:

    1. TimedAction
    2. CountTimer

    Non sono esattamente quelle originali ho apportato ad entrambe qualche variante ma non sostanziale.

    In sosnza la 1° è semplicemente una gestione di Task cicliche a tempo impostato, quindi sulla Loop principale aggiorna il timer e genera in CallBack l'evento scadenziato, nel mio caso ho 2 Task che girano ad 1sec e 200mills

    La Task a 200mils la uso per acquisire gli ingressi, la task a 1sec la uso per l'automazione delle uscite, essendo un processo lento non serve processare veloce.

    La 2° è un Counter con Start/Stop ecc… genera 2 CallBack uno in Running con cadenza 1 secondo con il quale si aggiorna il Display ed in questa gestisco le rampe di raccordo, l'altro CallBack è a scadenza del Timer.

    Quindi passo alla classe CountTimer il Tempo in Millisec della fase e do lo start, il dInc per ora è fisso viene determinato con segno a seconda che la rampa abbia pendenza positiva o negativa.

    Nell'esempio ho forzato CURVA1, se premo il Pulsante cablato su A5, metto il Counter in AUTO(quindi sotto profilo)  e se premo il pulsante cablato su A6 fa partire il profilo.

    #include "TimedAction.h"    // LIBRERIA PER LOOP TIMER LENTI
    #include "Countimer.h"      // LIBRERIA PER IL COUNTTIMER (START/STOP/RESTART...)
    
    #define LIGHT_PIN 4
    #define ALARM_PIN 5
    #define FAN_PIN 6
    #define M_HEATER_PIN 7
    #define AUXHEATER_PIN 8
    #define HEATERCOOLER_PIN 9
    #define COOLER_PIN 10
    #define HUMDEHUM_PIN 11
    #define HUM_PIN 12
    #define DEHUM_PIN 13
    
    #define HEATER 0
    #define COOLER 1
    #define AUTO 0
    #define MAN 1
    #define DOOR_CH 0
    #define DOOR_AP 1
    
    float InputT;
    float THysOut=0.2;
    float THysMode=1.0;
    float THysAlm=2.5;
    float TSp=27.0;
    float TSpAux;
    
    /* ----------------------------------------------------------------------------------
       BOOLEAN VARIABLE STATUS
       ----------------------------------------------------------------------------------
    */
    bool CounterState=MAN;               // Programmatore Profilo AUTO-MAN
    bool heatcoolout_S=HEATER;           // Comando Relay Riscaldamento-Raffreddamento
    bool mheaterout_S = false;           // Comando Relay riscaldatore principale 
    bool auxheaterout_S = false;         // Comando Relay riscaldatore ausiliario
    bool coolerout_S=false;              // Comando Relay Raffreddamento
    bool DoorState=DOOR_CH;
    
    bool AlarmEN=1;
    bool MainHeaterEN=1;
    bool AuxHeaterEN=1;
    bool CoolerEN=1;
    
    bool TAutMan=AUTO;
    
    /*----------------------------------------------------------------------------------
      STRUTTURA PROFILO TEMPERATURE
      ----------------------------------------------------------------------------------
    */
    enum eFollowProfile {RUNUP, RUNDOWN, INACTIVE};
    enum eCurves {CURVA1, CURVA2, CURVA3};
    
    constexpr uint8_t NUM_FASI = 3;           // Numero di Passi del profilo
    constexpr uint8_t NUM_CURVE = 3;          // Numero di profili
    uint8_t  CounterSteps=0;                  // Contatore passi del Profilo 
    uint8_t CurvaAttiva=CURVA1;
    uint8_t  StateProfile=INACTIVE;
    uint32_t totmills=0;                      // Totalizzatore Millisecondi
    char _readabletime[10];                   // Conversione Millisec in CHAR
    float nextTsp;
    float nextHsp;
    float dtTinc=0.3;
    float dtHinc=0.3;
    
    struct autosteps {
      uint32_t 		duration;
      float 	  	tsp;
      float		  	hsp;
    };
    autosteps CntSteps[NUM_CURVE ][NUM_FASI]
        {{{18000, 28.5,68.0},{15000, 25.0,68.0},{12000, 29.5,65.0}},
         {{14400000, 28.0,68.0},{7200000, 26.0,68.0},{28800000, 25.5,65.0}},
         {{28800000, 28.0,68.0},{14400000, 26.0,68.0},{7200000, 25.5,65.0}}};
    /*----------------------------------------------------------------------------------
      END STRUTTURA PROFILO TEMPERATURE
      ----------------------------------------------------------------------------------
    */
    
    /* ----------------------------------------------------------------------------------
       CLASS OBJECT DECLARATION
       ----------------------------------------------------------------------------------
    */
    Countimer tCounter;                                   // OGGETTO COUNTER
    
    TimedAction slowtask = TimedAction(1000,&Task1000);   // Task SLOW (1 secondo)
    TimedAction fasttask = TimedAction(200,&Task200);
    /*----------------------------------------------------------------------------------
      END CLASS OBJECT DECLARATION
      ----------------------------------------------------------------------------------
    */
    
    void setup() {
      pinMode(M_HEATER_PIN, OUTPUT);      // RISCALDATORE PRINCIPALE
      pinMode(AUXHEATER_PIN, OUTPUT);       // RISCALDATORE AUSILIARIO 
      pinMode(HEATERCOOLER_PIN, OUTPUT);    // RISCALDAMENTO-RAFFRESCAMENTO
      pinMode(COOLER_PIN, OUTPUT);          // RAFFREDDAMENTO
    
      pinMode(A0, INPUT_PULLUP);            // TEMPERATURA
      pinMode(A1, INPUT_PULLUP);            // UMIDITA'
      pinMode(A2, INPUT_PULLUP);            // FINE CORSA PORTA CHIUSA 
      pinMode(A3, INPUT_PULLUP);            // LIVELLOSTATO ACQUA
      // --------------------- DA RIMUOVERE -----------------------
      pinMode(A5, INPUT_PULLUP);            // TIMER STEPS (AUTO/MAN)
      pinMode(A6, INPUT_PULLUP);            // START TIMER
      pinMode(A7, INPUT_PULLUP);            // STOP/RESET TIMER
      // --------------------- DA RIMUOVERE -----------------------
      CounterState=MAN;
      tCounter.setCounter(0, 0, 10, tCounter.COUNT_UP, tCounterComplete);
      tCounter.setInterval(tCounterRunning, 1000);
    
      TSpAux=TSp-2.0;
      CurvaAttiva=CURVA1;
      Serial.begin(9600);
    }
    
    void loop() {
      fasttask.check();   //200ms
      slowtask.check();   //1sec
      tCounter.run(); 
    }
    
    void Task200(){
      // Task Veloce lettura della seriale Display
      bool doorsw;
      // USO ANALOGICO COME DIGITALE PER LO SW PORTA
      if(analogRead(A2) <512)     {doorsw=LOW;}
      else                        {doorsw=HIGH;}
      if (DoorState!=doorsw) {
          DoorState=doorsw;
          if (doorsw == DOOR_AP) {  
            // PORTA APERTA
          }
          else if (doorsw == DOOR_CH) {
            // PORTA CHIUSA 
          }
          /*
            Quando lo stato della porta cambia posso aggiornare subito le utenze
            Non indispensabile, nel LOOP1000 verranno aggiornate
    
          setHeaterCooler_S(heatcoolout_S);
          setMainHeater_S(mheaterout_S);
          setAuxHeater_S(auxheaterout_S);
          setCooler_S(coolerout_S);
          */
        }
    
    
      /*  ---------------------------------------------------------------------------
          ------------------------------ GESTIONE TIMER -----------------------------
          ---------------------------------------------------------------------------
      */
      if(analogRead(A5) <512)      {
          if (CounterState==MAN)      {CounterState=AUTO; Serial.println("AUTOTIMERSTEPS");}
          else                        {CounterState=MAN;Serial.println("MANUALTIMER");}
      }
      if(analogRead(A6) <512)  {
        if (CounterState==MAN) {
          if (!tCounter.isCounterRunning()) {
            tCounter.start();        
            Serial.println("Counter START");
          }
          else if (!tCounter.isStopped() && tCounter.isStarted())  {
            tCounter.pause();        
            Serial.println("Counter PAUSA");
          }
        }
        else if (CounterState==AUTO)        {RunAutoProfile(0);    }  
      }
      if(analogRead(A7) <512)  {
        if (!tCounter.isCounterCompleted()) {
          tCounter.stop();           
          Serial.println("Counter STOP");}
      }
    }
    
    void Task1000(){
      bool _heatcoolout_S=heatcoolout_S;
      bool _auxheaterout_S=auxheaterout_S;
      bool _mheaterout_S=mheaterout_S;
    
      InputT = mapFloat(analogRead(A0) , 0, 1023, 0.0, 50.0);
    
      if (TAutMan==AUTO) {
        // DETERMINA SE MODO RISCALDAMENTO/RAFFREDDAMENTO, POI ANALIZZA LE SOGLIE CON ISTERESI
        if (InputT <= (TSp - THysMode))       {_heatcoolout_S=0 ;}  // 27-1.0
        else if (InputT >= (TSp + THysMode))  {_heatcoolout_S=1 ;}  // 27+1.0
        if (InputT <= (TSpAux-THysOut))       {_auxheaterout_S=1;}  // 25-0.2
        else if (InputT >= (TSpAux+THysOut))  {_auxheaterout_S=0;}  // 25+0.2
        if (InputT <= (TSp-THysOut))          {_mheaterout_S=1;}    // 27-0.2
        else if (InputT >= (TSp+THysOut))     {_mheaterout_S=0;}    // 27+0.2
    
        setHeaterCooler_S(_heatcoolout_S);
        setAuxHeater_S(_auxheaterout_S);
        setMainHeater_S(_mheaterout_S);
        setCooler_S(!_mheaterout_S);
      } 
      /*
      Serial.print("T=" + String(InputT));
      Serial.print(" Mode=" + String(_heatcoolout_S));
      Serial.print(" Out=" + String(_mheaterout_S));
      Serial.println(" Aux=" + String(_auxheaterout_S));
      */
    }
    
    void tCounterRunning()
    {
      float newT=TSp;
      Serial.println("COUNTERRUNNING");
      if (CounterState==AUTO) {  
        newT=newT+dtTinc;
        switch (StateProfile) {
          case RUNUP:
            if (newT>nextTsp) {StateProfile=INACTIVE;newT=nextTsp;}  
            break;
          case RUNDOWN:
            if (newT<nextTsp) {StateProfile=INACTIVE;newT=nextTsp;}  
            break;
          case INACTIVE:
            newT=nextTsp;
            break;
        }
        TSp=newT;
        TSpAux=TSp-2.0;
        // AGGIORNA TIMER+STEP SE IN AUTOCOUNTER
        Serial.print(timetoreadabletime(totmills+tCounter.getCurrentMills()));
        Serial.print(" " + String(CounterSteps) + "°) ");
        Serial.print("Sp:" + String(TSp,1)+ " T:" + String(InputT,1));
        Serial.print(tCounter.getCurrentTimeLeft());
        Serial.print("-");
        Serial.println(tCounter.getCurrentTime());
      }
      else {
        // AGGIORNA TIMER SE IN MANUAL
        if (CounterSteps==NUM_FASI) {
          if (TSp!=nextTsp) {
            TSp=nextTsp;
            TSpAux=TSp-2.0;
          }
          Serial.print("Over (");
          Serial.print(timetoreadabletime(totmills+tCounter.getCurrentMills()));
          Serial.print(") ");
        }
        Serial.print("Sp:" + String(TSp,1)+ " T:" + String(InputT,1) );
        Serial.print(tCounter.getCurrentTimeLeft());
        Serial.print("-");
        Serial.println(tCounter.getCurrentTime());
      }
    }
    
    void tCounterComplete() {
      if (CounterState==AUTO) { 
        totmills+=CntSteps[CurvaAttiva][CounterSteps].duration;
    	  CounterSteps++;
        if (CounterSteps<NUM_FASI){
          RunAutoProfile(CounterSteps);
          // AGGIORNAMENTO DISPLAY
          // ...
        }  
        else {
          //FINITO I 3 STEPS AUTOMATICI
          CounterState=MAN;
          tCounter.setCounter(0,0,10);  // TIMER MANUALE EXTRATEMPO SE L'ULTIMO STEP AUTO NON FOSSE SUFFICIENTE
          tCounter.start();             // FA RIPARTIRE IL TIMER SE SCADUTO ULTIMO STEP
        }        
      }
      else if (CounterState==MAN)
      {
        // TIMEOUT
      }
    }
    
    void RunAutoProfile(uint8_t step) {
      if (step==0)  {
        totmills=0;
        CounterSteps=0;
        TSp=24; // T°C Ambiente punto di partenza
      }
      else {
        TSp=CntSteps[CurvaAttiva][step-1].tsp;
      }  
      nextTsp=CntSteps[CurvaAttiva][step].tsp;
      if (TSp<nextTsp) {
        StateProfile=RUNUP;
        dtTinc=0.3;
      }
      else if (TSp>nextTsp) {
        StateProfile=RUNDOWN;
        dtTinc=-0.3;
      }
      else if (TSp==nextTsp) {
        StateProfile=INACTIVE;
        dtTinc=0.0;
      }
      Serial.print(String(step) + " - ");
      Serial.print(StateProfile);
      Serial.print(" - ");
      Serial.print(dtTinc);
      Serial.print(" - ");
      Serial.print(TSp);
      Serial.print(" - ");
      Serial.print(nextTsp);
      Serial.print(" - ");
      Serial.println(timetoreadabletime(CntSteps[CurvaAttiva][step].duration));
    
      tCounter.setCounter(CntSteps[CurvaAttiva][step].duration);
      // AGGIORNAMENTO DISPLAY
      // ...
      if (!tCounter.isCounterRunning()) {tCounter.start();}
    }
    
    void setHeaterCooler_S(bool value)  { 
      heatcoolout_S = value;
      digitalWrite(HEATERCOOLER_PIN, heatcoolout_S && (DoorState==DOOR_CH));  
    }
    
    void setMainHeater_S(bool value) {
    	mheaterout_S = value && MainHeaterEN && (heatcoolout_S == HEATER);
    	digitalWrite(M_HEATER_PIN, mheaterout_S && (DoorState==DOOR_CH));
    }
    
    void setAuxHeater_S(bool value) {
    	auxheaterout_S = value && AuxHeaterEN && (heatcoolout_S == HEATER);
    	digitalWrite(AUXHEATER_PIN, auxheaterout_S && (DoorState==DOOR_CH));
    }
    
    void setCooler_S(bool value) {
    	coolerout_S = value && CoolerEN && (heatcoolout_S == COOLER);
    	digitalWrite(COOLER_PIN, coolerout_S && (DoorState==DOOR_CH));
    }
    
    float mapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh) {
    	return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; 
    }
    
    char* timetoreadabletime(uint32_t millisec) {
    	//uint16_t milliseconds  = millisec % 1000;         // ms remaining when converted to seconds
    	uint16_t allSeconds   = millisec / 1000;			// convert millseconds to seconds
    	uint16_t hours        = allSeconds / 36000;   		// convert millseconds to hours
    	uint16_t secondsRemaining = allSeconds % 36000;     // milliseconds left over
    	uint8_t minutes  = secondsRemaining / 60 ;          // convert milliseconds left over to minutes
    	uint8_t seconds  = secondsRemaining % 60;           // milliseconds left over
    	sprintf(_readabletime, "%02d:%02d:%02d", hours, minutes, seconds);
    	return _readabletime;
    } 

    Come indicato ad Antonio, mi piacerebbe capire se il codice ha ottimizzazioni funzionali e tecniche da apportare a scopo di “studio” personal, sfrutto questo “giochetto” come strumento per approfondire il C.

    Grazie per i suggerimenti

  • Re: [C Arduino] Array 2D di Struct

    Io nel frattempo cerco di proseguire, in sostanza il codice sopra era una bozza per ragionare, sto ottimizzando tutto il codice al fine di non inviare comandi o evitare controlli la dove non serva, quindi se le condizioni non cambiano, sto fermo e passo avanti.

    Ho inserito in fondo al codice delle Funzioni richiamabili, che altrimenti richiedevano codice ridondato.

    Sono consapevole che leggere codice di altri, oltretutto poco pratici, sia complicato… se volete dare contributo benvenga io sto studiando ugualmente. 

    #include "TimedAction.h"    // LIBRERIA PER LOOP TIMER LENTI
    #include "Countimer.h"      // LIBRERIA PER IL COUNTTIMER (START/STOP/RESTART...)
    #include "DHT.h"			// LIBRERIA SENSORE UMIDIT'/TEMPERATURA
    
    #define DHT1_PIN 2          // Digital pin connected to the DHT sensor
    #define DHT2_PIN 3          // Digital pin connected to the DHT sensor
    #define DHTTYPE DHT22       // DHT 22  (AM2302), AM2321
    
    #define RISC1_PIN 7			// PIN RISCALDATORE PRINCIPALE(SSR DC-AC)
    #define RISC2_PIN 8			// PIN RISCALDATORE AUSILIARIO(SSR DC-AC)
    #define RISC_RAFFR_PIN 9	// PIN SELEZIONE RISCALD./RAFFREEDDAM.
    #define RAFFR_PIN 10		// PIN COMPRESSORE FRIGORIFERO(SSR DC-AC)
    
    #define UM_PIN 11			// PIN UMIDIFICATORE(SSR DC-AC)
    #define UM_DEUM_PIN 12		// PIN SELEZIONE UMIDIFIC./DEUMIDIFICATORE
    #define DEUM_PIN 13			// PIN DEUMIDIFICATORE(Estrattore)
    
    #define LUCE_PIN 23			// PIN LAMPADA
    #define ALLARME_PIN 25		// PIN ALLARME ACUSTICO
    #define VENTOLA_PIN 27		// PIN VENTILILAZIONE
    
    #define RAMPA_T 51			// PIN BLINK IN RAMPA Temperatura
    #define RAMPA_H 53			// PIN BLINK IN RAMPA Umidità
    
    
    // Definizione delle costanti di STATO
    #define RISCALDA 1
    #define RAFFREDDA 0
    #define UMIDIFICA 1
    #define DEUMIDIFICA 0
    #define ATTIVA 1
    #define NONATTIVA 0
    #define AUTO 0
    #define MAN 1
    #define PORTA_AP HIGH               // Porta aperta
    #define PORTA_CH LOW                // Porta chiusa
    
    /* ----------------------------------------------------------------------------------
       ANALOG VARIABLE
       ----------------------------------------------------------------------------------
    */
    float tcamera=20.0;                 // Sensore PT100 in cella
    float tambiente=20.0;               // Sensore PT100 Ambiente
    float t1=20.0;                      // T da Sensore DHT22(1)
    float t2=20.0;                      // T da Sensore DHT22(2)
    float tavg=20.0;                    // Tavg=(T1+T2)/2
    float h1=60.0;                      // T da Sensore DHT22(2)
    float h2=60.0;                      // T da Sensore DHT22(2)
    float havg=60.0;                    // RHavg=(T1+T2)/2
    float TSp2Offset= 1.5;   			// Offset tra SP Principale e Ausiliario
    
    float TInput=22.0;                  // Temperatura letta (0÷50°C)
    float THysOut=0.2;                  // Isteresi Temperatura per ON-OFF
    float THysMode=1.0;                 // Isteresi per RISCALDAMENTO<-->RAFFREDDMANTO
    float THysAlm=2.5;                  // Isteresi per Allarme Alta/Bassa Temepratura
    float TSp1=27.0;                    // SetPoint Riscaldatore Principale(RISC1)
    float TSp2;                         // SetPoint Riscaldatore Ausiliario(RISC2)
    
    float RhInput=60.0;                 // Umidità letta (0÷100%)
    float RhHysOut=0.5;                 // Isteresi interna (De)Umidificatore ON/OFF
    float RhHysMode=2.5;                // Isteresi per MODO UMIDIFCATORE<-->DEUMIDIFCATORE
    float RhHysAlm=4.0;                 // Isteresi per Allarme Alta/Bassa Umidità
    float RhSp=67.0;                    // SetPoint Di default dell'umidità
    /* ----------------------------------------------------------------------------------
       BOOLEAN VARIABLE STATUS
       ----------------------------------------------------------------------------------
    */
    
    bool Timer_A_M=MAN;                 // Programmatore Profilo AUTO-MAN
    bool Risc_Raff_S=RISCALDA;          // Comando Relay Riscaldamento-Raffreddamento
    bool Risc1_S = 0;                   // Comando Relay riscaldatore principale 
    bool Risc2_S = 0;                   // Comando Relay riscaldatore ausiliario
    bool Raffr_S=0;                     // Comando Relay Raffreddamento
    
    bool Um_Deum_S=UMIDIFICA;           // Comando Relay Umidificatore-DeUmidificatore
    bool Um_S=0;                        // Comando Relay Umidificatore
    bool Deum_S=0;                      // Comando Relay DeUmidificatore
    
    
    bool Ventola_S=0;                   // Comando Relay Ventola
    bool Luce_S=0;                      // Comando Relay Lampada
    int8_t Allarme_S=0;			            // Comando Relay Allarme
    int8_t AllarmeT_S=0;			          // Comando Relay Allarme
    int8_t AllarmeRh_S=0;			          // Comando Relay Allarme
    /* ----------------------------------------------------------------------------------
       INPUT STATUS
       ----------------------------------------------------------------------------------
    */
    bool LivelloAcqua=0;                // Stato Ingresso Livellostato Acqua (NA)
    bool PortaFC=PORTA_CH;              // Stato Ingresso Fine Corsa Porta Aperta (NA)
    
    /* ----------------------------------------------------------------------------------
       ABILITAZIONE RELAIS DI USCITA
       ----------------------------------------------------------------------------------
    */
    bool AllarmEN=1;                    // ABILITA ALLARMI
    bool Risc1EN=1;                     // ABILITA RISCALDATORE 1
    bool Risc2EN=1;                     // ABILITA RISCALDATORE 2
    bool UmEN=1;                        // ABILITA UMIDIFICATORE
    bool DeUumEN=1;                     // ABILITA DEUMIDIFICATORE
    bool VentolaEN=1;                   // ABILITA VENTOLA
    bool RaffrrEN=1;                    // ABILITA RAFFREDDAMENTO
    bool LuceEN=1;                      // ABILITA LUCE
    /* ----------------------------------------------------------------------------------
       MODALITA' DI CONTROLLO T e Rh AUTO/MAN
       ----------------------------------------------------------------------------------
    */
    
    enum eControlMode {AUTO_ALL, AUTO_H, AUTO_C};
    eControlMode Tctrlmode=AUTO_ALL;
    eControlMode Hctrlmode=AUTO_ALL;
    
    /*----------------------------------------------------------------------------------
      STRUTTURA PROFILO TEMPERATURE
      ----------------------------------------------------------------------------------
    */
    enum eRampaProfilo {R_SALITA, R_DISCESA, R_STASI};
    enum eProfilo {CURVA1, CURVA2, CURVA3};
    
    constexpr uint8_t NUM_FASI = 3;     // Numero di Passi del profilo
    constexpr uint8_t NUM_CURVE = 3;    // Numero di profili
    uint8_t  ContaFasi=0;               // Contatore passi del Profilo 
    uint8_t ProfiloAttivo=CURVA1;       // Profilo selezionato
    uint8_t  DirRampaT=R_STASI;      	// Attivazione Rampa profilo
    uint8_t  DirRampaH=R_STASI;      	// Attivazione Rampa profilo
    bool  TRampa_S;                     // Stato di Rampa T, usato per BLINK
    bool  HRampa_S;                     // Stato di Rampa H, usato per BLINK
    uint32_t totmills=0;                // Totalizzatore Millisecondi
    char _TimerHHMMSS[10];              // Conversione Millisec in CHAR
    float prossTSp;                     // SetPoint Temperatura nella Fase da attivare
    float prossHSp;                     // SetPoint Umidità nella Fase da attivare
    float dtTinc=0.0;                   // Valore incremento Temperatura in rampa
    float dtHinc=0.0;                   // Valore incremento Umidità in rampa
    
    uint16_t CicloVentola[2]={30000, 60000};
    
    struct Profilo {
      uint32_t 		durata;               // Tempo della Fase
      float 	  	tsp;                  // Valore SetPoint di Temperatura della fase
      float		  	hsp;                  // Valore SetPoint di Umidità della fase
    };
    Profilo CntSteps[NUM_CURVE][NUM_FASI] = {
        {{18000, 28.5, 68.0}, {15000, 25.0, 70.0}, {12000, 29.5, 65.0}},
        {{14400000, 28.0, 68.0}, {7200000, 26.0, 68.0}, {28800000, 25.5, 65.0}},
        {{28800000, 28.0, 68.0}, {14400000, 26.0, 68.0}, {7200000, 25.5, 65.0}}
    };
    /*----------------------------------------------------------------------------------
      END STRUTTURA PROFILO TEMPERATURE
      ----------------------------------------------------------------------------------
    */
    
    /*----------------------------------------------------------------------------------
      STRUTTURA GESTIONE ALLARMI
      ----------------------------------------------------------------------------------
    */
    struct dataAlarm
    {
      uint8_t idx;
      bool Stato;
      bool Acq;
      char Desc[20];
      bool Mostrato;
      bool Rientrato;
    };
    
    constexpr uint8_t NUM_ALLARM=8;
    dataAlarm data[NUM_ALLARM] = // Aggiunta del segno di uguale per l'inizializzazione corretta
    {
      {0, 0, 0, "BASSO LIV. VASCA"},
      {1, 0, 0, "TEMPO SCADUTO"},
      {2, 0, 0, "PORTA APERTA"},
      {3, 0, 0, "BASSA TEMP. CAMERA"},
      {4, 0, 0, "ALTA TEMP. CAMERA"},
      {5, 0, 0, "BASSA UMID. CAMERA"},
      {6, 0, 0, "ALTA UMID. CAMERA"},
      {7, 0, 0, "FAULT SENSORI"}
    };
    
    /* ----------------------------------------------------------------------------------
       CLASS OBJECT DECLARATION
       ----------------------------------------------------------------------------------
    */
    Countimer tCounter;                                     // OGGETTO COUNTER
    DHT dht1(DHT1_PIN, DHTTYPE);                            // OGGETTO DHT SENSORE 1
    DHT dht2(DHT2_PIN, DHTTYPE);                            // OGGETTO DHT SENSORE 2
    
    TimedAction taskLenta = TimedAction(1000,&Task1000);    // Task SLOW (1 secondo)
    TimedAction taskVeloce = TimedAction(100,&Task200);	    // Task FAST (200 mills)	
    TimedAction taskVentola = TimedAction(30000,&TaskFAN);  // Task VENTOLA (su condizione)
    TimedAction taskLuce = TimedAction(60000,&TaskLIGHT);   // Task LUCE (su condizione) AUTOSPEGNIMENTO 60sec.
    
    /*----------------------------------------------------------------------------------
      END CLASS OBJECT DECLARATION
      ----------------------------------------------------------------------------------
    */
    
    void setup() {
      pinMode(LUCE_PIN, OUTPUT);            // LUCE
      pinMode(ALLARME_PIN, OUTPUT);         // ALLARME
      pinMode(RISC1_PIN, OUTPUT);           // RISCALDATORE PRINCIPALE
      pinMode(RISC2_PIN, OUTPUT);           // RISCALDATORE AUSILIARIO 
      pinMode(RISC_RAFFR_PIN, OUTPUT);      // RISCALDAMENTO-RAFFRESCAMENTO
      pinMode(RAFFR_PIN, OUTPUT);           // RAFFREDDAMENTO
      pinMode(UM_DEUM_PIN, OUTPUT);         // UMIFICATORE-DEUMIFICATORE 
      pinMode(UM_PIN, OUTPUT);              // UMIFICATORE 
      pinMode(DEUM_PIN, OUTPUT);            // DEUMIDIFICATORE
      pinMode(VENTOLA_PIN, OUTPUT);         // VENTOLA  
      pinMode(51, OUTPUT);                  // BLINK RAMPA T
      pinMode(53, OUTPUT);                  // BLINK RAMPA H
    
      pinMode(A0, INPUT_PULLUP);            // TEMPERATURA
      pinMode(A1, INPUT_PULLUP);            // UMIDITA'
      pinMode(A2, INPUT_PULLUP);            // FINE CORSA PORTA CHIUSA 
      pinMode(A3, INPUT_PULLUP);            // LIVELLOSTATO ACQUA
      // -------------- DA RIMUOVERE CON IL DISPLAY ---------------
      pinMode(A5, INPUT_PULLUP);            // TIMER STEPS (AUTO/MAN)
      pinMode(A6, INPUT_PULLUP);            // START TIMER
      pinMode(A7, INPUT_PULLUP);            // STOP/RESET TIMER
      // --------------------- DA RIMUOVERE -----------------------
    
        /*----------------------------------------------------------------------------------
        DHT22 inizializzazione CLASSE
        ----------------------------------------------------------------------------------
      */
      dht1.begin();                         // SENSORE 1 DHT22
      dht2.begin();                         // SENSORE 2 DHT22
    
      taskLuce.disable();
    
      setTcontrolmode(AUTO_ALL);
      setHcontrolmode(AUTO_ALL);
    
      Timer_A_M=MAN;
      tCounter.setCounter(0, 0, 10, tCounter.COUNT_UP, tCounterComplete);
      tCounter.setInterval(tCounterRunning, 1000);
    	
      //AggiornaSPT(TSp1);
      ProfiloAttivo=CURVA1;
      digitalWrite(DEUM_PIN,HIGH);
      Serial.begin(9600);
    }
    
    void loop() {
      taskVeloce.check();                   //200ms
      taskLenta.check();                    //1sec
      taskVentola.check();                  //Ciclico
      taskLuce.check();                     //su richiesta
      tCounter.run(); 
    }
    
    void Task200(){
      /* 
        Inserire quì la lettura della seriale Display
      */
      bool _PortaFC = (analogRead(A2) < 512) ? LOW : HIGH;
      bool _LivelloAcqua = (analogRead(A3) < 512) ? LOW : HIGH;
      /*---------------------------------------------------------
        Da Eliminare quandosi connette il Display TOUCH
        ---------------------------------------------------------
      */
      bool analogA5 = analogRead(A5) < 512;
      bool analogA6 = analogRead(A6) < 512;
      bool analogA7 = analogRead(A7) < 512;
    
      if (LivelloAcqua != _LivelloAcqua) {
          LivelloAcqua = _LivelloAcqua;
          setUm(Um_S);
          setAllarme(0, LivelloAcqua);
          verificaAllarme();
      }
    
      // USO ANALOGICO COME DIGITALE PER LO SW PORTA
      if (PortaFC!=_PortaFC) {
        PortaFC=_PortaFC;
    
        if (PortaFC == PORTA_AP) {  
          // PORTA APERTA
          taskLuce.disable();
          setLuce(HIGH);
        }
        else if (PortaFC == PORTA_CH) {
          // PORTA CHIUSA 
          Serial.println("DOOR is Close Light TURN-OFF In few seconds");
          if (Luce_S==1) {
            taskLuce.enable();
            taskLuce.reset();
          }        
        }
        set_Risc_Raffr(Risc_Raff_S);
        setRisc1(Risc1_S);
        setRisc2(Risc2_S);
        setRaffr(Raffr_S);
        setAllarme(2, PortaFC);
        verificaAllarme();
      }
    
      /*  ---------------------------------------------------------------------------
          ------------------------ GESTIONE PULSANTI TIMER --------------------------
          ---------------------------------------------------------------------------
      */
      if (analogA5) {
        Timer_A_M = (Timer_A_M == MAN) ? AUTO : MAN;
        Serial.println((Timer_A_M == MAN) ? "MANUALTIMER" : "AUTOTIMERSTEPS");
      }
    
      if (analogA6) {
        if (Timer_A_M == MAN) {
          if (!tCounter.isCounterRunning()) {
            tCounter.start();
            Serial.println("Counter START");
          } else if (!tCounter.isStopped() && tCounter.isStarted()) {
            tCounter.pause();
            Serial.println("Counter PAUSA");
          }
        } else if (Timer_A_M == AUTO) {
          EseguiProfilo(0);
        }
      }
    
      if (analogA7) {
        if (!tCounter.isCounterCompleted()) {
          tCounter.stop();
          Serial.println("Counter STOP");
        }
      }
    }
    
    void Task1000() {
      /*
      h1 = dht1.readHumidity();     // read Umidity 1
      t1 = dht1.readTemperature();  // read temperature 1
      h2 = dht2.readHumidity();     // read Umidity 2
      t2 = dht2.readTemperature();  // read temperature 2
    
      // Check if any reads failed and exit early (to try again).
      _SensorState= isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2)   
    
      if (SensorState != _SensorState) {
        SensorState=_SensorState;
        SetAlarm(7,SensorState);
        CheckAlarm();
      }
      if (SensorState==0) {
        tavg=(t1+t2)/2;
        havg=(h1+h2)/2;
        InputT = tavg;
        InputRh = havg;
      }
      */
    
      // Lettura dei sensori esterni e scalatura
      //tcamera=mapFloat(analogRead(A0) , 0, 1023, 0.0, 50.0);
      //tambiente=mapFloat(analogRead(A1) , 0, 1023, 0.0, 50.0);
      TInput = mapFloat(analogRead(A0), 0, 1023, 0.0, 50.0);
      RhInput = mapFloat(analogRead(A1), 0, 1023, 0.0, 100.0);
    
      if (Tctrlmode !=AUTO_C) {
        // DETERMINA SE MODO RISCALDAMENTO/RAFFREDDAMENTO
        if (hysControl(TInput,TSp1,THysMode,&Risc_Raff_S)) {
          set_Risc_Raffr(Risc_Raff_S);
        }
    
        // DETERMINA SE ATTIVARE UTENZA RISC1/RAFFR
        if (hysControl(TInput,TSp1,THysOut,&Risc1_S)) {
          setRisc1(Risc1_S);
          setRaffr(!Risc1_S);
        }
    
        // DETERMINA SE ATTIVARE UTENZA AUSILIARIA RISC2
        if (hysControl(TInput,TSp2,THysOut,&Risc2_S)) {
          setRisc2(Risc2_S);
        }
    
        if (AlmControl(TInput,TSp1,THysAlm,&AllarmeT_S)) {
          switch (AllarmeT_S) {
            case -1:    setAllarme(3, 1);
              break;
            case 1:     setAllarme(4, 1);
              break;
            case 0:     setAllarme(3, 0);setAllarme(4, 0);
              break;
          }
          verificaAllarme();
        }
      }
    
      if (Hctrlmode !=AUTO_H) {
        if (hysControl(RhInput,RhSp,RhHysMode,&Um_Deum_S)) {
          setUm_Deum(Um_Deum_S);
        }
    
        if (hysControl(RhInput,RhSp,RhHysOut,&Um_S)) {
          setUm(Um_S);
          setDeum(!Um_S);
        }
    
        if (AlmControl(RhInput,RhSp,RhHysAlm,&AllarmeRh_S)) {
          switch (AllarmeRh_S) {
            case -1:    setAllarme(5, 1);
              break;
            case 1:     setAllarme(6, 1);
              break;
            case 0:     setAllarme(5, 0);setAllarme(6, 0);
              break;
          }
          verificaAllarme();
        }
      }
      if (!tCounter.isCounterRunning()) {
        Serial.print("SpT:" + String(TSp1, 1) + " T:" + String(TInput, 1));
        Serial.println("|SpRh:" + String(RhSp, 1) + " Rh:" + String(RhInput, 1));
      }
    }
    
    void TaskLIGHT(){
      setLuce(LOW);
    } 
    
    void TaskFAN(){
      bool bFan=!Ventola_S;
      taskVentola.setInterval(CicloVentola[bFan]);
      setVentola(bFan);
    } 
    
    void tCounterRunning()
    {
      float newT=TSp1;
      float newH=RhSp;
      if (Timer_A_M==AUTO) {  
        newT=newT+dtTinc;
        newH=newH+dtHinc;	
        switch (DirRampaT) {
          case R_SALITA:
            TRampa_S=!TRampa_S;   // LAMPEGGIO
            if (newT>prossTSp) {DirRampaT=R_STASI;newT=prossTSp;}  
            break;
          case R_DISCESA:
            TRampa_S=!TRampa_S;   // LAMPEGGIO
            if (newT<prossTSp) {DirRampaT=R_STASI;newT=prossTSp;}  
            break;
          case R_STASI:
            TRampa_S=0;   // STOP LAMPEGGIO
            newT=prossTSp;
            break;
        }
        digitalWrite(RAMPA_T, TRampa_S);   // BLINK RAMPA T
    
    	  switch (DirRampaH) {
          case R_SALITA:
            HRampa_S=!HRampa_S;   // LAMPEGGIO
            if (newH>prossHSp) {DirRampaH=R_STASI;newH=prossHSp;}  
            break;
          case R_DISCESA:
            HRampa_S=!HRampa_S;   // LAMPEGGIO
            if (newH<prossHSp) {DirRampaH=R_STASI;newH=prossHSp;}  
            break;
          case R_STASI:
            HRampa_S=0;   // STOP LAMPEGGIO
            newH=prossHSp;
            break;
        }
        digitalWrite(RAMPA_H, HRampa_S);   // BLINK RAMPA H
    	  SPLimiter(&TSp1,newT,4,35);  
    	  SPLimiter(&RhSp,newH,50,90);
    
        // AGGIORNA TIMER+STEP SE IN AUTOCOUNTER
        Serial.print(timetoHHMMSS(totmills+tCounter.getCurrentMills()));
        Serial.print(" [" + String(ContaFasi) + "] ");
        Serial.print("SpT:" + String(TSp1,1)+ " T:" + String(TInput,1));
        Serial.print("|");
        Serial.print("SpH:" + String(RhSp,1)+ " H:" + String(RhInput,1));
        Serial.print("|");
        Serial.print(tCounter.getCurrentTimeLeft());
        Serial.print("-");
        Serial.println(tCounter.getCurrentTime());
      }
      else {
        // AGGIORNA TIMER SE PASSATO IN MANUALE
        if (ContaFasi==NUM_FASI) {
    	    SPLimiter(&TSp1,prossTSp,4,35);  
    	    SPLimiter(&RhSp,prossHSp,50,90);
    	
          Serial.print("Over (");
          Serial.print(timetoHHMMSS(totmills+tCounter.getCurrentMills()));
          Serial.print(") ");
        }
        Serial.print("Sp:" + String(TSp1,1)+ " T:" + String(TInput,1) );
        Serial.print(tCounter.getCurrentTimeLeft());
        Serial.print("-");
        Serial.println(tCounter.getCurrentTime());
      }
    }
    
    void tCounterComplete() {
      if (Timer_A_M==AUTO) { 
        totmills+=CntSteps[ProfiloAttivo][ContaFasi].durata;
    	ContaFasi++;
        if (ContaFasi<NUM_FASI){
          EseguiProfilo(ContaFasi);
          // AGGIORNAMENTO DISPLAY
          // ...
        }  
        else {
          //FINITO I 3 STEPS AUTOMATICI
          Timer_A_M=MAN;
          tCounter.setCounter(0,0,10);  // TIMER MANUALE EXTRATEMPO SE L'ULTIMO STEP AUTO NON FOSSE SUFFICIENTE
          tCounter.start();             // FA RIPARTIRE IL TIMER SE SCADUTO ULTIMO STEP
        }        
      }
      else if (Timer_A_M==MAN)
      {
        // TIMEOUT
      }
    }
    
    void EseguiProfilo(uint8_t FaseCorrete) {
      uint32_t _tempofase;
    
      if (FaseCorrete == 0) {   
        totmills = 0;
        ContaFasi = 0;
        TSp1 = 24.0;           //Primo Step uso Temperatura Ambiente come Inizio per la rampa
    	  RhSp=60.0;             //Primo Step uso Umidità ambiente come Inizio per la rampa
      } else {
        SPLimiter(&TSp1,CntSteps[ProfiloAttivo][FaseCorrete - 1].tsp,4,35);
        SPLimiter(&RhSp,CntSteps[ProfiloAttivo][FaseCorrete - 1].hsp,50,90);
      }
      if (FaseCorrete < NUM_FASI) {
        prossTSp = CntSteps[ProfiloAttivo][FaseCorrete].tsp;
    	  prossHSp = CntSteps[ProfiloAttivo][FaseCorrete].hsp;
        _tempofase=CntSteps[ProfiloAttivo][FaseCorrete].durata;
    
        dtTinc=CalcoloRaccordo(TSp1, prossTSp, _tempofase, &DirRampaT);
        dtHinc=CalcoloRaccordo(RhSp, prossHSp, _tempofase, &DirRampaH);
        
        Serial.print("Fase:[");
        Serial.print(FaseCorrete);
        Serial.print("] rmpT:");
        Serial.print(DirRampaT);
        Serial.print("| dtT:");
        Serial.print(dtTinc);
        Serial.print("| TSp1:");
        Serial.print(TSp1);
        Serial.print("->");
        Serial.print(prossTSp);
    
        Serial.print("| rmpH:");
        Serial.print(DirRampaH);
        Serial.print("| dtH: ");
        Serial.print(dtHinc);
        Serial.print("| HSp: ");
        Serial.print(RhSp);
        Serial.print("->");
        Serial.print(prossHSp);
        Serial.print("| Durata: ");
        Serial.println(timetoHHMMSS(_tempofase));
        
        tCounter.setCounter(_tempofase);
        // Update display
        // ...
        if (!tCounter.isCounterRunning()) {
          tCounter.start();
        }
      } else {
        // Handle error: FaseCorrete exceeds maximum steps
        Serial.println("Error: Step index exceeds maximum steps.");
      }
    }
    
    void setTcontrolmode(eControlMode value)   {
      if (Tctrlmode!=value) {
        Tctrlmode=value;
        switch (value)
        {
          case AUTO_ALL: 
            Risc_Raff_S = RISCALDA;
            break;
          case AUTO_H: 
            Risc_Raff_S = RISCALDA;
            break;
          case AUTO_C:  
            Risc_Raff_S = RAFFREDDA;      
            break;    
          default: break;
        }
        // SET ICON ON DISPLAY
        // ....
      }
    }
    
    void setHcontrolmode(eControlMode value)   {
      if (Hctrlmode!=value) {
        Hctrlmode=value;
        switch (value)
        {
          case AUTO_ALL: 
            Um_Deum_S = UMIDIFICA;
            break;
          case AUTO_H: 
            Um_Deum_S = UMIDIFICA;
            break;
          case AUTO_C:
            Um_Deum_S = DEUMIDIFICA;   
            break;     
          default: break;
        }
        // SET ICON ON DISPLAY
        // ....  
      } 
    }
    
    void set_Risc_Raffr(bool value)  { 
      Risc_Raff_S = value;
      digitalWrite(RISC_RAFFR_PIN, Risc_Raff_S==RAFFREDDA && (PortaFC==PORTA_CH));  
      setRisc1(Risc1_S);
      setRaffr(!Risc1_S);
      // SET ICON ON DISPLAY
      // ....
    }
    
    void setRisc1(bool value) {
      Risc1_S=value && RaffrrEN && (Risc_Raff_S == RISCALDA);
      digitalWrite(RISC1_PIN, Risc1_S && (PortaFC==PORTA_CH));
      // SET ICON ON DISPLAY
      // ....
    }
    
    void setRisc2(bool value) {
    	Risc2_S = value && Risc2EN && (Risc_Raff_S == RISCALDA);
    	digitalWrite(RISC2_PIN, Risc2_S && (PortaFC==PORTA_CH));
      // SET ICON ON DISPLAY
      // ....
    }
    
    void setRaffr(bool value) {
      Raffr_S=value && RaffrrEN && (Risc_Raff_S == RAFFREDDA);
      digitalWrite(RAFFR_PIN, Raffr_S && (PortaFC==PORTA_CH));
      // SET ICON ON DISPLAY
      // ....
    }
    
    void setUm_Deum(bool value)  { 
      Um_Deum_S = value;
      digitalWrite(UM_DEUM_PIN, (Um_Deum_S==DEUMIDIFICA)  && (PortaFC==PORTA_CH));  
      // SET ICON ON DISPLAY
      // ....
      setUm(Um_S);
      setDeum(!Um_S);
    }
    
    void setUm(bool value) {
      Um_S = value && UmEN && (Um_Deum_S == UMIDIFICA);
      digitalWrite(UM_PIN, Um_S && (PortaFC==PORTA_CH) && !LivelloAcqua);
      // SET ICON ON DISPLAY
      // ....
    }
    
    void setDeum(bool value) {
      Deum_S = value && DeUumEN && (Um_Deum_S == DEUMIDIFICA);
      digitalWrite(DEUM_PIN, Deum_S && (PortaFC==PORTA_CH));
      // SET ICON ON DISPLAY
      // ....
    }
    
    /*  ---------------------------------------------------------------------------
        -------------------------------- FAN  START -------------------------------
        ---------------------------------------------------------------------------
    */
    void setVentola(bool value)  {
      Ventola_S=value && VentolaEN ;
      digitalWrite(VENTOLA_PIN, Ventola_S && (PortaFC==PORTA_CH));  
      // SET ICON ON DISPLAY
      // ....
    }
    
    /*  ---------------------------------------------------------------------------
        ------------------------------- LIGHT  START ------------------------------
        ---------------------------------------------------------------------------
    */
    void setLuce(bool value)  {
      if ((Luce_S=!value) && Luce_S) {taskLuce.disable();}
      Luce_S=value && LuceEN;
      digitalWrite(LUCE_PIN, Luce_S || PortaFC==PORTA_AP);
      // SET ICON ON DISPLAY
      // .... 
    }
    
    void setAllarmeSonoro(bool value)  {
      if (Allarme_S!=value ) {
        Allarme_S=value && AllarmEN;
        digitalWrite(ALLARME_PIN, Allarme_S);
      // SET ICON ON DISPLAY
      // .... 
      }
    }
    
    float mapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh) {
      return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; 
    }
    
    char* timetoHHMMSS(uint32_t millisec) {
    	uint16_t allSeconds = millisec / 1000;           // convert millisecondi in secondi
    	uint16_t hours = allSeconds / 3600;              // calcola le ore correttamente
    	uint16_t secondsRemaining = allSeconds % 3600;   // secondi rimanenti
    	uint8_t minutes = secondsRemaining / 60;         // converti i secondi rimanenti in minuti
    	uint8_t seconds = secondsRemaining % 60;         // secondi rimanenti
    	sprintf(_TimerHHMMSS, "%02d:%02d:%02d", hours, minutes, seconds);
    	return _TimerHHMMSS;
    }
    
    /*  ---------------------------------------------------------------------------
        ------------------------------- ALARM  START ------------------------------
        ---------------------------------------------------------------------------
    */
    void setAllarme(uint8_t idx, bool value){
      // se l'allarme è RN e riuscito lo ri-visualizzo
      if (data[idx].Stato!=value) {
        data[idx].Stato=value;
        if (value==1) {
          data[idx].Acq=1;
          data[idx].Rientrato=0;
        }
        else {
          data[idx].Rientrato=1;
        }
        data[idx].Mostrato=0;
      }
      // SET ICON ON DISPLAY
      // ....
    }  
    
    void verificaAllarme(){
      bool _AlarmOut_S=0;
      if (AllarmEN) {
        for (uint8_t i = 0; i < NUM_ALLARM; i = i + 1) {
          _AlarmOut_S=_AlarmOut_S || data[i].Stato || data[i].Acq;
          if (data[i].Stato && data[i].Acq  && !data[i].Mostrato) {
            Serial.println(data[i].Desc);
            data[i].Mostrato=1;
          }
          else  if (data[i].Rientrato && !data[i].Mostrato) {
            Serial.print(data[i].Desc);
            Serial.println(" [RN]");
            data[i].Mostrato=1;
          }    
        }
        setAllarmeSonoro(_AlarmOut_S); 
      } 
      // SET ICON ON DISPLAY
      // ....
    }  
    
    void AcqAllarmi(){
      for (byte i = 0; i < NUM_ALLARM; i = i + 1) {
        if (data[i].Acq){
          data[i].Acq=0;
          if (data[i].Stato==0) {
            data[i].Mostrato=0;
            data[i].Rientrato=0;
          }
        }
      }
      setAllarmeSonoro(0);
      /*  BUZZER
          SET ICON ON DISPLAY
          ....
      */
    }            
    
    float CalcoloRaccordo(float sp_Y0, float sp_Y1, uint32_t tempo, uint8_t  *Direzione){
      // Calcolo coefficiente angolare della rampa considerando di esaurirla in 10min 
      // Sapendo che aggiorno a 1secondo...., divido per 600
      float dy=(sp_Y1 - sp_Y0)/600;  
      if (sp_Y0 < sp_Y1) {
        *Direzione = R_SALITA;
      } 
      else if (sp_Y0 > sp_Y1) {
        *Direzione = R_DISCESA;
      } 
      else   {
        *Direzione = R_STASI;
        dy = 0.0;
      }
      return dy;
    }
    
    void SPLimiter(float *setpoint,float valore, float minimo, float massimo) {
      float valorecompresso=valore;	
      if (valorecompresso<minimo){
        valorecompresso=minimo;
      }
      if (valorecompresso>massimo){
        valorecompresso=massimo;
      }
      *setpoint=valorecompresso;
    }
    
    bool hysControl(float input, float setpoint, float hysteresis,bool *state) {
      bool oldstate=*state;
      if (input <= (setpoint - hysteresis)) {
        *state = true;
      } else if (input >= (setpoint + hysteresis)) {
        *state = false;
      }
      return *state != oldstate;
    }
    
    bool AlmControl(float input, float setpoint, float hysteresis, uint8_t *state) {
      uint8_t oldstate=*state;
      if (input <= (setpoint - hysteresis)) {
        *state = -1; 
      } else if (input >= (setpoint + hysteresis)) {
        *state = 1;
      } else if (abs(input - setpoint) < hysteresis) {
        *state = 0;
      }  
      return *state != oldstate;
    }
Devi accedere o registrarti per scrivere nel forum
10 risposte