Cours pour débuter dans la programmation d’une carte Arduino

Nicolas Le Boulaire

Dans ce cours, vous apprendrez les bases, pas à pas, dans la programmation et l’utilisation d’une carte Arduino à partir d’exemples concrets. Vue de l’extérieur, la carte Arduino est une boîte noire avec plein de lignes de codes pour la faire fonctionner : en pratique, vous vous rendrez compte qu’avec les quelques règles simples de base traitées dans ce cours, vous pourrez vous aussi vous imprégner de ce monde numérique qui nous tend les bras. À l’issue de ce cours, vous pourrez interagir avec le milieu extérieur, en récupérant des informations externes pour commander d’autres dispositifs.

Commentez

- Titre : Cours pour débuter dans la programmation d’une carte Arduino

- Auteur : Nicolas Le Boulaire

- Parution : 21 février 2020

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2020 Nicolas Le Boulaire. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Aperçu de la plateforme Arduino et ses cartes d'interface (shields)

Sommaire

- I. Arduino, qu’est-ce que c’est ?

- I-A. Un peu d’histoire

- I-B. À quoi cela peut-il me servir ?

- I-C. Comment ça fonctionne ?

- II. Se préparer à programmer une carte Arduino

- II-A. Les types de cartes

- II-B. Le logiciel de développement

- II-B-1. Petit arrêt pour les utilisateurs Linux

- II-B-2. Présentation succincte du logiciel

- II-C. Le matériel de base

- II-D. Tinkercad : un outil pour simuler

- II-D-1. Simulation d’un montage électrique

- II-D-2. Écrire un programme Arduino dans Tinkercad

- III. Principe d’un programme Arduino

- III-A. Les étapes d’un programme Arduino

- III-B. La fonction setup

- III-C. La fonction loop

- III-D. Application : faire clignoter une DEL

- III-D-1. Une DEL, c’est quoi ?

- III-D-2. Branchement d’une DEL dans un circuit

- III-D-3. Utilisation d’une platine d’essai ou bredboard

- III-D-4. Faisons clignoter la DEL

- III-D-5. Et si on gérait plusieurs DEL différentes ?

- IV. Interagir avec l’environnement

- IV-A. Ajout d’un bouton-poussoir

- IV-A-1. Branchement d’un bouton-poussoir

- IV-A-2. Lire l’état d’un bouton-poussoir

- IV-A-3. Anomalies avec un bouton-poussoir

- IV-A-4. Allons un peu plus loin avec un bouton-poussoir

- IV-B. Application : gérer un feu tricolore voiture avec passage piéton

- IV-C. Amélioration de la lisibilité du code d’un programme

- V. Récupérer des données extérieures à l’aide de capteurs

- V-A. Mesure d’une température analogique

- V-A-1. Principe de la mesure analogique

- V-A-2. Mesure d’une température avec un LM35

- V-A-3. Amélioration des mesures

- V-A-4. Conception d’une petite alarme en cas de chaleur

- V-B. Mesure d’une température numérique

- V-B-1. Utilisation de bibliothèques

- V-B-2. Mesure d’une température

- V-B-3. Mesure de plusieurs températures « simultanément »

- V-C. Application : fabrication d’une mini station météo

- V-C-1. Le capteur DTH11

- V-C-2. Un capteur de luminosité, la photorésistance

- V-C-3. Ajout d’une fonction pour automatiser une tâche

- V-C-4. Affichage à l’aide d’un écran

- V-C-5. Le programme pour la mini station météo

- VI. Les précautions pour éviter de détruire la carte Arduino voire plus

- VII. Conclusion

- VIII. Remerciements

- IX. Sitographie

- IX-A. Quelques sites utiles

- IX-B. Les enseignes spécialisées

I - Arduino, qu’est-ce que c’est ?

I-A - Un peu d’histoire

Arduino est le nom d’un bar d’une ville du nord de l’Italie : quel est donc le rapport avec la programmation ?

En fait, c’est dans ce bar que les cofondateurs (des étudiants italiens) du projet Arduino ont développé en 2005 une petite carte électronique programmable avec un logiciel dédié. Mais le petit grand plus de leur projet est de l’avoir développée sous la forme de matériel libre : autrement dit, tous les schémas et le code source sont disponibles gratuitement et peuvent être améliorés par chacun d’entre nous.

Autre atout non négligeable, la carte ne coûte pas très cher et permet donc d’équiper de nombreuses institutions (écoles par exemple) à moindres frais.

I-B - À quoi cela peut-il me servir ?

L’étendue des possibilités d’une carte Arduino est immense : la limitation est l’imagination du programmeur qui sommeille en chacun de nous (enfin presque…).

Une carte Arduino, comme on le verra par la suite, dispose d’entrées et de sorties. On peut donc l’utiliser pour récupérer des informations sur l’environnement (température, ensoleillement, humidité, radioactivité, tension…), mais également transmettre une information à un circuit ou un composant (commande d’un moteur, allumage d’une lampe…).

D’un point de vue plus pédagogique, l’utilisation d’une carte Arduino permet de s’initier à la physique par la pratique, voire de consolider ses acquis : on peut faire un nombre très important d’expériences pour comprendre et modéliser (soumettre le résultat pratique à la théorie) les lois physiques. Quelques idées : oscillations verticales d’un pendule élastique, tracé de la caractéristique d’une diode, suivi de température dans une barre calorifugée, principe de la conversion numérique, et tant d’autres ! Pour les chimistes, la carte Arduino va permettre de fabriquer son propre appareil de mesure (citons les plus courants, pHmètre et conductimètre) à un prix défiant toute concurrence avec des résultats tout à fait acceptables.

La grande force d’Arduino est d’être (dé)composable à l’infini : on peut y ajouter des cartes d’extension (appelées shields) qui permettront d’automatiser des tâches et donc faciliter leur utilisation. Les plus emblématiques de ces cartes concernent la connexion à des réseaux (Ethernet, Wi-Fi, Bluetooth) : vous pouvez consulter à ce sujet l’article ici.

Schématiquement, on peut résumer ainsi le fonctionnement d’un projet à base d’une carte Arduino :

I-C - Comment ça fonctionne ?

Le composant au cœur d’une carte Arduino est un microcontrôleur qui est en fait un circuit intégré composé d’un processeur, de mémoire (de données et programmable) et d’interfaces d’entrée-sortie pour communiquer avec l’utilisateur essentiellement. Il existe plusieurs types de cartes et chaque carte possède son microcontrôleur avec chacun ses propres caractéristiques.

Par exemple, le microcontrôleur d’une carte Arduino Uno Rev 3 est l’ATmega328P, fabriqué par Microchip :

Tension d’alimentation

1,8 V à 5,5 V

Mémoire Flash (programmable)

32 ko

Mémoire vive statique (SRAM)

2 ko

Mémoire morte (EEPROM)

1 ko

Entrées/sorties (GPIO)

23

Fréquence d’horloge

20 MHz

Nombre de timers

2 (8 bit), 1 (16 bit)

Nombre d’interruptions externes

24

On peut voir les microcontrôleurs comme des mini, voire nano, ordinateurs. Ils n'ont certes que des performances réduites et aucun système d’exploitation, mais leur petite taille et leur faible consommation énergétique en font des systèmes parfaitement adaptés à une utilisation dans des dispositifs embarqués.

En pratique, le seul microcontrôleur de l’Arduino n’est pas suffisant pour disposer d’un appareil facilement utilisable. Les développeurs ont donc agrémenté la carte de composants additionnels pour améliorer son ergonomie :

- quartz cadencé à 16 MHz ;

- un régulateur de tension permettant à la carte de fonctionner en 5 V (la carte peut être alimentée entre 6 et 20 V, mais en pratique il vaut mieux se réduire à une plage entre 7 et 12 V) ;

- une connexion USB pour alimenter facilement la carte et communiquer avec elle depuis un ordinateur PC ;

- une connexion jack pour alimenter à l’aide d’un source externe de tension (une pile 9 V par exemple fait très bien l’affaire) ;

- des connecteurs pour accéder aux broches du microcontrôleur ;

- un bouton RESET pour réinitialiser la carte.

Dernière information intéressante concernant une carte Arduino, elle possède des entrées et/ou sorties numériques (la carte Arduino UNO en possède 13) et des entrées analogiques (5 pour la carte UNO) : on va donc pouvoir récupérer des informations et en transmettre.

Si vous souhaitez plus d’informations sur la carte Arduino UNO, je vous renvoie à la description de f-leb sur sa page ici.

II - Se préparer à programmer une carte Arduino

II-A - Les types de cartes

Comme indiqué brièvement précédemment, plusieurs versions de cartes Arduino existent qui diffèrent par leurs caractéristiques techniques et leur prix. Citons-en quelques-unes :

- Uno : la plus répandue, ~19,50 € ;

- Leonardo : le processeur est plus puissant avec un nombre identique de broches. Le grand plus de cette carte est la présence d’un port USB, ~21,60 € ;

- Mega : version la plus populaire après la UNO, elle se différencie surtout par un nombre bien plus important de broches, ~42 € ;

- Due : encore plus puissante que la précédente. À la différence des précédentes, elle s’utilise en 3,3 V et non 5 V, ~40,80 € ;

- nano : se caractérise par sa petite taille donc très utile pour des projets miniatures. Ses caractéristiques sont légèrement supérieures à la carte UNO, par contre, il n’est pas possible de l’alimenter autrement que par la liaison USB, ~24 €.

Les prix indiqués correspondent à ceux recensés sur les produits officiels Arduino fin décembre 2019.

II-B - Le logiciel de développement

Une fois la carte en main, il faut pouvoir implémenter le code dans le microcontrôleur : pour ce faire, on utilise le logiciel de programmation dédié appelé également IDE (Integrated Development Environment ou dans la langue de Molière, environnement de développement intégré). Il permet d’avoir une interface pratique et agréable à utiliser.

Pour les utilisateurs plus expérimentés, il est possible de passer outre le logiciel IDE d’Arduino, mais ce processus ne sera pas traité ici. Si vous souhaitez en savoir plus à ce sujet, vous pouvez consulter l’article dédié sur la page officielle d’Arduino ici.

Pour le télécharger, il faut se rendre sur le site officiel d’Arduino : https://www.arduino.cc/en/main/software (la version à jour au moment de l’écriture est la 1.8.10). Après avoir choisi votre système, vous pouvez l’installer.

Pour les utilisateurs de Windows, je ne détaillerai pas la procédure qui reste classique.

II-B-1 - Petit arrêt pour les utilisateurs Linux

Pour les utilisateurs de Linux, la procédure mérite qu’on s’y attarde un peu, car par défaut l’utilisateur ne peut écrire ou lire sur le port série ce qui risque de poser problème dès les premiers programmes ! Pour cela il faut réaliser quelques opérations : après l’installation de l'IDE Arduino, branchez la carte Arduino sur un port USB. On va ensuite lister les périphériques disponibles :

procedure 1

$ls -l /dev/tty*

Selon le type de carte, on verra dans la liste affichée /dev/ttyACM0 ou /dev/ttyUSB0. Si l'Arduino est sur /dev/ttyUSB0 on lira dans la console quelque chose du genre :

$crw-rw---- 1 root dialout 188, 0 5 apr 23.01 ttyUSB0

Si vous déconnectez la carte Arduino après cette opération, la ligne correspondante ne sera plus affichée dans la liste lors d’une recherche ultérieure des périphériques disponibles. Pour que l'utilisateur $USER puisse accéder au port série, il faut l'inscrire dans le groupe dialout (attention commande en sudo) :

$sudo usermod -a -G dialout $USER

puis modifier les droits de lecture et d'écriture sur le port /dev/ttyUSB0 (commande en sudo)

$sudo chmod a+rw /dev/ttyUSB0

II-B-2 - Présentation succincte du logiciel

Au premier démarrage, le logiciel peut être un peu long à s’ouvrir : c’est normal, pas d’inquiétude.

Un petit conseil pour les personnes travaillant dans des environnements contraints (je pense en particulier au réseau informatique des lycées) : le plus simple est d’installer le logiciel sur une clé USB et de l’utiliser via cette clé. En plus de faciliter son utilisation (rapidité), c’est beaucoup plus commode par la suite lors des mises à jour ou lors de l’utilisation de bibliothèques (ce point étant détaillé au chapitre Utilisation de bibliothèques) même s’il est possible de passer outre le proxy dans les réglages de l’IDE d’Arduino Fichiers → Préférences → Onglet réseau.

Une fois la carte connectée, il faut vérifier qu’elle est bien reconnue : pour cela, un petit tour dans le menu Outil → Type de carte → sélectionner le type et dans Outil → Port→ si le type de carte n’est pas indiqué, il vous faudra installer manuellement le driver (c’est le cas le plus fréquent avec les modèles non officiels Arduino) en passant par le gestionnaire de périphériques.

La fenêtre du logiciel comporte quatre grandes parties :

- interaction avec l’utilisateur  : les icônes permettent dans l’ordre de Compiler, Téléverser, Créer un nouveau programme, Ouvrir un programme, Enregistrer le programme en cours ;

- corps du programme : là où vous rédigerez le code, vous remarquerez que des lignes de code sont déjà inscrites ;

- moniteur série  : pour afficher des données ou des résultats et transmettre des données textuelles grâce au moniteur série ;

- console de débogage où s’affichent notamment les messages d’erreur de votre programme lors de la compilation.

II-C - Le matériel de base

L’essentiel de ce cours est basé sur la pratique (bon, il est vrai que jusqu’ici, c’était surtout théorique) : rien ne vaut d’essayer soi-même pour comprendre comment cela fonctionne.

Pour tester les possibilités de ce cours, il vous suffira d’avoir :

- une carte Arduino : le modèle UNO est largement suffisant ;

- un câble USB de type AB (dans certains kits vendus, le câble fourni est très court, dans ce cas, vous pouvez emprunter le câble de votre imprimante) ;

- de DEL (Diodes ElectroLuminescentes) de différentes couleurs (rouge, vert, orange) ;

- quelques résistances (je devrais dire pour être plus précis des conducteurs ohmiques) de (j’expliquerai lors de leur utilisation le pourquoi de cette valeur) et de  ;

- un capteur de température LM35 ;

- un capteur de température DS18B20 ;

- un bouton-poussoir ;

- un capteur DTH11 ou DHT21 ou DHT22 (température et humidité) ;

- un buzzer ou un mini haut-parleur ;

- une platine d’expérimentation ou bredboard ;

- des câbles Dupont (mâle-mâle et mâle-femelle)

Vous pouvez trouver ce matériel auprès d’enseignes spécialisées (voir Les enseignes spécialisées) ou en achetant un kit pour débuter qui contient tout ce qu’il faut pour ce cours (et même un peu beaucoup plus) hormis la sonde de température numérique DS18B20 qu’il faudra peut-être acheter séparément.

Voilà, maintenant vous êtes équipé : il ne reste plus qu’à mettre les mains dans le cambouis.

II-D - Tinkercad : un outil pour simuler

La liste que j’ai fournie au point précédent est jolie, mais comment faire si je veux tester tout de suite maintenant ce cours si je ne dispose pas encore du matériel ? Et bien, la solution est d’utiliser un simulateur en ligne : Tinkercad est l’outil idéal (cliquez ici pour y accéder).

Il va vous permettre de simuler les montages électriques, d’écrire le programme Arduino comme si vous utilisiez le logiciel dédié et surtout de tester en conditions quasi réelles. Le seul petit travail à faire au préalable est de s’inscrire. Une fois ceci fait, il faut sélectionner Circuit dans le bandeau latéral de gauche

puis de sélectionner Créer un nouveau circuit.

Tinkercad nomme les projets par des noms peu communs : je vous conseille de renommer vos projets directement pour ne pas vous y perdre.

Tinkercad ne permet pas de réaliser tous les schémas de ce cours, car certains composants ne sont pas disponibles, mais il vous fournira déjà une solide base.

II-D-1 - Simulation d’un montage électrique

Dans la fenêtre qui s’ouvre, on trouve à droite tous les composants, cartes Arduino, capteurs utilisables dans la simulation. Pour ajouter un élément au circuit, il suffit de le sélectionner en cliquant dessus (on peut faire une recherche par nom) puis de déplacer la souris jusqu’à la position souhaitée et de cliquer pour valider l’emplacement : voilà, le composant est ajouté au circuit.

Certains composants possèdent des caractéristiques propres : lorsqu’ils sont positionnés, une boîte de dialogue propose des valeurs par défaut. Par exemple, on peut changer la valeur d’une résistance (les habitués remarqueront que le code couleur de la résistance est directement mis à jour), choisir la couleur d’une LED (c’est l’acronyme anglais d’une DEL) ou d’un câble.

Ensuite, pour relier les composants entre eux, il est nécessaire de rajouter des câbles : pour cela, il suffit de cliquer sur une borne d’un composant (ou un trou de la platine d’essai – bredboard) puis de cliquer jusqu’à l’autre extrémité de branchement du câble.

Si on a besoin de tourner un composant, on utilise le bouton  : on peut alors facilement le placer sur la platine d’essai.

II-D-2 - Écrire un programme Arduino dans Tinkercad

Je ne détaillerai pas ici le principe d’un programme Arduino, mais seulement la méthode pour accéder au logiciel de programmation intégré à Tinkercad (je laisse un peu de suspens…)

On rentre dans le « logiciel » en cliquant sur Code : si aucun composant pouvant être programmé n’est présent dans la fenêtre, on obtient un message d’erreur.

Le logiciel permet de programmer par Blocs (par défaut), par Blocs+Texte ou par Texte.

Pour être utilisable par la suite dans le logiciel Arduino, il faut sélectionner Texte (cliquer sur Continuer lorsque la boîte de dialogue « Êtes-vous sûr ? » apparaît). La programmation par Blocs a l’avantage d’être plus visuelle, mais le code ne pourra pas être copié dans le logiciel Arduino lorsque vous souhaiterez passer à un test en conditions réelles avec du vrai matériel.

Vous remarquerez au passage que par défaut, un programme est déjà présent dans le logiciel : il s’agit du programme permettant de faire clignoter une DEL, mais on anticipe déjà sur la suite, je ne m’y attarderai pas encore.

Une fois le programme écrit, il suffit d’appuyer sur Démarrer la simulation : on voit sur le circuit électrique la prise USB se connecter à la carte et le programme est lancé.

Pour en terminer avec la présentation rapide de Tinkercad, j’ajouterai un point qui servira pour la première partie pratique (La fonction setup) : pour accéder au moniteur série, on clique sur .

III - Principe d’un programme Arduino

III-A - Les étapes d’un programme Arduino

Pour mettre en œuvre une carte Arduino, il faut suivre les étapes suivantes :

- écrire un programme : pour les plus expérimentés en programmation, le langage Arduino est proche du C et du C + + ;

- compiler le programme : il s’agit en fait de transformer le code « humain » saisi par l’utilisateur en langage « machine » c’est-à-dire une succession de 0 et de 1. L’ordinateur va alors vérifier la syntaxe du code ;

- téléverser le programme : c’est-à-dire transférer le programme sur la carte pour qu’il soit mis en route.

Pour bien comprendre la notion de compilation, je vais faire une comparaison avec un texte écrit dans un logiciel de traitement de texte.

Une fois le texte saisi, j’utilise le correcteur orthographique et grammatical qui va vérifier si je n’ai pas fait de fautes d’orthographe et si ma phrase est grammaticalement correcte (début par une majuscule, pas de double espace entre deux mots…).

Prenons par exemple les trois phrases suivantes :

- « j’adore cet excellent cours qui m’apprend comment débuter avec Arduino » ;

- « J’adore excellent cours m’apprend débuter avec Arduino. » ;

- « J’adore cet excellent cours qui m’apprend comment débuter avec Arduino. ».

La première me renverra une erreur, car la phrase ne débute pas par une majuscule même si le sens de la phrase est correct → en comparaison la compilation renverra une erreur, le programme ne pourra pas être téléversé sur la carte.

La seconde ne renverra aucune erreur, car la syntaxe est correcte. Toutefois, le logiciel est incapable de dire que la phrase ne veut rien dire, car il manque certains mots (bon même si là, il est vrai qu’on arrive quand même à en deviner le sens) → en comparaison, la compilation fonctionnera, mais le programme ne fera pas ce qu’on aimerait qu’il fasse.

La dernière ne renverra aucune erreur et est parfaitement compréhensible → la compilation fonctionnera et le programme fera ce qu’on lui dit de faire.

J’attire donc l’attention sur le fait que ce n’est pas parce qu’un programme a pu être téléversé sur la carte qu’il va fonctionner comme l’utilisateur souhaite qu’il fonctionne : pour cela, rien ne vaut la pratique !

De même, certaines perturbations matérielles peuvent rendre le programme inopérant alors que le montage et le programme sont corrects (par exemple, le cas des rebonds lors de l’utilisation de bouton-poussoir qui est évoqué au paragraphe Anomalies avec un bouton-poussoir).

III-B - La fonction setup

Pour comprendre à quoi correspond la partie setup() d’un programme Arduino, je vous propose de passer à la pratique.

Dans le logiciel d’Arduino, écrivez ces quelques lignes de code (vous pouvez également faire un copier-coller, mais c’est un peu moins parlant et ne vous permettra pas d’apprivoiser le principe du code) :

fonction setup

1. void setup() {

2. Serial.begin(9600);

3. Serial.print("Enfin mon premier ");

4. Serial.print("programme Arduino !");

5. }

7. void loop() {

9. }

Après compilation puis téléversement, on va voir dans le moniteur série ce qu’il se passe en cliquant sur , vous devriez obtenir un écran similaire à celui-ci :

Si le texte à l’écran comporte une suite de caractères étranges, comme ceux-là :

il faut changer la vitesse de communication entre le PC et le moniteur série. Pour cela, il faut se rendre tout en bas dans le moniteur série et modifier le nombre de bauds  : dans notre programme, il est indiqué 9600 donc il faut sélectionner la vitesse de 9600 bauds dans le menu déroulant central.

Décryptons maintenant le code pour voir comment il a pu afficher ce résultat.

Ligne 2 : on a une commande Serial.begin(9600) : cette ligne permet au moniteur série (d’où le Serial) de se préparer (begin) à recevoir ou transmettre des informations à la vitesse de 9600 bauds (le baud correspond au nombre de changements par seconde du signal émis). Dis autrement, c’est une ligne d’initialisation de la communication avec le moniteur série.

Ligne 3 : on affiche (print) sur le moniteur série (Serial) une chaîne de caractères (un texte) que l’on doit noter entre guillemets.

Ligne 4 : comme la ligne 3, on affiche sur le moniteur série une chaîne de caractères.

En termes de syntaxe générale du programme, vous remarquerez que la syntaxe d’écriture impose un point-virgule à la fin de chaque instruction.

Modifiez légèrement le programme précédent et téléversez-le sur votre carte Arduino.

setup println

1. void setup() {

2. Serial.begin(9600);

3. Serial.print("Enfin mon premier ");

4. Serial.println("programme Arduino !");

5. }

7. void loop() {

8. }

Seule la ligne 4 est modifiée avec Serial.println() au lieu de Serial.print(). Normalement, si tout s’est bien passé, vous devriez obtenir l’écran suivant :

Par rapport au premier programme, on voit qu’après avoir affiché le texte « Enfin mon premier » sur la 1re ligne, la seconde partie du texte (« programme Arduino ! ») s’affiche sur la ligne suivante.

Ainsi, si on veut afficher une suite de texte sur la même ligne, on utilisera la commande Serial.print() ; par contre, si on souhaite les afficher sur des lignes différentes alors on utilisera Serial.println() (le retour à la ligne avec la commande Serial.println() se faisant après le texte).

III-C - La fonction loop

Poursuivons notre exploration des commandes d’un programme Arduino en voyant ce que fait le bout de programme lié à la fonction loop().

fonction loop

1. void setup() {

2. Serial.begin(9600);

3. }

5. void loop() {

6. Serial.print("Enfin mon premier ");

7. Serial.println("programme Arduino !");

8. }

Les commandes utilisées sont les mêmes que précédemment donc pas besoin d’y revenir : si vous ne les avez pas comprises, dans ce cas, relisez le point précédent !

Une fois le programme écrit, compilé et téléversé, vous devriez obtenir dans le moniteur série une suite infinie des chaînes de caractères « Enfin mon premier programme Arduino ! » :

La partie loop() d’un programme se fait en boucle tant que la carte est alimentée à la différence de la partie setup() qui n’est exécutée qu’une seule fois.

Modifiez légèrement le programme précédent :

fonction loop

1. void setup() {

2. Serial.begin(9600);

3. }

5. void loop() {

6. Serial.print("Enfin mon premier");

7. Serial.println("programme Arduino !");

8. delay(2000);

9. }

Vous devriez obtenir à l’écran la même suite infinie des chaînes de caractères, mais avec une pause entre chaque affichage de ligne.

En comparant avec le programme précédent, on voit qu’une nouvelle commande a été ajoutée :

Ligne 8 : la commande delay() qui permet d’ajouter une pause dans le programme, le délai de pause doit être indiqué en millisecondes ainsi delay(2000) ajoute une pause de 2000 millisecondes soit 2 secondes.

Déjà, avec les commandes déjà vues, on est capable de créer une interface avec l’utilisateur, qui sera complétée dans les paragraphes prochains par des capteurs : ainsi, on peut faire afficher du texte ligne par ligne avec Serial.println() (c’est quand même plus lisible que d’avoir le texte à la suite sur une même ligne), faire des mesures en boucle (fonction loop()) ou uniques (fonction setup()), cadencer le rythme des mesures (commande delay()).

III-D - Application : faire clignoter une DEL

Jusqu’ici, on a testé quelques commandes avec du texte : certes, cela a permis de donner les bases de la programmation, mais cela limite les possibilités. Il est grand temps de passer à l’étape supérieure et d’utiliser la carte Arduino comme une carte programmable. L’objectif de cette partie va être de faire clignoter une DEL.

III-D-1 - Une DEL, c’est quoi ?

Une DEL est l’acronyme de Diode ElectroLuminescente (en anglais LED pour Light Emitting Diode). Comme toute diode, elle ne laisse passer le courant que dans un seul sens (cette phrase est à nuancer pour quelques diodes particulières) comme en témoigne son symbole ci-dessous :

La DEL a un truc en plus que les autres diodes : elle émet de la lumière lorsqu’elle est parcourue par un courant ce qui en fait un des composants à la mode, car elle ne consomme pas grand-chose et peut être très lumineuse. On retrouve cette particularité sur son schéma normalisé avec les flèches qui symbolisent l’émission de lumière.

Pour connaître la couleur d’une DEL, il faut se méfier du boîtier : ce n’est pas parce qu’il est transparent que la DEL s’illuminera en blanc. Certaines DEL, généralement bas de gamme, sont en fait des DEL blanches dont le boîtier est coloré donnant l’impression de la couleur. À l’inverse, la plupart des DEL plus puissantes et de meilleure qualité disposent d’un boîtier transparent, mais illuminent bien avec une couleur une fois branchée (dans le bon sens évidemment !)

III-D-2 - Branchement d’une DEL dans un circuit

Comme on l’a vu au point précédent, le courant doit circuler dans un certain sens pour que la DEL s’allume : comment faire pour savoir dans quel sens je dois la brancher ? Pour cela, deux méthodes sont possibles :

- la patte la plus longue est à l’origine la patte représentant le plus : il faut nuancer ce propos, car un petit malin a pu couper les pattes de la DEL auparavant…

- regarder le méplat de la DEL (quand on regarde du dessus une DEL, on voit qu’elle n’est pas complètement circulaire et dispose d’un côté plat) qui correspond au pôle moins :

En électricité, la plupart du temps, pour faire circuler un courant électrique, il faut imposer une certaine tension (ou différence de potentiel) aux bornes des composants. Pour être plus clair, il faut que la tension du côté du plus de la DEL soit plus grande que la tension du côté du moins : en pratique, on branchera donc la patte la plus longue de la DEL vers le 5 V délivré par Arduino tandis que la petite patte sera du côté du 0 V (ou GND).

Pour les amateurs de physique, précisons que pour une diode (donc également pour une DEL), il ne suffit pas d’avoir une différence de potentiel à ses bornes pour qu’elle conduise le courant : il faut avoir une valeur au moins égale à la tension seuil de la diode (valeur qui dépend du type de diode).

Mais ce n’est pas tout : comme tout composant, les caractéristiques électriques d’une DEL ne sont pas infinies et il faut donc respecter une fourchette de valeurs pour la tension et le courant sous peine de la voir griller. Pour les non-initiés à l’électronique, ces caractéristiques se trouvent dans la DATASHEET du composant :

Pour ce qui nous intéresse, les informations les plus intéressantes sont :

- Peak wavelenght : donne la longueur d’onde (donc la couleur) de la DEL ;

- Forward Voltage : la tension minimum à imposer aux bornes de la DEL pour qu’un courant « significatif » la traverse en d’autres termes, pour qu’elle éclaire ;

- Test Conditions : dans les conditions normales d’utilisation, le courant usuel de fonctionnement vaut IF.

On voit donc que, dans le cas de la DEL citée en exemple, elle fonctionne dans les conditions normales lorsqu’elle est parcourue par un courant d’intensité IF=20 mA et avec une tension à ses bornes de 1,65 V : elle émet alors un rayonnement lumineux de longueur d’onde 660 nm correspondant à une couleur rouge.

On a donc tout ce qu’il faut pour faire fonctionner notre DEL : on la branche sur Arduino et puis voilà. Sauf que, ce n’est pas aussi simple. Si on reprend les caractéristiques de notre carte Arduino, elle délivre grâce à son régulateur une tension de 5 V : c’est une bonne nouvelle, notre DEL a besoin d’une tension de 1,65 V. Mais en la faisant fonctionner avec une valeur trop élevée, elle risque de ne pas aimer du tout ! Pour vous en convaincre, vous pouvez essayer dans Tinkercad (surtout pas avec votre vraie carte Arduino !) de brancher une DEL entre l’entrée 5 V (côté anode, la plus longue broche de la DEL) et le GND (côté cathode, la plus courte broche) : en démarrant la simulation, on voit tout de suite que la DEL n’apprécie pas vraiment :

Il faut donc ajouter dans le circuit un composant dont le rôle va être d’absorber ce surplus, et éviter tout risque à notre DEL, ce composant c’est la résistance. La DEL va donc prendre ce dont elle a besoin puis laisser le reste à la résistance qui va l’évacuer sous forme de chaleur, c’est ce qu’on appelle le réchauffement par effet Joule.

La question qui vient donc maintenant, c’est comment je dois choisir la résistance pour mon circuit ? Pour cela, il faut revenir à deux relations importantes en électricité : la loi d’Ohm et la loi d’additivité des tensions (appelée également loi des mailles).

Pour la seconde, c’est une simple question de répartition des tensions : dans une boucle du circuit électrique, la tension se répartit (pas forcément équitablement) aux bornes des différents composants de la boucle. On peut schématiser la boucle comme ceci

Dans le cas de notre circuit, la tension délivrée par la carte (5 V) va se répartir entre la DEL et la résistance : comme on a vu que la tension aux bornes de la DEL doit valoir 1,65 V, il faut donc que la résistance ait à ses bornes une tension de 5-1,65=3,35 V.

il faut ensuite utiliser la fameuse loi d’Ohm, bourreau de la plupart des collégiens. Cette loi indique que pour une résistance, la tension U à ses bornes est proportionnelle à l’intensité I du courant électrique qui la traverse ce qui se traduit par la formule , R étant le facteur de proportionnalité qui s’exprime en Ohms W.

On connaît la tension aux bornes de la résistance (3,35 V) et on souhaite que l’intensité soit de 20 mA → on calcule donc à partir de la loi d’Ohm que la résistance doit valoir . Et voilà, la boucle est bouclée, enfin presque parce qu’en pratique, pas facile d’avoir une telle résistance. Les valeurs des résistances sont normalisées et seules certaines sont disponibles : qu’à cela ne tienne, on prendra donc la valeur la plus proche supérieure (si on prend une valeur inférieure, on risque d’avoir un courant trop important qui circule et donc un risque pour la DEL et la carte Arduino de griller) → dans la gamme des résistances, la valeur normalisée juste au-dessus est la  : ce qui est pile-poil celle que je vous conseillais dans le matériel à prévoir, magique ! Si vous avez un doute, il vaut mieux prendre une valeur plus élevée que trop petite : la luminosité de la DEL sera plus faible qu’attendue (car l’intensité du courant électrique sera moindre), mais cela évitera de détériorer les broches de la carte (40 mA maximum par entrée/sortie digitale) et la DEL.

J’ai raisonné avec la tension pour justifier la présence d’une résistance dans le circuit, mais pour être plus juste, c’est plutôt à cause de l’intensité qui y circule qu’il y en a besoin.

Les valeurs de tension et d’intensité peuvent être légèrement différentes selon la couleur de la DEL, sa qualité. En pratique, on pourra donc prendre des résistances entre et sans risque de détériorer la LED ou de pâtir d’une trop faible luminosité.

III-D-3 - Utilisation d’une platine d’essai ou bredboard

Pour faire les premiers essais, il est plus pratique d’utiliser une platine d’essai pour disposer les composants et les connecter plus facilement. Cependant, avant de les utiliser, il est nécessaire de connaître le principe de branchement pour éviter soit de griller un composant ou la carte (court-circuit) soit de passer des heures à comprendre le problème du programme alors qu’il s’agit simplement d’un décalage du branchement sur la platine d’essai !

Plusieurs modèles existent : je vais présenter ici une petite carte largement suffisante pour faire ce dont on a besoin. Bien évidemment, pour des montages plus grands, il faudra adapter la taille de la platine d’essai en conséquence.

J’ai encadré quelques groupes de bornes qui sont reliées électriquement : on voit donc qu’une platine d’essai contient deux types de bornes reliées :

- trous en colonne par groupe de 5 : pour la petite platine de l’exemple, on dispose donc de 30x2 groupes de 5 trous ;

- trous reliés en ligne (sur les bords inférieurs et supérieurs de la carte) : sur une même ligne, tous les trous sont reliés entre eux. Les lignes sont utilisées usuellement pour créer une ligne à 5 V (beaucoup de composants nécessitent d’être alimentés en 5 V) et une ligne à 0 V (le GND est également très présent dans les montages).

Certaines platines ne disposent pas de lignes reliées électriquement, mais uniquement des colonnes par groupe de 5.

Désormais, plus de raison de faire une fausse manipulation avec la platine d’essai !

III-D-4 - Faisons clignoter la DEL

C’est bon, on est prêt : on a vu tout ce qu’il fallait pour faire fonctionner la DEL sans risque de la détériorer.

Comment faire pour faire clignoter une DEL ? Tout simplement, en alternant des moments où le courant va la traverser (DEL allumée) et des moments où aucun courant ne passe (DEL éteinte). Dis autrement, on va alterner des moments pendant lesquels on va imposer une tension différente entre les bornes du circuit (DEL allumée) et des moments pendant lesquels la tension sera la même sur chaque patte (DEL éteinte) : une petite pause entre chaque état permettra de faire clignoter plus ou moins rapidement la DEL.

Le branchement est celui-là :

Vous remarquerez que j’ai bien utilisé une résistance pour protéger la DEL.

Dans le logiciel d’Arduino, on saisit (ou copie) le code suivant :

clignotement DEL

1. void setup()

2. {

3. pinMode(12, OUTPUT);

4. }

6. void loop()

7. {

8. digitalWrite(12, HIGH);

9. delay(1000);

10. digitalWrite(12, LOW);

11. delay(1000);

12. }

Si vous le compilez puis l’exécutez (et que vos branchements sont corrects !), vous devriez voir votre DEL qui s’allume pendant une seconde, s’éteint une seconde et ceci à l’infini !

Regardons plus en détail le code pour voir ce qu’il s’y passe.

Ligne 3 : on indique à la carte que la broche 6 est en mode (pinMode) sortie (OUTPUT) ce qui signifie qu’elle se comporte comme une sortie et pourra donc faire circuler un courant (dans la description de la carte, voir le schéma Comment ça fonctionne ?, j’ai indiqué que les broches numériques peuvent être des entrées ou des sorties, il faut donc préciser dans le programme si on utilise la broche comme une entrée ou une sortie).

Ligne 8 : on indique à la carte que la broche numérique (digital) 6 doit être mise au niveau HAUT (HIGH) → en fait cela signifie que la borne 6 est soumise à une tension de 5 V.
On a donc le côté plus de la DEL qui est à 5 V, le côté moins qui est à 0 V (GND) donc un courant peut circuler : la DEL s’allume.

Ligne 9 : on fait une pause de 1000 ms (1 seconde), temps pendant lequel la DEL reste allumée.

Ligne 10 : on indique à la carte que la broche numérique (digital) 6 doit être mise au niveau BAS (LOW) → en fait cela signifie que la borne 6 est soumise à une tension de 0 V.
On a donc le côté plus de la DEL qui est à 0 V, le côté moins qui est également à 0 V (GND) donc aucune différence de tension entre les bornes de la DEL→ aucun courant ne peut circuler : la DEL s’éteint.

Ligne 11 : on fait une pause de 1000 ms (1 seconde) temps pendant lequel la DEL reste éteinte.

Comme cet enchaînement d’états de la diode est dans la partie loop() du programme, le clignotement continue tant qu’on ne coupe pas l’alimentation de la carte Arduino.

III-D-5 - Et si on gérait plusieurs DEL différentes ?

En guise d’exercice, je vous propose un classique du genre : simuler un feu tricolore.

Le feu doit suivre la séquence suivante : feu vert allumé pendant 3 secondes, feu orange allumé pendant 1 seconde, feu rouge allumé pendant 3 secondes.

N’hésitez pas à chercher par vous-même, sans aller voir la réponse directement en dessous ou sur la Toile : le plaisir serait gâché. Si vous avez peur de faire une bêtise et de griller votre carte, simulez le tout en ligne via Tinkercad.

Pour vous aider, je vous joins le schéma électrique :

Et la solution se trouve juste en dessous :

feu tricolore

1. void setup()

2. {

4. //initialisation des modes

5. pinMode(10, OUTPUT);

6. pinMode(11, OUTPUT);

7. pinMode(12, OUTPUT);

9. }

10. void loop()

11. {

12. digitalWrite(10, HIGH);

13. digitalWrite(11, LOW);

14. digitalWrite(12, LOW);

15. delay(3000);

17. digitalWrite(10, LOW);

18. digitalWrite(11, HIGH);

19. digitalWrite(12, LOW);

20. delay(1000);

22. digitalWrite(10, LOW);

23. digitalWrite(11, LOW);

24. digitalWrite(12, HIGH);

25. delay(1000);

27. }

Le code proposé est volontairement non optimisé et pourrait être raccourci, mais il est encore trop tôt pour s’aventurer dans cette voie-là à ce stade.

IV - Interagir avec l’environnement

IV-A - Ajout d’un bouton-poussoir

IV-A-1 - Branchement d’un bouton-poussoir

Un bouton-poussoir est un dipôle c’est-à-dire qu’il n’a besoin que de deux pattes pour être branché dans un circuit. En électronique, pour des raisons de commodité, les boutons-poussoirs disposent de quatre pattes qui sont reliées deux à deux.

Les broches 1 et 2 sont reliées ensemble de même que les broches 3 et 4 (voir le schéma en bas à droite).

Lorsque je branche un bouton-poussoir sur une platine d’essai, il y a deux possibilités de connexion :

1er cas (schéma de gauche) : les pattes « 2 à 2 » sont reliées dans une même colonne de la platine → le courant ne circule donc des bornes 1/2 aux bornes 3/4 que si on appuie sur le bouton → c’est donc le bon sens de branchement

2e cas (schéma de droite) : les pattes « 2 à 2 » sont reliées dans deux colonnes différentes → le courant circule donc toujours des bornes 1/2 aux bornes 3/4 dans le bouton-poussoir, qu’il soit appuyé ou non !

Fondamentalement, il y a une chance sur deux pour que vous soyez dans le cas n° 2 et évidemment, c’est toujours sur ce branchement que vous tombez lors du premier essai ! Une astuce pour connaître le sens des broches d’un bouton-poussoir est de câbler le montage suivant :

Si la DEL s’allume en permanence, sans appuyer sur le bouton-poussoir alors vous êtes dans le cas n° 2 pour le branchement du bouton→ il suffit de tourner le bouton-poussoir d’un quart de tour pour retrouver un fonctionnement normal.

Et si la DEL ne s’allume qu’en appuyant sur le bouton-poussoir, félicitations, vous avez trouvé le bon branchement !

À l’avenir, pour éviter des erreurs (et des angoisses à essayer de trouver d’où vient l’erreur dans votre montage !), je vous conseille de noter avec un code couleur (ou tout autre signe distinctif) les pattes reliées 2 à 2.

Si le sens de branchement vous importe peu, mais que vous souhaitez être sûr du branchement du bouton poussoir, il suffit de le connecter à cheval sur la zone centrale de la platine d’essais et d’utiliser les deux pattes opposées en diagonale comme sur le schéma ci-dessous :

Dans tous les montages de ce tutoriel, j’ai utilisé des boutons-poussoirs de type ON-(OFF) c’est-à-dire qu’ils sont normalement en position ouverte (le courant ne peut donc pas circuler) et ne passent en position fermée (circulation du courant possible) que lorsqu’on garde appuyé le bouton. Ils ne fonctionnent qu’en appui momentané, car une fois relâché, le contact est rompu et le bouton-poussoir revient en position ouverte.

IV-A-2 - Lire l’état d’un bouton-poussoir

L’objectif ici est de comprendre comment on peut programmer la carte Arduino pour lire si un bouton-poussoir est appuyé ou non.

Dans un premier temps, téléverser le programme suivant et réaliser le montage correspondant :

bouton-poussoir

1. void setup()

2. {

3. Serial.begin(9600);

4. pinMode(2, INPUT);

5. }

6. void loop()

7. {

8. int etatBouton = digitalRead(2);

9. Serial.println(etatBouton);

10. }

En ouvrant le moniteur série, vous devriez obtenir le chiffre 0 quand le bouton-poussoir est appuyé. Lorsqu’il n’est pas appuyé, l’affichage est aléatoire entre des 0 et des 1 : ce qui peut apparaître comme une anomalie dans le montage réel (pas si vous utilisez Tinkercad) sera abordé dans le point suivant. On voit ici une limite de la simulation : rien ne vaut la « vraie »pratique !

Explicitons le code :

Ligne 3 : on démarre la liaison avec le moniteur série pour l’affichage de l’état du bouton ;

Ligne 4 : on indique à la carte que la broche 2 est une entrée (INPUT), elle va donc recevoir une information ;

Ligne 8 : on créée une variable etatBouton de type entière (integer) qui va contenir l’état de la broche numérique (digitalRead) 2. La commande digitalRead() ne renvoie que des 0 et des 1 : 0 quand le courant ne peut pas circuler (bouton-poussoir non appuyé) et 1 quand le courant circule (bouton-poussoir appuyé) ;

Ligne 9 : on affiche avec un saut de ligne à chaque fois (Serial.println()) le résultat stocké dans la variable etatBouton.

IV-A-3 - Anomalies avec un bouton-poussoir

Je reviens ici sur les « anomalies » constatées avec le programme précédent, lorsque le bouton-poussoir n’est pas appuyé. Pour les comprendre, le mieux est de schématiser le montage comme ci-dessous par exemple :

Lorsque le bouton-poussoir est appuyé, l’état électrique de la broche est bien défini et vaut 0 V ; par contre lorsque le bouton-poussoir n’est pas (complètement) appuyé, l’état électrique de la broche est mal défini, car la broche est « en l’air » et pourra donc aléatoirement être interprétée comme une valeur HIGH ou une valeur LOW.

La solution consiste à rajouter une résistance entre le 5 V de la carte et la broche de mesure (2 dans notre cas) : on parle de « tirage vers le haut» (montage pull-up) :

Lorsque le bouton n’est pas appuyé : la broche est reliée au 5 V de la carte via la résistance ; lorsque le bouton est appuyé, la broche est reliée au GND. L’état électrique de la broche est donc toujours bien défini et pourra être correctement interprété.

Je ne détaillerai pas le calcul de la résistance du montage pull-up. Si cela vous intéresse, vous pouvez consulter la discussion sur le forum qui l’explique Commentez .

Il est également possible de résoudre ce problème avec un tirage vers le bas (pull-down) en intercalant une résistance entre le GND et la broche. La seule différence est que la logique est inversée : quand le bouton est appuyé, la broche est reliée au 5 V, quand il ne l’est pas, elle est reliée au GND via la résistance.

En réalité, on n’est pas obligé de rajouter une résistance en paramétrant la carte de manière différente. En effet, le microcontrôleur contient des résistances de pull-up internes (de ) qu’il faut activer au préalable. Dans les exemples de ce cours, on n’utilisera pas cette fonctionnalité.

IV-A-4 - Allons un peu plus loin avec un bouton-poussoir

Jusque-là, on a vu uniquement la partie « récupération de données » or comme je l’ai évoqué au point À quoi cela peut-il me servir ?, l’intérêt est de pouvoir agir ensuite sur l’environnement. Le cas pratique précédent n’était pas très visuel : on appuyait ou non sur le bouton-poussoir et on regardait simplement une suite de 0 et de 1 s’afficher sur le moniteur série.

Pour rendre plus visuel l’état du bouton-poussoir et commencer à faire des programmes plus poussés, j’ajoute une contrainte supplémentaire : si le bouton-poussoir est appuyé alors je vais allumer une DEL sinon elle restera éteinte. Le rendu sera le même que pour le montage avec en série la DEL et le bouton-poussoir (voir au début du point Branchement d’un bouton-poussoir), mais le principe du montage est complètement différent. Dans le premier cas, la carte Arduino ne sert que de source d’alimentation (aucune programmation) et pourrait très bien être remplacée par un générateur continu 5 V ; dans le second cas, l’allumage de la LED est programmé pour s’allumer lorsque le microcontrôleur détecte un appui sur le bouton.

Le schéma du montage à réaliser est le suivant :

Vous remarquerez que j’ai ajouté une résistance de pull-down pour éviter les anomalies électriques.

Le code à téléverser sur la carte Arduino est le suivant :

bouton-poussoir DEL

1. void setup()

2. {

3. pinMode(6, OUTPUT);

4. pinMode(2, INPUT);

5. }

7. void loop()

8. {

9. int etatBouton = digitalRead(2);

10. if (etatBouton == HIGH)

11. {

12. digitalWrite(6, HIGH);

13. }

14. else

15. {

16. digitalWrite(6, LOW);

17. }

18. }

Si tout se passe bien, la DEL devrait s’allumer lorsque vous pressez le bouton-poussoir.

Essayons de voir ce qui se passe en termes de programmation.

Ligne 3 : on indique à la carte que la broche 6 est une sortie (OUTPUT), elle va donc pouvoir faire circuler un courant électrique → cela correspond à la broche de branchement de la DEL.

Ligne 4 : on indique à la carte que la broche 2 est une entrée (INPUT), elle va donc recevoir une information, celle liée au bouton-poussoir.

Ligne 9 : on créée une variable etatBouton de type int (integer) qui va contenir l’état de la broche numérique (digitalRead) 2. La commande digitalRead() ne renvoie que des 0 et des 1 : 0 quand le courant ne peut pas circuler (bouton-poussoir non appuyé) et 1 quand le courant circule (bouton-poussoir appuyé).

Ligne 10 : une nouvelle instruction if qui en bon français signifie si : on teste si la variable etatBouton vaut HIGH (correspondant au bouton appuyé)→ si c’est le cas, alors on exécute toutes les commandes indiquées entre les accolades {} qui suivent : dans notre programme (ligne 12), on indique à la carte que la broche numérique (digital) 6 doit être mise au niveau HAUT (HIGH) → la DEL est parcourue par un courant et va donc s’allumer.

Si le test est faux (donc si la variable etatBouton vaut LOW), on exécute les commandes indiquées entre les accolades qui suivent l’instruction else : dans notre programme (ligne 16), on indique à la carte que la broche numérique (digital) 6 doit être mise au niveau BAS (LOW) → la DEL n’est parcourue par aucun courant et va donc être éteinte.

Lors d’un test (ligne 9 du programme) qui cherche à comparer une valeur à un nombre (1 ici), il faut bien mettre un double signe égal sinon cela ne fonctionnera pas. L’explication sera donnée au point Amélioration de la lisibilité du code d’un programme.

Le test effectué dans le if doit se trouver entre parenthèses.

IV-B - Application : gérer un feu tricolore voiture avec passage piéton

L’objectif va être de simuler un feu tricolore pour voiture avec un bouton-poussoir qui commande un feu bicolore pour les piétons.

En fonctionnement normal, la séquence du feu tricolore reprend celle du point Et si on gérait plusieurs DEL différentes ? à savoir feu vert pendant 3 secondes, feu orange 1 seconde, feu rouge 3 secondes avec pendant toute cette séquence le feu pour piéton au rouge. Si l’utilisateur appuie sur le bouton-poussoir au moment de l’extinction du feu vert alors le feu tricolore passe à l'orange (1 seconde) puis au rouge (5 secondes), le feu piéton passant au vert quand le feu tricolore passe au rouge : après cette séquence, le feu tricolore repasse en fonctionnement normal.

Pour vous aider, je vous donne le schéma électrique (la partie feu tricolore pour les voitures est la même que dans l’exercice sur le feu tricolore) :

Allez, je vous laisse faire et si vous n’y arrivez pas, vous pouvez jeter un œil à une solution (plusieurs existent) :

feu tricolore piéton

void setup()
{
//initialisation des modes
pinMode(12, OUTPUT);
pinMode(11, OUTPUT);
pinMode(10, OUTPUT);
pinMode(9, OUTPUT);
pinMode(8, OUTPUT);
pinMode(2, INPUT);
}

void loop()
{
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
delay(3000);

digitalWrite(10, LOW);

int etatBouton = digitalRead(2);
if (etatBouton == HIGH)
{
digitalWrite(11, HIGH);
delay(1000);
digitalWrite(11, LOW);

digitalWrite(12, HIGH);
digitalWrite(9, LOW);
digitalWrite(8, HIGH);
delay(5000);

digitalWrite(12, LOW);
digitalWrite(8, LOW);
}
else
{
digitalWrite(11, HIGH);
delay(1000);

digitalWrite(11, LOW);

digitalWrite(12, HIGH);
delay(3000);

digitalWrite(12, LOW);
}
}

IV-C - Amélioration de la lisibilité du code d’un programme

Jusqu’ici, on a programmé notre carte Arduino avec des petits programmes, conçus par nos soins : il est assez aisé de s’y retrouver. Par la suite, les programmes vont devenir un peu plus longs et surtout, si vous cherchez à développer un projet, vous risquez de récupérer des bouts de code par-ci par-là, glanés sur Internet notamment.

Par exemple, reprenons le cas pratique précédent du feu tricolore avec piéton. En fait, je me suis trompé dans les broches que je voulais utiliser : le schéma électrique souhaité était celui-là :

Comme disait le sage Eusaebius dans Les visiteurs « c’est une catastrophe ! » : je vais devoir réécrire mon code en changeant tous les numéros des broches, quelle galère ! En effet, quand je programme à l’aide de la commande digitalWrite(10, HIGH) cela signifie que seule la broche 10 est impactée : si je câble la DEL sur la broche 7, je dois renseigner cette broche dans ma commande et écrire digitalWrite(7, HIGH). Et donc, si j’ai écrit cette commande 20 fois dans mon programme, je dois la modifier 20 fois !

Mais une solution existe, qui en plus de faire gagner du temps en cas de modification de broche, va augmenter considérablement la lisibilité du programme : on va utiliser une variable. Une variable en programmation est un petit espace de stockage que l’on nomme comme on veut (avec quelques réserves, le langage Arduino interdit certains noms qu’il utilise lui-même comme commande) et qui prendra la valeur que l’on souhaite lui donner. Pour affecter une valeur à une variable, on utilise le signe égal : c’est d’ailleurs ce qui a été fait dans le programme sur le bouton-poussoir

variable

int etatBouton = digitalRead(2);

Dans cette ligne, on affecte à la variable etatBouton la valeur lue sur la broche numérique 2.

À cet égard, je reviens sur une remarque formulée dans la compréhension du programme sur le bouton-poussoir : lors du test de la condition

condition double

if (etatBouton == 1)

j’avais indiqué qu’il fallait bien mettre un double égal et non pas un simple, car la signification est complètement différente

- etatBouton = 1 signifie qu’on affecte la valeur 1 à la variable etatBouton ;

- etatBouton == 1 sert à tester si la valeur de la variable etatBouton est 1.

L’espace de stockage alloué pour la variable dépendra du type d’informations que l’on veut sauvegarder : un petit nombre entier (par exemple 6) prendra moins de place qu’un grand nombre à virgule (123456,789). Dans le langage Arduino, lorsqu’on crée une variable, il est obligatoire de lui indiquer le type de données qu’il va devoir sauvegarder. Le tableau suivant indique les types les plus couramment utilisés :

Type de variable

Valeurs prises

char

Caractère

byte

Entier compris entre 0 et 255

int

Entier compris entre -32765 et 32767

long

Entier entre -2147483648 et 2147483647

float

Nombre à virgule compris entre -3,4028235E+38 et 3,4028235E+38

boolean

2 choix possibles : 0 ou 1 ou de manière équivalente LOW ou HIGH ou encore false ou true

String

Chaîne de caractères

On aura tout intérêt à choisir un type de variables qui dépend de ce que l’on souhaite en faire, car plus le type admet de grands nombres plus il faut de place pour le stockage : par exemple, pour créer une variable qui contient le numéro d’une broche de la carte Arduino, le type byte est largement suffisant. À l’inverse, si vous souhaitez compter le nombre de grains de riz dans votre paquet (on ne sait jamais, certaines personnes comptent tout ce qui leur tombe sous la main…), une variable de type int, voire long sera nécessaire.

Bien évidemment, on donnera un nom de variable qui informe sur la signification de cette variable. Par exemple, pour stocker la broche sur laquelle on connecte la DEL rouge pour la voiture, on pourra utiliser la variable rougeVoit ; pour le bouton-poussoir, le nom boutonPoussoir est plus parlant que 2 ! Ainsi, à chaque fois que l’on aura besoin d’indiquer la broche à utiliser, on utilisera le nom de variable plutôt que son numéro.

Souvent, on définit les variables en tout début du programme pour faciliter la lecture. Par exemple :

variable

1. byte boutonPoussoir = 2;

3. byte rougeVoit = 12;

4. byte orangeVoit = 11;

5. byte verteVoit = 10;

7. byte rougePiet = 9;

8. byte vertePiet = 8;

On peut encore améliorer le code précédent avec l’ajout suivant :

ajout const

1. const byte boutonPoussoir = 2;

3. const byte rougeVoit = 12;

4. const byte orangeVoit = 11;

5. const byte verteVoit = 10;

7. const byte rougePiet = 9;

8. const byte vertePiet = 8;

Il a été ajouté l’instruction const à certaines lignes : si on regarde de plus près, on peut remarquer qu’il s’agit des lignes où la variable reste inchangée au cours du programme. En effet, quand on branche le bouton sur la broche 10, on sait qu’elle n’évoluera pas au long du programme.

Il ne faut pas modifier une variable avec l’attribut const au cours du programme sinon il se passera une erreur à la compilation.

On voit donc l’intérêt des variables : si on souhaite brancher la DEL rouge pour piéton sur la broche 4, il suffira de modifier la ligne 7 en const byte rougePiet = 4;.

Pour les noms de variables, certaines règles de bon usage facilitent la lecture : la principale est de faire commencer le nom de variable par une minuscule.

Si vous souhaitez ajouter un 2e mot au nom de variable, il est conseillé d’accoler ce mot au 1er, mais de le faire commencer par une majuscule.

Exemples : boutonPoussoir, rougeVoit.

Au moment où vous écrivez vos programmes, vous savez ce que vous voulez faire, là où vous voulez en venir. Le problème est que si vous laissez de côté votre projet pendant quelque temps pour le reprendre plus tard, vous risquez de ne plus trop savoir à quoi correspondent certaines lignes de code : c’est dommage de perdre un temps si précieux à tout comprendre ! Pour vous faciliter ce travail, vous pouvez insérer des commentaires dans votre code : il s’agit de lignes qui sont visibles dans le programme, mais qui ne sont pas interprétées comme du code et n’interviennent donc pas dans le programme. Pour insérer un commentaire, on utilise le double slash // : tous les caractères suivants de la ligne ne seront pas compilés.

J’aimerais également évoquer l’intérêt de bien structurer visuellement votre code : à la différence d’autres langages, on peut mettre autant d’espaces que l’on souhaite devant une ligne d’instructions sans que cela nuise au programme. Pourtant, cette précaution s’avère bien utile pour comprendre son code.

Par défaut, quand vous saisissez votre ligne avec l’instruction if et que vous tapez sur entrée pour changer de ligne (soit avant le { soit après, cela ne change rien), vous voyez que le nombre d’espaces est augmenté (en programmation, on parle d’indentation) permettant de repérer toutes les lignes d’instructions liées à ce if. Cet effet visuel est d’autant plus nécessaire quand plusieurs boucles s’imbriquent les unes dans les autres : je reviendrai sur ce point plus loin dans ce cours avec un exemple plus parlant. En attendant, je vous laisse digérer tous ces conseils !

Un outil rapide à utiliser pour structurer son code est de passer par Outils → Formatage automatique : les espaces superflus seront supprimés, les accolages seront alignées.

J’ai réécrit mon code du feu tricolore avec le feu piéton en adoptant ces quelques règles : vous verrez que la lecture du code en est facilitée !

feu tricolore pieton variable

1. // Définition des broches pour les LED

2. const byte rougeVoit = 12;

3. const byte orangeVoit = 11;

4. const byte verteVoit = 10;

5. const byte rougePiet = 9;

6. const byte vertePiet = 8;

8. // Définition de la broche pour le bouton-poussoir

9. const byte boutonPoussoir = 2;

11. // Variable pour connaître l'état du bouton-poussoir

12. int etatBouton ;

14. void setup()

15. {

16. //initialisation des modes

17. pinMode(rougeVoit, OUTPUT);

18. pinMode(orangeVoit, OUTPUT);

19. pinMode(verteVoit, OUTPUT);

20. pinMode(rougePiet, OUTPUT);

21. pinMode(vertePiet, OUTPUT);

22. pinMode(boutonPoussoir, INPUT);

23. }

25. void loop()

26. {

27. // fonctionnement normal : feu piéton sur rouge

28. digitalWrite(rougePiet, HIGH);

30. // allumage led verte voiture

31. digitalWrite(verteVoit, HIGH);

33. delay(3000);

35. // on éteint la led verte après le délai de 3s

36. digitalWrite(verteVoit, LOW);

38. //test de l'état du bouton

39. etatBouton = digitalRead(boutonPoussoir);

40. if (etatBouton == HIGH) //le bouton est appuyé

41. {

42. digitalWrite(orangeVoit, HIGH); // orange voiture allumé

43. delay(1000);

44. digitalWrite(orangeVoit, LOW); // on éteint l'orange après 1s

46. digitalWrite(rougeVoit, HIGH); // rouge voiture allumée

47. digitalWrite(rougePiet, LOW); // éteint rouge piéton

48. digitalWrite(vertePiet, HIGH); //vert piéton allumée

50. delay(5000);

52. digitalWrite(rougeVoit, LOW); // éteint rouge voiture après délai

53. digitalWrite(vertePiet, LOW); //vert piéton éteint

54. }

55. else

56. {

57. // allumage led orange

58. digitalWrite(orangeVoit, HIGH);

60. delay(1000);

62. // on éteint la led orange après le délai de 1s

63. digitalWrite(orangeVoit, LOW);

65. // allumage led rouge

66. digitalWrite(rougeVoit, HIGH);

68. delay(3000);

70. // on éteint la led rouge après le délai de 3s

71. digitalWrite(rougeVoit, LOW);

72. }

73. }

Je vous engage vivement à essayer de respecter ces consignes pour vous éviter ensuite d’énormes pertes de temps parce que vous ne trouvez pas une erreur dans votre programme ! J’ai évoqué précédemment l’outil de formatage automatique, bien pratique, mais qui n’empêche pas de faire attention lorsque vous codez votre programme : ce n’est pas parce que le code est bien formaté que le programme va fonctionner comme vous le souhaitez.

Prendre du temps lors de l’écriture d’un programme pour améliorer la lisibilité de son code n’est pas du tout une perte de temps, c’est surtout un gain de temps lors de son utilisation ultérieure.

V - Récupérer des données extérieures à l’aide de capteurs

V-A - Mesure d’une température analogique

V-A-1 - Principe de la mesure analogique

Jusqu’ici, nous n’avons utilisé que des grandeurs numériques : leur valeur était soit 0 soit 1. Or dans la vraie vie, beaucoup de grandeurs ne prennent pas de valeurs fixes, mais présentent une infinité de valeurs : la température peut valoir 23,5 °C ; -0,5 °C ; 11,0000003 °C… On dit que ce sont des valeurs analogiques.

Les valeurs analogiques sont compréhensibles pour les humains, mais pas par les machines : le mécanisme de mesure n’utilise que le code binaire avec des 0 et des 1. La machine a besoin d’un convertisseur analogique – numérique (CAN) pour transformer notre signal infini de valeurs en une suite de 0 et de 1. Selon la puissance du CAN, la précision de cette conversion est plus ou moins fine : elle se définit à l’aide d’un nombre de bits. Schématiquement, un bit correspond à une case contenant 0 ou 1 → un CAN avec 1 bit ne pourra donc prendre en sortie que 2 valeurs ; si le CAN dispose de 2 bits, il pourra prendre 4 valeurs (chaque bit prenant la valeur 0 ou 1, il y a donc 4 possibilités : 00, 01, 10 et 11) ; si le CAN dispose de n bits, il y aura 2n bits en sortie.

Le CAN de la carte Arduino dispose de 10 bits soit 1024 valeurs : la grandeur analogique d’entrée sera donc convertie en une valeur entre 0 et 1023. Dans la description rapide de la carte, j’ai indiqué que la tension de fonctionnement de la carte était de 5 V : la résolution de la mesure analogique est donc de 5/1024=0,00488 V → ainsi, une tension d’entrée entre 0 V et 0,00488 V vaudra 0 en sortie, une tension entre 0,00488 V et 0,00976 V vaudra 1 en sortie, etc.

V-A-2 - Mesure d’une température avec un LM35

Le LM35 est un composant électronique : dans sa DATASHEET, on apprend qu’il permet de mesurer des températures sur une étendue assez vaste pour nos besoins (de -55 °C à 150 °C pour la version la plus évoluée du capteur, le LM35A), avec une tension d’alimentation entre 4 et 30 V (donc adaptée pour Arduino) et dont la sortie vaut 10 mV par °C.

Dans un premier temps, câblez le circuit suivant :

Téléversez ensuite le programme suivant :

lm35

1. /*

2. * Programme : LM35

3. * Date : 13/12/2019

4. * Auteur : nlbmoi

5. *

6. * Ce programme permet de convertir la tension mesurée aux

7. * bornes d'un capteur LM35 en une température, le

8. * capteur étant alimenté en 5V

9. */

11. // variable constante de type entier pour la broche de mesure A0

12. const byte mesureTemp = 0;

14. //variables pour les constantes utilisées dans le programme

15. const unsigned long flux = 9600; //débit de la liaison série

16. const unsigned int delai = 1000; //délai entre deux mesures en millisecondes

19. void setup()

20. {

21. //on initialise le port série pour communiquer avec le PC

22. Serial.begin(flux);

23. }

25. void loop()

26. {

27. // Mesure la valeur sur la broche définie mesureTemp

28. int valeurTemp = analogRead(mesureTemp);

30. //conversion en tension : convertisseur 10 bit

31. float tension = valeurTemp * 5.0 / 1023.0;

33. //conversion en température : d'après datasheet

34. float temperature = tension * 100;

36. // Envoi la mesure au PC pour affichage

37. Serial.print(valeurTemp);

38. Serial.print(" ; ");

39. Serial.print(tension);

40. Serial.print(" ; ");

41. Serial.println(temperature);

43. //délai entre deux mesures

44. delay(delai);

45. }

Ouvrez le moniteur série pour vérifier que la température indiquée (dernière valeur sur chaque ligne) est proche de la valeur attendue. En posant votre doigt sur le capteur, vous verrez la valeur qui augmente.

Une autre méthode, plus visuelle, est d’utiliser une fonctionnalité récente dans l’IDE d’Arduino : le traceur série. Il permet d’afficher sous forme graphique les valeurs numériques reçues par le port série du PC (celles que l’on affiche avec l’instruction Serial.begin()) ; Pour y accéder, il faut passer par Outils → Traceur série : une fenêtre s’ouvre et les données s’affichent sous forme de points reliés.

Il n’est pas possible d’utiliser en parallèle le moniteur série et le traceur série : il faut donc faire un choix et fermer au préalable l’outil ouvert.

Explorons un peu ce code

Lignes 1 à 9 : il s’agit de lignes de commentaires pour expliquer ce que fait le programme. Pour éviter d’avoir à noter // à chaque ligne, il est possible d’utiliser la syntaxe /* pour commencer un commentaire le et terminer par */ : tout ce qui est entre ces symboles est ignoré à la compilation.

Lignes 11 à 16 : définition des variables utilisées dans le programme à savoir la broche pour la mesure de température (mesureTemp), le flux pour la liaison avec le moniteur série (flux), le délai entre deux mesures (delai). Vous pouvez remarquer que le type de ces variables est différent pour prendre en compte l’étendue des valeurs possibles (vous pouvez revenir au tableau IV.C pour vérifier que le type est adapté).
Par rapport à ce que l’on a déjà vu, il y a une petite modification : pour le flux et le délai, l’instruction unsigned a été rajoutée. Cela permet de spécifier que la variable ne prend que des valeurs positives, son intérêt est de décaler l’étendue du type de variable dans les valeurs positives. Ainsi, une variable de type int « normale » peut prendre des valeurs entre -32765 et 32767 alors qu’une variable de type unsigned int peut prendre des valeurs entre 0 et 65535.

Ligne 28 : on récupère la valeur mesurée sur la broche analogique mesureTemp à l’aide de la commande analogRead (en français, lecture analogique) → cette valeur est stockée dans une variable nommée valeurTemp qui est de type int (entier).

Ligne 31 : on convertit la valeur numérique (codée sur 10 bits, de 0 à 1023) en une tension à l’aide d’un simple produit en croix (à une tension de 5 V correspond une valeur de 1023, donc une valeur de x correspond une tension de ) → cette valeur est stockée dans une variable nommée tension de type float (flottant, un nombre à virgule).

Ligne 34 : comme 10 mV correspondent à 1 °C, cela signifie qu’à une tension de 1 V correspondent 100 °C → on multiplie donc par 100 la valeur de la tension (en volts) pour obtenir la valeur de la température, que l’on stocke dans une variable nommée temperature de type float.

Lignes 37 à 41 : on affiche dans le moniteur série sur la même ligne (Serial.print) la valeur de la variable mesureTemp, le texte « ; », la valeur de la variable tension, le texte « ; » et on termine par afficher la valeur de la variable temperature avec un retour à la ligne ensuite (Serial.println).

Ligne 44 : on ajoute une pause de delai microsecondes soit 1 seconde dans le cas du programme→ les valeurs seront donc affichées toutes les secondes (tant que la carte est alimentée !)

À la ligne 31, on convertit en fait une valeur d’une échelle (qui va de 0 à 1023) en une valeur dans une autre échelle (qui va de 0 V à 5 V). Au lieu de faire un produit en croix, on peut utiliser une instruction Arduino qui fait la même chose :

map(valeur, limite_inf_1, limite_sup_1, limite_inf_2, limite_sup₂)

où valeur est la valeur à convertir, limite_inf_1 et limite_sup_1 sont les limites de la 1re échelle dans laquelle est mesurée la valeur (dans notre cas 0 et 1023), limite_inf_2 et limite_sup_2 sont les limites de la nouvelle échelle (dans notre cas 0 et 5 V) → la ligne 31 pourrait donc s’écrire de manière équivalente :

conversion_echelle

float tension = map(valeurTemp, 0, 1023, 0, 5);

Il faut simplement se méfier car la valeur map ne s’applique que sur des entiers : changer d’échelle dans une échelle restreinte peut conduire à des problèmes.

V-A-3 - Amélioration des mesures

Le programme proposé donne une précision modeste de la température même si visuellement on a une impression de faire une mesure de la température à 0,01 °C près. Il ne faut pas oublier que la résolution avec un CAN 10 bits est de 0,0488 V soit en convertissant en température une résolution de 0,48 °C : on ne peut donc pas mesurer à priori une température à plus de 0,5 °C près. Certes, pour avoir une idée de la température ambiante, c’est suffisant, mais pour d’autres applications, cela risque d’être un peu juste.

Pour améliorer la précision de la mesure, deux solutions s’offrent à nous que je détaillerai ensuite : une première, logicielle, permettra d’apprendre d’autres commandes Arduino et est adaptable à tout type de capteur ; une seconde, matérielle, n’est adapté qu’à quelques capteurs, dont le LM35.

Une manière rapide d’améliorer les résultats d’une mesure est en fait de faire plusieurs mesures puis d’en faire la moyenne : pour ce faire, on va utiliser les boucles. Le principe est simple : pour un nombre de fois décidé au préalable, on va répéter la mesure du capteur et en faire la somme puis à la fin on divisera par le nombre de mesures faites.

Avant d’utiliser cette boucle (instruction for) dans notre programme, voyons le principe de programmation de cette boucle. Pour le montage, il est très simple : il suffit de brancher la carte Arduino à l’aide du câble USB :

principe for

1. void setup() {

2. // put your setup code here, to run once:

3. Serial.begin(9600);

4. for (int compteur = 0 ; compteur < 3 ; compteur = compteur + 1)

5. {

6. Serial.println(compteur);

7. }

8. }

10. void loop() {

11. // put your main code here, to run repeatedly:

13. }

Après téléversement, dans le moniteur, vous devez voir les chiffres de 0 à 2 sur des lignes différentes qui s’affichent. Comment comprendre ce résultat ?

Ligne 8 : la variable compteur est déclarée (int compteur) et vaut initialement 0, elle est incrémentée de 1 à chaque boucle (compteur = compteur +1) jusqu’à ce que la condition compteur < 3 soit fausse. Pour chaque valeur de compteur qui respecte la condition, on effectue les opérations contenues entre les accolades { } soit ici écrire avec un retour à la ligne la valeur contenue dans la variable compteur.

Valeur de compteur

Est-ce que compteur est inférieur à 3 ?

Affichage

0

Oui

0

1

Oui

1

2

Oui

2

3

Non

Rien, car on quitte la boucle for

Dans les boucles for, il faut bien faire attention au paramètre de sortie que l’on utilise : ainsi, la condition compteur < 3 fera afficher les valeurs 0 1 2 tandis que la condition compteur <= 3 fera afficher 0 1 2 3.

Souvent (pour ne pas dire quasiment tout le temps), pour incrémenter de 1 le compteur, on simplifie la ligne compteur = compteur +1 en compteur ++ (de la même manière, compteur -- remplace compteur = compteur -1).

On peut désormais passer à la vraie pratique et adapter le code LM35 pour faire la moyenne de nos mesures de température.

Le programme à téléverser sur la carte est le suivant :

moyenne temperature

1. /*

2. * Programme : LM35_moyenne

3. * Date : 13/12/2019

4. * Auteur : nlbmoi

5. *

6. * Ce programme permet de mesurer une température grâce à un

7. * capteur LM35 en moyennant les valeurs

8. */

10. // variable constante de type entier pour le pin de mesure A0

11. byte mesure = 0;

13. //variables pour les constantes utilisées dans le programme

14. long flux = 9600; //débit de la liaison série

15. int delai = 2000; //délai entre deux mesures en millisecondes

16. int nbBoucle = 10;

18. void setup()

19. {

20. //on initialise le port série pour communiquer avec le PC

21. Serial.begin(flux);

22. }

25. void loop()

26. {

27. //on met à zéro les variables

28. float resultatTemp = 0.0 ;

29. float moyenneValeur = 0.0 ;

30. int sommeValeur = 0;

32. for (int compteur = 0 ; compteur < nbBoucle ; compteur ++)

33. {

34. int valeur = analogRead(mesure);

36. //on ajoute la valeur à la somme

37. sommeValeur = sommeValeur + valeur;

38. }

40. //on calcule la moyenne en divisant par le nombre de boucles

41. moyenneValeur = sommeValeur / nbBoucle;

43. //calcul de la température

44. resultatTemp = moyenneValeur * 5.0 / 1023.0 * 100;

46. // Envoi de la mesure au PC pour affichage

47. Serial.print("Temperature moyenne : ");

48. Serial.print(resultatTemp, 2);

49. Serial.println(" °C");

51. //délai entre deux mesures

52. delay(delai);

53. }

Dans le moniteur série, vous devez voir la température moyenne qui s’affiche. Étudions un peu plus en détail le code (je passe sous silence les premières lignes, car elles ont été déjà détaillées précédemment).

Lignes 28 à 30 : on remet à zéro les variables qui contiennent les valeurs mesurées ou calculées. En effet, une fois que le programme a tourné une fois, les variables resultatTemp, moyenneValeur et sommeValeur contiennent bien une valeur (celle du tour précédent) : il faut donc remettre à zéro après chaque affichage.

Lignes 32 : le compteur de tours va de 0 (int compteur = 0) jusqu’à nbBoucle-1 (puisque le critère de fin est compteur < nbTour) avec un incrément de 1 au nombre de tour à chaque boucle (compteur ++), on effectue la suite de commandes suivantes : on enregistre dans la variable valeur de type int la mesure de la valeur lue sur la broche analogique (analogRead), on ajoute cette valeur à la variable sommeValeur qui contient donc la somme des valeurs analogiques lues sur la broche du capteur de température pendant le parcours de la boucle.

La variable valeur (de type int) est définie dans la boucle for : elle ne peut donc servir que dans la boucle et n’existera donc pas pour le reste du programme. On ne peut donc pas l’utiliser en dehors de la boucle : si on voulait réutiliser cette variable ailleurs dans le programme, il faudrait la définir en en-tête du programme comme on l’a fait pour les autres variables. On parle de variables locales.

Ligne 41 : à la fin de la boucle, quand on a mesuré notre nombre de valeurs, on calcule la moyenne des valeurs en divisant la somme (cumulée dans la variable sommeValeur) par le nombre de tours nbBoucle.

Ligne 44 : on convertit la valeur numérique moyennée moyenneValeur en une tension à l’aide du coefficient 5,0/1023,0 que l’on multiplie ensuite par 100 pour la conversion en température (j’ai condensé ici en un seul calcul les lignes 28 et 31 du programme lm35).

Ligne 48 : lorsqu’un second argument est rajouté à l’instruction Serial.print() ou Serial.println(), cela permet de spécifier soit le nombre de décimales après la virgule à utiliser (pour une valeur de type float) soit la base à utiliser pour l’affichage (byte, bin oct, dec ou hex pour une valeur de type int).

Et voilà, on a réussi à calculer une moyenne de plusieurs valeurs : bien sûr, plus on fera de tours de boucle, plus la valeur sera précise. Mais il y a une limite, tout dépend du délai entre deux séries de mesures, car faire des calculs et des commandes prend du temps au microcontrôleur de la carte : si le nombre de boucles est trop important par rapport au délai, vous risquez d’avoir de mauvaises surprises.

Les mesures sont plus précises certes (il faudrait plutôt dire fidèles), mais cette méthode n’améliore pas la justesse du résultat. Effectivement, faire une moyenne de plusieurs mesures permet de les « rassembler » autour d’une même valeur, mais si toutes ces valeurs sont décalées (par exemple à cause d’un problème du composant ou d’un mauvais étalonnage), on n’obtiendra pas la valeur attendue.

Par exemple, un tireur au pistolet très bon est capable de tirer ses balles dans un même endroit de la cible, mais si le viseur est mal réglé, toutes les balles se ne rassembleront pas autour du 10 :

L’utilisation du traceur série permet de visualiser les notions de fidélité et de justesse. Par exemple, à l’aide d’un même montage mesurant la température, on a suivi son évolution au cours du temps pour plusieurs cartes UNO. On obtient selon les cartes des valeurs moins justes et peu fidèles (image en haut à gauche), valeurs fidèles et justes (images de droite) et des valeurs moins justes et fidèles (image en bas à gauche).

Il faut également préciser qu’en cas de valeurs aberrantes, la moyenne est faussée ; néanmoins avec un grand nombre de mesures, des mesures peu vraisemblables impactent moins la moyenne.

Au début de ce point, j’ai abordé une autre manière d’améliorer la précision de la mesure de la température. Pour la comprendre, il faut se rappeler que par défaut, la tension de référence de la carte Arduino est de 5 V : les 1024 valeurs possibles se répartissent entre 0 et 5 V. Or dans le cas de notre capteur, la valeur maximale de température est de 110 °C (j’ai utilisé la version LM35C du composant) soit une tension maximale de 1,1 V en sortie : on a donc toutes les valeurs de 1,1 V à 5 V qui ne seront pas utilisées ce qui nuit fortement à la précision de la mesure. Pour contourner ce problème, il est possible de programmer un changement de tension de référence : la carte Arduino dispose d’une autre tension de référence à 1,1 V , valeur qui tombe bien (mais c’est un pur hasard !), car elle correspond à la valeur maximale de cette version du composant (par exemple, la version LM35A va jusqu’à 150 °C donc 1,5 V en sortie, avec la tension de référence à 1,1 V, les températures au-dessus de 110 °C seront donc évaluées à 110 °C). Pour indiquer à la carte la modification de référence, on doit saisir dans la partie setup() du programme la commande :

chgt reference

analogReference(INTERNAL) ;

Le reste du programme est identique : hormis la ligne 44 qu’il faut modifier puisque la tension de référence est 1,1 V et non plus 5 V :

temperature etalonnage

resultatTemp = moyenneValeur * 1.1 / 1023.0 * 100;

La commande analogReference(INTERNAL) n’est vraie que pour la carte UNO. Si vous disposez d’une carte Mega, il faudra utiliser analogReference(INTERNAL1V1).

Pour les capteurs dont l’étendue en tension est plus élevée (jusqu’à 3 V), il existe une autre solution en utilisant une référence externe, mais elle comporte quelques risques, je ne l’aborderai donc pas dans ce cours basique. Mais libre à vous de chercher sur la Toile plus de renseignements.

V-A-4 - Conception d’une petite alarme en cas de chaleur

L’objectif ici est de mettre en place un montage électrique de mesure de la température (ça, on sait faire désormais) : si la valeur mesurée dépasse une certaine valeur alors on fait déclencher une alarme sonore, une alarme visuelle (une DEL qui s’allume) et l’affichage d’un texte. Si vous avez bien suivi jusque-là, vous pouvez écrire le programme tout seul sauf pour l’alarme, car il vous manque les commandes pour faire fonctionner un buzzer.

Pour faire jouer un son à un buzzer, plusieurs solutions, la plus simple à mettre en œuvre utilise la fonction spécifique tone(). Un petit montage d’essai pour comprendre :

Et le programme à téléverser :

gamme

1. void setup() {

2. pinMode(13, OUTPUT);

3. tone(13, 261, 200);

4. delay(400);

5. tone(13, 294, 200);

6. delay(400);

7. tone(13, 330, 200);

8. delay(400);

9. tone(13, 349, 200);

10. delay(400);

11. tone(13, 392, 200);

12. delay(400);

13. tone(13, 440, 200);

14. delay(400);

15. tone(13, 494, 2000);

16. }

18. void loop() {

19. // put your main code here, to run repeatedly:

21. }

Si tout se passe bien, vous devriez entendre la gamme tempérée. Pour votre confort auditif, je vous conseille de déconnecter l’une des broches du buzzer pour éviter un bourdonnement désagréable.

La fonction tone() a besoin de deux ou trois arguments : le numéro de la broche à laquelle le buzzer est connecté, la fréquence du son émis, et éventuellement la durée de la note en millisecondes.

Si plusieurs buzzer sont branchés sur des broches différentes, il faut utiliser la fonction noTone(broche) pour stopper le son d’un buzzer.

Maintenant que vous savez tout sur le buzzer, enfin assez pour construire notre petite alarme, je vous laisse chercher le programme à téléverser sur la carte, le schéma électrique étant celui-là :

alarme lm35 simple

1. //LM35 détecteur incendie

3. //déclaration des variables

4. const unsigned long flux = 9600; //flux de la liaison série

5. const unsigned int delai = 1000; //délai entre deux mesures en millisecondes

6. const int tempSeuil = 25; //température seuil de 25°C

8. //définition des broches utilisées dans le programme

9. const byte mesureTemp = 0;   // sélection de la borne A0 à laquelle est reliée le capteur

10. const byte delRouge = 7;   //broche de branchement de la DEL Rouge

11. const byte buzzer = 5; //broche de branchement du buzzer

14. // le setup est lancé une fois lors du branchement de la carte

15. // ou lorsqu'on appuie sur le bouton reset de la carte

16. void setup()

17. {

18. //on initialise le port série pour communiquer avec le PC

19. Serial.begin(flux);

21. //Initialisation de la del rouge en sortie

22. pinMode(delRouge, OUTPUT); //on indique que la broche de la del rouge est une sortie

23. pinMode(buzzer, OUTPUT); //le buzzer est une sortie

24. pinMode(mesureTemp, INPUT); //la broche de la température est une entrée

25. }

27. // Fonction loop(), appelée continuellement en boucle tant que la carte Arduino est alimentée

28. void loop()

29. {

30. // Mesure la tension sur la broche définie mesureTemp et conversion en température

31. int temperature = analogRead(mesureTemp) * 5.0 / 1023.0 * 100;

33. Serial.print("temperature mesurée : ");

34. Serial.print(temperature);

35. Serial.print("  °C ");

37. //test si la température dépasse la valeur seuil

38. if (temperature > tempSeuil)

39. {

41. //si la température est dépassée

42. //on alimente en 5V la Del

43. digitalWrite(delRouge, HIGH);

45. tone(buzzer, 440);

47. //on affiche un message d'erreur sur le moniteur série

49. Serial.println("Attention : la température de consigne est dépassée");

50. Serial.println(""); // permet d’ajouter un saut de ligne

51. }

53. //sinon on éteint la DEL et le buzzer

54. else

55. {

56. digitalWrite(delRouge, LOW);

57. noTone(buzzer);

58. }

60. //délai entre deux mesures

61. delay(delai);

62. }

V-B - Mesure d’une température numérique

Dans la partie précédente, on a mesuré une température à l’aide d’un capteur analogique c’est-à-dire qu’il prend « théoriquement » toutes les valeurs comprises dans sa gamme de mesure. Ici, on va voir l’autre grand type de capteur, un capteur numérique, qui donne en sortie une valeur binaire (suite de 0 et de 1) qui dépend de la mesure.

Le capteur numérique utilisé est le DS18B20. C’est un capteur de température de bonne précision (0,5 °C) dans une gamme suffisante pour des applications courantes (-55 °C à 125 °C). Dans ses avantages, on peut citer : facilité de mesurer des températures négatives (dans le cas du LM35, il faut un montage particulier utilisant des diodes), existence de modèles étanches et non étanches (pratique pour un projet dans lequel l’humidité peut venir jouer un rôle !), possibilité de connecter plusieurs capteurs sur une même broche (et donc obtenir à la volée plusieurs températures différentes).

Cette partie peut paraître plus technique au premier abord et donc un peu plus compliquée. Mais elle donne les bases pour explorer beaucoup de capteurs par la suite.

V-B-1 - Utilisation de bibliothèques

Avant d’explorer plus en détail la mesure de la température à l’aide d’un capteur numérique, il faut que je vous parler des bibliothèques (libraries en anglais). Pour comprendre à quoi cela correspond, je vais utiliser une comparaison avec une voiture.

Lorsque vous utilisez votre voiture, vous tournez la clé (ou appuyez sur un bouton) ce qui met en route le moteur. Ensuite, vous passez une vitesse et commencez à accélérer pour faire rouler la voiture. C’est simple et pourtant, ce qui se passe « derrière », dans le moteur, ne l’est pas du tout : si vous deviez manuellement enclencher le démarreur à l’aide de la batterie, faire tourner le moteur, engager le carburant et de l’air dans l’admission, enclencher la compression du mélange, produire l’arc électrique à l’aide de la bougie, faire s’échapper les gaz : il serait beaucoup moins agréable d’utiliser une voiture. Pour nous faciliter la vie, les constructeurs ont créé une boîte noire (le moteur) que l’on actionne par quelques dispositifs d’entrée/sortie (clé, pédales, levier de vitesse) sans qu’on ait besoin de savoir ce qu’il y a à l’intérieur pour que cela fonctionne : les plus bricoleurs peuvent par contre farfouiller dans la « boîte noire » pour modifier les éléments constitutifs du moteur.

Dans le cas de nombreux composants, le principe est identique pour fonctionner, ils ont besoin d’une suite d’étapes dont l’utilisateur n’a pas besoin de connaître tous les détails pour l’utiliser. Pour revenir à notre carte Arduino, la boîte noire est constituée de bibliothèques c’est-à-dire de bouts de code, enregistrés dans des fichiers, qui vont faire le lien entre l’utilisateur (qui n'aura besoin que de quelques commandes simplifiées) et le composant. La programmation de ces bibliothèques est souvent complexe, mais un utilisateur averti peut y accéder pour optimiser le fonctionnement du composant.

Pour une majorité de composants « classiques », les fichiers de bibliothèque sont préinstallés sur la carte et sont accessibles via Outils → Gérer les bibliothèques → sélectionner la bibliothèque souhaitée, puis cliquer sur Installer. Pour les autres composants, il faudra les installer manuellement : après avoir récupérer les fichiers (via le constructeur du composant généralement ou sur le site Github), il faut passer par Croquis → Inclure une bibliothèque → Ajouter la bibliothèque .ZIP.

Pour utiliser une bibliothèque, il faut indiquer au programme Arduino qu’on va en avoir besoin. : on indique alors en début de programme #include <la_bibliothèque.h>.

Par exemple, je dispose d’un petit écran pour afficher du texte : pour qu’il fonctionne, le constructeur m’indique d’utiliser deux bibliothèques, je dois donc définir en début de programme

bibliothèques

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Ensuite, il faut créer un objet, que l’on nommera correctement (dans mon cas ce sera tout simplement lcd) qui permettra de faire le lien entre mon programme et les bibliothèques. Avec l’exemple de mon écran, le code à écrire sera :

creation objet

1. Adafruit_SSD1306 lcd(4);

Cette ligne permet également d’initialiser l’objet.

Dans cette syntaxe, on définit l’objet lcd comme étant l’écran Adafruit_SSD1306 qui reçoit les données sur la broche 4. Ainsi, pour indiquer au programme d’afficher du texte, j’utiliserai la commande lcd.print("Mon texte").

Si cela vous semble un peu compliqué, il ne faut pas vous en faire. Quand vous allez sur le site du revendeur (ou sur Github), tout est expliqué, en anglais, avec les branchements, les bibliothèques à utiliser, les commandes possibles : chaque constructeur a ses propres codes ! Mais c’est rarement compliqué à comprendre et au pire, les forums spécialisés sur Arduino (par exemple, le forum officiel Arduino ou le forum sur le site developpez.com) regorgent de personnes disponibles pour aider les utilisateurs.

Pour d’autres types d’écrans, la création de l’objet est différente et il faut parfois rajouter d’autres paramètres par exemple :

bibliotheque lcd 2

1. #include<LiquidCrystal_I2C.h>

2. LiquidCrystal_I2C lcd(0x27, 20, 4);

Les paramètres de la dernière ligne correspondent à quelques caractéristiques du lcd : adresse, nombre de caractères par ligne, nombre de lignes.

V-B-2 - Mesure d’une température

Si je vous ai parlé des bibliothèques au point précédent, ce n’est pas juste pour le plaisir : c’est parce qu’on en a besoin pour notre capteur de température. Le capteur DS18B20 est un capteur de température numérique qui intègre toute l’électronique de la mesure analogique, la conversion analogique-numérique jusqu’à la communication série de type « 1-wire » (prononcer « one wire »), c’est-à-dire qu’il nécessite un seul fil pour récupérer des données (il faut également alimenter le capteur) : on utilise donc la bibliothèque associée (Onewire, à installer au préalable comme indiqué dans le point précédent). En parallèle, il faut intégrer une bibliothèque supplémentaire qui va servir en gros à interpréter les mesures de température, c’est la bibliothèque DallasTemperature (à installer également).

Pour la mesure numérique de la température, la sonde envoie une série d’impulsions électriques soit hautes (on obtient un « 1 ») soit basses (on obtient alors un « 0 ») en suivant un protocole particulier conçu par Dallas Semiconductor, et géré en toute transparence pour l’utilisateur grâce aux bibliothèques : on obtient une valeur numérique binaire qui est ensuite affichée au format température en degrés Celsius.

Le montage est le suivant :

Il est impératif d’associer une résistance () dite de tirage à l’alimentation sur la broche de données.

Pour tester la mesure de température par un capteur numérique, on va utiliser un exemple de programme fourni par la bibliothèque : Fichier → Exemples → DallasTemperature → Simple

Le code doit ressembler à ça (j’ai traduit en français les commentaires du code pour améliorer sa compréhension) :

ds18b20

1. // chargement des bibliothèques nécessaires

2. #include <OneWire.h>

3. #include <DallasTemperature.h>

5. // Définition de la broche de connexion du capteur de température

6. #define ONE_WIRE_BUS 2

8. // déclaration de l'objet one wire qui permet de gérer le bus one wire

9. OneWire oneWire(ONE_WIRE_BUS);

11. // déclaration de l'objet DallasTemperature qui permet de gérer le bus one wire

12. DallasTemperature sensors(&oneWire);

14. // La fonction setup. On initialise le capteur

15. void setup(void)

16. {

17. // start serial port

18. Serial.begin(9600);

19. Serial.println("Dallas Temperature IC Control Library Demo");

21. // Démarrage de la bibliothèque

22. sensors.begin();

23. }

25. /*

26. Fonction principale, obtention et affichage de la température

27. */

28. void loop(void)

29. {

30. // demande la température aux capteurs

31. // demande à tous appareils connectés sur le bus

32. Serial.print("Requesting temperatures...");

33. sensors.requestTemperatures(); // Envoi de la commande de récupération de la température

34. Serial.println("DONE");

35. // Après réception des températures, on peut les afficher

36. // La fonction ByIndex est utilisée, mais à titre d’exemple, seule la température du premier capteur est affichée

37. Serial.print("Temperature for the device 1 (index 0) is: ");

38. Serial.println(sensors.getTempCByIndex(0));

39. }

Lignes 2 et 3 : on définit les bibliothèques nécessaires pour le programme.

Ligne 6 : on définit une variable ONE_WIRE_BUS correspondant à la broche à laquelle est branchée le capteur one wire. Il s’agit ici d’une syntaxe prise au langage C (remarquez la fin de ligne sans le point-virgule) ; elle a le même effet que la syntaxe déjà vue.

syntaxe

const byte ONE_WIRE_BUS = 12;

Je conseille d’utiliser la syntaxe avec const plutôt que celle avec define, car même si elles ont le même rôle, la première permet de préciser le type de données à utiliser (byte dans l’exemple).

Ligne 8 : on crée un objet oneWire (et on l’initialise) qui est branché sur la broche définie à la ligne 6.

Ligne 12 : on crée un objet sensors et on l’initialise ; il prend en paramètre un objet oneWire.

Ligne 24 : avant de faire une mesure, on doit d’abord démarrer la connexion avec la sonde de température.

Ligne 35 : on demande au capteur de faire une mesure de la température.

Ligne 40 : on récupère la valeur mesurée pour l’afficher dans le moniteur série.

Cette procédure peut paraître complexe, elle n’a pas forcément vocation à être comprise (du moins dans une première approche de la programmation Arduino), mais simplement à être codée !

V-B-3 - Mesure de plusieurs températures « simultanément »

Comme je l’ai indiqué en préambule de cette partie, l’énorme avantage de ce modèle one-wire est de pouvoir faire des mesures à partir de plusieurs capteurs différents montés sur le bus.

Pour tester la mesure de température, vous pouvez téléverser ce code :

ds18b20 multiple

1. //chargement des bibliothèques nécessaires

2. #include <OneWire.h>

3. #include <DallasTemperature.h>

5. //définition des constantes du programme

6. const unsigned long baud = 9600; //débit de la liaison série

7. const unsigned byte oneWireBus= 7; //broche de connexion des capteurs température

9. //déclaration de l'objet one wire qui permet de gérer le bus one wire

10. OneWire oneWire(oneWireBus );

11. DallasTemperature sensors(&oneWire);

13. byte nbTemperature ;  //variable qui contiendra le nombre de capteurs détectés

16. //variable pour le délai

17. unsigned int delai = 5000;

20. void setup(void)

21. {

22. Serial.begin(baud);

23. sensors.begin();

25. //on récupère le nombre de capteurs détectés par le système et on l'affiche

26. nbTemperature = sensors.getDeviceCount() ;

27. Serial.println("Nombre de capteurs de température : " + String(nbTemperature)) ;

28. }

30. void loop(void)

31. {

32. sensors.requestTemperatures();

34. //Mesure des températures sur le nombre de sondes détectées

35. for(int i = 0 ; i < nbTemperature ; i++)

36. {

37. Serial.print(sensors.getTempCByIndex(i));

38. Serial.print(" ; ");  //pour séparer les mesures de chaque sonde

39. }

41. Serial.println(""); //passage à la ligne à la fin des mesures

43. //délai entre deux mesures

44. delay(delai);

45. }

Comme d’habitude, explorons ce code.

Lignes 2 et 3 : les bibliothèques dont on a besoin.

Lignes 6 et 7 : les constantes utilisées dans le programme.

Lignes 10 et 11 : on définit les objets nécessaires pour utiliser les commandes spécifiques du capteur de température DS18B20 et on les initialise.

Ligne 26 : on stocke dans la variable nbTemperature le nombre de capteurs détectés à l’aide de la commande sensors.getDeviceCount().

Ligne 27 : on affiche dans le moniteur série le texte « Nombre de capteurs de température : » et le nombre de capteurs nbTemperature qui est converti en chaîne de caractères à l’aide de la fonction String() → on fait ce qu’on appelle une concaténation de chaînes.

Ligne 32 : on demande au capteur de faire une mesure des températures.

Lignes 35 à 39 : on fait une boucle for qui va afficher les températures mesurées sur la même ligne, séparées par un point-virgule : la boucle se fait sur le nombre de capteurs détectés à la ligne 26.

Ligne 41 : la commande Serial.println("") affiche une chaîne de caractères vide et fait un retour à la ligne → on aurait pu écrire de manière équivalente l’instruction Serial.print("\n").

Il est à remarquer que la mesure de la température ne se fait qu’une seule fois (ligne 32), mais qu’on doit appeler à chaque fois que nécessaire la fonction sensors.getTempCByIndex().

La mesure de la température ne se fait pas instantanément et dure près d’une seconde avec la résolution maximale de mesure (il est possible de diminuer cette résolution, mais la procédure ne sera pas décrite dans ce cours d’initiation). Donc plus on dispose de capteurs de température, plus il faut prévoir du temps de mesure : le délai de cadence doit donc être adapté en conséquence.

V-C - Application : fabrication d’une mini station météo

Précédemment, on a utilisé une sonde de température « simple ». Pour une station météo, on a besoin de mesurer d’autres caractéristiques : l’humidité, la luminosité… On devrait donc, à priori, utiliser un capteur spécifique à chacune de ces grandeurs. Sauf que certains constructeurs ont fabriqué des capteurs multiparamètres permettant de mesurer (plus ou moins) simultanément plusieurs grandeurs. Pour cette mini station météo, je me suis borné à mesurer la température, l’humidité et la luminosité : les mesures seront affichées sur un écran LCD comportant 4 lignes de 20 caractères (que j’ai brièvement cité dans le paragraphe sur les bibliothèques Utilisation de bibliothèques). Sur cet écran seront également affichées les valeurs minimales et maximales de la journée.

Pour la mesure de la température et de l’humidité, j’ai opté pour le capteur DHT11 ; pour la luminosité une photorésistance ou LDR (Light Dependant Resistance) : la présentation et l’essai de ces capteurs sera fait dans un point séparé avant d’évoquer le programme complet.

Pour être plus réaliste, il aurait été judicieux de mesurer la pression qui permet de savoir si on s’attend à du beau temps ou du mauvais temps, mais les kits débutant ne disposant pas d’un tel capteur (qui coûte « relativement » cher (~15 €-20 €) par rapport aux autres capteurs utilisés, je n’ai pas pris le parti de mesurer ce paramètre. Mais si vous comprenez le principe des autres capteurs, vous pourrez l’adapter pour mesurer la pression.

Je ne souhaite pas aborder ici l’enregistrement de données sur une carte SD ni l’utilisation d’une mémoire à moyen terme (EEPROM) permettant un suivi plus régulier de ces paramètres : cela fera l’objet, sans doute, d’un cours ultérieur. Autrement dit, une fois la carte Arduino éteinte, les valeurs ne sont pas sauvegardées.

V-C-1 - Le capteur DTH11

Ce capteur, que l’on trouve dans beaucoup de kits pour débutant (d’où son utilisation ici), permet à moindres coûts d’obtenir des valeurs relativement fiables et précises de la température et de l’humidité. Je joins quelques caractéristiques de ce composant ainsi que de deux autres versions que l’on trouve couramment

DHT11

DHT22

Tension d’alimentation

3 à 5 V

3,3 à 6 V

Humidité relative

Plage

20 à 80 %

0 à 100 %

Précision

± 5 %

± 2 %

Température

Plage

0 à 50 °C

-40 à 80 °C

Précision

± 2 °C

± 0,5 °C

Fréquence de mesure

1 par seconde

4 par seconde

Pour le câblage de ce composant, il est similaire à celui utilisé pour le capteur de température numérique DS18B20 avec une résistance de tirage entre les broches de données et d’alimentation :

Pour le branchement, si vous avez des doutes, il faut se référer à la DATASHEET du composant qui nous indique (de gauche à droite si la « grille » du composant est face à nous) :

- broche 1 : alimentation( VDD ou power supply) ;

- broche 2 : données (DATA ou signal) ;

- broche 3 : non connectée (NULL) ;

- broche 4 : zéro (GND).

Dans certains kits, le composant est intégré sur un support qui dispose de trois broches : la broche n° 3 du composant n’est reliée à rien. Les branchements sont donc pour ces supports (de gauche à droite en regardant la grille) : alimentation, données, zéro.

Pour faire fonctionner ce capteur, il faut une fois encore installer sa bibliothèque que l’on trouve dans Croquis → Inclure une bibliothèque → Gérer les bibliothèques puis chercher la bibliothèque DHT sensor library by Adafruit. En regardant le programme (juste en dessous), on voit qu’il faut également installer la bibliothèque Adafruit Unified Sensor Lib.

On va tester ce capteur en utilisant un fichier exemple tiré de la bibliothèque (comme on l’avait fait pour le capteur DS18B20). On sélectionne donc Fichier → Exemples → DHT sensor library → DHTester qui donne le code ci-dessous (version du programme en décembre 2019) :

test DHT11

1. // Exemple de programme pour différentes versions de capteur d’humidité et de température

2. // Écrit par  ladyada, public domain

4. // Nécessite les bibliothèques Arduino suivantes :

5. // - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library

6. // - Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor

8. #include "DHT.h"

10. #define DHTPIN 2     // broche digitale de connexion du capteur DHT

11. // Si vous utilisez la carte Feather HUZZAH avec module Wi-Fi ESP8266 , il faut utiliser les broches : 3, 4, 5, 12, 13 ou 14 --

12. // La broche 15 peut fonctionner, mais il faut déconnecter le capteur DHT pendant le téléversement du programme.

14. // Décommenter selon le type de capteur DHT utilisé

15. #define DHTTYPE DHT11   // DHT 11

16. //#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

17. //#define DHTTYPE DHT21   // DHT 21 (AM2301)

19. // Connecter la broche 1 située à gauche du capteur  à la broche 5 V de la carte

20. // Si vous utilisez une carte fonctionnant en 3,3 V comme la carte Arduino Due, connectez la broche 1 sur la broche 3,3 V

22. // Connecter la broche 2 du capteur à la broche DHTPIN définie

23. //  Connecter la broche 2 située à droite du capteur  à la broche GROUND de la carte

24. // Connecter une résistance de  10 K  entre les broches 1 et 2 du capteur

26. // Initialisation du capteur DHT.

27. // Les anciennes versions de la bibliothèque demandent un 3e paramètre optionnel

28. //pour gérer le temps pour des processeurs plus rapides.

29. //Ce paramètre n’est plus nécessaire avec les nouvelles versions

30. DHT dht(DHTPIN, DHTTYPE);

32. void setup() {

33. Serial.begin(9600);

34. Serial.println(F("DHTxx test!"));

36. dht.begin();

37. }

39. void loop() {

40. // Attendre quelques secondes entre les mesures

41. delay(2000);

43. // La lecture de la température ou du taux d’humidité prend 250 millisecondes!

44. // Les mesures peuvent prendre jusqu’à 2 secondes (c’est un capteur très lent)

45. // Lecture du taux d’humidité

46. float h = dht.readHumidity();

47. // lecture de la température en degrés Celsius (par défaut)

48. float t = dht.readTemperature();

49. // lecture de la température en degrés Fahrenheit (le paramètre isFahrneheit est égal à true)

50. float f = dht.readTemperature(true);

52. //  Vérification si les lectures ont échoué : si c’est le cas, on réessaie une mesure.

53. if (isnan(h) || isnan(t) || isnan(f)) {

54. Serial.println(F("Failed to read from DHT sensor!"));

55. return;

56. }

58. // Calcul de la chaleur ressentie en Fahrenheit (par défaut)

59. float hif = dht.computeHeatIndex(f, h);

60. // Calcul de la chaleur ressentie en Celsius (par défaut) (paramètre isFahrenheit est égal à false)

61. float hic = dht.computeHeatIndex(t, h, false);

63. Serial.print(F("Humidity: "));

64. Serial.print(h);

65. Serial.print(F("%  Temperature: "));

66. Serial.print(t);

67. Serial.print(F("°C "));

68. Serial.print(f);

69. Serial.print(F("°F  Heat index: "));

70. Serial.print(hic);

71. Serial.print(F("°C "));

72. Serial.print(hif);

73. Serial.println(F("°F"));

74. }

Vous devriez obtenir sur l’écran quelque chose de similaire à l’image ci-dessous :

Ce programme paraît long, mais si on y regarde de plus près, il est constitué quasi exclusivement de commentaires : le constructeur a donné toutes les informations utiles à une bonne utilisation du capteur.

Étudions les quelques lignes intéressantes qui font fonctionner notre capteur.

Ligne 8 : on inclut la bibliothèque dont on a besoin.

Ligne 10 : on indique la broche de la carte sur laquelle est connectée la broche de données du capteur.

Lignes 15 à 17 : le programme test est le même pour trois versions du capteur (DHT11, DHT 22 et DHT21), il suffit de « décommenter » la ligne qui correspond à la version du capteur utilisé c’est-à-dire enlever les deux barres (par défaut le capteur DHT22 est sélectionné).

Ligne 30 : on crée un objet nommé dht lié à la bibliothèque DHT et on l’initialise.

Ligne 33 : initialisation de la liaison avec le moniteur série à la vitesse de 9600 bauds.

Ligne 34 : on écrit dans le moniteur série le texte « DHTxx test ! ». La syntaxe utilisée est légèrement différente de ce qu’on a vu jusqu’à présent. En termes de programmation pure, cette écriture ne fait aucune différence, le texte sera écrit de la même manière. Par contre, l’intérêt de cette syntaxe est de diminuer l’utilisation de la mémoire vive par le programme ce qui est un atout non négligeable pour des gros programmes.

Je vous incite donc dorénavant à utiliser la syntaxe :

syntaxe texte F

Objet.print(F("Mon texte"));

Plutôt que :

syntaxe texte sans F

Objet.print("Mon texte");

Ligne 36 : on démarre le processus de mesure du capteur.

Ligne 45 : on mesure le taux d’humidité à l’aide de l’instruction dht.readHumidity() que l’on stocke dans une variable de type float (nombre à virgule) nommée h.

Ligne 47 : on mesure la température en degrés Celsius à l’aide de l’instruction dht.readTemperature() que l’on stocke dans une variable de type float (nombre à virgule) nommée t.

Ligne 49 : on mesure la température en degrés Fahrenheit à l’aide de l’instruction dht.readTemperature(true) que l’on stocke dans une variable de type float (nombre à virgule) nommée f.

Ligne 52 : on teste avec l’instruction if si une des variables h, t ou f (les deux barres verticales || signifient « ou ») n’est pas un chiffre à l’aide de la commande isnan (isnan = is not a number) : si c’est le cas, il y a donc une erreur de lecture dans les lignes précédentes et on renvoie un texte (ligne 53) pour notifier une erreur à l’utilisateur.

Ligne 58 : on convertit la valeur de la température en Fahrenheit réelle en température ressentie à l’aide de l’instruction dht.computeHeatIndex() à partir de la mesure de la température f et de l’humidité h → c’est pour cette raison qu’on a testé précédemment (ligne 52) si les mesures ont bien été faites pour éviter une erreur dans l’exécution du programme.

Ligne 60 : on convertit la valeur de la température en degrés Celsius en température ressentie (le 3e paramètre de l’instruction dht.computeHeatIndex() est false pour dire qu’on n’utilise pas la température en Fahrenheit).

Lignes 62 à 72 : on affiche les résultats des mesures.

V-C-2 - Un capteur de luminosité, la photorésistance

Une photorésistance est une résistance particulière dont la valeur dépend de la lumière qu’elle reçoit : elle fait partie de la famille des capteurs dits résistifs. Son symbole normalisé est

Dans l’obscurité, la résistance est grande, typiquement de l’ordre de , tandis qu’elle est « faible » en pleine lumière (de l’ordre de ).

Pour visualiser le niveau de luminosité, il faut donc pouvoir remonter à la résistance de ce composant ce qui n’est pas aisé dans un circuit électrique. Pour ce faire, on utilise un montage particulier, peu compliqué, mais qui fait appel à quelques notions de base en électricité et en mathématiques : mais ne commencez pas à avoir des sueurs froides si ces mots vous rendent malades, tout va très bien se passer !!

Avant d’aborder le montage réel, je vais détailler le principe avec un montage plus simple comprenant une alimentation et deux résistances en série dont on connaît les valeurs R_1 et R_2.

Comme le montre le schéma, les deux résistances sont parcourues par le même courant : il est faux de dire que la première résistance va « user » du courant diminuant d’autant le courant dans la seconde résistance. En réalité, la première résistance va consommer de l’énergie électrique : c’est d’ailleurs ça qui est facturé par le prestataire d’énergie.

Pour les plus tatillons, il serait plus juste de dire que le courant I qui traverse les deux résistances est le même, car le courant qui passe dans les appareils de mesure est négligeable par rapport à celui des résistances du circuit électrique lui-même (on dit que l' impédance d’entrée des appareils est très grande par rapport aux résistances du circuit).

On peut utiliser la loi d’additivité des tensions dans ce circuit .

Comme les tensions U_1 et U_2 sont celles aux bornes des résistances, on peut utiliser la loi d’Ohm qui s’écrit respectivement et → la loi d’additivité des tensions se réécrit donc , relation qu’on peut écrire également .

En prenant cette expression de l’intensité I dans le circuit, on peut écrire la loi d’Ohm pour chaque résistance de manière un peu différente, mais complément équivalente et  : la tension aux bornes de chaque résistance ne dépend donc que de la tension d’alimentation U et de la valeur des résistances dans le circuit : la tension aux bornes de chaque résistance est donc « divisée » par un facteur qui ne dépend que de la valeur des résistances, c’est pour cette raison que ce circuit est appelé pont diviseur de tension.

Quelques exemples numériques :

pour U=5 V (tension de référence de la carte Arduino) et → → on a divisé la tension par 2.

En prenant et , on arrive à U_1=1,67 V et U_2=3,33 V.

Si maintenant mon circuit comporte une résistance de valeur connue et une résistance inconnue, il suffit de mesurer une des deux tensions (U_1 ou U_2) aux bornes d’une des résistances pour remonter à la valeur de la résistance inconnue: c’est exactement ce qu’on l’on fait avec un capteur de type résistif comme la photorésistance.

On alimente à l’aide de la tension de référence 5 V de la carte Arduino un montage constitué d’une résistance de valeur R_2 connue et de notre capteur de résistance R_1 inconnue : on connecte à l’aide d’un câble une entrée analogique de la carte au point commun aux deux résistances pour mesurer la tension aux bornes de la résistance connue tel qu’indiqué sur le schéma ci-dessous que vous pouvez reproduire :

Pour tester ce montage, téléversez le programme ci-dessous :

test photoresistance

1. /* Programme pour mesurer la résistance aux bornes d'une

2. *  photorésistance permettant d'avoir une évolution de la

3. *  luminosité ambiante

4. */

6. const byte mesureCapteur = 0;

7. const unsigned long flux = 9600;

8. const unsigned delai = 1000;

9. //const unsigned resistConnue = 10000; //valeur en ohm

11. void setup() {

12. Serial.begin(flux) ;

14. }

15. void loop() {

16. int valeurCapteur = analogRead(mesureCapteur);

17. float valeurTension = valeurCapteur * 5.0 / 1023.0;

18. //float valeurResistance = (5.0 / valeurTension – 1) * resistConnue;

19. Serial.println(valeurCapteur);

20. //Serial.println(valeurResistance, 0);

21. delay(delai);

22. }

En passant votre main devant le capteur, vous devriez voir la valeur affichée dans le moniteur série qui diminue.

Le programme (je ne parle pas pour l’instant des lignes en commentaires) est très ressemblant à ce que l’on a déjà vu : on lit la valeur analogique (ligne 16) que l’on convertit en tension (ligne 17) avant de l’afficher (ligne 19).

Avant de revenir sur les lignes commentées du programme précédent, je précise qu’ici j’ai fait le choix de suivre l’évolution de la luminosité au travers de la tension aux bornes de la résistance connue R2, car elles varient toutes deux de la même manière.

On ne peut pas utiliser directement la résistance de la photorésistance pour suivre l’évolution de la luminosité, car comme je l’ai précisé un peu plus haut, la résistance et la luminosité varient en sens inverse : grande résistance faible luminosité et faible résistance haute luminosité.

En parlant de luminosité, je fais un petit abus de langage, car même si la résistance dépend de la luminosité, on ne peut pas remonter directement à la valeur « réelle » de la luminosité, car il y a trop de paramètres qui rentrent en ligne de compte : on peut simplement constater l’évolution au cours du temps.

Si vous souhaitez voir l’évolution de la luminosité au travers de la résistance de la photorésistance, vous pouvez utiliser les lignes de code que j’ai rajoutées en commentaires :

Ligne 9 : valeur de la résistance connue en Ohms.

Ligne 18 : à partir de la relation donnant U_2 en fonction des grandeurs U, R_1 et R_2 on peut remonter à la valeur de la résistance R_1 qui correspond à la valeur de la résistance de la photorésistance dans la luminosité ambiante.

Ligne 19 : il faut mettre en commentaire (ou supprimer) cette ligne pour éviter d’afficher la valeur de la tension.

On peut améliorer le code précédent en mémorisant les valeurs minimales et maximales de la luminosité.

ldr min et max

1. /*

2. * Code d'exemple pour une photorésistance.

3. */

5. //déclaration des variables

6. const unsigned long flux = 9600; //flux de la liaison série

7. const unsigned int delai = 5000; //délai entre deux mesures en millisecondes

8. int minval = 1023; //valeur minimale de départ

9. int maxval = 0; //valeur maximale de départ

11. //définition des broches utilisées dans le programme

12. const byte mesureCapteur = 0;   // sélection de la borne A0

15. // Fonction setup(), appelée au démarrage de la carte Arduino

16. void setup()

17. {

18. // Initialise la communication avec le PC

19. Serial.begin(flux);

20. }

22. // Fonction loop(), appelée continuellement en boucle tant que la carte Arduino est alimentée

23. void loop()

24. {

25. // Mesure la tension sur la broche A0

26. int valeur = analogRead(mesureCapteur);

27. if (valeur > maxval)

28. {

29. maxval = valeur;

30. }

31. if (valeur < minval)

32. {

33. minval = valeur ;

34. }

36. // Envoi la mesure au PC pour affichage et attends 250 ms

37. Serial.print(F("valeur actuelle : "));

38. Serial.print(valeur);

39. Serial.print(F(" ; valeur maximale : "));

40. Serial.print(maxval);

41. Serial.print(F(" ; valeur minimale : "));

42. Serial.println(minval);

43. delay(delai);

44. }

Le code proposé n’est pas beaucoup plus compliqué que ce que nous avons vu.

Ligne 8 : pour stocker la valeur minimum, il faut partir de la valeur maximale mesurable sur la broche.

Ligne 9 : logique similaire à la précédente sauf qu’ici on doit partir de la valeur minimale mesurable.

Ligne 27 ou 31 : on teste si la valeur mesurée est supérieure (ou inférieure) à la valeur stockée maximale (ou minimale) → si c’est le cas, on met à jour la valeur maximale (ou minimale) par la valeur mesurée.

V-C-3 - Ajout d’une fonction pour automatiser une tâche

Dans la partie précédente, le dernier programme donnait une structure de code pour stocker et conserver (du moins tant que la carte est allumée) les valeurs minimale et maximale relevées. Dans le cas de la mesure de la luminosité, on pouvait très bien faire apparaître ces lignes directement dans le code principal sans que cela nuise réellement à sa compréhension. Mais dans notre projet global, on souhaite également stocker les valeurs minimale et maximale relevées pour la température et le taux d’humidité, voire, si le cœur vous en dit, de toute autre grandeur que vous estimerez importante (la pression…) : on ne va pas pour chacun de ces paramètres rajouter les 8 lignes qui seront identiques pour chacun hormis les noms de variables utilisées.

Pour automatiser certaines tâches redondantes, en programmation, on peut créer nos propres fonctions : il s’agit de bouts de code, écrits une seule fois dans le programme, que l’on pourra appeler à partir du nom de la fonction que l’on aura choisie.

Comme d’habitude, je vais expliquer le principe de construction d’une fonction à l’aide d’un exemple simple avant de l’adapter à notre projet.

Mettons que vous souhaitiez dans votre programme faire afficher cinq fois les mêmes lignes de texte (par exemple les premières lignes de code que nous avions vues dans ce cours avec « Enfin mon » « premier programme Arduino ! » sur deux lignes séparées). Pour cela, il faudra donc écrire dix lignes de code (cinq blocs de deux lignes identiques) dans notre programme : évidemment, plus la séquence répétée est longue et plus le code principal sera long ! Au lieu de cela, on va créer une fonction, que l’on va nommer affiche_texte, qui contiendra nos lignes de code d’affichage et que l’on appellera quand on le souhaite dans le programme principal.

Téléversez le programme suivant (pas besoin de circuit électrique pour cet essai) :

principe fonction

1. /*Programme pour voir le principe d'écriture d'une fonction

2. *

3. */

5. // déclaration des variables utilisées dans le programme

6. const unsigned long flux = 9600;

8. void setup() {

9. // put your setup code here, to run once:

10. Serial.begin(flux);

11. }

13. void loop() {

14. // put your main code here, to run repeatedly:

15. affiche_texte();

16. delay(1000);

17. affiche_texte();

18. delay(2000);

19. affiche_texte();

20. delay(3000);

21. affiche_texte();

22. delay(4000);

23. affiche_texte();

24. delay(5000);

25. }

27. void affiche_texte()

28. {

29. Serial.print("Enfin mon premier ");

30. Serial.println("programme Arduino !");

31. }

Pour l’explication de ce programme, je vais commencer par la fin :

Ligne 27 : on définit une fonction à l’aide de l’instruction void() suivie par le nom de la fonction et des parenthèses (on verra par la suite qu’il peut y avoir des éléments dans les parenthèses).

On utilise ici l’instruction, void() car la fonction ne renvoie pas de valeur au programme : elle ne fait qu’exécuter une série d’instructions (ici l’affichage de lignes de texte). On verra par la suite une autre façon de procéder quand on veut renvoyer une valeur au programme.

Vous avez sans doute remarqué que la syntaxe est la même que celle de la partie setup() et loop() du programme ce qui est logique puisqu’il s’agit tout simplement de fonctions (à la différence de la fonction que l’on crée, ces deux fonctions sont spécifiques à Arduino et obligatoires dans tout programme !).

Lignes 24 et 25 : les lignes de code de la fonction sont écrites entre les accolades { et }, de la même manière qu’on le fait pour le setup() et le loop().

Lignes 15, 17, 19, 21 et 23 : on appelle la fonction par son nom (ici affiche_texte) avec les parenthèses () → le programme va donc exécuter les actions de la fonction avant de revenir aux lignes du programme principal.

Schématiquement, le programme effectue les opérations dans l’ordre suivant :

Ligne du programme

Action effectuée

15

Appel de la fonction affiche_texte

29 et 31

Affichage du texte

16

Attente 1 seconde

17

Appel de la fonction affiche_texte

29 et 31

Affichage du texte

18

Attente 2 secondes

19

Appel de la fonction affiche_texte

29 et 31

Affichage du texte

20

Attente 3 secondes

21

Appel de la fonction affiche_texte

29 et 31

Affichage du texte

22

Attente 4 secondes

23

Appel de la fonction affiche_texte

29 et 31

Affichage du texte

24

Attente 5 secondes

Dans le programme, l’instruction delay() figure après chaque affichage : il est donc dommage de ne pas l’inclure dans la fonction affiche_texte. Par contre, il faut pouvoir avertir le programme de la valeur du délai souhaitée. C’est ici que les paramètres que j’évoquais précédemment vont nous servir : si vous avez suivi l’explication du programme principal, vous aurez compris qu’on va utiliser les parenthèses qui suivent le nom de la fonction !
On modifie donc un peu le programme précédent :

fonction avec parametre

1. /*Programme pour voir le principe d'écriture d'une fonction

2. *

3. */

5. // déclaration des variables utilisées dans le programme

6. const unsigned long flux = 9600;

8. void setup() {

9. // put your setup code here, to run once:

10. Serial.begin(flux);

11. }

13. void loop() {

14. // put your main code here, to run repeatedly:

15. affiche_texte(1000);

16. affiche_texte(2000);

17. affiche_texte(3000);

18. affiche_texte(4000);

19. affiche_texte(5000);

20. }

22. void affiche_texte(int delai)

23. {

24. Serial.print("Enfin mon premier ");

25. Serial.println("programme Arduino !");

26. delay(delai);

27. }

Ligne 22 : la définition de la fonction indique qu’il s’agit d’une fonction qui ne renvoie aucune valeur au programme (instruction void), mais qui demande au programme une valeur de type int comme entrée.

Lignes 15 : on appelle la fonction avec un paramètre chiffré entier 1000 ce qui signifie que la fonction va être exécutée en prenant comme valeur pour la variable delai de 1000 dans la fonction → concrètement, la ligne affiche_texte(1000) va donc afficher le texte puis faire un délai de 1000 millisecondes avant que le programme ne poursuive son exécution à la ligne 16.

Ici, la représentation schématique de fonctionnement du programme est celui-ci :

Ligne du programme

« Lieu » de l’action

Action effectuée

15

Programme principal

Appel de la fonction affiche_texte → la variable delai de la fonction prend pour valeur 1000

24 et 25

Fonction affiche_texte

Affichage du texte

26

Attente de delai (soit 1) seconde

16

Programme principal

Appel de la fonction affiche_texte → la variable delai de la fonction prend pour valeur 2000

24 et 25

Fonction affiche_texte

Affichage du texte

26

Attente de delai (soit 2) secondes

17

Programme principal

Appel de la fonction affiche_texte → la variable delai de la fonction prend pour valeur 3000

24 et 25

Fonction affiche_texte

Affichage du texte

26

Attente de delai (soit 3) secondes

18

Programme principal

Appel de la fonction affiche_texte → la variable delai de la fonction prend pour valeur 4000

24 et 25

Fonction affiche_texte

Affichage du texte

26

Attente de delai (soit 4) secondes

19

Programme principal

Appel de la fonction affiche_texte → la variable delai de la fonction prend pour valeur 5000

24 et 25

Fonction affiche_texte

Affichage du texte

26

Attente de delai (soit 5) secondes

On peut encore aller un peu plus loin dans les paramètres sans toucher la structure de la fonction affiche_texte, pour voir toutes les possibilités offertes par l’utilisation d’une fonction :

fonction parametre boucle

1. /*Programme pour voir le principe d'écriture d'une fonction

2. *

3. */

5. // déclaration des variables utilisées dans le programme

6. const unsigned long flux = 9600;

8. void setup()

9. {

10. // put your setup code here, to run once:

11. Serial.begin(flux);

12. }

14. void loop()

15. {

16. // put your main code here, to run repeatedly:

17. for (int i = 1000; i < 6000; i = i + 1000)

18. {

19. affiche_texte(i);

20. }

21. }

23. void affiche_texte(int delai) {

24. Serial.print("Enfin mon premier ");

25. Serial.println("programme Arduino !");

26. delay(delai);

27. }

Ce programme permet de montrer que le paramètre qui intervient dans une fonction lorsqu’elle est appelée dans le programme principal (ligne 19) peut être également une variable et pas forcément une valeur comme dans le programme précédent. Bon, c’est vrai je l’avoue, l’utilisation dans le cas précis du programme d’affichage d’une variable pour ajouter un délai incrémenté n’est pas très utile, mais il permet de comprendre facilement le principe et de voir l’étendue des possibilités et la facilité d’utilisation des fonctions dans un programme.

J’attire votre attention sur une notion que j’ai abordée brièvement dans un point précédent (voir Amélioration des mesures) : la portée des variables. Lors de la définition de la fonction affiche_texte (ligne 23 du dernier programme), la variable en paramètre (delai, de type int) n’a d’existence que dans les lignes de code de la fonction : si on utilisait cette variable dans le programme principal, on obtiendrait une erreur, car la variable n’est pas déclarée au préalable.

Dernier point à relever : il n’y a aucune obligation d’utiliser le même nom de variable entre le paramètre lors de l’appel de la fonction (affiche_texte(i) dans l’exemple du programme) et le paramètre lors de sa définition (affiche_texte(int delai) dans le programme).

Maintenant que l’on dispose des bases pour écrire des fonctions, je vais présenter le cas de fonctions qui doivent renvoyer une valeur au programme en s’appuyant sur le projet de mini station météo. On aimerait pouvoir automatiser le stockage des valeurs minimale et maximale de chaque grandeur mesurée (humidité, température, luminosité). La fonction que l’on va définir devra donc recevoir en entrée la valeur courante mesurée et la valeur minimale (ou maximale) ; elle renverra en sortie la valeur minimale (ou maximale) qui pourra être différente ou non de la valeur d’entrée.

On va partir sur du concret : définissons la fonction qui renvoie la valeur minimale pour le taux d’humidité. Les paramètres d’entrée sont de type float tout comme la valeur de sortie. On définira donc la fonction de la manière suivante :

minimale humidite

1. float mini_humidite(float valeur, float mini_mesure)

2. {

3. float mini = mini_mesure;

4. if (valeur < mini)

5. {

6. mini = valeur;

7. }

8. return mini;

9. }

Expliquons un peu le code de cette fonction.

Ligne 1 : on définit la fonction en indiquant d’abord le type de valeur retournée (float), son nom (mini_humidite) et entre parenthèses les paramètres avec leur type (float valeur, float mini_mesure).

Ligne 3 : on définit une nouvelle variable, spécifique à la fonction (et donc non utilisable ailleurs dans le programme principal), qui contient initialement la valeur minimale indiquée comme paramètre de la fonction.

Ligne 4 à 7 : on cherche à savoir si la valeur courante est inférieure à la valeur minimale ou non → si c’est le cas, on met à jour la variable mini avec la valeur courante.

Ligne 8 : la commande return termine la fonction et renvoie la valeur qui suit au programme principal.

Et voilà, si dans le programme principal on appelle la fonction de la manière suivante :

test fonction min

float valeur = 20.60;
float mini = 23.1;
float test = mini_humidite(valeur, mini);
Serial.print(test);

On obtiendra le résultat suivant (pour changer un peu, j’ai pris l’écran du moniteur série présent dans Tinkercad) :

qui est bien conforme à ce que l’on attendait puisque 20,60 est bien inférieur à 23,1.

Désormais, c’est bien, on peut savoir si une mesure du taux d’humidité est le minimum mesuré ou non, mais qu’en est-il pour la température ? Si on regarde, le code de la fonction, dans le détail, on se rend compte qu’il n’est pas spécifique au taux d’humidité et reste très général. La seule différence réside dans les paramètres d’entrée de la fonction :

- pour le taux d’humidité, l’appel de la fonction sera :

mini humidite

float test = mini_humidite(valeur_humid, mini_humid);

- Pour la température, l’appel se fera avec :

mini temperature

float test = mini_humidite(valeur_temp, mini_temp);

Alors, bien évidemment, dans ce contexte, le nom de la fonction n’est pas adapté : il est en effet bizarre de calculer le minimum de mesures de température à l’aide de la fonction mini_humidite → on utilisera donc plutôt un nom plus généraliste, par exemple min_valeur.

Si on prend tout en compte, on peut réécrire le programme de mesure du taux d’humidité et de la température avec affichage de la valeur mesurée, du minimum et du maximum.

Pour le principe de la mesure du taux d’humidité et de la température, je vous renvoie au point Le capteur DTH11.

Pour la fonction maximum, le principe est similaire à celle pour la fonction minimum.

humidite temperature min max

1. /*Programme qui affiche la température et le taux d'humidité

2. * mesurés à l’aide d'un capteur DHT11 et qui affiche également

3. *les valeurs minimales et maximales de ces deux grandeurs

4. */

6. //Chargement des bibliothèques nécessaires

7. #include "DHT.h" //bibliothèque du capteur

9. //Définition des broches utilisées

10. #define DHTPIN 2     // broche de mesure du DHT11

12. //Définition du type de capteur

13. #define DHTTYPE DHT11   // DHT 11

15. //Déclaration des constantes du programme

16. const unsigned long flux = 9600; //débit de la liaison série

17. const unsigned long delai = 2000; //délai entre deux mesures

19. //Déclaration des variables du programme

20. float valeur_humid, valeur_temp; //valeurs courantes

21. float min_humid = 100; //valeur prise dans la DATASHHET

22. float max_humid = 0; //valeur prise dans la DATASHHET

23. float min_temp = 80;  //valeur prise dans la DATASHHET

24. float max_temp = -40;  //valeur prise dans la DATASHHET

26. // Initialisation du capteur

27. DHT dht(DHTPIN, DHTTYPE);

29. void setup()

30. {

31. Serial.begin(9600);

32. dht.begin();

33. }

35. void loop()

36. {

37. // Délai entre deux séries de mesures

38. delay(delai);

40. valeur_humid = dht.readHumidity();

41. valeur_temp =  dht.readTemperature();

43. // Test d'une éventuelle erreur de mesure

44. if (isnan(valeur_humid) || isnan(valeur_temp))

45. {

46. Serial.println(F("Erreur de mesure!"));

47. return;

48. }

50. min_humid = min_valeur(valeur_humid, min_humid);

51. max_humid = max_valeur(valeur_humid, max_humid);

53. min_temp = min_valeur(valeur_temp, min_temp);

54. max_temp = max_valeur(valeur_temp, max_temp);

56. //Affichage des résultats

57. Serial.print(F("Humidité: "));

58. Serial.print(valeur_humid);

59. Serial.print(F("%  Minimum : "));

60. Serial.print(min_humid);

61. Serial.print(F("%  Maximum : "));

62. Serial.print(max_humid);

63. Serial.println(F("%"));

65. Serial.print(F("Température: "));

66. Serial.print(valeur_temp);

67. Serial.print(F("°C  Minimum : "));

68. Serial.print(min_temp);

69. Serial.print(F("°C  Maximum : "));

70. Serial.print(max_temp);

71. Serial.println(F("°C"));

72. Serial.println("\n");

73. }

75. float min_valeur(float valeur, float mini_mesure)

76. {

77. float mini = mini_mesure;

78. if (valeur < mini)

79. {

80. mini = valeur;

81. }

82. return mini;

83. }

85. float max_valeur(float valeur, float maxi_mesure)

86. {

87. float maxi = maxi_mesure;

88. if (valeur > maxi)

89. {

90. maxi = valeur;

91. }

92. return maxi;

93. }

Au passage, vous voyez que l’utilisation de fonctions dans notre code facilite grandement la lecture du programme en diminuant les lignes de code dans le programme principal.

On a déjà bien avancé : avec le capteur DHT11 (ou équivalent), on arrive à obtenir une bonne partie des informations souhaitées pour notre mini station météo. Désormais, il faut intégrer la partie liée à la mesure de la luminosité. Et là, on se retrouve face à un cas de conscience, car, rappelons-le, nous ne mesurons pas la vraie luminosité, mais la valeur de la tension aux bornes d’une photorésistance dans un montage pont diviseur de tension : et cette tension n’est pas du même type de variables que celui pour le taux d’humidité et la température. Quand le capteur DHT11 permettait d’obtenir des valeurs de type float, la tension est de type int. Pour remédier à ce petit problème, je vous propose deux solutions simples à mettre en œuvre :

- convertir au préalable la valeur int en float à l’aide de l’instruction float(valeur_tension) → pour le reste, il suffit de conserver le même type de formalisme que pour le taux d’humidité et la température → cette solution est la plus facile à mettre en œuvre ;

- faire la moyenne de plusieurs valeurs de tension (comme vu au point Amélioration des mesures), la variable moyenne étant de type float pour prendre en compte tous les cas possibles → cette solution a l’avantage d’améliorer la précision du résultat et donc de rendre plus fiable les données de la station météo. Pour cette solution, le plus simple est de créer une fonction calcul_moyenne qui ne prend aucun paramètre d’entrée (la boucle de mesures se fera dans la fonction) et qui renvoie la valeur de la moyenne.

Je vous laisse le choix de la méthode à utiliser, je ne pense pas qu’il n’y ait de grande difficulté puisque vous avez réussi à en arriver jusque-là de ce cours !

V-C-4 - Affichage à l’aide d’un écran

En dernier point de programmation, j’aimerais vous parler d’un composant assez utilisé dans le monde de la programmation embarquée : l’ajout d’un écran LCD pour lire les données. J’ai déjà commencé à en parler lors du point sur les bibliothèques. Comme nous avons un certain nombre d’informations à afficher, j’ai opté pour un écran comportant 4 lignes de 20 caractères.

Pour afficher du texte, un écran LCD a besoin de plusieurs éléments :

- un branchement électrique : selon les modèles, ils ont besoin de 12 ou 16 bornes (transmission directe des informations) ou de 4 bornes (programmation via le protocole I^2C) → c’est ce dernier que j’aborderai ;

- charger une bibliothèque (ce point a déjà été vu au Utilisation de bibliothèques) ;

- définir un objet lcd et l’initialiser ;

- initialiser la communication ;

- régler certains paramètres et en particulier placer le curseur à l’endroit souhaité.

Pour le branchement, il est utile de choisir un écran LCD qui accepte le protocole I^2C (Inter Integrated Circuit) dont je n’aborderai pas le principe de la communication.

Pour les personnes intéressées, vous pouvez consulter l’article de f-leb qui détaille ce protocole : https://f-leb.developpez.com/tutoriels/arduino/bus-i2c/

L’intérêt est qu’avec 4 connexions, on peut faire fonctionner un écran LCD (ou d’autres capteurs d’ailleurs) : une alimentation (5 V de la carte Arduino), le zéro (GND de la carte), une connexion pour les données (SDA, Serial Data) et une connexion pour synchroniser et cadencer les échanges de données avec un signal d’horloge (SCL, Serial Clock). Sur une carte Arduino Uno, ces deux dernières connexions sont respectivement la broche A4 et A5.

Le chargement de la bibliothèque, la définition de l’objet lcd et son initialisation ont été vus au point Utilisation de bibliothèques).

Pour en apprendre davantage sur les instructions spécifiques de l’écran LCD, le plus simple est de faire comme on l’a déjà fait, prendre le programme exemple fourni avec le matériel. Dans mon cas, le programme est le suivant :

ecran lcd

1. #include <Wire.h>

2. #include<LiquidCrystal_I2C.h>

4. //-----Adressage matériel -----

5. // En cas de non-fonctionnement, mettez la ligne 8 en

6. // commentaire et retirez le commentaire à la ligne 9.

8. LiquidCrystal_I2C lcd(0x27, 20, 4);

9. //LiquidCrystal_I2C lcd(0x3F,20,4);

11. void setup()

12. {

13. lcd.init(); // initialisation de l'afficheur

14. }

16. void loop()

17. {

18. lcd.backlight();

20. // Envoi du message

21. lcd.setCursor(0, 0);

22. lcd.print(" Test ecran");

23. lcd.setCursor(0, 1);

24. lcd.print(" ");

25. lcd.setCursor(0, 2);

26. lcd.print(" I2C Serial");

27. lcd.setCursor(0, 3);

28. lcd.print(" LCD");

29. }

Lignes 1 et 2 : les bibliothèques à inclure (et à avoir installé au préalable bien sûr !) ;

Ligne 4 à 9 : on suit les instructions qui sont indiquées pour créer l’objet lcd et l’initialiser ;

Ligne 13 : on initialise la communication avec l’écran LCD.

Ligne 18 : on active le rétroéclairage → cette ligne n’est pas obligatoire, elle dépend de votre envie et de votre écran !

Ligne 21 : on place le curseur de l’écran (lcd.setCursor) au caractère n° 0 et la ligne 0 (en informatique, les numéros de lignes et de colonnes commencent généralement à zéro.

Ligne 22 : on affiche sur le LCD (lcd.print()) un texte → vous remarquerez la similitude avec la syntaxe d’affichage dans le moniteur série (Serial.print()).

Ligne 23 : on place le curseur de l’écran (lcd.setCursor) au caractère n° 0 et la ligne n°1 (donc située sur la 2e ligne).

Je dois également mentionner une instruction très utile lcd.clear() qui permet d’effacer l’écran.

Bien sûr, il est tout à fait possible, voire conseillé de définir une fonction affichage_ecran qui prendra en paramètre d’entrée le numéro de colonne et de ligne ainsi que le texte à afficher : le code de cette fonction permettra de placer le curseur à la position définie et d’afficher le texte correspondant.

La syntaxe pour l’écran que j’ai choisi n’est pas spécifique à ce modèle : beaucoup d’écrans utilisent les mêmes instructions. Parfois, il y a de petits changements, mais globalement vous ne devriez pas trop avoir de problèmes. En cas de doutes, il faut se référer à la documentation du constructeur qui contient toutes les informations utiles.

V-C-5 - Le programme pour la mini station météo

Avec tous les derniers points que l’on a vus, on peut désormais écrire le programme complet associé au montage électrique suivant :

Il permet de mesurer le taux d’humidité, la température et la luminosité ambiants, de stocker les valeurs minimale et maximale de chacune de ces grandeurs et d’afficher les résultats sur un écran LCD (le composant noir en bas à droite est généralement directement fixé à l’écran LCD avec branchement en I^2C).

Je vous invite à essayer par vous-même à écrire ce programme et le tester. Si cela ne marche pas, vous pouvez jeter un œil sur ma proposition qui respecte le cahier des charges :

programme complet

1. /*Programme qui affiche la température, le taux d'humidité

2. * et la luminosité mesurés à 'laide d'un capteur DHT22 et

3. * d'une photorésistance. Il affiche également les valeurs

4. * minimales et maximales de ces deux grandeurs

5. */

7. //Chargement des bibliothèques nécessaires

8. #include "DHT.h" //bibliothèque du capteur DHT11

9. #include <Wire.h>

10. #include<LiquidCrystal_I2C.h> //pour l'écran LCD branché en I2C

12. //Définition des broches utilisées

13. #define DHTPIN 2     // broche de mesure du DHT11

14. const byte lumin = 0; // broche pour l’évolution de la luminosité

16. //Définition du type de capteur

17. #define DHTTYPE DHT11   // DHT 11

20. //Déclaration des constantes du programme

21. const unsigned long flux = 9600; //débit de la liaison série

22. const unsigned long delai = 200; //délai entre deux mesures

24. //Déclaration des variables du programme

25. float valeur_humid, valeur_temp, valeur_lumin; //valeurs courantes

26. float min_humid = 100; //valeur prise dans la DATASHEET

27. float max_humid = 0; //valeur prise dans la DATASHEET

28. float min_temp = 80;  //valeur prise dans la DATASHEET

29. float max_temp = -40;  //valeur prise dans la DATASHEET

30. float min_lumin = 1023.0; //valeur max mesurable par Arduino

31. float max_lumin = 0.0; //valeur min mesurable par Arduino

33. // Initialisation des composants

34. DHT dht(DHTPIN, DHTTYPE);

35. LiquidCrystal_I2C lcd(0x27, 20, 4); //à remplacer par la ligne suivante si problème d'écran

36. //LiquidCrystal_I2C lcd(0x3F, 20, 4);

38. void setup()

39. {

40. Serial.begin(flux);

41. dht.begin();

42. lcd.init();

43. }

45. void loop()

46. {

47. // Délai entre deux séries de mesures

48. delay(delai);

50. //activation du rétroéclairage

51. lcd.backlight();

53. valeur_humid = dht.readHumidity();

54. valeur_temp =  dht.readTemperature();

55. valeur_lumin = analogRead(lumin);

57. // Test d'une éventuelle erreur de mesure sur capteur DHT22

58. if (isnan(valeur_humid) || isnan(valeur_temp))

59. {

60. //affichage dans le moniteur série

61. Serial.println(F("Erreur de mesure"));

62. affiche_texte(0, 0, "Erreur de mesure");

63. return;

64. }

66. min_humid = min_valeur(valeur_humid, min_humid);

67. max_humid = max_valeur(valeur_humid, max_humid);

69. min_temp = min_valeur(valeur_temp, min_temp);

70. max_temp = max_valeur(valeur_temp, max_temp);

72. min_lumin=min_valeur(valeur_lumin, min_lumin);

73. max_lumin=max_valeur(valeur_lumin, max_lumin);

75. //Affichage des résultats sur l'écran lcd

76. affiche_texte(0, 0, "Valeur ; Mini ; Maxi");

77. lcd.setCursor(1, 0); lcd.print(valeur_humid);

78. affiche_texte(1, 6, " ; ");

79. lcd.setCursor(1, 9); lcd.print(min_humid);

80. affiche_texte(1, 13, " ; ");

81. lcd.setCursor(1, 16); lcd.print(max_humid);

83. lcd.setCursor(2, 0); lcd.print(valeur_temp);

84. affiche_texte(2, 6, " ; ");

85. lcd.setCursor(2, 9); lcd.print(min_temp);

86. affiche_texte(2, 13," ; ");

87. lcd.setCursor(2, 16); lcd.print(max_temp);

89. lcd.setCursor(3, 0); lcd.print(valeur_lumin);

90. affiche_texte(1, 6, " ; ");

91. lcd.setCursor(1, 9); lcd.print(min_lumin);

92. affiche_texte(1, 13, " ; ");

93. lcd.setCursor(1, 16); lcd.print(max_lumin);

96. //Affichage des résultats dans le moniteur série

97. Serial.print(F("Humidité: "));

98. Serial.print(valeur_humid);

99. Serial.print(F("%  Minimum : "));

100. Serial.print(min_humid);

101. Serial.print(F("%  Maximum : "));

102. Serial.print(max_humid);

103. Serial.println(F("%"));

105. Serial.print(F("Température: "));

106. Serial.print(valeur_temp);

107. Serial.print(F("°C  Minimum : "));

108. Serial.print(min_temp);

109. Serial.print(F("°C  Maximum : "));

110. Serial.print(max_temp);

111. Serial.println(F("°C"));

112. Serial.println("\n");

114. Serial.print(F("Luminosité relative : "));

115. Serial.print(valeur_lumin);

116. Serial.print(F(" ;  Minimum : "));

117. Serial.print(min_lumin);

118. Serial.print(F(" ;  Maximum : "));

119. Serial.println(max_lumin);

120. Serial.println("\n");

122. //Affichage des résultats sur l'écran LCD

124. }

126. float min_valeur(float valeur, float mini_mesure)

127. {

128. float mini = mini_mesure;

129. if (valeur < mini)

130. {

131. mini = valeur;

132. }

133. return mini;

134. }

136. float max_valeur(float valeur, float maxi_mesure)

137. {

138. float maxi = maxi_mesure;

139. if (valeur > maxi)

140. {

141. maxi = valeur;

142. }

143. return maxi;

144. }

146. void affiche_texte(byte colonne, byte ligne, String texte)

147. {

148. lcd.setCursor(colonne, ligne);

149. lcd.print(texte);

150. }

VI - Les précautions pour éviter de détruire la carte Arduino voire plus

Tout composant électrique ou électronique possède ses propres caractéristiques (résistance par exemple), mais également ses limites. Dit autrement, un composant ne peut fonctionner correctement que dans une certaine plage de conditions : température, pression, humidité, tension d’alimentation, puissance, intensité… Certains de ces paramètres sont facilement évaluables et peuvent être facilement contrôlables : par exemple, un composant qui nécessite une tension d’alimentation comprise entre 3 V et 7 V n’aura aucun risque d’être endommagé par une trop forte tension puisque la carte Arduino est limitée à 5 V. Par contre, d’autres paramètres (et en particulier l’intensité) sont beaucoup plus difficiles à contrôler et nécessitent un examen plus attentif des montages électriques.

Le danger le plus courant est le court-circuit c’est-à-dire la circulation d’un courant important due à la connexion (in)volontaire de deux points d’un circuit de potentiel électrique différent reliés par une faible résistance.

Je vais prendre un cas concret à ne surtout pas reproduire :

Dans ce cas, la tension entre les deux points est de 5 V ; la résistance d’un fil est quasi nulle (mettons pour le calcul) : l’application de la loi d’Ohm donne un courant d’intensité I=U/R=50 A → la broche ne supportant que 40 mA et la carte 200 mA (au total sur l’ensemble des broches) ne vont pas faire long feu !

S’il y a bien une chose à retenir sur les précautions lors de l’utilisation d’une carte Arduino est de ne jamais relier un connecteur de tension haute (5 V) à un connecteur de tension basse (0 V) sans une résistance. Cette situation peut se produire de plusieurs manières différentes en reliant :

- bornes 5 V et GND ;

- bornes 3,3 V et GND ;

- bornes 5 V et 3,3 V ;

- borne 5 V et broche numérique programmée à l’état LOW ;

- borne GND et broche numérique programmée à l’état HIGH ;

- bornes numériques programmées l’une à l’état HIGH et l’autre à l’état LOW.

J’ajouterai également une situation peu courante, mais qui peut arriver : si vous posez votre carte Arduino sur une surface conductrice, vous allez relier les soudures des broches (situées en dessous de la carte) entre elles et donc provoquer un court-circuit.

Les trois premières situations ne peuvent que résulter d’une erreur humaine de branchement et peuvent donc être facilement empêchées par un examen attentif du circuit électrique.

Pour les trois dernières, on peut éviter de détériorer la carte en ajoutant systématiquement une résistance après une broche numérique pour diminuer l’intensité du courant électrique : d’accord, mais de quelle valeur doit-être la résistance pour protéger notre carte ?
Normalement, vous devriez pouvoir être capable de répondre sans avoir à lire la réponse puisque vous êtes désormais familier avec la loi d’Ohm !

La valeur minimale est de , mais il vaut mieux prévoir un peu plus et opter pour une résistance de .

Si on poursuit également sur les problèmes liés à l’intensité du courant électrique, il faut également veiller à ne pas dépasser 200 mA au total : même si sur chaque broche, la valeur maximale de 40 mA n’est pas atteinte, la carte ne peut pas débiter 200 mA sur l’ensemble des broches.

Même si l’intensité est le paramètre le plus dangereux pour la carte, il ne faut pas négliger les problèmes liés à la tension. Ainsi, il ne faut jamais connecter une tension supérieure à 5 V sur les ports d’entrée de la carte tout comme il ne faut pas brancher une source d’alimentation sur les broches « générateur » de la carte (5 V, 3,3 V, GND, Vin).

VII - Conclusion

À travers ce cours, nous avons vu les différentes étapes pour programmer une carte Arduino : écriture du programme, mise en place du montage électrique, téléversement du programme sur la carte, affichage des résultats.

Les quelques exemples de capteurs utilisés dans ce cours constituent la base des capteurs « grand public » (si preuve en est, ils figurent dans la quasi-totalité des kits tout-en-un pour Arduino) et peuvent paraître simplistes au premier abord. Mais en fait, quel que soit le capteur utilisé, la procédure reste la même : consultation de la datasheet (pour connaître les caractéristiques, le montage électrique associé), installation des bibliothèques associées (si nécessaire) puis téléversement du programme.

Il faut signaler que la documentation est très souvent, pour ne pas dire quasi exclusivement, en anglais et il vous faudra manier la langue de Shakespeare pour vous en sortir.

Désormais, il ne vous reste plus qu’à essayer et vous faire plaisir dans ce nouveau monde qui s’offre à vous, celui du DIY (Do It Yourself, faites-le vous-même)…

VIII - Remerciements

Ce cours a vu le jour suite à une formation auprès de mes collègues débutant dans l’utilisation d’une carte Arduino : ils ont joué les cobayes et m’ont permis de faire évoluer le déroulé, pas à pas, avec parfois des questions que certains utilisateurs aguerris ne se posent même plus tant les habitudes sont ancrées. Qu’ils soient remerciés pour la pertinence de leurs remarques et les quelques coquilles (sur les schémas en particulier).

Je remercie également les relecteurs techniques qui ont contribué à améliorer le contenu de ce cours : f-leb, Vincent Petit, Auteur (en particulier pour la partie sur l’installation sous Linux) et Naute (pour ses résultats sur la justesse et la fidélité à l’aide du traceur série).

Merci également à Claude Leloup qui a pris le temps de relire le cours pour supprimer les dernières fautes d’orthographe qui s’y trouvaient.

IX - Sitographie

IX-A - Quelques sites utiles

En tapant dans un moteur de recherche une requête avec Arduino, vous risquez de tomber sur une montagne de sites : il est parfois difficile de faire le tri. Pour vous aider dans ce choix, je vous conseille ces quelques sites :

- site officiel Arduino (déjà évoqué avec le forum notamment) : il est en anglais, sauf pour le forum qui existe en version francophone (beaucoup plus pratique !).

- la page dédiée du site developpez.com.

- Site mon-club-elec.fr (de Xavier Hinault) qui regorge d’applications, de conseils et surtout qui traduit beaucoup d’éléments du site officiel d’Arduino.

IX-B - Les enseignes spécialisées

Cette partie sera très brève, car vous n’aurez pas loin à aller pour trouver une enseigne spécialisée : en allant sur cette page, vous disposerez d’une liste (non exhaustive) des enseignes où vous pourrez vous procurer le matériel nécessaire pour vous équiper en capteurs, cartes…

N’hésitez pas à y proposer vos fournisseurs favoris !