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 variables. Les types de variables prennent des espaces mémoires de tailles différentes sur le microcontrôleur.
Voici un tableau récapitulatif des tailles de variables
Type | Size | Range |
boolean | 1 byte | 0 to 1 |
char | 1 byte | -128 to 127 |
unsigned char | 1 byte | 0 to 255 |
int | 2 bytes | -32,768 to 32,767 |
unsigned int | 2 bytes | 0 to 65,535 |
word | 2 bytes | 0 to 65,535 |
long | 4 bytes | -2,147,483,648 to 2,147483,647 |
unsigned long | 4 bytes | 0 to 4,294,967,295 |
float | 4 bytes | 3.4028235E-38 to 3.4028235E+38 |
double | 4 bytes | 3.4028235E-38 to 3.4028235E+38 |
string | 1 byte + # of chars | N/A |
array | 1 byte + (sizeOfType * # of elements) | N/A |
enum | N/A | N/A |
struct | N/A | N/A |
pointer | N/A | N/A |
void | N/A | N/A |
Pour bien choisir le type de variable, il est bon de connaître ses valeurs extrêmes et de savoir quels types d’opérations vont être effectués 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 effectuez 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 au cours de l’exécution 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() lors de serial.printf, cela permet de faire gagner énormément de place lorsque beaucoup d’informations sont envoyées sur le moniteur série.
serial.printf(F("my string"));
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