Stavo facendo un piccolo progetto con un Arduino Uno. Ha coinvolto interruzioni poiché utilizzo gli encoder per misurare quanto il sistema della ruota differenziale si sposta in avanti. Il mio robot si muove solo in avanti. Quindi utilizzo un solo canale da ogni codificatore. Ecco le mie due routine di interrupt:
ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; }
Le variabili encoderRPos
e encoderLPos
sono di tipo volatile int
. Capisco che le variabili che subiscono modifiche in qualsiasi routine di interrupt devono essere di tipo volatile. Questo per avvertire altre parti del codice che utilizzano queste variabili che potrebbero cambiare in qualsiasi momento.
Ma quello che è successo nel mio codice era un po strano e non sono riuscito a spiegarlo. Ecco come calcolo la distanza percorsa dalla ruota sinistra:
#define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;
Ma quando stampo quanto segue sul mio monitor seriale, noto unanomalia:
Se guardi il terzo colonna, (SL) il suo valore è troppo alto solo per un po di tempo. Questo sconvolge tutti i miei calcoli.
Lunico indizio che posso ottenerlo, se prendo il valore di SL che ho ottenuto ( 3682), che è sempre una costante, e ricalcolo (encodeLPos - encoderLPosPrev)
, otterrò 65519,66, che è vicino al valore massimo di unsigned int
Il che significa che (encoderLPos - encoderLPosPrev)
sta causando un overflow mentre entrambi i valori la cui differenza è presa è da qualche parte intorno a 5000 solo!
E sono riuscito a risolverlo. È stato da fortuna. Ecco come ho modificato il codice:
static int encoderRPosPrev = 0; static int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev); int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;
Non riesco “a capire cosa sia successo. Cè qualcosa sulle variabili volatili di cui avrei dovuto sapere?
Aggiornamento: Ecco lintero codice se vuoi dare unocchiata. E funziona molto bene dopo averlo modificato in quanto suggerito nella risposta accettata.
Commenti
Risposta
Devi conoscere le sezioni critiche .
Cosa probabilmente sta accadendo è che le variabili vengono modificate dalle routine di interrupt a metà dei calcoli. La tua “correzione” riduce il tempo speso a fare il calcolo con le variabili volatili rendendo così meno probabile che ci sia una collisione.
Quello che dovresti fare è copiare le variabili volatili nelle variabili locali con gli interrupt disabilitati per quello breve periodo.
cli(); int l = encoderLpos; int r = encoderRpos; sei();
Poiché Arduino è una CPU a 8 bit, sono necessarie più istruzioni di assemblaggio per eseguire operazioni matematiche su valori a 16 bit. Il virgola mobile è anche peggio usando molte molte istruzioni per una semplice aggiunta. La divisione e la moltiplicazione usano molto di più. Uninterruzione ha molte possibilità di attivarsi durante lelenco di istruzioni. Eseguendo unassegnazione del genere e quindi utilizzando le nuove variabili locali nei calcoli, le istruzioni necessarie per gestire le variabili volatili vengono ridotte al minimo. Disattivando gli interrupt durante lassegnazione garantisci che le variabili non possono essere modificate mentre le stai utilizzando. Questo frammento di codice è chiamato sezione critica .
Commenti
- Questo potrebbe essere solo il caso. Mi chiedevo solo, potresti spiegare perché non accade in modo casuale ma in un momento specifico ogni volta che eseguo il codice? E anche perché dà il valore particolare?
- Ecco un ottimo riferimento al cli / sei. nongnu.org/avr-libc/user-manual/… . Con la barriera di memoria la dichiarazione volatile non è realmente necessaria nel codice precedente. Ecco alcune letture divertenti su questo argomento. kernel .org / doc / Documentation / volatile-included-dangerous.txt
- @MikaelPatel Bello, ma non rilevante per gli MCU.Volatile è richiesto in questa situazione per impedire al compilatore di ottimizzare le istanze in cui ritiene che ‘ non venga utilizzato (il valore non cambia mai). Il cli / sei è lì per rendere loperazione atomic WRT lunico altro thread (interrupt) che viene eseguito.
- Hai provato a compilare il codice con e senza volatile? Ma con la sezione critica (cli / sei). Quello che sto cercando di discutere è il concetto di barriera di memoria e come questo fornisce laccesso volatile (e lordinamento corretto) dal compilatore con la necessità di dichiarare le variabili come volatili. Alla maggior parte dei programmatori viene insegnato che qualsiasi variabile a cui si accede in un ISR deve essere dichiarata volatile, ma cè molto di più in questa storia.
- Non ‘ penso che il compilatore ha molto concetto di cosa fanno cli () e sei () e di come ciò influirebbe su cose come lottimizzazione di variabili che non dovrebbero ‘ essere ottimizzate. Tutto quello che fanno sei () e cli () è manipolare il flag abilitato allinterrupt globale nel suo registro. Non fanno nulla per il flusso di codice.
could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value?
– Probabilmente potrei farlo se vedessi lintero codice. Nel frattempo leggi questo: gammon.com.au/interrupts3683 / .056196868 = 65537
quindi sembra che sia aumentato nel momento sbagliato, sì? Stai accedendo a una variabile che potrebbe essere modificata in un interrupt più volte in quel codice, quindi ottenere una copia locale, mentre gli interrupt sono disattivati, sarebbe molto più sicuro.