Datalogger di pressione

Buongiorno,
sperando che la sezione sia giusta, sono qui a presentarvi il mio ultimo progetto: Datalogger di pressione.
Apro questo post per tenere traccia dei miei progressi e ricevere consigli.

La realizazione si tratta di un registratore atto a tenere traccia del valore di pressione e temperatura (in modo da emulare un manodensostato) di interruttori AT

Come da regolamento non verrà trattata la parte di installazione in impianto, ma solo la parte riguardante la bassa tensione percui a valle del convertitore isolato 110VDC -> 24VDC (successivamente regolati in 5VDC).

Questo circuito non avrà nessuna funzione salvavita/protezione (apertura interruttore in caso di bassa pressione ecc), ma solo di monitoraggio per successive analisi.
Sarà galvanicamente isolato da qualsiasi potenziale pericoloso

HW as is:

  • Arduino nano
  • SD shield (Adattatore per SD) + microSD 4Gb
  • DHT22 (Sensore di temperatura)
  • DS3231 (RTC)
  • ADS1115
  • Potenziometro (atto a simulare i valori del manodensostato)
  • Relè (Non in foto)

`

HW to be:
Mantenendo l'interfaccia IDE di arduino si va a rimuovere tutte le componenti superflue trasformando il tutto in un circuito stampato SMD con componenti solo da un lato.
Al momento sono ancora in fase di selezione dei componenti riguardanti la parte di alimentazione e la parte di misura di temperatura (sto valutando una termocoppia da posizionare al'esterno del quadro).



Software as is: al momento si tratta solamente di qualche linea di codice mal scritta e mal commentata atta a controllare se effetivamente tutti i componenti funzionano.

#include <DHT.h>
#include <DHT_U.h>
#include <ADS1115_WE.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "RTClib.h"

#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
#define I2C_ADDRESS 0x48 //definisce indirizzo ADC
 
const int PIN_Temp = 7; //definisce pin per il sensore di temoperatura
const int PIN_Int = 5;  //definisce pin per Allarme RTC DS3231
const int CS = 4;       //Definisce il pin per il CS
const int LED_PIN = 13;  //led interno arduino

DHT dht(PIN_Temp, DHTTYPE);
RTC_DS3231 rtc;
ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS);

void setup() {
  pinMode(LED_PIN, OUTPUT);  //setta il led come uscita
  digitalWrite(LED_PIN, HIGH);//accende il led
  
  Serial.begin(9600); //avvia la seriale a 9600
  while (!Serial) {  } ; // wait for serial port to connect. Needed for native USB port only 
   
  while (!SD.begin(CS)) //attende che la SD si inizializzi
  {
    Serial.println("Card failed, or not present");
    Serial.flush();
    delay(3000);
  }

    Serial.println("card initialized.");

  while (!rtc.begin())  //aspetta che RTC Risponda
  {
    Serial.println(("Unable to find DS3231MM. Checking again in 3s."));
    Serial.flush();
    delay(3000);
  } 
  
Serial.println("RTC OK");
dht.begin();
delay(100);

Serial.println("DHT22 OK");

Wire.begin();
while (!adc.init())  //aspetta che ADC Risponda
  {
    Serial.println(("Unable to find ADC. Checking again in 3s."));
    Serial.flush();
    delay(3000);
  }



}

void loop() {
  DateTime now = rtc.now();
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  adc.setVoltageRange_mV(ADS1115_RANGE_6144); //setta lettura +/- 6.114v
  adc.setMeasureMode(ADS1115_SINGLE); //single shot
if (dataFile) {
    dataFile.print(now.day(),DEC);
    dataFile.print("/");
    dataFile.print(now.month(),DEC);
    dataFile.print("/");    
    dataFile.print(now.year(),DEC);
    dataFile.print(" ");
    dataFile.print(now.hour(),DEC);
    dataFile.print(".");
    dataFile.print(now.minute(),DEC);
    dataFile.print(" ");
    delay(2000);
    dataFile.print(dht.readHumidity());
    dataFile.print("% ");
    dataFile.print(dht.readTemperature());
    dataFile.print("°C ");
    float voltage = 0.0;
    voltage = readChannel(ADS1115_COMP_0_GND);
    dataFile.print(voltage);
    dataFile.println("V");  
    dataFile.close(); 
    Serial.println("scrittura file completata");
  }
  
  else {
    Serial.println("error opening datalog.txt");// if the file isn't open, pop up an error:
  }
  delay(100);
  Serial.println("tutto ok");
  digitalWrite(LED_PIN, LOW);
  abort();
}
float readChannel(ADS1115_MUX channel) {
  float voltage = 0.0;
  adc.setCompareChannels(channel);
  adc.startSingleMeasurement();
  while(adc.isBusy()){}
  voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
  return voltage;
}

Software to be: il software dovrà cosi comportarsi

Inizializazione -> Set allarme ds3231 -> Sleep ........Interupt generato dal ds3231 -> lettura e conversione valori di pressione e temperatura -> scrittura su SD -> set nuovo allarme -> Sleep ..... Interupt -> lettura ecc
Includera anche qualche led per eseguire un primo debug visivo e Watchdog per evitare il blocco del micro.
Spero di riuscire a compattare il tutto all'interno del 328P, in caso contrario passerò al 32U4.
L'estrazione dei dati avverà con lo spegnimento del circuito e l'estrazione della SD. Una volta inserita nel PC tramite EXCEL i valori verranno convertiti in forma grafica per la consultazione.

Mi potete confermare che passare dal 328P al 32U4 sarà quasi "indolore" ? eventualmente consgiliate un altra tipologia di micro ?
EDIT: Controllando le specifiche ho visto che il 328P e il 32U4 hanno la stessa memoria, a questo punto dovrei passare ad un 2560, la domanda rimane invariata: é tanto il lavoro (SW + HW) per implementare un 2560 al posto di un 328P
Quale è il miglior modo per "fare perdere" tempo ad arduino in attesa del interupt (Circa 1 ogni ora), ritengo inproponibile usare delay() ?
Come faccio a compattare le immagini ? non ho trovato nulla a riguardo.....

Spero di non avervi annoiato con questa lunga presentazione

Saluti

ThEnGI (Davide)

Aggiornamenti:
sono state modificate le seguenti parti del circuito

  • Sensore di temperatura
    Il sensore sulla scheda (SHT20) è stato sostituito da una termocoppia K + MAX6675, questo permetterà di misurare la temperatura al esterno del cabinet dove verà posto il circuito, nel caso fosse necessario misurare la temperatura della scheda è possibile farlo con il DS3231.

  • Nuovo circuito di alimentazione
    Il precedente circuito di alimentazione gestito dal AIC2510 è stato sostituito dal un altro circuito creato attorno all'integrato RT8272. A fronte di un maggiore numero di componenti permette di utilizare induttore e condensatori più adatti allo scopo, al momento è stato progettato per erogare fino ad 1A di corrente (Overkill)

Sapete se possono esserci interferenza tra la libreria del MAX6675 e la libreria che gestisce le SD ?
Ho selezionato pin diversi per i due integrati (SD e MAX) è corretto ?

Le precedenti domande rimangono.......

Saluti
ThEngi

Imbastendo il codice continuo ad imbattermi in errori:

#include <DHT.h>
#include <DHT_U.h>
#include <ADS1115_WE.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "RTClib.h"
#include <avr/wdt.h>

#define DHTTYPE DHT22   // DHT 22 
#define I2C_ADDRESS 0x48 //definisce indirizzo ADC
 
const int PIN_Temp = 7; //definisce pin per il sensore di temoperatura
#define PIN_Int 2   //definisce pin per Allarme RTC DS3231
const int CS = 5;       //Definisce il pin per il CS
const int RELAY = 4;  //led interno arduino
float X=0.00;
float ID=0;
float voltage = 0.00;

DHT dht(PIN_Temp, DHTTYPE); //inizializza oggetto DHT
RTC_DS3231 rtc; //inizializza oggetto RTC

    





ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS); //inizializza ADC




void setup() {

    rtc.disable32K(); // disabilita uscita 32K
    rtc.writeSqwPinMode(DS3231_OFF);
  Serial.begin(9600); //avvia la seriale a 9600
  delay(100);  // attende la stabilizazione della seriale  
  while (!SD.begin(CS)) //attende che la SD si inizializzi
  {
    Serial.println("Impossibile trovare SD, Ritento tra 5 secondi");
    Serial.flush();
    delay(5000);
  }
    Serial.println("SD Inizializata");

  while (!rtc.begin())  //aspetta che RTC Risponda
  {
    Serial.println(("Impossibile trovare DS3231, Ritento tra 5 secondi"));
    Serial.flush();
    delay(5000);
  } 
  rtc.disable32K(); // disabilita uscita 32K
  rtc.writeSqwPinMode(DS3231_OFF);
  rtc.clearAlarm(1);
  rtc.disableAlarm(1);
  



    
  Serial.println("RTC OK");
  dht.begin(); //inizializza DHT22
  delay(100);
  Serial.println("DHT22 OK");

  Wire.begin();
  while (!adc.init())  //aspetta che ADC Risponda
    {
      Serial.println(("Impossibile inizializare ADC, Ritento tra 5 secondi"));
      Serial.flush();
      delay(5000);
    }
  adc.setVoltageRange_mV(ADS1115_RANGE_6144); //setta lettura +/- 6.114v
  adc.setMeasureMode(ADS1115_SINGLE); //single shot
  wdt_enable(WDTO_1S); //avvia WDT 1 seocnd0

  attachInterrupt(digitalPinToInterrupt(PIN_Int),ISR,FALLING);

}

void loop() {

  DateTime now = rtc.now(); //acquisisce data/ora
  voltage = readChannel(ADS1115_COMP_0_GND);
  X=(voltage*100)/5;    //calcola percentuale
  File dataFile = SD.open("datalog.csv", FILE_WRITE);
  
if (dataFile) {

    noInterrupts(); //disabilita gli interupts per evitare strane cose durante la scrittura
    dataFile.print(now.day(), DEC);
    dataFile.print('/');
    dataFile.print(now.month(), DEC);
    dataFile.print('/');
    dataFile.print(now.year(), DEC);
    dataFile.print(" ");
    dataFile.print(now.hour(), DEC);
    dataFile.print(':');
    dataFile.print(now.minute(), DEC);
    dataFile.print(':');
    dataFile.print(now.second(), DEC);
    dataFile.print(" ");
    dataFile.print(voltage);
    dataFile.print(" ");
    dataFile.print(X);
    dataFile.close();
    interrupts();  //riabilita gli interrupts per il normale funzionamento
    Serial.println("scrittura file completata");
  }
  
  else {
    Serial.println("Impossibile apreire datalog.txt");// if the file isn't open, pop up an error:
  }
  wdt_reset();

  
  rtc.setAlarm1(now.year(),now.month(),now.day(),0,0,0,DS3231_A1_Hour );
  
}


float readChannel(ADS1115_MUX channel) {
  float voltage = 0.0;
  adc.setCompareChannels(channel);
  adc.startSingleMeasurement();
  while(adc.isBusy()){}
  voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
  return voltage;
}

void ISR(){abort();}

Gli errori:

C:\Users\a452559\OneDrive - Enel Spa\Datalogger_pressione_V1\Datalogger_pressione_V1.ino: In function 'void setup()':
Datalogger_pressione_V1:80:50: error: 'ISR' was not declared in this scope
   attachInterrupt(digitalPinToInterrupt(PIN_Int),ISR,FALLING);
                                                  ^~~
C:\Users\a452559\OneDrive - Enel Spa\Datalogger_pressione_V1\Datalogger_pressione_V1.ino: In function 'void loop()':
Datalogger_pressione_V1:126:71: error: no matching function for call to 'RTC_DS3231::setAlarm1(uint16_t, uint8_t, uint8_t, int, int, int, Ds3231Alarm1Mode)'
   rtc.setAlarm1(now.year(),now.month(),now.day(),0,0,0,DS3231_A1_Hour );
                                                                       ^
In file included from C:\Users\a452559\OneDrive - Enel Spa\Datalogger_pressione_V1\Datalogger_pressione_V1.ino:7:0:
C:\Users\a452559\Documents\Arduino\libraries\RTClib\src/RTClib.h:339:8: note: candidate: bool RTC_DS3231::setAlarm1(const DateTime&, Ds3231Alarm1Mode)
   bool setAlarm1(const DateTime &dt, Ds3231Alarm1Mode alarm_mode);
        ^~~~~~~~~
C:\Users\a452559\Documents\Arduino\libraries\RTClib\src/RTClib.h:339:8: note:   candidate expects 2 arguments, 7 provided
In file included from C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:30:0,
                 from sketch\Datalogger_pressione_V1.ino.cpp:1:
C:\Users\a452559\OneDrive - Enel Spa\Datalogger_pressione_V1\Datalogger_pressione_V1.ino: At global scope:
Datalogger_pressione_V1:140:6: error: expected unqualified-id before string constant
 void ISR(){abort();}
      ^
Datalogger_pressione_V1:140:6: error: expected unqualified-id before 'void'
 void ISR(){abort();}
      ^
Datalogger_pressione_V1:140:6: error: expected ')' before 'void'
exit status 1
'ISR' was not declared in this scope

Sembrerebbe un errore che riguarda l'abilitazione dell'interrupt, ma non riesco a risolverlo.

Grazie in anticipo
ThEnGI
ps. scusate la poca formattazione del codice

ISR probabilmente non è un nome di funzione valido, inoltre la funzione abort() cosa dovrebbe fare? Non è definita da nessuna parte e non mi pare faccia parte del framework Arduino.

L'altro errore è perché stai passando dei parametri alla funzione setAlarm1() in modo non previsto.
Prova cosi:

rtc.setAlarm1(DateTime(now.year(),now.month(),now.day(), 0, 0, 0), DS3231_A1_Hour);

Grazie della risposta, appena ho un PC sotto mano provo e ti do un feedback !
Uso abort perché non ho ancora scritto la funzione di gestione del interrupt, se arriva l'interupt correttamente mi blocca l'arduino.volevo usare un serial.print("evento interrupt") ma ho letto che la funzione di gestione dell'interup deve essere rapida

... NON solo deve essere rapida, ma al suo interno NON si deve richiamare nulla che, a sua volta, usi gli interrupt (che, in una ISR, sono disabilitati), quindi ... nessuna operazione sulla seriale (che è gestita a interrupt), millis() non funziona (perché avanza grazie ad un interrupt), delay() non funziona (perché usa millis()) e così via ...

Piccolo breviario:

When writing an Interrupt Service Routine (ISR):

  • Keep it short
  • Don't use delay ()
  • Don't do serial prints
  • Make variables shared with the main code volatile
  • Variables shared with main code may need to be protected by "critical sections" ( Atomic access)
  • Don't try to turn interrupts off or on

Guglielmo

grazie cotestatnt e gpb01

Effettuate modifiche: ISR -> pippo e rtc.setAlarm1(DateTime ......
Non capisco il motivo perché non gli piaccia ISR (interrupt service routine) ma qualsiasi altro nome va bene......
Nel secondo caso è un errore mio di comprensione, non pensavo servisse il DateTime ma che bastassero i valori (now.year, ecc...)
Il programma compila correttamente, adesso vedo di sistemarlo per poterlo passare ad arduino

saluti
ThEnGI

... perché ISR è un nome riservato del compilatore. Se non usi il "core" arduino che ti semplifica tutta la gestione delle ISR, ma vai a basso livello allora scopri che ...

#define ISR (vector, attributes)

Introduces an interrupt handler function (interrupt service routine) that runs with global interrupts initially disabled by default with no attributes specified.

The attributes are optional and alter the behaviour and resultant generated code of the interrupt routine. Multiple attributes may be used for a single function, with a space seperating each attribute.

Valid attributes are ISR_BLOCK, ISR_NOBLOCK, ISR_NAKED and ISR_ALIASOF(vect).

vector must be one of the interrupt vector names that are valid for the particular MCU type.

Guglielmo

Perché ISR() è definita nel core di Arduino (dentro ad "avr/interrupt.h" per l'esattezza).

In ogni caso evita nomi tutti maisucoli per le funzioni, il maiuscolo per convenzione è riservato a simboli o costanti. Ti consiglierei poi di dare anche un nome più significativo per le funzioni, per evidenziare lo scopo, che so, ISR_Alarm() o qualcosa del genere.

Grazie a tutti per i chiarimenti.

al momento ho una domada che non riesco a risolvere:

Come faccio a far perdere tempo ad arduino ? ovvero arrivato ad un certo punto del programma in Loop() vorrei che si fermasse e aspettase l'interupt generato dal ds3231 (allarme).
Pensavo di metterlo a dormire, ho letto alcuni tutorial ma ancora non ho ben capito cosa si spegne e cosa rimane acceso, se ne avete uno in italiano è ben gradito.
L'altra alternativa era di mettere un while vuoto in modo che il programma si blocchi e aspetti per l'interrupt:due righe d'esempio

volatile boolean status = LOW 
..........
wihile (status)
{}
status = !status
........

Void IST_allarm{
status=!status
}

Secondo voi quale è la soluzione migliore ?
Il circuito verrà alimentatto da un alimentatore percui il risparmio energetico non mi interessa

Grazie
ThEnGI

.... dato che NON hai problemi di risparmio energetico, senza complicarti la vita, va bene la soluzione di un while() che aspetta che un flag diventi FALSE per uscire.

Quindi, metti la flag a TRUE prima di entrare nel while( tuaFlag ) e la ISR metterà la flag a FALSE, facendo si che si esca dal while.

Mi raccomando, la flag va dichiarata 'volatile' o non funziona ... :roll_eyes:

Guglielmo

Ma scusa, la loop() è già una while() (anzi while(1)), perché aggiungerne una tua? Tra l'altro ti impedirà in futuro di fare anche altre cose...
In questi casi la cosa migliore, e che permette maggiore flessibilità anche in futuro, è implementare una "macchina a stati finiti" e "lasciare libera" la loop() di girare. Se non hai mai avuto a che fare con macchine a stati finiti chiedi pure, comunque sia in linea di massima sarebbe una cosa del tipo:

volatile boolean evento = false;
// Stati:
#define S_START 0
#define S_WAIT 1
#define S_EXEC 2
// Variabile di stato
int stato = 0; // 0=stato iniziale, 1= attesa, 2=fai qualcosa dopo l'attesa
...
void setup()
{
  ...
}

void loop()
{
  switch (stato)
  {
    case S_START:
      if (Miacondizione) {
        evento = false;
        // Attivo l'interrupt, ad esempio:
        attachInterrupt(digitalPinToInterrupt(PIN_ALARM), IST_alarm, RISING;
        stato = S_WAIT;
      }
      else
        FaiQualcosa();
      break;

    case S_WAIT:
      if (evento)  {  // E' stato attivato l'interrupt!
        detachInterrupt(digitalPinToInterrupt(PIN_ALARM));
        stato = S_EXEC; // Procedi
      }
      break;

    case S_EXEC:
      FaiQualcosaltro();
      stato = S_START; // Torno all'inizio (?)
      break;
}
...
void IST_alarm{
  evento = true; // Attenzione agli interrupt multipli, eviterei evento = !evento;
}

Ciao e grazie docdoc,
Non ho mai usato una macchina a stati finiti, anche perché praticamente tutti i programmi che ho scritto hanno uno sviluppo "lineare". C'è qualche tutorial in italiano ? Anche se dal tuo esempio mi sembra di aver capito.
Non sono molto convinto che mi possa servire perché al momento (ma anche in futuro) il micro deve andare in standby fino al ricevimento del'interrupt da parte del RTC, nel momento che dovrà avere altre funzionalità dovrò comunque stravolgere il programma.

Al momento sto avendo difficoltà a settare l'allarme in modo progressivo (ora x +1) con la rtclib.h

Grazie ciao

Beh direi che, oltre ad iniziare ad imparare questa logica che si applica abbastanza spesso nei processi di controllo, iniziare comunque ad impostarlo "bene" prevedendo la possibilità in futuro di estenderne le funzionalità ti direi che ne vale la pena, tanto più se attualmente il tuo codice è molto semplice.. :wink:

Magari emplice per te :stuck_out_tongue:

Mi sono letto qualche tutorial, non è difficilissimo da implementare appena ho un pò di tempo ci provo. Fondamentalemte è uno switch(stato) che gira trai i vari case, Giusto ?

Non è uno spreco non inserire delay e far continuamente girare il programma ? Non rischio che si inchiodi più spesso ?

Mettiamo di avere tre stati (ABC), quello che mi interessa è eseguire il programma B quando avviente l'interupt. Con il flag attuale entro in C, avviene l'interupt: setto lo il flag = B, continua l'esecuzione di C. A questo punto non devo aspettare che C finisca ritorni alla linea del programma switch(flag) ?

Grazie
Ciao

@docdoc ti posto un estratto del codice, dovrei avere implementato correttamente la macchina a stati, sono ancora dubbioso anche perchè di 3 stati usato praticamente è solo 1.........
rimane ancora come impostare l'allarme, non riesco a caricare nel oggetto DateTime le funzioni matematiche...... se non ci riesco passo al tempo in unix tanto indipendentemente dal orario a me serve avere misurazioni ogni ora

#define S_Start 0 //stato di inizializazione 
#define S_Wait 1 //stato di attesa interupt
#define S_Save 2 //stato di salvataggio dati 

void loop() {

  switch(Status){
      case 0:  
      digitalWrite(RELAY, HIGH); //eccito il relè/chiudo il contatto NO
      Status = S_Save;
      break;

      case 1:
      break;

      case 2:
        detachInterrupt(digitalPinToInterrupt(PIN_Int));
        DateTime now = rtc.now(); //carica in now data e ora attuali
        voltage0 = readChannel(ADS1115_COMP_0_GND); //Avvia lettura canale 0
        voltage1 = readChannel(ADS1115_COMP_1_GND); //Avvia lettura canale 1
        voltage2 = readChannel(ADS1115_COMP_2_GND); //Avvia lettura canale 2
        voltage3 = readChannel(ADS1115_COMP_3_GND); //Avvia lettura canale 3
        dataFile = SD.open("datalog.csv", FILE_WRITE); //apre file csv
  
        if (dataFile) {
            noInterrupts(); //disabilita gli interupts per evitare strane cose durante la scrittura
            dataFile.print(now.day(), DEC);
            dataFile.print('/');
            dataFile.print(now.month(), DEC);
            dataFile.print('/');
            dataFile.print(now.year(), DEC);
            dataFile.print(" ");
            dataFile.print(now.hour(), DEC);
            dataFile.print(':');
            dataFile.print(now.minute(), DEC);
            dataFile.print(':');
            dataFile.print(now.second(), DEC);
            dataFile.print(" ");
            dataFile.print(voltage0);
            dataFile.print(" ");
            dataFile.print(voltage1);
            dataFile.print(" ");
            dataFile.print(voltage2);
            dataFile.print(" ");
            dataFile.print(voltage3);
            dataFile.close();  //chiude file CSV
            interrupts();  //riabilita gli interrupts per il normale funzionamento
            Serial.println("scrittura file completata");
                        }
  
      else {
            Serial.println("Impossibile apreire datalog.txt");// if the file isn't open, pop up an error:
           }

    attachInterrupt(digitalPinToInterrupt(PIN_Int),AllarmISR, FALLING);
    rtc.setAlarm1(DateTime(now.year(),now.month(),now.day(), 0, 0, 0), DS3231_A1_Hour);  
    Status = S_Wait;
    break;
}

}

void AllarmISR() {
  Status=S_Save;
}

Ciao ThEnGI

Forse ho risolto sono riuscito a fargli compilare il "now.hour() +1" non so come si comporta in caso di overflow, per il momento lo lascio girare tutto il WE, lunedì torno e vedo sul monitor seriale cosa scrive !