Timer e ritardi di codifica in Arduino
Aggiornamento del 5 settembre 2019: la rimozione delle chiamate delay () è il primo passo per ottenere il multi-tasking semplice su qualsiasi scheda Arduino. Il semplice Multi-tasking istruibile di Arduino su Any Board copre tutti gli altri passaggi necessari.
Aggiornamento del 5 maggio 2019: rinominato isFinished () in justFinished (), poiché restituisce VERO solo una volta appena terminato il ritardo. Aggiunto esempio di ritardo di blocco / pausa
introduzione
Non usare il ritardo ()
L'uso di delay () provoca il blocco del sistema in attesa della scadenza del ritardo. Tuttavia, la sostituzione dei ritardi richiede alcune cure. Questa pagina spiega in modo graduale come sostituire Arduino delay () con una versione non bloccante che consente al codice di continuare l'esecuzione durante l'attesa del timeout del ritardo.
Ecco una serie di semplici schizzi ciascuno dei quali accende un Led quando la scheda Arduino viene accesa (o ripristinata) e quindi 10 secondi dopo la spegne. Il primo è un esempio di come NON scrivere il codice. Il secondo è un esempio di codice che funziona e il terzo è un esempio dell'uso della libreria millisDelay per semplificare il codice. Ci sono anche esempi di timer a colpo singolo e a ripetizione.
Se hai già capito perché non dovresti usare delay () e hai familiarità con Arduino, l'importanza dell'uso di long unsigned, overflow e sottrazione unsigned, puoi semplicemente saltare a Usare la libreria millisDelay (Step 4)
La libreria millisDelay fornisce ritardi e timer di funzionalità, è semplice da usare e facile da comprendere per i nuovi utenti di Arduino.
Questo manuale è anche online su Come codificare timer e ritardi in Arduino
Passaggio 1: Come non codificare un ritardo in Arduino
Ecco come NON codificare un ritardo in uno schizzo.
int led = 13;
ritardo prolungato senza segno Inizio = 0; // l'ora di inizio del ritardo bool delayRunning = false; // true se è ancora in attesa del termine del ritardo
void setup () {pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, ALTA); // accende il led on delayStart = millis (); // start delay delayRunning = true; // non ancora finito }
void loop () {// controlla se il ritardo è scaduto dopo 10sec == 10000mS if (delayRunning && ((millis () - delayStart)> = 10000)) {delayRunning = false; // // impedisce l'esecuzione di questo codice più di una volta digitalWrite (led, LOW); // disattiva il led Serial.println ("LED spento"); } // Altro codice di loop qui. . . Serial.println ("Esegui altro codice"); }
Nel metodo setup (), che Arduino chiama una volta all'avvio, il led è acceso. Una volta che setup () è finito, Arduino chiama ripetutamente il metodo loop () . Qui è dove la maggior parte di voi va, leggendo i sensori che inviano output ecc. Nello schizzo sopra, viene chiamato il primo loop temporale (), il ritardo (10000) arresta tutto per 10 secondi prima di spegnere il led e continuare. Se esegui questo codice, vedrai che Esegui altro codice non viene stampato per 10 secondi dopo l'avvio, ma dopo che il led è spento (ledOn equivale a falso), viene stampato molto velocemente quando loop () viene richiamato ripetutamente ancora.
Il punto da notare qui è che in realtà non dovresti affatto la funzione delay () nel codice loop (). A volte è conveniente usare delay () nel codice setup () e spesso puoi cavartela molto piccola usando ritardi molto piccoli di pochi millisecondi nel codice loop (), ma dovresti davvero evitare di usare il metodo loop ()
Passaggio 2: come scrivere un ritardo senza blocco in Arduino
Lo schizzo precedente utilizzava un ritardo di blocco, ovvero uno che impediva completamente al codice di fare qualsiasi altra cosa mentre il ritardo era in attesa di scadere. Il prossimo schizzo mostra come scrivere un ritardo non bloccante che consente al codice di continuare l'esecuzione mentre attende che il ritardo scada.
int led = 13;
ritardo prolungato senza segno Inizio = 0; // l'ora di inizio del ritardo bool delayRunning = false; // true se è ancora in attesa del ritardo per il completamento dell'installazione vuota () {pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, ALTA); // accende il led on delayStart = millis (); // start delay delayRunning = true; // non ancora finito} void loop () {// controlla se il ritardo è scaduto dopo 10sec == 10000mS if (delayRunning && ((millis () - delayStart)> = 10000)) {delayRunning = false; // // impedisce l'esecuzione di questo codice più di una volta digitalWrite (led, LOW); // disattiva il led Serial.println ("LED spento"); } // Altro codice di loop qui. . . Serial.println ("Esegui altro codice"); }
Nello schizzo sopra, nel metodo setup (), la variabile delayStart è impostata sul valore corrente di millis () .
millis () è un metodo integrato che restituisce il numero di millisecondi dall'accensione della scheda. Si avvia come 0 ogni volta che la scheda viene ripristinata e viene incrementata ogni millisecondo da un contatore hardware della CPU. Maggiori informazioni su millis () più tardi. Una volta che setup () è finito, Arduino chiama ripetutamente il metodo loop ().
Ogni ciclo temporale () è chiamato controllo del codice
a) che il ritardo è ancora in corso e
b) se il millis () si è spostato su 10000 mS (10sec) dal valore memorizzato in delayStart .
Quando il tempo è trascorso di 10000mS o più, delayRunning è impostato su false per impedire che il codice nell'istruzione if venga eseguito di nuovo e il led sia spento.
Se esegui questo schizzo, vedrai Esegui altro codice stampato molto rapidamente e dopo 10 secondi il LED verrà spento e se sei veloce potresti vedere il messaggio LED spento prima che scorra dallo schermo.
Vedere il passaggio 4 di seguito per come la libreria millisDelay semplifica questo codice
Passaggio 3: Sottrazione lunga, trabocco e non firmata senza segno
Se hai familiarità con long senza segno, overflow, aritmetica senza segno e l'importanza dell'uso di una variabile lunga senza segno, puoi semplicemente saltare al passaggio 4 usando la libreria millisDelay.
La parte importante dello schizzo precedente è il test
(millis () - delayStart)> = 10000
Questo test deve essere codificato in questo modo molto specifico per funzionare.
Non firmato lungo e troppo pieno
La variabile delayStart e il numero restituiti dalla funzione integrata millis () sono lunghi senza segno . Questo è un numero compreso tra 0 e 4.294.967.295.
Se aggiungi 1 a un long senza segno con il valore massimo di 4.294.967.295 la risposta sarà 0 (zero). Questo è il numero traboccato e racchiuso intorno a 0. Puoi immaginare che il bit di trabocco sia appena stato eliminato. ad es. in un 111 senza segno a 3 bit è il valore massimo (7) aggiungendo 1 si ottiene 1000 (8) ma il 1 iniziale trabocca lo spazio di archiviazione a 3 bit e viene lasciato cadere quindi a 000.
Questo significa, alla fine, quando la CPU aggiunge un'altra variabile con il risultato millis (), si sposta intorno a 0. Questo è millis () che riprende a contare da 0. Questo accadrà se lasci la scheda Arduino in esecuzione per 4.294.967.295mS, ovvero circa 49 giorni 17 ore, diciamo 50 giorni.
Consideriamo ora un altro modo di codificare il test (millis () - delayStart)> = 10000
Aritmeticamente questo test è uguale a millis ()> = (delayStart + 10000)
Tuttavia, se si avvia il ritardo dopo quasi 50 giorni, ad esempio quando millis () restituisce 4.294.966.300 mS, allora delayStart + 10000 traboccerà a 995 e il test, millis ()> = (delayStart + 10000), sarà immediatamente vero e lì non ci sarà alcun ritardo. Quindi questa forma del test non funziona sempre.
Sfortunatamente, è improbabile che ti imbatti in questo durante i test, ma potrebbe verificarsi inaspettatamente in un dispositivo di lunga durata, come una porta da garage che controlla le corse continuamente per mesi. Si verificherà un problema simile se si tenta di utilizzare
delayEnd = millis () + 10000
e quindi il test (millis ()> = delayEnd)
Infine, la variabile delayStart deve essere lunga senza segno . Se invece usi un long (cioè long int ) o int o unsigned int, il valore massimo che possono contenere è inferiore al long senza segno restituito da millis () . Alla fine il valore risintonizzato da millis () traboccerà della variabile più piccola in cui viene memorizzato e scoprirai che il tempo è improvvisamente tornato indietro. Ad esempio, se si utilizza un int senza segno per startDelay, ciò avverrà dopo 65 secondi su una scheda Uno.
Sottrazione non firmata
Un altro punto di interesse è quello che succede a millis () - delayStart quando delayStart è 4.294.966.300 e vogliamo un ritardo di 10000mS.
millis () tornerà a 0 prima che ciò accada. Ricorda che l'aggiunta di 1 al valore massimo che un long senza segno può memorizzare torna indietro a 0. Quindi un modo di vedere il calcolo di millis () - delayStart, dove millis () si è spostato ed è più piccolo di delayStart, è dire "W numero di cappello devo aggiungere a delayStart a uguale millis () (dopo l'overflow)? "ovvero che cosa è X nel ritardo dell'equazione Start + X == millis ()
Ad esempio, usando nuovamente una variabile senza segno a 3 bit, per calcolare 2-4 (senza segno), pensa a un quadrante di orologio che inizia da 0 e aggiunge 1 tutto intorno a 111 (7) e poi torna a 0. Ora per ottenere da 4 a 2 devi aggiungere 6 (5, 6, 7, 0, 1, 2), quindi 2-4 = 6 e questo è in effetti come funziona il calcolo, anche se la CPU eseguirà il calcolo in modo diverso.
Quindi la differenza tra due long senza segno sarà sempre un numero positivo nell'intervallo da 0 a 4.294.967.295. Ad esempio, se startDelay è 1 e millis () si è spostato su 0 (dopo 50 giorni), allora millis () - startDelay sarà pari a 4.294.967.295. Ciò significa che è possibile specificare un DELAY_TIME ovunque nell'intervallo compreso tra 0 e 4.294.967.295 mS e (millis () - delayStart)> = DELAY_TIME funzionerà sempre come previsto, indipendentemente da quando viene avviato il ritardo.
Passaggio 4: utilizzo della libreria MillisDelay
Per installare la libreria millisDelay. Scaricato il file millisDelay.zip.
Decomprimi questo file nella tua directory Arduino / librerie (apri la finestra delle preferenze File-> IDE per vedere dove si trova la tua directory Arduino locale). Alcune volte le istruzioni per Come installare una libreria - L'installazione automatica funziona, ma non sempre. La decompressione manuale del file è la più sicura. Una volta installata la libreria millisDelay, saranno disponibili tre esempi interattivi che è possibile caricare sulla scheda Arduino e quindi aprire il monitor seriale (entro 5 secondi) a 9600baud per usarli. Ecco il precedente schizzo di ritardo non bloccante riscritto usando la libreria millisDelay.
#include "millisDelay.h" int led = 13; millisDelay ledDelay; void setup () {pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, ALTA); // turn led on ledDelay.start (10000); // avvia un ritardo di 10 secondi} void loop () {// controlla se il ritardo è scaduto if (ledDelay.justFinished ()) {digitalWrite (led, LOW); // disattiva il led Serial.println ("LED spento"); } // Altro codice di loop qui. . . Serial.println ("Esegui altro codice"); }
Se guardi il codice della libreria millisDelay noterai che il codice dello schizzo precedente è stato appena spostato nei metodi start () e solo Finished () nella libreria.
È un ledDelay o un ledTimer? Puoi usare il termine che preferisci. Tendo a usare ... il ritardo per i ritardi a colpo singolo che vengono eseguiti una volta e usano ... il timer per ripetere quelli.
Passaggio 5: esempi di ritardo e timer
Ecco due schizzi di ritardo e timer di base e i loro equivalenti della libreria millisDelay. Questi esempi sono per un ritardo una tantum (scatto singolo) e un ritardo / timer ripetuto.
Ritardo colpo singolo
Un ritardo di scatto singolo è uno che viene eseguito solo una volta e poi si ferma. È la sostituzione più diretta per il metodo Arduino delay () . Inizi il ritardo e poi quando hai finito fai qualcosa. BasicSingleShotDelay è il codice semplice e SingleShotMillisDelay utilizza la libreria millisDelay.
BasicSingleShotDelay
Questo schizzo è disponibile in BasicSingleShotDelay.ino
int led = 13; // Il pin 13 ha un LED collegato sulla maggior parte delle schede Arduino.
unsigned long DELAY_TIME = 10000; // 10 sec unsigned long delayStart = 0; // l'ora di inizio del ritardo bool delayRunning = false; // true se è ancora in attesa del ritardo per il completamento dell'installazione vuota () {pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, ALTA); // accende il led // start delay delayStart = millis (); delayRunning = true; } void loop () {// controlla se il ritardo è scaduto if (delayRunning && ((millis () - delayStart)> = DELAY_TIME)) {delayRunning = false; // ritardo finito - scatto singolo, una volta solo digitalWrite (led, LOW); // disattiva led}}
Nel codice sopra il ciclo () continua a funzionare senza essere bloccato in attesa della scadenza del ritardo.
Durante ogni passaggio del loop (), la differenza tra il millis () corrente e il tempo di delayStart viene confrontato con DELAY_TIME . Quando il timer supera il valore dell'intervallo, viene eseguita l'azione desiderata. In questo esempio il timer di ritardo viene arrestato e il LED si spegne.
SingleShotMillisDelay
Ecco lo schizzo BasicSingleShotDelay riscritto usando la libreria millisDelay. Questo schizzo è disponibile in SingleShotMillisDelay.ino
Ecco la versione di millisDelay in cui il codice sopra è stato racchiuso nei metodi di classe della classe millisDelay.
#includere
int led = 13; // Il pin 13 ha un LED collegato sulla maggior parte delle schede Arduino. millisDelay ledDelay; void setup () {// inizializza il pin digitale come output. pinMode (led, OUTPUT); digitalWrite (led, ALTA); // accende il led // avvia il ritardo ledDelay.start (10000); } void loop () {// controlla se il ritardo è scaduto if (ledDelay.justFinished ()) {digitalWrite (led, LOW); // disattiva led}}
Timer a ripetizione
Questi sono semplici esempi di ritardo / timer ripetuti. BasicRepeatingDelay è il codice semplice e RepeatingMillisDelay utilizza la libreria millisDelay.
BasicRepeatingDelay
Questo schizzo è disponibile in BasicRepeatingDelay.ino
int led = 13; // Il pin 13 ha un LED collegato sulla maggior parte delle schede Arduino.
unsigned long DELAY_TIME = 1500; // 1, 5 sec ritardo prolungato senza segno Inizio = 0; // l'ora di inizio del ritardo bool delayRunning = false; // true se è ancora in attesa del ritardo per il completamento bool ledOn = false; // tiene traccia dello stato vuoto del led setup () {pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, LOW); // disattiva led ledn = false; // start delay delayStart = millis (); delayRunning = true; } void loop () {// controlla se il ritardo è scaduto if (delayRunning && ((millis () - delayStart)> = DELAY_TIME)) {delayStart + = DELAY_TIME; // questo impedisce la deriva nei ritardi // attiva / disattiva il led ledOn =! ledOn; if (ledOn) {digitalWrite (led, HIGH); // attiva led} else {digitalWrite (led, LOW); // disattiva il led}}}
Il motivo per l'utilizzo
delayStart + = DELAY_TIME;
per reimpostare il ritardo per eseguire nuovamente, è possibile che millis () - delayStart possa essere> DELAY_TIME perché millis () si è appena incrementato o a causa di qualche altro codice nel loop () che lo rallenta. Ad esempio una lunga dichiarazione di stampa. (Vedi Aggiunta di un Loop Montor al punto 7)
Un altro punto è avviare il ritardo alla fine dell'avvio () . Ciò garantisce che il timer sia preciso all'inizio del loop (), anche se startup () impiega del tempo per essere eseguita.
RepeatingMillisDelay
Ecco lo schizzo BasicRepeatingDelay riscritto usando la libreria millisDelay. Questo schizzo è disponibile in RepeatingMillisDelay.ino
#includere
int led = 13; // Il pin 13 ha un LED collegato sulla maggior parte delle schede Arduino. bool ledOn = false; // tenere traccia dei millis di stato ledDelay ledDelay; void setup () {// inizializza il pin digitale come output. pinMode (led, OUTPUT); // inizializza il pin digitale come output. digitalWrite (led, LOW); // disattiva led ledn = false; // start delay ledDelay.start (1500); } void loop () {// controlla se il ritardo è scaduto if (ledDelay.justFinished ()) {ledDelay.repeat (); // avvia nuovamente il ritardo senza deriva // attiva / disattiva il led ledOn =! ledOn; if (ledOn) {digitalWrite (led, HIGH); // attiva led} else {digitalWrite (led, LOW); // disattiva il led}}}
Passaggio 6: Altre funzioni della libreria MillisDelay
Oltre alle funzioni start (delay), just Finished () e repeat () illustrate sopra, la libreria millisDelay ha anche
stop () per interrompere il timeout del ritardo,
isRunning () per verificare se non è già scaduto e non è stato arrestato,
restart () per riavviare il ritardo da ora, utilizzando lo stesso intervallo di ritardo,
finish () per forzare la scadenza anticipata,
rimanente () per restituire il numero di millisecondi fino al termine del ritardo e
delay () per restituire il valore di delay che è stato passato a start ()
Versione del microsecondo della libreria
millisDelay conta il ritardo in millisecondi. Puoi anche cronometrare per microsecondi. È un esercizio per il lettore scrivere quella classe. ( Suggerimento: rinominare la classe in microDelay e sostituire le occorrenze di millis () con micros () )
Blocco / Pausa di un ritardo
È possibile congelare o mettere in pausa un ritardo salvando i rimanenti () millisecondi e arrestando il ritardo, quindi successivamente sbloccandolo riavviandolo con il restante mS come ritardo. ad es. vedi l'esempio FreezeDelay.ino
mainRemainingTime = mainDelay.remaining (); // ricorda quanto tempo resta da eseguire nel ritardo principale mainDelay.stop (); // stop mainDelay NOTA: mainDelay.justFinished () non è MAI vero dopo stop ()… mainDelay.start (mainRemainingTime); // riavvia dopo il blocco
Passaggio 7: Avvertenza: aggiungere un monitor loop
Sfortunatamente molte delle librerie Arduino standard usano delay () o introducono pause, come AnalogRead e SoftwareSerial. Di solito i ritardi che introducono sono piccoli ma possono sommarsi, quindi ti suggerisco di aggiungere un monitor nella parte superiore del tuo loop () per verificare la velocità con cui viene eseguito.
Il monitor loop è molto simile all'esempio di lampeggiamento. Un piccolo pezzo di codice nella parte superiore del metodo loop () attiva o disattiva il Led ogni volta che viene eseguito il ciclo () . È quindi possibile utilizzare un multimetro digitale con scala a Hz per misurare la frequenza dell'uscita sul pin LED (pin 13 in questo caso)
Il codice è: -
// Loop Monitor: controlla che il loop () venga eseguito almeno una volta ogni 1 mS
// (c) 2013 Forward Computing and Control Pty. Ltd. // www.forward.com.au> // // Questo codice di esempio è di dominio pubblico. int led = 13; // non utilizzare su FioV3 quando la batteria è collegata // Il pin 13 ha un LED collegato sulla maggior parte delle schede Arduino. // se si utilizza Arduino IDE 1.5 o versione successiva è possibile utilizzare // LED_BUILTIN predefinito invece di 'led' // la routine di installazione viene eseguita una volta quando si preme reset: void setup () {// inizializza il pin digitale come output. pinMode (led, OUTPUT); // aggiungi qui il tuo altro codice di configurazione} // la routine del loop viene ripetuta all'infinito: void loop () {// attiva / disattiva l'uscita del led in ciascun loop La frequenza del led deve misurare> 500Hz (ovvero <1mS off e <1mS on ) if (digitalRead (led)) {digitalWrite (led, LOW); // spegne il LED rendendo la tensione BASSA} else {digitalWrite (led, HIGH); // accendi il LED (ALTO è il livello di tensione)} // aggiungi qui il resto del codice del ciclo}
Puoi scaricare il codice del monitor qui. Quando eseguo questo codice sulla mia scheda Uno, il multimetro sulla gamma Hz collegato tra il pin 13 e GND indica 57, 6Khz. cioè circa 100 volte> 500hz.
Man mano che aggiungi il codice a loop () la lettura Hz si riduce. Basta controllare che rimanga ben al di sopra dei 500Hz (esecuzione 1mS per loop ()) in tutte le situazioni.