Cum se utilizează corect variabile volatile în Arduino?

Făceam un proiect mic cu un Arduino Uno. A implicat întreruperi, deoarece folosesc codificatoare pentru a măsura cât de mult se mișcă sistemul roții diferențiale. Robotul meu se mișcă doar înainte. Deci, folosesc doar un singur canal de la fiecare codificator. Iată cele două rutine de întrerupere ale mele:

ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; } 

Variabilele encoderRPos și encoderLPos sunt de tip volatile int. Înțeleg că variabilele care suferă modificări în orice rutină de întrerupere trebuie să fie de tip volatile. Aceasta este pentru a avertiza alte părți ale codului care utilizează aceste variabile că se pot modifica oricând.

Dar ceea ce s-a întâmplat în codul meu a fost cam ciudat și nu am putut să-l explic. Iată cum calculez distanța mișcată de roata stângă:

 #define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

Dar când imprim următoarele pe monitorul meu serial, observ o anomalie:

introduceți descrierea imaginii aici

Dacă vă uitați la a treia coloana, (SL) valoarea sa este prea mare doar pentru o perioadă de timp. Acest lucru îmi supără toate calculele.

Singurul indiciu pe care îl pot obține, dacă iau valoarea SL pe care am obținut-o ( 3682), care este întotdeauna o constantă și calculați înapoi (encodeLPos - encoderLPosPrev), voi primi 65519.66, care este aproape de valoarea maximă a unsigned int . Ceea ce înseamnă că (encoderLPos - encoderLPosPrev) cauzează o revărsare, în timp ce ambele valori ale căror diferențe sunt luate doar undeva în jurul valorii de 5000!

Și am reușit să o rezolv. noroc. Așa am modificat codul:

 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; 

Nu pot să înțeleg ce s-a întâmplat. Există ceva despre variabilele volatile despre care ar fi trebuit să știu?

Actualizare: Aici este întregul cod dacă vreți să aruncați o privire. Și funcționează foarte bine după ce l-ați modificat la ceea ce a fost sugerat în răspunsul acceptat.

Comentarii

  • Întrebarea dvs. spune care este cea de-a treia coloană din ieșirea este … care sunt celelalte coloane? Vă rugăm să editați întrebarea și să adăugați titluri de coloană
  • @ jwpat7 Le-am eliminat în mod intenționat, deoarece acest lucru nu va face decât să deruteze cititorul.
  • Este greu să dai răspunsuri detaliate din fragmentele tale. 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? – Aș putea face asta dacă aș vedea întregul cod. Între timp citește acest lucru: gammon.com.au/interrupts
  • @NickGammon Iată-l: paste.ubuntu.com / 14085127
  • 3683 / .056196868 = 65537 deci se pare că a crescut la momentul greșit, da? Accesați o variabilă care ar putea fi modificată într-o întrerupere de mai multe ori în acel cod, astfel încât obținerea unei copii locale, în timp ce întreruperile sunt dezactivate, ar fi mult mai sigur.

Răspuns

Trebuie să aflați despre secțiunile critice .

Ce probabil că se întâmplă este că variabilele sunt modificate de rutinele de întrerupere la jumătatea calculelor. „Remediul” dvs. reduce timpul petrecut făcând calculul cu variabilele volatile, făcând astfel mai puțin probabil că există o coliziune.

Ce ar trebui să faceți este să copiați variabilele volatile în variabile locale cu întreruperi dezactivate pentru asta scurtă perioadă.

cli(); int l = encoderLpos; int r = encoderRpos; sei(); 

Deoarece Arduino este un procesor de 8 biți, este nevoie de mai multe instrucțiuni de asamblare pentru a efectua operații matematice pe valori de 16 biți. Punctul flotant este și mai rău folosind multe instrucțiuni pentru o adăugare simplă. Împărțirea și multiplicarea folosesc mult mai mult. O întrerupere are multe oportunități de a declanșa în timpul listei de instrucțiuni. Făcând o astfel de misiune și apoi folosind noile variabile locale în calculele dvs., instrucțiunile necesare pentru a face față variabilelor volatile sunt menținute la un minim absolut. Dezactivând întreruperile în timpul atribuirii, vă asigurați că variabilele nu pot fi modificate niciodată în timp ce le utilizați. Acest fragment de cod se numește secțiune critică .

Comentarii

  • Acest lucru s-ar putea să fie doar cazul. Întrebându-vă, ați putea explica de ce nu se întâmplă la întâmplare, dar la un moment specific de fiecare dată când rulez codul? De asemenea, de ce oferă valoarea particulară?
  • Iată o referință excelentă la cli / sei. nongnu.org/avr-libc/user-manual/… . Cu bariera de memorie, declarația volatilă nu este într-adevăr necesară în codul de mai sus. Iată câteva lecturi distractive despre acest subiect. kernel .org / doc / Documentation / volatile-considerat-dăunător.txt
  • @MikaelPatel Nice, dar nu este relevant pentru MCU-uri.Volatile sunt necesare în această situație pentru a împiedica compilatorul să optimizeze instanțele în care crede că ‘ nu este utilizat (valoarea nu se schimbă niciodată). Cli / sei este acolo pentru a face operațiunea atomică WRT singurul alt fir (întreruperi) care se execută.
  • Ați încercat să compilați codul cu și fără volatil? Dar cu secțiunea critică (cli / sei). Ceea ce încerc să discut este conceptul de barieră de memorie și modul în care acesta oferă acces volatil (și ordonare corectă) de la compilator, cu nevoia de a declara variabilele ca fiind volatile. Majoritatea programatorilor sunt învățați că orice variabilă accesată într-un ISR trebuie declarată volatilă, dar există mult mai multe lucruri în această poveste.
  • Nu cred că divizorul este compilatorul. are mult concept despre ceea ce fac cli () și sei () și cum ar afecta acest lucru lucruri precum optimizarea din variabile care nu ar trebui ‘ să fie optimizate. Tot ce faceți sei () și cli () este să manipulați semnalizatorul activat pentru întreruperea globală din registrul său. Nu fac nimic pentru fluxul de cod.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *