Recuperare i valori da interrupt e calcolare media

Ciao a tutti! Mi chiamo Davide, e sono relativamente nuovo sia del mondo dell'elettronica ma sopratutto di Arduino. Ci sto smanettando da un mesetto grazie ad uno starter kit, ho fatto un po' di prove, ho acquistato anche altri sensori, e adesso mi trovo alle prese con il primo problemone...

Antefatto: devo misurare il tempo di pendolamento di una grossa attrezzatura; per darvi l'idea si tratta di tempi nell'ordine di 1,5-2,5 secondi circa. Si tratta di prove omologative per le macchine agricole, e attualmente utilizziamo un vecchissimo PC con porta seriale collegata ad un sensore magnetico, e con un programma in C che richiamiamo da MS-Dos.

Abbiamo deciso di aggiornare "l'hardware" e ho provato a costruire una piccola cosina in Arduino, che sorprendentemente funziona. Ho utilizzato anche qua un banale sensore Hall lineare, collegato al pin 2 e che lavoro in Interrupt. L'idea dell'interrupt l'ho trovata su questo forum se non sbaglio, relativamente al calcolo degli RPM.

Il codice è il seguente, e gestisce bene il tutto, nel senso che il sensore funziona, sull'LCD e sul monitor seriale vedo i valori di tempo che ad occhio sono corretti (verrà poi verificato questo), e anche il "contatore" che ho fatto con un if...else funziona, nel senso che mi conta il numero di "pendolate" e arrivati alla fine mi ferma il tutto dando un "Prova terminata" (Count è pari a 21 perchè devo fare 10 pendolate complete, quindi 20 mezze pendolate, e la prima è per "azzerare" il tempo, altrimenti ho visto che il primo dato dipende anche da quanto ho acceso la scheda).

#include <LiquidCrystal.h>

LiquidCrystal lcd(11, 10, 6, 5, 4, 3);

int Tempo_Attuale;       //variabile che conterra il tempo attuale misurato dalla funzione millis()
int Tempo_Precedente;    //variabile che conterra la copia del tempo attuale per poter valutare il
                        //tempo trascorso tra due eventi consecutivi
                       
int Tempo_Evento;        //variabile che conterra il risultato del calcolo del tempo trascorso tra due
                        //eventi in presenza di segnale HALL

int Count = 21; //definizione numero pendolate

//*********************************************************************************************************
void setup(){

lcd.begin(16,2);
lcd.print((Count-1)/2);
lcd.setCursor(3, 0);
lcd.print(" pendolamenti");

attachInterrupt(0,Evento,RISING);  //funzione che serve a "interrompere" il programma alla presenza di
                                  //un segnale sul pin interrupt 0 (pin 2 dei pin digitali) se trova un
                                  //segnale RISING ovvero da 0V sale a 5V
                                  //Evento è la funzione dove andrà in caso di interrupt
                                 
Serial.begin(9600);                //abilita il comando trasmissione seriale a 9600 bps


}

//*********************************************************************************************************
void loop() {                      //loop infinito          

Tempo_Attuale = millis();                    //assegna alla variabile il tempo in mS dall'accensione di Arduino

}

//**********************************************************************************************************
void Evento(){        //la nostra funzione eseguita ogni qual volta si incontra un segnale del sensore HALL.

if(Count>0){
Count--;             //ciclo if per il contatore, se è superiore a 0 va avanti con il calcolo evento, altrimenti "stampa" prova terminata
 
Tempo_Evento = Tempo_Attuale - Tempo_Precedente;


Serial.println(Tempo_Evento);                            //invia alla seriale il contenuto della variabile  

lcd.clear();
lcd.setCursor(0, 0);
lcd.print(Tempo_Evento);
Tempo_Precedente = Tempo_Attuale;            //uguaglia il tempo precedente a quello misurato attualmente,
                                            //serve a permettere la misurazione del tempo trascorso tra due
                                            //eventi successivi

}
else {
  lcd.clear();
  lcd.print("Prova terminata");
  
}
//il dato trasmesso alla variabile è il tempo trascorso tra due eventi segnale HALL, ovvero, se abbiamo un unico
//dente sulla ruota fonica da misurare, significa che ogni giro impiega quel tempo in mS quindi basterà poi
//calcolare (sul nostro programma PC) i giri/min o RPM così:  RPM = (1000 / Tempo_Attuale) * 60 ).

}  //fine funzione

Sicuramente non sarà elegante, e ho "scopiazzato" anche molta parte del codice. Adesso però mi sovviene un problema. Io mi ritrovo alla fine con sul monitor seriale una fila di 20 valori (21 compreso il primo che è "nullo"), che ho visto anche sull'LCD in sequenza.

Ora, purtroppo manualmente, devo sommarli e fare la media. Come posso automatizzare questa funzione direttamente su Arduino e visualizzarla sull'LCD, rendendo quindi magari indipendente il tutto dal PC?

Grazie in anticipo a chi mi vorrà rispondere!

//*********************************************************************************************************
void loop() {                      //loop infinito          

Tempo_Attuale = millis();                    //assegna alla variabile il tempo in mS dall'accensione di Arduino

}

ne sei proprio sicuro?

Non sono sicuro di avere capito bene il problema, ma puoi tenere i valori misurati in un array e alla fine calcolare la media, oppure aggiornare la media passo dopo passo (tipo media = (media * iterazioni + misura) / (iterazioni + 1)).

Di cosa CoreZilla? So che il codice funziona, per quanto riguarda il commento era nel codice che ho copiato e così l'ho lasciato, quindi non saprei se è corretto...

SukkoPera, grazie per il consiglio...come posso integrarlo nel codice?

Ripeto che non sono sicuro di capire bene cosa vuoi fare, e ci sono parecchie osservazioni che si potrebbero fare al codice, ma forse quel che ti serve è cambiare:

int Tempo_Evento;

in

int Tempo_Evento[20];

Così Tempo_Evento diventa un array in grado di contenere 20 misure. Ad ogni iterazione ne riempi una e alla fine puoi iterare su tutte, sommarle e dividere per 20. Cerca in giro cosa sono e come si usano gli array!

Ok, più o meno ho capito come fare...nel frattempo ho guardato un attimo in giro, e penso di aver capito qualcosina in più sull'array...ora ci provo!

Grazie :slight_smile:

Intendo che non puoi fare una chiamata di setup (inizializzazione) nel Loop ....

CoreZilla, continuo a non capire...la variabile è stata dichiarata sopra nel setup, nel Loop semplicemente assegno alla variabile il valore "millis()"...non è corretto?

SukkoPera, ho provato a trasformare la variabile in un array, ma mi ritorna questo errore "incompatible types in assignment of 'int' to 'int [20]'" sulla riga dove faccio il calcolo

Tempo_Evento = Tempo_Attuale - Tempo_Precedente;

come mai?

Se il commento è

//assegna alla variabile il tempo in mS dall'accensione di Arduino

per eseguirlo UNA SOLA VOLTA, l'istruzione va messa nel Setup(). Mettendola nel Loop() viene aggiornata di continuno ....

Poi se il ciclo di misura non necessita di risposte "real time" eviterei di usare un interrupt e piuttosto metterei un ciclo di polling nel Loop

CoreZilla:
Se il commento è

//assegna alla variabile il tempo in mS dall'accensione di Arduino

per eseguirlo UNA SOLA VOLTA, l'istruzione va messa nel Setup(). Mettendola nel Loop() viene aggiornata di continuno ....

Poi se il ciclo di misura non necessita di risposte "real time" eviterei di usare un interrupt e piuttosto metterei un ciclo di polling nel Loop

Io in realtà ho bisogno che quel valore sia continuamente aggiornato, ed ho bisogno di visualizzare "real time" i tempi di pendolamento, per verificare che tutto sia corretto...si tratta di prove abbastanza difficili da fare e da ripetere, e poter vedere in tempo reale se i valori "sfagiolano" ci è di grande aiuto...

Adesso provo a vedere cosa è il polling :slight_smile: sono completamente digiuno su queste cose...

Una parola su quanto rilevato da CoreZilla: mah, mica deve per forza essere una "chiamata di setup", anzi non avrebbe proprio senso nel setup. Non ne ha nemmeno nel loop comunque, perché dovunque viene utilizzata quella variabile si potrebbe chiamare direttamente millis() :). Come dicevo, ci sono mooolte osservazioni da fare sul codice, ma concentriamoci su quanto richiesto.

@giordy: Beh, ora che è un array può contenere 20 elementi, per cui devi dirgli esplicitamente quale dei 20 vuoi leggere/scrivere. Per questo ti serve un'altra variabile che conta le misure effettuate: Count va bene, solo che la usi in modo decrescente, il che magari è poco intuitivo ma va bene, per cui dovrai fare qualcosa tipo:

Tempo_Evento[Count] = Tempo_Attuale - Tempo_Precedente;

Occhio però che i 20 elementi dell'array sono numerati da 0 a 19, per cui dovrai assicurarti che Count si muova in questo range, chiaro?

PS: Per convenzione, i nomi delle variabili dovrebbero perlomeno iniziare con una minuscola. Per i nomi composti poi si è soliti usare gli underscore (soprattutto in C) o la notazione camelCase (più comune nei linguaggi orientati agli oggetti), per cui nomi "corretti" per le tue variabili potrebbero essere:

  • count
  • tempo_evento o tempoEvento
  • tempo_ettuale o tempoAttuale
  • tempo_precedente o tempoPrecedente

Attenzione che stiamo parlando di un prodotto che serve ad omologare altre apparecchiature.
Qualsiasi scheda arduino è solo un "prototipo", come tale andrebbe prima certificata, poi utilizzata per produzione.

#include <LiquidCrystal.h>

#define COUNT 21


LiquidCrystal lcd(11, 10, 6, 5, 4, 3);

int Tempo_Inizio;
int Tempo_Attuale;
int Tempo_Evento;
int Tempo_Medio;

int Count = COUNT; //definizione numero pendolate

int pinEVENT = 2;

bool pin_rise = false;
bool first    = true;

//*********************************************************************************************************
void setup(){

lcd.begin(16,2);
lcd.print((Count-1)/2);
lcd.setCursor(3, 0);
lcd.print(" pendolamenti");

Serial.begin(9600);                //abilita il comando trasmissione seriale a 9600 bps
pinMode(pinEVENT, INPUT);



}

//*********************************************************************************************************
void loop() {                      //loop infinito

  if (digitalRead(pinEVENT)) {
    if (pin_rise) {
      if (first) {
        Tempo_Inizio = millis();
        Tempo_Attuale= millis();
        first = false;
      }
      else if (Count--) {
        Tempo_Evento = millis() - Tempo_Attuale;
        Tempo_Attuale= millis();
        Serial.println(Tempo_Evento);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(Tempo_Evento);
      }
      else {
        Tempo_Medio = (millis() - Tempo_Inizio) / COUNT;
        lcd.clear();
        lcd.print("Prova terminata");
        // prevedere stop programma
      }
      pin_rise = false;
    }
  }
  else
    pin_rise = true;

}

io farei cosi :stuck_out_tongue:

SukkoPera:
Una parola su quanto rilevato da CoreZilla: mah, mica deve per forza essere una "chiamata di setup", anzi non avrebbe proprio senso nel setup. Non ne ha nemmeno nel loop comunque, perché dovunque viene utilizzata quella variabile si potrebbe chiamare direttamente millis() :). Come dicevo, ci sono mooolte osservazioni da fare sul codice, ma concentriamoci su quanto richiesto.

@giordy: Beh, ora che è un array può contenere 20 elementi, per cui devi dirgli esplicitamente quale dei 20 vuoi leggere/scrivere. Per questo ti serve un'altra variabile che conta le misure effettuate: Count va bene, solo che la usi in modo decrescente, il che magari è poco intuitivo ma va bene, per cui dovrai fare qualcosa tipo:

Tempo_Evento[Count] = Tempo_Attuale - Tempo_Precedente;

Occhio però che i 20 elementi dell'array sono numerati da 0 a 19, per cui dovrai assicurarti che Count si muova in questo range, chiaro?

PS: Per convenzione, i nomi delle variabili dovrebbero perlomeno iniziare con una minuscola. Per i nomi composti poi si è soliti usare gli underscore (soprattutto in C) o la notazione camelCase (più comune nei linguaggi orientati agli oggetti), per cui nomi "corretti" per le tue variabili potrebbero essere:

  • count
  • tempo_evento o tempoEvento
  • tempo_ettuale o tempoAttuale
  • tempo_precedente o tempoPrecedente

Perfetto! Così per ora funziona tutto...ho anche dato una sistemata al codice seguendo i tuoi consigli. Ora, pensavo che la parte di calcolo matematico della media fosse molto semplice...ma in realtà mi sto incasinando!! Come faccio a richiamare i valori dell'array, sommarli e fare la media? Richiamare i valori uno ad uno mi sembra eccessivo...ho provato anche una libreria particolare (Average-master) ma non riesco a farla funzionare...

speedyant:
Attenzione che stiamo parlando di un prodotto che serve ad omologare altre apparecchiature.
Qualsiasi scheda arduino è solo un "prototipo", come tale andrebbe prima certificata, poi utilizzata per produzione.

Tranquillo...da questo punto di vista siamo "auto-referenziali", nel senso che siamo autorizzati dall'unione europea ad utilizzare le apparecchiature che preferiamo, senza nessun problema di certificazione! :slight_smile:

Mai sentito parlare dei cicli for? :slight_smile: Dai un'occhiata qua:

Allora, ho provato a risistemare un po' il codice, ho tolto la funzione dall'interno del loop, e funziona tutto. Ho provato a inserire un ciclo for come ho visto su altre pagine, ma sebbene tutto giri, non mi calcola la media ma semplicemente mi divide per 20 quello che è l'ultimo valore dell'array...
Cosa sbaglio? A dichiarare qualcosa del ciclo for?

Ecco qua il codice...

#include <LiquidCrystal.h>                //include libreria lcd

LiquidCrystal lcd(11, 10, 6, 5, 4, 3);    //dichiaro pin per lcd

int tempo_attuale;      //variabile tempo attuale
int tempo_precedente;   //variabile tempo precedente
int count = 19;         //inizializzazione contatore
int tempo_evento [20];  //inizializzazione array tempo eventi da 20 posti
int somma = 0;          //per somma
int media = 0;          //per media


void setup() {
  lcd.begin(16,2);
  lcd.print("Tempi di pendolamento");
  lcd.setCursor(0,1);
  lcd.print("10 pendolate");          //inizializza l'LCD e scrive l'apertura

  Serial.begin(9600);                 //inizializza comunicazione seriale

  attachInterrupt(0,Tempo,RISING);
}
//*********************************************************************************************************
void loop(){
  
  }

//*********************************************************************************************************
void Tempo(){       //void per la funzione tempo dell'interrupt

  if(count>=0) {    //funzione if, se count è maggiore o uguale a zero va avanti, altrimenti else successivo
      
      count--;      //riduce count di una unità ad ogni evento
      
      tempo_evento[count] = millis() - tempo_precedente;   //calcola tempo evento come differenza tra millis e tempo precedente

      tempo_precedente = millis();                        //uguaglia tempo precedente a quello attuale per calcolare differenza

      Serial.println(tempo_evento[count]);                //stampa su seriale il valore

      lcd.clear();
      lcd.print(tempo_evento[count]);                     //stampa su lcd il valore

   }

   else {
       
    lcd.clear();
    lcd.print("Prova terminata");     //stampa su lcd termine prova
    delay(5000);                      //metto tutto in attesa per 5 secondi

   somma = 0;
   for(int i=19; i<0; i--)          //ciclo for per calcolo media; i=19
    somma = somma + tempo_evento[i];
    
    media = somma / 20;
    
   
    
   

    Serial.println(media);
    lcd.clear();
    lcd.print(media);
    lcd.setCursor(5,0);
    lcd.print("millis");
    
 
        
    }
   }

Bravo, hai fatto un bel lavoro! Devi solo correggere il ciclo:

for(int i=19; i >= 0; i--)
  somma = somma + tempo_evento[i];

Ti lascio capire da solo il perché :). Nota: non c'è motivo di iterare al contrario sull'array a questo punto, per cui, per mere ragioni di leggibilità, scriverei:

for(int i=0; i < 20; i++)
  somma = somma + tempo_evento[i];

Ma è perfettamente equivalente!

Un'altra cosa più importante: se inizializzi count a 19, devi decrementarlo alla fine dell'operazione di misura, altrimenti il primo valore che valorizzi è il 18, non il 19!

giordy87:
Tranquillo...da questo punto di vista siamo "auto-referenziali", nel senso che siamo autorizzati dall'unione europea ad utilizzare le apparecchiature che preferiamo, senza nessun problema di certificazione! :slight_smile:

il commento è OT cosa intendi per "autoreferenziali"

grazie
Stefano

SukkoPera:
Bravo, hai fatto un bel lavoro! Devi solo correggere il ciclo:

for(int i=19; i >= 0; i--)

somma = somma + tempo_evento[i];




Ti lascio capire da solo il perché :). Nota: non c'è motivo di iterare al contrario sull'array a questo punto, per cui, per mere ragioni di leggibilità, scriverei:



for(int i=0; i < 20; i++)
  somma = somma + tempo_evento[i];



Ma è perfettamente equivalente!

Un'altra cosa più importante: se inizializzi *count* a 19, devi decrementarlo **alla fine** dell'operazione di misura, altrimenti il primo valore che valorizzi è il 18, non il 19!

Grazie mille del consiglio! Ora che ci ho ragionato sopra anche con più calma ci sono arrivato! Praticamente prima gli dicevo di sommare solo quelli con "i" sotto zero, ma avendo i di partenza a 19 e 20 valori sommava solo l'ultimo esatto?
Ho corretto un po' ancora il codice, trasformando media e somma in "float" per poter calcolare la media con la virgola, e ho corretto il ciclo for per eliminare il primo dato (dichiarando i = 19 con un array aumentato a 21 e quindi eliminando il primo dato). Questo perchè il primo valore è dall'accensione di Arduino, mentre in questo modo elimino quello e prendo i valori "nudi" delle pendolate. Il tutto funziona alla perfezione, e ho verificato la correttezza anche stampando in seriale i valori e calcolando la media a parte con excel!

Ecco qua il codice quasi definitivo...

#include <LiquidCrystal.h>                //include libreria lcd

LiquidCrystal lcd(11, 10, 6, 5, 4, 3);    //dichiaro pin per lcd

int tempo_attuale;      //variabile tempo attuale
int tempo_precedente;   //variabile tempo precedente
int count = 20;         //inizializzazione contatore - 20 posti compreso lo zero per eliminare la prima
int tempo_evento [21];  //inizializzazione array tempo eventi da 21 posti (per eliminare il primo)
float somma = 0;          //per somma
float media = 0;          //per media

void setup() {
  lcd.begin(16,2);
  lcd.print("Tempi pendolo");
  lcd.setCursor(0,1);
  lcd.print("10 pendolate");          //inizializza l'LCD e scrive l'apertura

  Serial.begin(9600);                 //inizializza comunicazione seriale

  attachInterrupt(0,Tempo,RISING);    //inizializza interrupt per evento su pin interrupt0 (pin2)
}
//*********************************************************************************************************
void loop(){}

//*********************************************************************************************************
void Tempo(){       //void per la funzione tempo dell'interrupt

  if(count>=0) {    //funzione if, se count è maggiore o uguale a zero va avanti, altrimenti else successivo
      
      tempo_evento[count] = millis() - tempo_precedente;    //calcola tempo evento come differenza tra millis e tempo precedente

      tempo_precedente = millis();                          //uguaglia tempo precedente a quello attuale per calcolare differenza

      Serial.println(tempo_evento[count]);                  //stampa su seriale il valore

      lcd.clear();
      lcd.print(tempo_evento[count]);                       //stampa su lcd il valore

      count--;      //riduce count di una unità ad ogni evento
   
   }

   else {
       
    lcd.clear();
    lcd.print("Prova terminata");     //stampa su lcd termine prova
    delay(5000);                      //metto tutto in attesa per 5 secondi

   somma = 0;
   for(int i=19; i>=0; i--)            //ciclo for per calcolo media; i=19 per eliminare il primo valore dall'array
    somma = somma + tempo_evento[i];  //calcolo totale
    
    media = somma / 20;               //calcolo media
      
    Serial.println(media);            //codice per print su seriale e su lcd la media
    lcd.clear();
    lcd.print(media);
    lcd.setCursor(0,1);
    lcd.print("millisecondi");
    }
   }

Adesso sistemo le ultime cose e poi passerò alla realizzazione del circuito finale saldato e non sulla breadbord!

Grazie ancora a tutti :slight_smile: