[RISOLTO] Allarme con sensore ad ultrasuoni

Buonasera,

dopo un po' di tempo trascorso ad apprendere i fondamenti del linguaggio di programmazione di Arduino, prendendo spunto da un codice fornito da un collega universitario, ho voluto creare un semplice allarme, utilizzabile tanto in casa quanto in una rimessa per le auto.

L'hardware consiste in un sistema di alimentazione a 12V pilotabile ad esempio da un radiocomando, una sirena a 12V, un relè, un buzzer ed un misuratore ad ultrasuoni orientato su un riscontro posizionato sulla porta di accesso.

Il funzionamento, riassunto per passi, è il seguente:

  • alimento Arduino
  • il buzzer suona 3 volte per confermare l'accensione
  • attendo un certo tempo necessario a chiudere la porta
  • dopo 5 secondi il buzzer suona una volta per segnalare l'inizio delle misurazioni della distanza
  • allarme operativo
  • apro la porta
  • il buzzer suona il countdown
  • se entro la fine del countdown tolgo l'alimentazione al microcontrollore non accade nulla
  • se ancora alimentato, attivo il relè che suona la sirena

Allego il codice:

const int tempo_chiusura = 5000;           // tempo impegato per chiudere la porta
const int tempo_spegnimento = 8000;        // tempo impiegato per spegnere l'allarme
const int tempo_assestamento = 1000;
const int triggerPort = 9;
const int echoPort = 8;
const int rele = 11;
const int buzzer = 10;
long distanza;
int accensione = 0;


void setup() {

  pinMode(rele, OUTPUT);
  digitalWrite(rele, HIGH);
  pinMode(triggerPort, OUTPUT);
  pinMode(echoPort, INPUT);
  pinMode(buzzer, OUTPUT);
  Serial.begin(9600);
}


void loop() {

  digitalWrite(rele, HIGH);                 // se quando finisco il loop la sirena suona, adesso si spegne

  if (accensione == 0) {
    for (int x = 0; x < 3; x++) {
      tone(buzzer, 1500, 100);               // suona una nota per 3 volte sul pin buzzer alla frequenza di 1500 Hz per 100 ms (#2)                    |  tone(buzzer,1500);
      delay(200);                            // fa una pausa di 200 ms che inizia contare appena esegue la precedente istruzione, che dura 100 ms      |  delay(200);
    }                                        // (quindi in realta per 100 ms suona e per 100 ms no)                                                    |  noTone(buzzer)
    delay(tempo_chiusura);
    tone(buzzer, 800, 1500);                 // suona una nota sul pin buzzer alla frequenza di 800 Hz per 1500 ms (#4)

    delay(5000);

    digitalWrite(triggerPort, LOW);          // porta bassa l'uscita del trigger
    digitalWrite(triggerPort, HIGH);         // invia un impulso di 10microsec su trigger
    delayMicroseconds(10);
    digitalWrite(triggerPort, LOW);

    long durata = pulseIn(echoPort, HIGH);
    long distanza = 0.034 * durata / 2;
    Serial.println(distanza);

    accensione = 1;
  }


  digitalWrite(triggerPort, LOW);           // porta bassa l'uscita del trigger
  digitalWrite(triggerPort, HIGH);          // invia un impulso di 10microsec su trigger
  delayMicroseconds(10);
  digitalWrite(triggerPort, LOW);

  long durata_attuale = pulseIn(echoPort, HIGH);
  long distanza_attuale = 0.034 * durata_attuale / 2;

  Serial.println(distanza_attuale);

  if (distanza_attuale > distanza) {
    delay(tempo_assestamento);

    if (distanza_attuale > distanza) {
      for (int x = 0; x < 10; x++) {        // esgui il countdown per 10s in attesa dello spegnimeto
        tone(buzzer, 1000, 250);            // suona una nota sul pin buzzer alla frequenza di 1000 Hz per 250 ms     | tone(buzzer,1000);
        delay(1250);                        //                                                                        | delay(250);
      }                                     //                                                                        | noTone(buzzer);
      while (accensione > 0) {              // se non è spento, ripete all'infinito (condizione sempre verificata)
        digitalWrite(rele, LOW);            // (LOW = relè acceso)
        delay(5000);                        // suona per 5s
        digitalWrite(rele, HIGH);           // (HIGH = relè spento)
        delay(1000);                        // non suonare per 1s
      }
    }
  }

}

All'atto pratico si verificano una serie di problemi:

  • le distanze visualizzate nel monitor seriale sono degli interi (forse è solo legato alla visualizzazione?) e comunque, nonostante le prove siano state effettuate senza muovere il sensore (HC-SR04) non sono uguali!
  • nonostante la distanza_attuale risulti dal monitor seriale minore della distanza letta inizialmente, il cowntdown inizia comunque (a dire il vero, senza il delay dopo il suono di inizio misurazione, il cowntdown si sovrappone a quest'ultimo). In pratica la misurazione della distanza_attuale viene effettuata una sola volta, in quanto il codice entra subito nel ciclo if.

Dal punto di vista logico il codice mi sembra corretto e compila senza errori. Non riesco a comprendere questo comportamento anomalo...
Ringrazio chi mi vorrà aiutare.

Che valore hai dato alla variabile "distanza"?
Ciao Uwe

La variabile "distanza" viene calcolata solamente una volta all'accensione in base al tempo letto dal sensore ad ultrasuoni. In seguito viene confrontato con la "distanza attuale" letta ad ogni ciclo.
Ho dichiarato la variabile come long ma nel monitor seriale vedo un valore intero...è normale?
In ogni caso, come detto, non esegue il confronto ma passa direttamente al countdown.

Ho posto quella domada per farti ragionare un po sollo sketch perché secondo me la logica che adotti é errata.

Leggi all inizio una distanza e poi controlli se le distanze successive sono maggiori o no.
La prima misura dovrebbe essere dal sensore al muro dicciamo come esempio 3 metri.

La misura succesiva la cotrolli se é maggior di quella iniziale e accendi la sirena.
Mi spieghi come la distanza letta in stato di arlarme attivo possa essere maggiore di quella iniziale? Inoltre a causa di un incertezza di misura un valore di 3,01 m risulta un alarme ma un 2,99m no?

Per primo il controllo deve essere che i valori letti siano minori di quello iniziale e per secondo devi togliere un po dalla lettura iniziale per evitare delle falsi alarmi a causa di una misura un pochettino instabile.

Ciao Uwe

In realtà il funzionamento è diverso, ma concettualmente cambia solo il segno della disuguaglianza.
Nel caso in esame intendo fissare sopra alla porta/portone/basculante il sensore che "guardi" in verticale verso il pavimento. Poi sul bordo superiore della porta fisso un riscontro (una semplice piastrina ad L), rispetto alla quale il sensore legge la misura da confrontare. Nel momento in cui la porta viene aperta verrà letta la misura ad esempio fino al pavimento, quindi maggiore.
Ma come detto, cambiando la disposizione di sensore e riscontro cambierebbe solo il segno ">" o "<", ma concettualmente il programma resterebbe identico.

Per quanto riguarda l'incertezza, anche io mi sono posto il problema ed ho provato ad utilizzare distanza > distanza_attuale * n, con diversi valori di n, ma nonostante questo non cambia nulla: il countdown, che dovrebbe partire solo quando la condizione è soddisfatta, parte anche quando le istanze stampate nel seriale (senza virgola!) palesemente non la soddisfano!

Non capisco il motivo...

Ti ringrazio per l'interessamento.

photobyfs:
le distanze visualizzate nel monitor seriale sono degli interi (forse è solo legato alla visualizzazione?)

Eh certo, se la distanza metti in una variabile "long" invece di una float...
Devi fare:

float distanza = = 0.034F * durata / 2;

e:

float distanza_attuale = 0.034F * durata_attuale / 2;

comunque, nonostante le prove siano state effettuate senza muovere il sensore (HC-SR04) non sono uguali!

Beh intento è un sensore economico e non precisissimo, oltre alla qualità del sensore stesso ci sono tanti altri fattori come eventuali altri oggetti e riflessi, nel tuo caso devi anche fare attenzione alla frequenza con cui vai a fare le letture, se le lasci "libere" nel loop() ovviamente fai dei ping talmente ravvicinati che ci possono essere echi dei precedenti... Devi prevedere almeno 50 millisecondi (meglio 100) tra una misurazione e l'altra. Visto poi che non credo tu debba misurare con tale precisione temporale, basta anche uno al secondo o poco meno.

Ma lasciami dire che io ho SEMPRE avuto problemi con gli HC-SR04, mai trovato uno affidabile, molto spesso arrivavano anche a bloccarsi quando non era presente alcun ostacolo entro il range massimo del sensore! Il mio consiglio (anche se non è detto che sia questo il tuo problema) è di prendere sempre e solo gli SRF05.

Tra l'altro a suo tempo feci una libreria dedicata proprio a questi sensori (nonostante il nome, non è riservata agli SRF05 ma si possono usare anche gli SR04 per i quali prevede un "recupero" automatico del blocco), te la metto in allegato se vuoi provarla.

nonostante la distanza_attuale risulti dal monitor seriale minore della distanza letta inizialmente, il cowntdown inizia comunque (a dire il vero, senza il delay dopo il suono di inizio misurazione, il cowntdown si sovrappone a quest'ultimo). In pratica la misurazione della distanza_attuale viene effettuata una sola volta, in quanto il codice entra subito nel ciclo if.

Beh se le letture sono "variabili" corri questo rischio perché alla prima lettura minore del normale (magari proprio per gli echi di precedenti ping puoi riceverli in un tempo minore del previsto) la successiva, buona, sarà maggiore ed ecco che ti entra nella if().

Correggi le variabili in float, aggiungi un delay(200) tra una lettura (loop) e l'altra, e vedi cosa succede.

E dopo averlo sistemato, fai una prova con la libreria SRF05.. :wink:

SRF05.ZIP (5.24 KB)

Secondo me hai sbagliato sensore.
Un bel vecchio interuttore magnetico montato sulla cornice della porta e il magnete sulla porta va molto meglio.
Ciao Uwe

uwefed:
Secondo me hai sbagliato sensore.
Un bel vecchio interuttore magnetico montato sulla cornice della porta e il magnete sulla porta va molto meglio.
Ciao Uwe

Su questo non ci piove!

Eh certo, se la distanza metti in una variabile "long" invece di una float...

Hai ragione, come poteva esserci la virgola con un long...mea culpa!

In ogni caso, dopo aver giustamente cambiato le variabili in float, il programma continuava a non funzionare.

Ho risolto, finalmente, dichiarando all'inizio le diverse variabili float, e non all'interno del loop.
Presumo che il confronto eseguito nel ciclo if abbia dei problemi se eseguito con variabili locali, mentre con variabili globali ha funzionato senza problemi.

Allego il codice funzionante, magari potrà essere di aiuto a qualcuno.

Ringrazio entrambi per gli utili consigli.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////// *** ALLARME *** //////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////


//      (#1) alimento Arduino (sirena sempre alimentata)
//      (#2) il buzzer suona 3 volte
//      (#3) chiudo la porta
//      (#4) dopo 5 secondi il buzzer suona una volta per segnalare l'inizio delle misurazioni della distanza
//      (#5) allarme operativo
//      (#6) apro la porta
//      (#7) il buzzer suona il countdown
//      (#8_1) stacco dall'alimentazione Arduino (sirena sempre alimentata)
//      (#8_2) se ancora alimentato, suona la sirena


const int tempo_chiusura = 5000;           // tempo impegato per chiudere la porta
const int tempo_assestamento = 2000;
const int triggerPort = 9;
const int echoPort = 8;
const int rele = 11;
const int buzzer = 10;
float durata;
float distanza;
float durata_attuale;
float distanza_attuale;
int accensione = 0;


void setup() {
  pinMode(rele, OUTPUT);
  digitalWrite(rele, HIGH);
  pinMode(triggerPort, OUTPUT);
  pinMode(echoPort, INPUT);
  pinMode(buzzer, OUTPUT);
  Serial.begin(9600);
}


void loop() {

  digitalWrite(rele, HIGH);                  // se quando finisco il loop la sirena suona, adesso si spegne

  if (accensione == 0) {
    for (int x = 0; x < 3; x++) {
      tone(buzzer, 1500, 100);               // suona una nota per 3 volte sul pin buzzer alla frequenza di 1500 Hz per 100 ms (#2)                    |  tone(buzzer,1500);
      delay(200);                            // fa una pausa di 200 ms che inizia contare appena esegue la precedente istruzione, che dura 100 ms      |  delay(200);
    }                                        // (quindi in realta per 100 ms suona e per 100 ms no)                                                    |  noTone(buzzer)
    delay(tempo_chiusura);
    tone(buzzer, 1000, 1500);                // suona una nota sul pin buzzer alla frequenza di 1000 Hz per 1500 ms (#4)

    digitalWrite(triggerPort, LOW);          // porta bassa l'uscita del trigger
    digitalWrite(triggerPort, HIGH);         // invia un impulso di 10microsec su trigger
    delayMicroseconds(10);
    digitalWrite(triggerPort, LOW);

    durata = pulseIn(echoPort, HIGH);
    distanza = 0.034F * durata / 2;

    Serial.print("distanza = ");
    Serial.println(distanza);
    Serial.println();

    delay(1000);

    accensione = 1;
  }


  digitalWrite(triggerPort, LOW);           // porta bassa l'uscita del trigger
  digitalWrite(triggerPort, HIGH);          // invia un impulso di 10microsec su trigger
  delayMicroseconds(10);
  digitalWrite(triggerPort, LOW);

  durata_attuale = pulseIn(echoPort, HIGH);
  distanza_attuale = 0.034F * durata_attuale / 2;

  delay(500);

  Serial.print("distanza attuale = ");
  Serial.println(distanza_attuale);

  if (distanza_attuale > distanza * 1.15 || distanza_attuale < distanza * 0.85 ) {
    delay(tempo_assestamento);

    digitalWrite(triggerPort, LOW);           // porta bassa l'uscita del trigger
    digitalWrite(triggerPort, HIGH);          // invia un impulso di 10microsec su trigger
    delayMicroseconds(10);
    digitalWrite(triggerPort, LOW);

    durata_attuale = pulseIn(echoPort, HIGH);
    distanza_attuale = 0.034F * durata_attuale / 2;

    delay(1000);

    Serial.print("distanza attuale = ");
    Serial.println(distanza_attuale);

    if (distanza_attuale > distanza * 1.15 || distanza_attuale < distanza * 0.85 ) {
      for (int x = 0; x < 10; x++) {        // esgui il countdown per 10s in attesa dello spegnimeto
        tone(buzzer, 1000, 250);            // suona una nota sul pin buzzer alla frequenza di 1000 Hz per 250 ms     | tone(buzzer,1000);
        delay(1250);                        //                                                                        | delay(250);
      }                                     //                                                                        | noTone(buzzer);
      while (accensione > 0) {              // se non è spento, ripete all'infinito (condizione sempre verificata)
        digitalWrite(rele, LOW);            // (LOW = relè acceso)
        delay(5000);                        // suona per 5s
        digitalWrite(rele, HIGH);           // (HIGH = relè spento)
        delay(1000);                        // non suonare per 1s
      }
    }
  }

}

Secondo me non ha senso controllare col sensore ultrasuoni l' apertura della porta. Secondo me é piú sensato controllare la presenza di una persona nella stanza e non l' apertura della porta. Se qualcuno entra dalla finestra, la porta resta chiusa.

Ciao Uwe

photobyfs:
Ho risolto, finalmente

Bene, sono contento che tu abbia risolto. Però mi resta il dubbio su questa cosa (e quando ci sono cose apparentemente "misteriose" non mi piace ignorarle):

dichiarando all'inizio le diverse variabili float, e non all'interno del loop.
Presumo che il confronto eseguito nel ciclo if abbia dei problemi se eseguito con variabili locali, mentre con variabili globali ha funzionato senza problemi.

Diciamo che in genere (per mia personale abitudine, non che sia l'ideale in tutti i casi) preferisco usare variabili globali per avere maggiore controllo sull'occupazione in memoria che resta quindi costante, però nel tuo caso non ho capito bene. Diciamo che una variabile ha lo stesso contenuto ed uso, che sia locale o globale, mentre tu dici che mettendo le definizioni dentro al loop ti dà problemi, ma di che tipo? Ossia cosa succede se sposti le dichiarazioni dentro al loop?

...
void loop() {
  float durata;
  float distanza;
  float durata_attuale;
  float distanza_attuale;

  digitalWrite(rele, HIGH);                  // se quando finisco il loop la sirena suona, adesso si spegne
  ...

(ovviamente la variabile "accensione" invece deve restare globale).

Secondo me non ha senso controllare col sensore ultrasuoni l' apertura della porta. Secondo me é piú sensato controllare la presenza di una persona nella stanza e non l' apertura della porta. Se qualcuno entra dalla finestra, la porta resta chiusa.

In effetti nell'ultimo codice postato, quello funzionante, ho utilizzato :

if (distanza_attuale > distanza * 1.15 || distanza_attuale < distanza * 0.85 )

Quindi funziona benissimo anche come intendi tu. In questo modo può essere utilizzato nella maniera più generale possibile: misura una distanza, la confronta e nel momento in cui questa varia, per l'introduzione di qualcuno nel raggio d'azione del sensore, oppure per l'apertura di un basculante o porta, parte l'allarme.

cosa succede se sposti le dichiarazioni dentro al loop?

Se inserisco le variabili nel loop, come nel codice postato inizialmente (ovviamente con le variabili di tipo float e non double), il confronto che avviene nell'if (distanza_attuale > distanza) non è corretto. In pratica anche se distanza_attuale < distanza parte comunque il countdown acustico. Sembra strano, ma definendo le variabili come globali funziona perfettamente!

photobyfs:
Se inserisco le variabili nel loop, come nel codice postato inizialmente (ovviamente con le variabili di tipo float e non double), il confronto che avviene nell'if (distanza_attuale > distanza) non è corretto. In pratica anche se distanza_attuale < distanza parte comunque il countdown acustico. Sembra strano, ma definendo le variabili come globali funziona perfettamente!

Mai capitata una cosa del genere, per cui mi pare comunque strano. Mi piacerebbe investigare meglio.

Ma oltre a quello, è sicuramente utile la modifica per prevedere una certa tolleranza nella lettura, ossia una "isteresi".
Anche se l'importante è che funzioni, invece di:

if (distanza_attuale > distanza * 1.15 || distanza_attuale < distanza * 0.85 )

io avrei fatto una cosa simile ma più leggibile o comodamente gestibile:

#define TOLLERANZA 0.15
...
if ( abs(distanza_attuale - distanza) > TOLLERANZA )
...

:slight_smile:

Se quindi, nel programma ora funzionante, tu spostassi semplicemente le definizioni delle variabili dentro al loop() cosa fa? E mi raccomando, insieme all'esito posta anche TUTTO il codice che usi per fare questa prova.

Ma sono l'unico che si è ricordato che dichiarare una variabile dentro una funzione "maschera" una eventuale variabile pari nome globale?
Ri-dichiarare la stessa variabile, globale e poi nella loop, come lo OP ha fatto nel suo primo esempio porta a problemi, non ci si deve stupire che NON farlo vada bene
Semmai ci si dovrebbe stupire che vada bene "farlo"...

Standardoil:
Ri-dichiarare la stessa variabile, globale e poi nella loop, come lo OP ha fatto nel suo primo esempio porta a problemi, non ci si deve stupire che NON farlo vada bene

Si ma quello vale solo per il primo sketch, poi le altre versioni non avevano quel problema, e la cosa che lamenta ora (se mette le variabili nel loop) è la questione da chiarire. Ma se non ci posta il codice con le variabili definite nel loop non possiamo sapere cosa abbia combinato realmente (potrebbe aver DUPLICATO le definizioni, anche delle variabili che invece devono essere solo globali)...
Aspettiamo che ci dica l'esito della prova che gli ho chiesto, e vediamo.

Giusto...

Standardoil:
Ma sono l'unico che si è ricordato che dichiarare una variabile dentro una funzione "maschera" una eventuale variabile pari nome globale?
Ri-dichiarare la stessa variabile, globale e poi nella loop, come lo OP ha fatto nel suo primo esempio porta a problemi, non ci si deve stupire che NON farlo vada bene
Semmai ci si dovrebbe stupire che vada bene "farlo"...

Ecco il problema! Involontariamente in fase di debugging ho ri-dichiarato dentro al loop la stessa variabile globale. Come dici tu il problema è nato da questo, ed ecco perchè anche se le condizioni non erano soddisfatte il programma procedeva ugualmente.

Si ma quello vale solo per il primo sketch, poi le altre versioni non avevano quel problema, e la cosa che lamenta ora (se mette le variabili nel loop) è la questione da chiarire. Ma se non ci posta il codice con le variabili definite nel loop non possiamo sapere cosa abbia combinato realmente (potrebbe aver DUPLICATO le definizioni, anche delle variabili che invece devono essere solo globali)...
Aspettiamo che ci dica l'esito della prova che gli ho chiesto, e vediamo.

Confermo che anche in questo caso hai ragione. Come scritto sopra, anche definendo le variabili come globali ho inavvertitamente lasciato la dicitura float davanti al nome della variabile anche nel loop, generando di fatto il problema.
Con l'ultimo codice postato, definire le distanze come globali o come locali non cambia nulla!

In ogni caso una cosa l'ho capita: sempre meglio lavorare con variabili globali!

photobyfs:
In ogni caso una cosa l'ho capita: sempre meglio lavorare con variabili globali!

Uhmm, diciamo che la teoria della buona programmazione prevederebbe il contrario. Meno variabili globali possibili, giusto quelle indispensabili che bisogna condividere tra le funzioni e che sarebbe troppo brigoso o poco chiaro passare tramite parametri. Quello di definire una locale con lo stesso nome di una globale è solo un errore di "disordine", anche se inizialmente è in effetti più facile ragionare con un'unica "paginona" di dati globali :wink:

La verità secondo me come sempre sta "nel mezzo" tra photobyfs e Claudio_FF. :wink:

Ossia, per come la vedo io, i pro delle variabili globali, su sistemini come questi, sono quando si desidera evitare di passare troppi parametri a funzioni e/o far restituire più di un valore senza fare cose relativamente "criptiche" come creare strutture e passarne il puntatore. Inoltre evita la continua allocazione/deallocazione di variabili locali (che includono anche i parametri), e consente di controllare a compile time l'occupazione effettiva del programma in termini di memoria, o per variabili "ingombranti" (come gli array o buffer).
Come "contro" c'è il fatto che se le si usa in modo indiscriminato non sarà evidente chi utilizza cosa, inoltre si rischia di utilizzare la stessa variabile per due scopi differenti causando malfunzionamenti.

I pro invece delle variabili locali, oltre ovviamente a rendere più "snello" e leggibile il codice, sono per evitare affollamenti di variabili che non hanno un reale scopo generale, soprattutto se vengono usate solamente da una funzione. Inoltre anche qui, il loro uso oculato consente una migliore gestione della memoria in quanto se io avessi diciamo 20 funzioni ognuna con le sue variabili locali, queste risulteranno allocate solamente durante l'esecuzione della relativa funzione, mentre dichiarandole tutte globali avrei costantemente (ed inutilmente) 20 blocchi di variabili, di cui uno solo è in uso.

Per cui il criterio che mi sento di consigliare è semplice ed è quello che ho descritto per primo: usare variabili globali solo per quelle che realmente hanno uno scopo in tutto (o quasi) lo sketch, per le altre è possibile includere quelle che consentono di evitare di passare molti parametri o se ci sono funzioni che dovrebbero restituire più di un valore (in quanto gestiscono/modificano/valorizzano tali valori) il tutto a patto di rendere chiara l'associazione della variabile con la/le funzioni relative, o con commenti o con il nome (es. un prefisso).

Come più volte spiegato, Atmel dice chiaramete, per quanto possibile, di preferire le "locali" dato che, per come sono trattate, permettono di avere un codice più veloce. :slight_smile:

Guglielmo