Débuter avec Arduino

Partie 5 : Les boucles incrémentielles et conditionnelles, les bibliothèques.

Arts, Maths, Sciences et Technologies | Deuxième cycle

schéma

DIFFICULTÉ MOYENNE | 6 À 8 HEURES


Bienvenue dans la partie 5 ! Ici nous allons commencer à programmer sérieusement ! Vous devriez donc très bien maîtriser les points suivants :
  1. Lecture et écriture d'un signal numérique avec les fonctions digitalRead(); et digitalWrite();.
  2. Lecture et écriture d'un signal analogique avec les fonctions analogRead(); et analogWrite();.
  3. Compréhension des intervalles de valeurs d'un signal analogique entrant (lu) et sortant (écrit):
    • Signal analogique entrant (analogRead();) : valeur comprise entre 0 et 1023 (intervalle [0 1023]).
    • Signal analogique sortant (analogWrite();) : valeur comprise entre 0 et 255 (intervalle [0 255]).
  4. La manipulation des variables n'a plus de secrets pour vous grâce à la fonction map();.

Si vous maîtrisez ces 4 points, que vous avez bien compris le rôle des "void setup()" et "void loop()" et que vous êtes à l'aise avec le concept de variables, alors vous êtes réellement prêts pour la suite !


Les boucles incrémentielles : "for".


Ce titre peut faire peur mais rassurez vous les boucles incrémentielles ne sont pas si compliquées ! Surtout, elles pemettent de simplifier considérablement les programmes !
On les appelle également boucles for car elles sont appelées dans le programme avec le mot anglais "for".
Mais ça veut dire quoi "incrémentielle" ? Et bien sachez qu'en informatique, un incrément est la quantité dont on augmente une variable à chaque phase de l'exécution d'un programme.
Comme d'habitude, une petite définition théorique !

L'instruction "for" est utilisée pour répéter l'exécution d'un bloc d'instructions regroupées entre des accolades. Un compteur incrémentiel est habituellement utilisé pour incrémenter et finir la boucle. L'instruction "for" est très utile pour toutes les opérations répétitives.

Bon essayons maintenant de comprendre !
Et la meilleure façon pour comprendre, c'est la pratique !

Voici une simulation. Je l'appelle la "DEL respirante". Une DEL s'allume et s'éteint graduellement. On retrouve parfois ceci dans les appareils électroniques lorsqu'ils sont en veille.
Observez la simulation puis nous allons décortiquer le programme ensemble.


Les premières lignes du programmes ne posent pas vraiment de difficultés :
  • Déclaration des variables : int del = 3;
  • "void setup()" : pinMode (del, OUTPUT);
Tout est dit : la DEL est branchée sur la prise 3 qui doit se comporter comme une sortie, vous êtes maintenant à l'aise avec ça.

C'est le "void loop()" qui est un peu plus obscur :

void loop(){
  
   for (int i=0; i < 256; i++){ 
      analogWrite(del, i); 
      delay(10);
   }
  
   for (int i=255; i > 0; i--){ 
      analogWrite(del, i); 
      delay(10);
   } 
}

Il y a ici 2 boucles for.

Concentrons nous sur la première afin de bien comprendre le fonctionnement de ces boucles incrémentielles.

for (int i=0; i < 256; i++){ 
    analogWrite(del, i); 
    delay(10);
    }

Nous avons :
  • L'instruction "for" qui comprend 3 paramètres entre parenthèse : for (int i=0; i < 256; i++).
  • Entre 2 accolades, la partie du programme à éxecuter dans la boucle "for".

Nous pourrions résumer ceci de la façon suivante :

for (initialisation; condition; incrémentation) {
    //instruction(s) à exécuter;
    }

Reprenons notre exemple. Dans cette simulation on veut tout d'abord que la DEL éclaire graduellement, puis s'éteigne graduellement. Cette première boucle "for" permet d'allumer la DEL graduellement.

Rappelez vous l'initialisation de la boucle "for" :
for (initialisation; condition; incrémentation){}


Il y a 3 points à définir :
  1. Initilisation : nous déclarons une variable ("i"), dont la valeur de départ est 0 (int i = 0;);
  2. Condition : c'est la condition qui permet d'exécuter la boucle. Si elle est vraie, le programme continue d'exécuter la boucle "for", si elle est fausse le programme ne l'exécute plus.
    Ici notre condition est i < 256;. Cela signifie que tant que la variable "i" est plus petite que 256, le programme peut continuer d'exécuter la boucle "for". Lorsque la variable "i" sera égale à 255, la dermière condition sera atteinte et la boucle for ne sera plus exécutée.
  3. Incrémentation : c'est la quantité dont on augmente la variable "i" à chaque boucle. Ici nous avons i++. En langage C, ceci équivaut à écrire i = i + 1. Donc, à chaque boucle d'éxecution du "for", la variable "i" augmente de 1. Jusqu'à atteindre 255, la condition que nous avons fixé préalablement.
A l'intérieur du bloc d'éxecution de la boucle "for" (entre les 2 parenthèses), nous avons :
  • analogWrite (del, i); : Arduino envoie un signal analogique dans la prise de la DEL (#3) d'une valeur "i".
    • Première boucle : i = 0, la DEL est éteinte.
    • Seconde boucle : i = 1, la DEL commence à s'allumer.
    • 123ème boucle : i = 122, la DEL est à moitié allumée.
    • 256ème boucle : i = 255, la DEL est allumée au maximum.
    • 257ème boucle : i = 256, la condition i < 256; est fausse, le programme n'éxecute plus le "for".
  • delay(10); : une pause de 10 millisecondes entre les boucles afin de percevoir l'augmentation de luminosité. Sans délai, le programme va plus vite que nos yeux et nous verrions la DEL éclairer au maximum d'un coup.
    Jouez avec le délai afin de ralentir ou accélérer la "respiration" de la DEL.

Pour la seconde boucle "for", on fait l'inverse : for (int i=255; i > 0; i--){}.
  • Initialisation : int i = 255;. On démarre la variable "i" à 255.
  • Condition : i > 0;. La boucle "for" s'exécutera tant que la variable "i" est plus grande que 0.
  • Incrémentation : i--. La variable "i" diminue de 1 à chaque boucle "for". Ceci équivaut à écrire i = i - 1.
Cette boucle "for" aura donc pour effet de diminuer l'intensité de la DEL dès que la variable "i" aura atteint la valeur 255.
Et comme tout ceci est dans le "void loop", ça recommence indéfiniment.

Un autre exemple d'utilisation de la boucle for : dans la première série d'exercice vous deviez reproduire l'effet spécial du pare choc de K2000. Déclarer chaque prise (les "pinMode") et allumer et éteindre chaque DEL était long.
Grâce aux boucles "for", le programme est nettement simplifié.
Voici donc le K2000 revisité. Essayez et regardez bien le programme !




Les boucles conditionnelles : "if-else".


Comme son nom l'indique, la boucle conditionnelle permet d'éxecuter une partie de programme sous certaines conditions.
On l'appelle également boucle if ou if-else car on la déclare avec les mots anglais "if" et "else", que l'on pourrait traduire par "si"..."sinon".
On pourrait simplifier le concept en s'imaginant dire au programme "si tu remplis cette condition, tu fais ceci; sinon tu fais cela".
Définition d'une boucle conditionnelle :

L'instruction "if" ("si" en français), utilisée avec un opérateur logique de comparaison, permet de tester si une condition est vraie, par exemple si la mesure d'une entrée analogique est bien supérieure à une certaine valeur.

Une nouvelle simulation pour bien comprendre cette histoire de "if-else" et "d'opérateur logique de comparaison".
Jouez avec l'interrupteur et observez ce qu'il se passe.


Dans une position, l'interrupteur permet d'allumer la DEL, dans l'autre position, l'interrupteur fait clignoter la DEL.
Si nous étions le programme, il faudrait nous dire :
  • SI l'interrupteur est à la position HIGH, tu clignotes la DEL.
  • SINON tu allumes la DEL.

Regardons le programme de plus près. Passons la déclaration des variables et le "void setup" qui ne sont pas compliqués et allons directement au "void loop".

void loop()
{
  valInterrupt = digitalRead(interrupteur);
  
  if (valInterrupt == LOW){
    digitalWrite (del, HIGH);
  }
  
  else {
    digitalWrite (del, LOW);
    delay (100);
    digitalWrite (del, HIGH);
    delay (100);
  }
}

Tout d'abord la ligne valInterrupt = digitalRead (interrupteur); permet de stocker l'état de l'interrupteur (HIGH ou LOW) dans la variable "valInterrupt".
Ensuite arrive le "if-else". Concentrons-nous sur le "if" :

if (valInterrupt == LOW){
    digitalWrite (del, HIGH);
  }

Littéralement, ceci veut dire : "SI la variable valInterrupt est égale à HIGH, tu allumes la DEL".

Et pour le "else" :

  else {
    digitalWrite (del, LOW);
    delay (100);
    digitalWrite (del, HIGH);
    delay (100);
  }

Ce que nous pourrions traduire en langage d'humain par : "SINON tu éteins la DEL pendant 100 millisecondes puis tu l'allumes pendant 100 millisecondes".

De façon générale, une boucle conditionnelle est formatée comme suit :

if (uneVariable opérateurDeComparaison uneValeur) {
  // faire quelque chose 
  }

else {
  // faire autre chose
}

Entre les parenthèses, on compare une variable à une valeur, et entre les accolades, on éxecute une ou des actions tant que la comparaison est vraie.

Il se peut également que vous n'ayez pas à utiliser le "else" si vous n'avez rien a exécuter si la condition est fausse.
Il est aussi possible d'avoir autant de "if" que vous le désirez. Vous pouvez même avoir des "if" dans des "if", des "if" dans des "for", des "for" dans des "if". Bref, toutes les combinaisons possibles !

On peut utiliser tous les opérateurs de comparaison mathématiques, mais on les écrit de façon différente en langage C.

Les voici :

Opérateur symbole mathématique Syntaxe dans un programme
Plus grand x > y x > y
Plus grand ou égal x ≥ y x >= y
Plus petit x < y x < y
Plus petit ou égal x ≤ y x <= y
Différent x ≠ y x != y
Égal x = y x == y


Les bibliothèques.


Voici encore une nouveauté : les bibliothèques ("library" en anglais).
Dans certains projets Arduino, nous voulons utiliser certains composants (entrés ou sorties : capteurs ou effecteurs) plus complexes à programmer. Nous pourrions par exemple vouloir utiliser dans notre projet un servo-moteur, un écran LCD, un pHmètre, un capteur de fréquence cardiaque, un piano MIDI, une télécommande bref, divers composants périphériques qui rendent Arduino si versatile.
Heureusement pour nous, les fabricants de ces produits ainsi que la communauté de développeurs partout dans le monde mettent à notre disposition des "bibliothèques". C'est à dire un ensemble de procédures toutes faites, contenu dans un fichier séparé, qu'il nous suffira de déclarer dans notre programme pour faire fonctionner le matériel.
Voici la définition des bibliothèques données par la fondation Arduino :

L'environnement Arduino peut être amélioré à l'aide de bibliothèques, comme la plupart des plates-formes de programmation. Les bibliothèques fournissent des fonctionnalités supplémentaires à utiliser dans les programmes, par exemple pour travailler avec des périphériques électroniques ou manipuler des données. Pour utiliser une bibliothèque dans un programme, sélectionnez-la dans Croquis → Importer une bibliothèque. Un certain nombre de bibliothèques sont installées avec l'IDE, mais vous pouvez également télécharger ou créer les vôtres.

Parfait, la définition vous indique comment insérer une bibliothèque dans votre programme lorsque vous travaillez directement avec l'IDE Arduino (le logiciel Arduino).
Sur Tinkercad c'est légèrement différent. Voyons cela en images !



Pour insérer une bibliothèque dans un programme sur Tinkercad, il suffit de cliquer sur l'icône en forme de tiroir (encadré en rouge dans la capture d'écran). La liste des bibliothèques incluses par défault apparaît alors.
Remarquez sur l'image que de nombreuses bibliothèques sont incluses. Par exemple la bibliothèque "Servo" permettra de programmer un servo-moteur. La bibliothèque "LiquidCrystal" servira à programmer un écran à cristaux liquides."IRremote" permet d'utiliser une télécommande infrarouge (télécommande de TV par exemple).
Bref, les projets Arduino peuvent devenir de plus en plus fous grâce aux bibliothèques !

Chaque bibliothèque a ses spécificités, ses fonctions, ses façons de "parler" à Arduino. En général il faut consulter la documentation des bibliothèques pour savoir par exemple comment afficher du texte sur un ecran LCD, comment faire tourner un moteur, comment récupérer les information d'une télécommande.
Heureusement, les créateurs de bibliothèques fournissent toujours des exemples d'utilisation. Ils ne sont pas faciles à trouver dans Tinkercad, mais ils sont très accessibles dans le logiciel Arduino IDE



Cliquez sur "fichiers (file) → exemple (example). Un menu déroulant apparaît, vous pouvez y voir tous les exemple de programmes fournis dans le logiciel Arduino IDE. Dans le sous menu "Servo" par exemple, vous y trouverez des exemples de programmes permettant d'utiliser un servo-moteur grâce à la bibliothèque "Servo".

Justement, je vous propose ci-dessous une simulation qui permet de contrôler un servo-moteur grâce à un potentiomètre. Si vous tournez le potentiomètre dans un sens, le moteur tourne dans le même sens, si vous tournez dans l'autre sens, le moteur suit.
Essayez la simulation et surtout, lisez attentitevement le programme. Nous allons essayer de le comprendre ensemble.


Dans ce programme, il y a seulement 4 lignes de code que vous ne connaissez pas :
  1. Ligne #1 : #include <Servo.h> (au début du programme).
  2. Ligne #3 : Servo monMoteur; (au début du programme).
  3. Ligne #10 : monMoteur.attach (3); (dans le "void setup()").
  4. Ligne #18 : monMoteur.write (variable); (dans le "void loop()").
Mais je suis sur qu'en les lisant vous commencez à comprendre !

  • #include <Servo.h> : c'est la ligne de code qui apparaît toute seule lorsque vous choisissez d'inclure une bibliothèque. Elle dit au programme d'aller chercher un petit fichier (qui est caché dans le dossier d'Arduino) qui s'appelle "Servo.h".
  • Servo monMoteur; : on créé une instance de l'objet "servo" qui s'appelle "monMoteur". Qu'est-ce que c'est ? Ben en gros c'est un peu comme quand on déclare les variables : on dit ici au programme que dans le reste du programme le moteur s'appelera "monMoteur". J'aurai aussi pu écrire Servo poutine et mon moteur aurait eu "poutine" comme nom.
    Vous pouvez créer autant d'instances qu'il y a de moteurs. Si vous utilisez 2 moteurs, vous pourriez écrire :
    Servo moteur1;
    Servo moteur2;
    .
  • monMoteur.attach (3); : dans le "void setup()", c'est l'équivalent du pinMode();. Il faut bien que le programme sache sur quelle prise est branchée le moteur ! C'est comme ceci qu'on le lui indique. Ici on dit au programme que le moteur, qui s'appelle "monMoteur" est branché sur la prise 3.
  • monMoteur.write (variable); : c'est cette fonction qui indique au moteur de fonctionner. De la même façon que la fonction analogWrite(); ou digitalWrite();, la fonction monMoteur.write(); prend un argument entre parenthèse qui est la valeur, en degrès, de la rotation que doit effectuer le moteur.
    Par exemple, monMoteur.write(90); indique au moteur de tourner de 90°. monMoteur.write (variable); indique au moteur de tourner d'un angle qui correspond à la variable. Si cette variable = 720, le moteur fera 2 tours.
  • Également, si j'avais initialement appelé mon moteur "poutine" en écrivant Servo poutine; au début du programme, j'aurai du écrire poutine.attach(3); et poutine.write(variable); pour rester cohérant.


Résumé de la partie 5.


Tableau des fonctions apprises

Syntaxe Emplacement Utilisation Argument(s)
for (i = a; i * b; c) {d} loop() Exécute une action répétitive en utilisant un compteur i = variable à incrémenter
a = valeur de départ : initialisation
*= opérateur logique de comparaison: <, >, <=, >=, ==, !=
b = condition : valeur d'arrivée
c = incrémentation de la variable i
Exemple: for (i=0; i > 100; i=i+10) {blablablabla}
If (a * b) {c} loop() Exécute la partie de programme entre accolades {c} si et uniquement si la condition a*b est remplie a et b = variables à comparer
*= opérateur logique de comparaison: <, >, <=, >=, ==, !=
Exemple: if (valeurPhoto > seuil) {blablablabla}
#include <a.h> Inclus la bibliothèque "a.h" dans le programme ex : a = Servo
bibliothèque servo.h
Les fonctions suivantes sont spécifiques aux SERVO-MOTEURS
Servo a; Nomme une variable "servo" (ex : "a" = moteur)
a.attach (b); setup() Indique en argument la pin sur laquelle est branchée la variable "servo" a = nom de la variable "servo" (ex : "a" = moteur)
b = nom ou # de la prise
Exemple : moteur.attach (servo);
a.write (x); loop() Envoie la position, en degrès, que doit prendre le "servo" associé à la variable a a = variable servo (ex : a = moteur)
x = position en degrès, ou variable stockant cette position
Exemple : moteur.write (valeurPotentiometre)