Optimizați digitalWrite pe Arduino
Astăzi voi testa viteza reală a funcției digitalWrite pe Arduino Mega2560 și vă voi spune cum să accelerați programul de 50 de ori! Miezul cardului de depanare Arduino Mega2560 este microcontrolerul AT2560. care lucrează cu o viteză de 16 MHz. Dacă traducem aceste 16 milioane de oscilații într-un interval de timp, atunci avem o perioadă destul de mică, egală cu 62,5 ns. Este rapid, dar Arduino efectuează operații la aceeași viteză? Să vedem.
Comenzile pe care le scriem în limba cablajului. în procesul de compilare sunt convertite în comenzi mai simple, așa-numitul cod de mașină, pe care microcontrolerul deja îl efectuează direct. Unele comenzi ale microcontrolerului sunt executate într-un singur ciclu de ceas al microcontrolerului, unele necesită mai multe cicluri, respectiv runtime. Prin urmare, unul, în opinia noastră, o comandă sau o funcție simplă va fi efectuată de către microcontroler pentru câteva, poate chiar câteva zeci sau sute de cicluri. În plus, în afară de calcule, avem nevoie de operații de citire / scriere în memorie, astfel încât frecvența medie a procesării comenzilor va fi semnificativ diferită de frecvența ceasului microcontrolerului. Întrebarea este cât de mult.
Să încercăm să aflăm cât de mult este o comandă simplă pentru schimbarea valorii de pe ieșirea digitală Arduino - digitalWrite. Să facem niște experimente. În primul experiment, executați următorul cod.
Acest cod clipește LED-ul aflat pe placă și conectat la pinul 13. Schimbați alternativ starea acestei ieșiri. Dacă executăm acest cod, vom vedea că LED-ul va fi aprins continuu, dar, de fapt, nu este. LED-ul se aprinde și se stinge, doar că ochii noștri nu pot percepe oscilații peste 25 Hz și vedem astfel de oscilații ca un LED cu ardere constantă, cu o luminozitate determinată de ciclul de funcționare al semnalului.
Pentru a vedea ce se întâmplă de fapt la cea de-a 13-a ieșire, voi folosi osciloscopul. Oscilograma semnalului la 13 pini arată astfel:
Se pare că comanda digitalWrite (13, HIGH) este executată în 6,6 μs, iar digitalwrite (13, LOW) în 7,2 μs. Total 13,8 microsecunde. Acest lucru este mult mai mare decât 62,5 ns, de fapt, de 220 de ori mai mult. De asemenea, puteți vedea că starea LOW (7.2 μs) durează mai mult decât starea HIGH (6.6 μs).
Să facem următorul experiment.
Ca urmare a acestei execuții a codului, oscilograma semnalului la 13 pini arată astfel:
Singura instruire HIGH durează 6,8 μs - cam la fel cum era de așteptat (6,6 μs). Două comenzi consecutive care au ieșirea în starea LOW ocupă 13,8 μs este puțin mai mică decât cea estimată de 14,4 μs (7,2 × 2).
Ce veți obține? În buclă buclă (). utilizând funcția digitalWrite. putem modifica starea pinului cu o frecvență maximă de 72 kHz și, în unele cazuri, această frecvență poate fi mai mică, de exemplu, ca în cel de-al doilea caz - aproximativ 37 kHz. Această frecvență este mult mai mică decât ceasul 16 MHz, dar dacă utilizați întreruperi de temporizare. atunci putem crește în mod semnificativ această cifră.
Implementarea funcției digitalWrite în limba cablajului este, pentru a spune ușor, nu este optimă. Dacă te uiți la implementarea lui în Assembler, poți găsi mai multe verificări, în special verifică dacă este necesar să oprești temporizatorul PWM după funcția anterioară analogWrite (). Cea mai rapidă implementare, dar în același timp cea mai consumatoare de timp, ar putea fi în Assembler. Dar scrierea codului pe Assemler este încă violență asupra ta. Nu vorbesc despre depanarea codului de asamblare. Unul dintre compromisuri poate fi utilizarea unor biblioteci optimizate care implementează diferite funcții I / O. În publicațiile mele viitoare, voi examina unele dintre aceste biblioteci.
Un alt motiv pentru întârziere este funcția buclă (). Când toate comenzile din bucla () sunt executate, este implicit pentru noi să executăm un alt cod care revine la execuția comenzilor din cadrul acestei bucla. Acesta este motivul pentru care timpul de secvență de la două stări identice la sfârșitul bucla () nu este egal cu suma duratelor unei comutări de stare unică - aici microcontrolerul execută încă instrucțiuni care revin la începutul ciclului.
Pentru a reduce timpul de schimbare a stării unei ieșiri, puteți utiliza comenzi directe de scriere în registrul porturilor. Pentru a modifica valorile pinii 8-13 este utilizat PORTB registru în care să scrie cuvântul de date (doi biți mai puțin semnificativi ai cuvântului de date, adică, 1 și 2 biți nu sunt utilizate, iar restul de șase - stabilit doar starea porturilor digitale cu 8 la 13) . Pentru a schimba starea pinilor digitali de la 0 la 7, se utilizează PORTD.
Și din nou ne îndreptăm spre ajutorul osciloscopului:
După cum se poate observa, frecvența a scăzut de la 4 MHz la 1 MHz. Timpul de ședere în starea HIGH nu sa schimbat și este încă egal cu 62,5 ns (pe oscilograma este pur și simplu afișat, Wid (1)<100.0ns ). Следовательно, остальное время уходит на возврат к началу, и на это уходит в восемь раз больше времени, то есть 8 тактов. Вывод: функция loop не столь уж и эффективна.
În acest exemplu, am obținut o creștere de patru ori a vitezei, dar acesta este un caz limitator. De fapt, salvăm câteva cicluri de ceas și atunci când timpul de execuție al codului din interiorul bucla crește, efectul reducerii timpului programului va fi redus semnificativ.
Deci, permiteți-mi să rezum:
- Utilizarea regiștrilor de porturi în loc de digitalWrite vă permite să măriți în mod semnificativ viteza programului.
- Utilizând o buclă infinită în timp ce (1) încorporată în bucla (), putem salva câteva cicluri ale procesorului la fiecare rotire a buclei.
Cum evaluați această publicație? (64 voturi, evaluare medie: 4.86 din 5)
Mai multe despre acest subiect
Cu siguranță e misto. Numai cu aceasta, întreaga esență a muncii cu arduino este pierdută - comoditate și distanțare față de arhitectura microcontrolerului. Dacă trebuie să scrieți un cod rapid și compact, atunci cu siguranță nu ar trebui să-l scrieți la Wiring. )))))
mulțumesc foarte mult m-au ajutat. Mă întreb cum va duce arduino în acest caz? cu o viteză a ceasului de 84 MHz.
Teoretic, viteza programului ar trebui să crească proporțional cu creșterea frecvenței ceasului de 84MHz / 16MHz = 5,25 ori.
în timp ce () funcționează cu adevărat!
a dat peste un articol, a decis să verifice. deoarece există un osciloscop, m-am gândit de ce nu, dar lasă-mă să mă uit.
scrieți la porturi nu funcționează (nano v3.1). Nu știu cum funcționează m, nu am găsit portul de care am nevoie.
dar în timp ce () diferența sa dovedit. pe un exemplu simplu cu hi low după scădere, dacă fără timp, sunt generate 290 ns suplimentar. poate că nu este mult, dar tot. este probabil rezultatul operației buclă ().
atunci întrebarea este de ce este nevoie deloc?
nu a știut că așa este simplu este posibil să se adreseze mk (DDRB = B10000000).
multumesc pentru idee)
Ceea ce facem este o distrugere a limbajului de cablare, a cărui putere este în simplitate. Când încercăm să maximizăm utilizarea capacităților controlorului, de exemplu, lucrăm direct cu registrele, ne îndepărtăm de la ideologia cablajului. Dacă aveți nevoie de performanță, este mai bine să scrieți în C / C ++ pur și să maximizați caracteristicile arhitecturii microcontrolerului.
Fără o buclă în schiță, Cablarea refuză să compileze și produce o eroare.
Ei bine, Arduino este primul pas în stăpânirea Mk.
Pentru viitor, "hartă" a concluziilor arduino este descrisă în fișierul pins_arduino.h, dar există mai multe astfel de fișiere, există o bază care se află în dosar
Pentru unii dintre Arduin (Leonardo, Mega, Micro, etc.) există excepții pentru aceștia, deoarece există dosarele lor care se află
Numele portului (PORTA, PORTB.) Este specificat în matricea constantă digital_pin_to_port_PGM
Masca bit în portul din array digital_pin_to_bit_mask_PGM (de fapt, este numărul de biți).
Indicele este clar de la zero.
Deci, ia numărul de ieșire: 13, uite ce avem
digital_pin_to_bit_mask_PGM [13] = _BV (5)
_BV este o macro declarată în pachetul AVR
#define _BV (bit) (1 < și avem nevoie doar de numărul în paranteze obțineți PortB, bitul 5 Din fericire, rezultatele s-au dovedit a fi aproape adevărate, dar cu un singur plus: "Folosind buclă infinită în timp ce sunt imbricate în buclă (), putem salva câteva cicluri CPU pe ciclu", dar pierdem ceea ce ne oferă serialEvent. Anume, procesarea datelor de intrare pe porturile seriale va fi oprită. O altă opțiune este de a permite fuziunii să trimită semnalul de ceas la ieșire, 16 MHz să obțină imediat hardware. De la cel interesant, puteți genera un semnal de ceas RC cu un lanț și schimbați frecvența programabil, înregistrând corecția de frecvență. Dacă lanțul RC extern, schimbând parametrii RC sau tensiunea de alimentare, generatorul de frecvență se schimbă în general fără probleme și se autocalibrează cu precizie cel puțin din cuarț în fiecare secundă, chiar și de la sateliții GPS. Articolul trebuie actualizat. timpul trece. se fac schimbări. După publicarea articolului, au durat 3 ani. cod digitalWrite (13, HIGH); digitalWrite (13, LOW); este mai rapid. timpul total este de aproximativ 7 nsec, frecvența este de aproximativ 150 kHz. (IDE 1.8.1, UNO R3 328 16MHz). și da. după cum sa menționat mai sus - codul comenzii directe de scriere la registrul portului nu funcționează.