Optimizarea programelor în mediul Arduino IDE

Optimizarea procesului de compilare

Mediul Arduino IDE utilizează compilatorul GCC. Pe lângă procesul de compilare propriu-zis, mediul de dezvoltare efectuează o serie de operații (pre-procesare) ce permit implementarea funcțiilor specifice Arduino (de exemplu includerea automată a definițiilor din Arduino.h). Pentru detalierea procesului de compilare se poate parcurge materialul „Arduino Build Process”.

Procesul de compilare este efectuat, invizibil pentru programator, la un anumit nivel de optimizare (-Os sau -O2 în funcție de versiunea mediului de dezvoltare). Pentru detalierea nivelurilor de optimizare recomandăm parcurgerea materialului „Using the GNU Compiler Collection (GCC): Optimize Options”. Modificarea nivelului de optimizare la compilarea programului se poate face la nivel de fișiere de configurare a mediului de dezvoltare sau chiar la nivel de program.

Prin introducerea unor directive de compilare putem modica nivelul de optimizare pentru întregul program:

#pragma GCC optimize (“-O0”)

#pragma GCC push_options

void setup() { }

void loop() { }

#pragma GCC pop_options

sau doar pentru o anumită secțiune, funcție sau procedură:

void loop() __attribute__((optimize(“-O0”)));

void loop() { }

 

Optimizarea instrucțiunilor specifice mediului Arduino IDE

Unul din obiectivele principale ale mediului Arduino IDE este înlesnirea accesului programatorului la diversele mecanisme interne ale microcontrolerului. Din aceste motiv s-a efectuat rebotezarea pinilor și tot din același motiv au fost introduse instrucțiuni specifice mediului Arduino IDE de tipul pinMode, digitalRead sau digitalWrite. Din punct de vedere al procesului de învățare și dezvoltare aceste facilități accelerează foarte mult timpul de lucru dar din punctul de vedere al eficienței execuției aduc penalizări destul de mari. Cel mai bun exemplu, prezentat și în materialul „Arduino Is Slow”, este înlocuirea instrucțiunii digitalWrite cu o instrucțiune de modificare a registrului intern ce stochează starea pinului pe care dorim să-l modificăm. Se poate testa diferența dintre timpul de execuție a celor două variante rulând următorul exemplu de program:

void setup()

{

 Serial.begin(9600);

}

void loop()

{

 int initial = 0;

 int final = 0;

 initial = micros();

 for(int i = 0; i < 500; i++)

 {

    digitalWrite(13,HIGH);

    digitalWrite(13,LOW);

 }

 final = micros();

 Serial.print(“Time for digitalWrite(): “);

 Serial.print(final-initial);

 Serial.println(“”);

 initial = micros();

 for(int i = 0; i < 500; i++)

 {

    PORTB |= _BV(PB5);

    PORTB &= ~_BV(PB5);

 

 }

 final = micros();

 Serial.print(“Time for true c command: “);

 Serial.print(final-initial);

 while(1);

}

Programul înlocuiește instrucțiunea digitalWrite cu o scriere în registrul intern al microcontrolerului (pinul 13 este pinul PB5). Rezultatul este destul de convingător:

2

Instrucțiunea digitalWrite nu este singura care poate fi înlocuită în cadrul programelor Arduino pentru a obține performanțe mai bune. O altă instrucțiune ”delicată” ce poate afecta funcționarea unor montaje mai complicate este instrucțiunea shiftOut.  Dacă examinăm fișierul wiring_shift.c din directorul Arduino putem vedea că această funcție se bazează pe instrucțiunea digitalWrite deci poate fi accelerată prin înlocuirea acestei instrucțiuni.

void shiftOut(uint8_t dataPin, uint8_t clockPin,

uint8_t bitOrder, uint8_t val)

{

    uint8_t i;

    for (i = 0; i < 8; i++)  {

         if (bitOrder == LSBFIRST)

             digitalWrite(dataPin, !!(val & (1 << i)));

         else

             digitalWrite(dataPin, !!(val & (1 <<

(7 – i))));

         digitalWrite(clockPin, HIGH);

         digitalWrite(clockPin, LOW);     

    }

}

ATENȚIE!!! Optimizările prezentate conduc la pierderea portabilității programelor între diverse plăci Arduino. Toate indicațiile se aplică direct doar pentru plăcile ce sunt echipate cu un microcontroler ATmega328P (Arduino Uno de exemplu).

Pentru exemplificarea optimizării instrucțiunii shiftOut vom considera următorul montaj bazat pe o placă de dezvoltare Arduino Uno, un registru de deplasare 74HC595 și 8 leduri:

3

Programul propus va implementa un mini joc de lumini ce constă în aprinderea succesivă a câte un led din cele 8. Programul obișnuit (ce utilizează instrucțiunea shiftOut) este:

const int latchPin = 4;

const int clockPin = 3;

const int dataPin = 2;

void setup() {               

  pinMode(latchPin, OUTPUT);

  pinMode(dataPin, OUTPUT); 

  pinMode(clockPin, OUTPUT);

  digitalWrite(latchPin, LOW);

  shiftOut(dataPin, clockPin, MSBFIRST, 0); 

  digitalWrite(latchPin, HIGH);

}

void loop() {

  int afisare = 1;

  while (1) {

   for (int i=0;i<8;i++) {

      digitalWrite(latchPin, LOW);

      shiftOut(dataPin, clockPin, MSBFIRST, afisare << i); 

      digitalWrite(latchPin, HIGH);

      delay(500);

     }

   }

}

Varianta nouă ce nu folosește instrucțiunile shiftOut și digitalWrite este:

void loop() {

  int afisare = 1;

  while (1) {

   for (int i=0;i<8;i++) {

    PORTD |= _BV(PD4);

    for (uint8_t b = 0; b < 8; b++)  {

      if(!!((afisare << i) & (1 << (7 – b)))) PORTD |=

_BV(PD2);

      else PORTD &= ~_BV(PD2);

      PORTD |= _BV(PD3);

      PORTD &= ~_BV(PD3);

    }

    PORTD &= ~_BV(PD4);

    delay(500);

   }

  }

}

Chiar dacă la prima vedere exemplul funcționează la fel, se poate justifica necesitatea modificării codului prin imaginarea situației în care procesul de transmitere serială se repetă de un număr suficient de mare de ori încât produce o întârziere inacceptabilă (de exemplu încascadarea unui număr mare de registre de deplasare).