Sensore HC-SR04 e ciclo while

Ciao a tutti,
come da oggetto, stavo “giocando” con il sensore ad ultrasuoni HC-SR04, ho una domanda da farvi:

il datasheet indica che bisogna impostare il pin di Trigger in HIGH per 10 microsec, e di intervallare i viri cicli di misura ogni 60 ms… ho pensato così di inserire un ciclo while per mantenere attivo il Trigger per 10 microsecondi per poi uscire e proseguire con il loop, vi posto il codice:

const byte TriggerRadar = 2;  // pin del trigger del radar
const byte EchoRadar = 3;  // pin di echo del radar
unsigned long MicroContatore = 0; // contatore microsecondi...
unsigned long LastMicroContatore = 0;
unsigned long LastTriggerContatore = 0;
unsigned long MicroTempo = 60000; // in microsecondi
byte TriggerTempo = 12; //microsecondi
long Durata = 0; //variabile per salvare il tempo di attivazione del pin ECHO
long Distanza = 0; // variabile per salvare la distanza...


void setup()
{
  Serial.begin(9600);
  pinMode(TriggerRadar, OUTPUT);
  pinMode(EchoRadar, INPUT);

}

void loop()
{
  MicroContatore = micros();



  if ((MicroContatore - LastMicroContatore) > MicroTempo) // funzione per gestire il ciclo di lettura totale ogni 60 ms, per evitare interferenze con le letture del segnale ECHO e Trigger

  {
    digitalWrite (TriggerRadar, LOW); // ad ogni passaggio tiro giu il pin del trigger...

    LastTriggerContatore = MicroContatore; // per mantenere attivo il trigger per 10 microsecondi

    while ((MicroContatore - LastTriggerContatore) < TriggerTempo) // per mantenere attivo il trigger per 10 microsecondi
    {
      MicroContatore = micros(); // se non metto dentro il contatore non esce dalla loop while...
      digitalWrite (TriggerRadar, HIGH);
      
    }

    digitalWrite (TriggerRadar, LOW); // tiru giu il pin del trigger..
    Durata = pulseIn (EchoRadar, HIGH);
    Distanza = Durata * 0.0343 / 2;
    Serial.print(Distanza);
    Serial.println(" cm");
    LastMicroContatore = MicroContatore;

  }

Come potete vedere ho usato micros() per il timer generale ed è all’inizio del loop salvato all’interno di una variabile globale… nel ciclo while, per esaudire la condizione dei famosi 10 microsecondi, ho dovuto inserire nuovamente all’interno la variabile globale del timer MicroContatore con micros(), altrimenti il conteggio non avveniva con il conseguente blocco del programma all’interno del ciclo while…perchè?
Una volta entrati nel while il programma non può verificare l’avanzamento del contatore micros() immagino… corretto?
Grazie
Giorgio

micros(), come millis(), è solo una funzione che ritorna il valore di un contatore intero al "core" ... come tale o la chiami e ti restituisce il valore o non la chiami ed allora non hai il nuovo valore, quindi ... ... è ovvio che, se non assegni alla variabile MicroContatore il valore aggiornato di ritorno dalla funzione micros() chiamandola, non è che MicroContatore si può aggiornare da solo ... ::)

Guglielmo

Firestarter83: Come potete vedere ho usato micros() per il timer generale

Scusa, ma perché non usi la funzione delayMicroseconds() che fa esattamente quello che ti serve (come tra l'altro indicato in tutti gli esempi di sketch sull'uso del sensore ad ultrasuoni)?

Ma delayMicroseconds(), essendo parente di delay(), risulta bloccante. micros() invece no.

Esatto, non voglio bloccare il flusso del programma, ormai da tempo non uso più i vari delay... infatti lo step successivo è quello di sostituire la funzione pulseIn (che è bloccante) con altro sistema....

Conosco le funzioni micros() e millis(), ma non le avevo mai usate nel ciclo while... ed il mio dubbio era nato dal fatto che già nella parte iniziale del loop c'è la variabile globale MicroContatore associata al contatore micros(), però all'interno del ciclo while ho dovuto reinserirla, perchè altrimenti il valore non si aggiornava... quindi di fatto, una volta entrati nel ciclo while, la funzione non va a verificare l'avanzamento della variabile globale MicroContatore, bisogna per forza di cose inserirla anche li... è corretto?

Come già detto da Guglielmo, una variabile non è che si modifica da sola, si modifica quando le assegnamo un nuovo valore con l'operatore =

Ciao Claudio,
si lo so :slight_smile:
Il mio dubbio è nato sul ciclo while, di fatto lui esegue solo le istruzioni al suo interno fino a quando la condizione diventa falsa, escludendo tutto ciò che è all’esterno (quindi anche il contatore micros() globale che avevo inserito all’interno del loop principale)…
Quindi come spiegato da Guglielmo e da te, obbligatoriamente per far si che la variabile globale MicroContatore si aggiorni anche all’interno del ciclo while, questa va inserita anche al suo interno.

Grazie mille

Giorgio

NO, NON hai ancora capito ... ... NESSUNA variabile, né dentro un while, né fuori di un while si "associa" a NULLA ... o TU gli assegni un valore o questa NON cambia valore, quindi, ogni volta che vuoi che la tua variabile "MicroContatore" venga aggiornata, OVUNQUE sia, DEVI assegnargli il valore di micros() o essa NON cambia.

Guglielmo

Scusa Guglielmo,
o mi sono spiegato male, o non ci stiamo capendo :slight_smile: :
io il valore della variabile generale MicroContatore la aggiorno ad ogni ciclo del loop:

void loop()
{
  
  SensoreUltrasuoniHandler();
  MicroContatore = micros();
}

Per dirti, Io la funzione millis() la uso da molto tempo, la variabile generale in cui salvo il valore del contatore la inserisco in cima al loop generale (Contatore = millis(); o come si preferisce definirla), non vado poi a richiamarla in ogni funzione che vado a creare, perchè di fatto è già presente nel loop e si aggiorna ad ogni ciclo…
Nel caso specifico, una volta entrato nel ciclo while, di fatto il flusso del programma non va oltre fino a quando la condizione diventa falsa… quindi se non richiamo all’interno del ciclo while la variabile globale, vado ad utilizzare un valore “non aggiornato”, quindi nel mio caso, non uscivo più dal while perchè la condizione non diventava mai falsa…

LastTriggerContatore = MicroContatore; // per mantenere attivo il trigger per 10 microsecondi

      while ((MicroContatore - LastTriggerContatore) < TriggerTempo) // per mantenere attivo il trigger per 10 microsecondi
      {
        MicroContatore = micros(); // se non metto dentro il contatore non esce dalla loop while...da capire...
        digitalWrite (TriggerRadar, HIGH);

      }

era questo che mi aveva portato fuori strada…
Ciao
Giorgio

Il "problema" nasce dalla mescolanza di codice non bloccante (corretto leggere una volta millis all'inizio del loop) con una parte di codice bloccante (il while). Scrivendo anche quell'attesa in modo non bloccante il "problema" non si manifesta, perché si ripassa continuamente dal via ;)

Ciao Claudio, già, ma per mantenere attivo per 10 microsecondi il pin alto del trigger l'unico modo che mi è venuto in mente è il ciclo while... tenuto conto anche del fatto che consigliano di effettuare il ciclo di lettura ogni 60 ms, quindi quando effettuo il "primo passaggio" nel loop, "ripasso dal via" dopo 60 ms :), e questo non mi farebbe mantenere il trigger attivo per 10 microsecondi..

speedyant: Ma delayMicroseconds(), essendo parente di delay(), risulta bloccante. micros() invece no.

Vero, ma ... per come usa lui la micros() È bloccante, visto che non esce da quel while, quindi, molto più semplicemente, poteva usare la delayMicroseconds() ottenendo esattamente lo stesso risultato ... ::)

Guglielmo

Firestarter83:
nel loop, “ripasso dal via” dopo 60 ms :), e questo non mi farebbe mantenere il trigger attivo per 10 microsecondi…

Questo è un caso limite specifico, perché l’attesa è comparabile con il tempo di esecuzione di una sola digitalWrite (circa 5µs), quindi digitalWrite, delayMicroseconds di 5µs e successiva digitalWrite le possiamo considerare tre istruzioni non bloccanti, senza scomodare loop e altre questioni di design.

Ma per tempi più lunghi un codice non bloccante non ripassa dal via ogni 60ms, ma migliaia (o decine di migliaia) di volte al secondo, e ogni 60ms “scatta” la condizione per avviare quello che serve.

Quindi Claudio sostanzialmente potrei effettivamente evitare il while ed usare il delaymicrosecond… interessante, provo…

Grazie

Giorgio

gpb01:
Vero, ma … per come usa lui la micros() È bloccante, visto che non esce da quel while, quindi, molto più semplicemente, poteva usare la delayMicroseconds() ottenendo esattamente lo stesso risultato … ::slight_smile:

Esattamente quello che dicevo anche io. Tra l’altro se non volesse usare la delayMicroseconds() mi chiedo quale sistema mission-critical stia implementando su Arduino per il quale non sia accettabile attendere 10 microsecondi (un decimillesimo di secondo) per altre elaborazioni, quando poi comunque per completare la misurazione della distanza debba attendere l’eco di ritorno…:wink:

Ciao docdoc, si hai assolutamente ragione. Sto realizzando un Robot, la funzione Pulse in e’ molto comoda ma purtroppo e’ bloccante (l’attesa dell’’echo)... c’è la possibilità di inserire il timeout, però mi piacerebbe farlo andare senza nessuna pausa... quale potrebbe essere l’alternativa alla Pulse In per calcolare la durata dell’Echo e determinare poi la distanza? C’è l’Interrupt, ma il micros() non avanzerebbe correttamente...

Firestarter83: C’è l’Interrupt, ma il micros() non avanzerebbe correttamente...

In che senso scusa? :o

Normalmente in una ISR non ti metti certo ad aspettare ... leggi un valore e lo salvi, alzi una flag, incrementi un contatore, ecc. ecc., ma comunque la fai la più breve e la più rapida possibile ...

Spiega cosa intendevi.

Guglielmo

“Generally, an ISR should be as short and fast as possible. If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have. millis() relies on interrupts to count, so it will never increment inside an ISR. Since delay() requires interrupts to work, it will not work if called inside an ISR. micros() works initially but will start behaving erratically after 1-2 ms. delayMicroseconds() does not use any counter, so it will work as normal.”

Mi ricordavo di aver letto qualcosa sulle Reference: micros() non si incrementa correttamente dopo 1-2 ma... Allora: il sensore ad ultrasuoni, dopo aver inviato l’inpulso di Trigger, attende il ritorno del segnale, a quel punto il pin di Echo va ad High, ritorna low appena l’inpulso finisce, quindi potrei usare attachInterrupt CHANGE per determinare inizio e fine e calcolare il tempo trascorso per il calcolo della distanza... dovrebbe funzionare giusto? E a differenza della PulseIn non dovrebbe essere bloccante..

Firestarter83: quale potrebbe essere l’alternativa alla Pulse In per calcolare la durata dell’Echo e determinare poi la distanza?

Se stiamo parlando di ATmega328 (Arduino Uno, Nano, Mini), un modo per misurare con precisione la durata di un impulso è usare l'interrupt associato all'Input Capture Unit sul pin D8. Purtroppo è una funzione molto poco documentata online e per un principiante potrebbe essere un po' ostica da digerire. Se vuoi approfondire, ti rimando ad uno dei post più completi (secondo me) disponibili online. http://gammon.com.au/forum/?id=11504&reply=12#reply12

Firestarter83: Sto realizzando un Robot, la funzione Pulse in e’ molto comoda ma purtroppo e’ bloccante (l’attesa dell’’echo)... c’è la possibilità di inserire il timeout, però mi piacerebbe farlo andare senza nessuna pausa...

Eh, però, perdonami, continuo a non capire per quale motivo tu abbia imposto (anzi "auto-imposto";) ) questo requisito. Se misuri distanze entro diciamo 5 metri, con la velocità del suono di 340 m/s, il tempo TOTALE per la lettura della distanza va tra 0.1 ms (distanze inferiori a 30 cm circa) e 30 ms (5 metri): tu hai necessità che il tuo robot non resti in attesa ed abbia realmente risposte (per altre attività) per tempi minori di 30 ms?

Ovviamente se il tuo problema è legato all'assenza di ostacoli (in tal caso il tempo di attesa si allunga), allora usa la pulseIn() con il parametro per specificare un timeout in modo che tu non vada ad attendere inutilmente per distanze maggiori di quelle per te necessarie, ad esempio:

...
#define SRF_TIMEOUT 30000UL // 30 ms
...
    // ping procedure
    digitalWrite( TrigPin, LOW );
    delayMicroseconds(2)
    digitalWrite( TrigPin, HIGH );
    delayMicroseconds(10);
    digitalWrite( EchoPin, LOW );  
    long pulseDuration = pulseIn( EchoPin, HIGH, SRF_TIMEOUT); 
...