Étiquettes : , ,

L’espace mémoire est limité sur une carte Arduino, il peut être important d’améliorer son programme Arduino afin d’éviter certains problèmes. Plus on avance en programmation, plus on écrit des programmes longs et complexes. Il est important, au plus tôt, de prendre les bons réflexes. De bonnes habitudes peuvent être prises pour faciliter le partage et la lecture de votre travail; ainsi que pour améliorer son exécution et l’espace pris en mémoire.

Nous allons voir dans cet article certaines méthode pour améliorer votre programme Arduino.

Exemple d’application: il est dommage d’acheter une carte Mega pour quelques boutons et LEDs parce qu’on a mis une centaine d’affichage sur le moniteur série et qu’il n’y a plus assez de mémoire.

Prérequis : Programmer avec Arduino

Nom de variable et de fonctions

Utiliser des noms de fonctions et de variables clairs:

des noms explicites plutôt que des lettres (Ex: « sensorValue » plutôt que « a« )

Création de boucle et de fonctions

Lorsque un bout de code est répété plusieurs fois dans un programme créer une fonction ou une boucle.

Utiliser des boucles sur des vecteurs (array). Nous allons effectuer la lecture de toutes les entrées analogiques de l’Arduino.

int a,b,c,d,e,f;
void setup() {
  Serial.begin(9600);
}

void loop() {
   a=analogRead(A0);
   b=analogRead(A1);
   c=analogRead(A2);
   d=analogRead(A3);
   e=analogRead(A4);
   f=analogRead(A5);
  
  Serial.print(a);
  Serial.print(b);
  Serial.print(c);
  Serial.print(d);
  Serial.print(e);
  Serial.print(f);
}

Résultat:

Le croquis utilise 1952 octets (6%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 196 octets (9%) de mémoire dynamique, ce qui laisse 1852 octets pour les variables locales. Le maximum est de 2048 octets.

Introduction d’une boucle for et d’un array

int a[6];
void setup() {
  Serial.begin(9600);
}

void loop() {
  for(int i=0;i<6;i++){
    a[i]=analogRead(14+i);
    Serial.print(a[i]);
  }
}

Nous pouvons voir, qu’en plus d’être plus lisible, l’espace mémoire pris par le code est diminué après cette opération

Le croquis utilise 1746 octets (5%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 184 octets (8%) de mémoire dynamique, ce qui laisse 1864 octets pour les variables locales. Le maximum est de 2048 octets.

Type de variables

Utiliser les bons types de variable. Les types de variable prennent des espaces mémoires de tailles différentes sur le microcontrôleur.

Voici un tableau récapitulatif des tailles de variable 

TypeSizeRange
boolean1 byte0 to 1
char1 byte-128 to 127
unsigned char1 byte0 to 255
int2 bytes-32,768 to 32,767
unsigned int2 bytes0 to 65,535
word2 bytes0 to 65,535
long4 bytes-2,147,483,648 to 2,147483,647
unsigned long4 bytes0 to 4,294,967,295
float4 bytes3.4028235E-38 to 3.4028235E+38
double4 bytes3.4028235E-38 to 3.4028235E+38
string1 byte + # of charsN/A
array1 byte + (sizeOfType * # of elements)N/A
enumN/AN/A
structN/AN/A
pointerN/AN/A
voidN/AN/A

Pour bien choisir le type de variable, il est bon de connaitre ses valeurs extrêmes et de savoir quels types d’opération vont être effectué sur cette variable. Il faut choisir le type le plus petit permettant de décrire la variable et l’opération.

Exemple: écrire float a=analogRead(A0); n’a pas de sens car la fonction analogRead() renvoie une valeur comprise entre 0 et 1023. Donc la variable devrait être de type unsigned int

Défini comme un float

float a;
void setup() {
  Serial.begin(9600);
}

void loop() {
   a=analogRead(A0);
  Serial.print(a);
}

Le croquis utilise 2968 octets (9%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 196 octets (9%) de mémoire dynamique, ce qui laisse 1852 octets pour les variables locales. Le maximum est de 2048 octets.

Défini comme un int

int a;
void setup() {
  Serial.begin(9600);
}

void loop() {
   a=analogRead(A0);
  Serial.print(a);
}

Le croquis utilise 1736 octets (5%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 184 octets (8%) de mémoire dynamique, ce qui laisse 1864 octets pour les variables locales. Le maximum est de 2048 octets.

Éviter le dépassement de capacité des variables

Comme nous l’avons vu dans le tableau précédent, les différents types de variables ont une limite numérique. Il se peut que lorsque vous effectué un calcul sur un int, par exemple, le résultat intermédiaire de ce calcul dépasse la capacité du type sélectionné.

Voici un code permettant de convertir une valeur analogique en une valeur physique. Ce qui peut être utile lorsque vous voulez afficher la valeur physique d’un capteur.

int MIN_VAL=500;
int MAX_VAL=2500;
int MIN_RAW=0;
int MAX_RAW=1023;

void setup() {
  Serial.begin(115200);
  Serial.println("system on");
}

void loop() {
  // put your main code here, to run repeatedly:
  for(int i=MIN_RAW;i<MAX_RAW;i++){
    Serial.print(F("raw val :"));Serial.print(i);Serial.print(F("  -->  val :"));Serial.println(rawToPhys(i));
    delay(200);
  }
}

int rawToPhys(int x){
  int phys=(x - MIN_RAW)*(MAX_VAL-MIN_VAL)/(MAX_RAW-MIN_RAW) + MIN_VAL; 
  return phys; 
}

Lorsqu’on observe le résultat de ce calcul, on voit que les valeurs sont incohérentes. Alors que la variable i augmente, la valeur phys revient à 470 après être passée par des valeurs intermédiaires.

raw val :0 --> val :500
raw val :1 --> val :501
raw val :2 --> val :503
raw val :3 --> val :505
raw val :4 --> val :507
raw val :5 --> val :509
raw val :6 --> val :511
raw val :7 --> val :513
raw val :8 --> val :515
raw val :9 --> val :517
raw val :10 --> val :519
raw val :11 --> val :521
raw val :12 --> val :523
raw val :13 --> val :525
raw val :14 --> val :527
raw val :15 --> val :529
raw val :16 --> val :531
raw val :17 --> val :470
raw val :18 --> val :472

Une méthode pour corriger ce problème est d’utiliser un changement de type (cast) sur un résultat intermédiaire

int rawToPhys(int x){
  int phys=double(x - MIN_RAW)*(MAX_VAL-MIN_VAL)/(MAX_RAW-MIN_RAW) + MIN_VAL; 
  return phys; 
}

raw val :0 --> val :500
raw val :1 --> val :501
raw val :2 --> val :503
raw val :3 --> val :505
raw val :4 --> val :507
raw val :5 --> val :509
raw val :6 --> val :511
raw val :7 --> val :513
raw val :8 --> val :515
raw val :9 --> val :517
raw val :10 --> val :519
raw val :11 --> val :521
raw val :12 --> val :523
raw val :13 --> val :525
raw val :14 --> val :527
raw val :15 --> val :529
raw val :16 --> val :531
raw val :17 --> val :533
raw val :18 --> val :535

Type de variable constant

Lorsque la variable ne change pas lors du fonctionnement du programme, utilisez « const » ou #define pour définir la variable.

#define NB_LED 4
const int vitesse_max=100;

Vous pouvez mettre n’importe quel type de variable après le « const ».

Faites bien attention à la syntaxe du #define. Il n’y a pas de signe « = » ni de « ; » à la fin de l’instruction.

Variable globale ou locale

Lorsque la variable est utilisée dans différentes fonctions, il peut être intéressant de la définir comme global. S’il s’agit d’une variable qui n’existe que pour afficher un résultat intermédiaire, il vaut mieux la définir en local.

Fonction F() pour l’affichage sur le moniteur série

Mettre les Strings dans la fonction F(), cela permet de faire gagner énormément de place lorsque beaucoup d’information sont envoyer sur le moniteur série.

Exemple: j’ai trouvé un code sur internet avec une quantité phénoménale de print() (J’ai même écrit un script Python pour modifier le code afin d’inclure la fonction F()). Voici le résultat de la compilation:

Le croquis utilise 36966 octets (14%) de l'espace de stockage de programmes. Le maximum est de 253952 octets.
Les variables globales utilisent 6500 octets (79%) de mémoire dynamique, ce qui laisse 1692 octets pour les variables locales. Le maximum est de 8192 octets.
La mémoire disponible faible, des problèmes de stabilité pourraient survenir.

Après avoir modifié tout les Serial.print() et les Serial.println() dans le script .INO, voici le résultat de la compilation:

Le croquis utilise 36770 octets (14%) de l'espace de stockage de programmes. Le maximum est de 253952 octets.
Les variables globales utilisent 4830 octets (58%) de mémoire dynamique, ce qui laisse 3362 octets pour les variables locales. Le maximum est de 8192 octets.

Une réduction de 79 à 58% et plus de problème de stabilité. Plutôt pratique.

Utiliser la fonction millis()

En principe, il faut bannir l’utilisation de fonctions qui bloquent l’exécution du code comme delay(). Pour des codes simples, cela ne pose pas de problème mais lorsque vous devez exécuter plusieurs tâches en parallèle, vous ne pouvez plus vous le permettre.

Plusieurs solutions existent pour se passer de delay(): millis(), Timer.h, Timer2.h, interrupt. Je vous montre dans cet article, la solution millis() qui suffit dans la plupart des cas mais n’hésitez pas à vous renseigner sur les autres.

Voici l’exemple d’un code qui lit l’entrée analogique A0 toutes les 500ms:

int a;
void setup() {
  Serial.begin(9600);
}

void loop() {
  a=analogRead(A0);
  Serial.print(a);
  delay(500);
}

Lorsqu’on modifie le code pour intégrer la fonction millis(), on obtient le même résultat avec, en plus, la capacité de faire autre chose pendant ces 500ms

int a;
unsigned long previousTime=0;
void setup() {
  Serial.begin(9600);
}

void loop() {
  if(millis()-previousTime)>500{
    previousTime=millis();
    a=analogRead(A0);
    Serial.print(a);
  }
}

Si vous connaissez d’autres méthodes ou bonnes pratiques pour écrire et améliorer votre programme Arduino, n’hésitez pas à laisser un commentaire.

Sources

Retrouvez nos tutoriels et d’autres exemples dans notre générateur automatique de code
La Programmerie