Arduino avancé

Programmation Modulaire avec Arduino

Lorsque je parle de programmation modulaire, j’entends que mon programe est assimilable à un mur que l’on construit en assemblant des briques distinctes (1 brique = 1 fonctionnalité/fonction/fichier). Cela possède beaucoup d’avantages, notament supprimer facilement des fonctionnalités dans un code un peu lourd, travailler à plusieurs sur un même projet, alléger facilement un code en supprimant très simplement des fonctionnamlités inutiles…

Pour cela :

  • Je programme en utilisant différents fichiers : 1 fichier (fichier source + header) = 1 composant.
  • Je fais des déclarations conditionnelles (même pour des fonctions).
  • J’utilise des pointeurs.

J’ai rédigé cet article durant un stage ; le code que je faisais à l’époque et qui peut vous fournir des exemples pour mieux comprendre ces notions.

Gestion d’un projet contenant des fichiers multiples :

  • Le fichier principal doit être un .ino ou .pde (je n’ai pas encore cherché lequel est mieux).
  • Si l’on crée des fichiers sans extension, ils seront concaténé avec le fichier principal. Attention à l’ordre d’import qui semble être alphabétique (je n’ai pas trop creusé ce point).
  • Pour chaque document externe créé avec une extension, qui seront compilés séparement. Ces documents externes doivent respecter la norme suivante :
    • header .hpp / ou .h
    • fichier source .cpp / Attention cela ne fonctionne pas si le fichier source possède l’extension .c ! Dans une version antérieure de ce document, je disais que le fichier source peut avoir pour extension .c, or cela génère des erreures du type :

      ...undefined reference to 'Nom_de_la_fonction(type)'
            
      collect2.exe: error: ld returned 1 exit status
      
      exit status 1
      
  • Chaque fichier externe sera compilé séparement avant d’être inclus au projet, donc évidement il faut importer la bibliothèque Arduino.h (#include )
  • En en-tête et pieds de page des fichiers .h il faut absolument ajouter les lignes suivantes :

      #ifndef NAME_H
      #define NAME_H
          
      /* Contenu du fichier
       * code...
       *
       * ...
       *
       */
          
      #endif
    
    • Lors de cette étape, il est nécessaire d’écrire en majuscules le nom du fichier, j’ai pour ma part suivit la convention où l’on rajoute “_H” à la fin, dans tous les cas il faut juste s’assurer que ce nom soit unique pour le préprocesseur responsable des imports de bibliothèques.
    • Ces lignes servent à éviter les imports multiples de headers identiques (le compilateur n’aime pas). Plus on ajoute de fichiers dans le projet et plus il est probable que cela se produise.
  • Petit rappel : Les fichiers sources (fichiers .c/.cpp) associés aux librairies (headers - fichiers .h) possèdent le même nom que la librairie associée et contient l’import de la librairie (#include “myLib.h” dans le cas du fichier myLib.cpp). On n’importe JAMAIS les fichiers sources.
  • Pour un peu plus complexe, la création de variables “externe”. Pour échanger des informations d’un fichier à l’autre, on doit DECLARER dans un header une variable avec le mot clef “extern” (exemple : extern int a;) ; Puis dans le fichier source associé, on DEFINIT et DECLARE cette dernière SANS le mot clef “extern” (exemple : int a = 0;), puis on importe le header dans chaque fichier où la variable est nécessaire. Si l’on définit une CONSTANTE, il ne faut pas mettre le mot clef “extern” (à la place on utilise “const” ou avec la directive préprocesseur “#define”) et la définir uniquement dans le header qui sera importé dans chaque fichier où elle est nécessaire. Si l’on n’applique pas cette technique, on peut voir apparaitre des erreurs de type ‘multiple declarations’.

Limites :

L’IDE Arduino est assez simple pour débuter le code, cependant il possède encore quelques problèmes (je suis actuellement en version 1.8.3), notament :

  • Pas de gestion simplifiée de projet avec de multiples fichiers (pour changer d’onglet, si on en a trop il faut utiliser la flèche à droite et choisir le document spécifique… Les onglets de sont pas réarangeables dynamiquement).
  • Lorsque l’on renome des onglets, il faut fermer puis ré-ouvrir l’IDE pour que le tout s’actualise. C’est même fortement conseillé avant de recompiler le tout.
  • Pas de gestion simple de l’éditeur externe.

Sources :

https://arduinodilettante.wordpress.com/2011/03/10/multiple-file-sketches/

https://stackoverflow.com/questions/11055802/static-and-extern-global-variables-in-c-and-c

Déclarations conditionnelles avec le préprocesseur :

Dans certains cas, des fonctionnalités telles que l’utilisation du port série (via l’USB), l’ajout d’un lecteur de carte SD, d’un écran,… doivent pouvoir être désactivées facilement. Pour cela, on fait appel au préprocesseur du compilateur en définissant une constante indiquant l’état de la fonctionnalité. Si la constante est définie, la fonctionnalité est présente ; si la constante est commentée, les portions de codes utilisant cette fonctionnalité ne sont pas compilées - le programme est plus léger et dans certains cas, on peut supprimer des fichiers avec juste un commentaire (celui de la variable activant ou non la fonctionnalité) sans casser l’intégralité du code.

La définition des constantes est faite avec la commande suivante (le nom des constantes est toujours mis en majuscules par convention) :

#define MA_FONCTIONNALITE

Bien évidement cette commande est à mettre en haut de programme (pour qu’elle s’applique dès le début et que l’on ait pas à lire l’intégralité du programme pour la trouver).

Puis, à chaque fois que l’on a une portion de code liée à cette fonctionnalité, on teste si la variable est définie avec des macros comme les suivantes :

#ifdef MA_FONCTIONNALITE
  // action liée à cette fonctionnalité
#else
  // do something else
#endif

Ce code peut sembler lourd, cependant en utilisant ce type de macros, on s’adresse directement au pré-processeur. Ce dernier, lors de son parcours du code, exécutera ces commandes et donc remplacera les portions de codes en fonction de la déclaration des constantes (si l’on a rien de déclaré et que les #else impliquent de ne rien importer, le programme sera très léger et vice-versa.

L’inconvénient de ces macro est qu’en s’attaquant directement au pré-processeur, on échappe à la détection d’erreurs des étapes suivantes du compilateur. C’est pourquoi il faut vraiment limiter l’usage des “#define” pour avoir un code léger, mais pas pour déclarer des constantes (ou le moins possible, dans mon cas je définis les variables des pins d’entrée/sortie avec). On peut cependant définir des erreurs, warnings ou messages avec les macros suivantes : #error, #warning et #message.

Sources :

http://www.deviceplus.com/how-tos/arduino-guide/arduino-preprocessor-directives-tutorial/

Liste des erreurs basiques à éviter:

  • Utiliser les entrées / sorties numériques liées au port série (pin 0 et 1 sur Arduino Uno et Nano) dans un programme utilisant le port série pour communiquer avec l’ordinateur (ces pins relayent les données du port série et sont donc inutilisables de manière binaire).

Changelog :

Modifié

  • 2018-05-31 : Correction du nomage des extensions de fichiers, avec ajout de l’erreur pouvant apparaître.
  • 2018-06-11 : Ajout de la liste d’erreurs.
Written on July 5, 2017