=====TD pour promo 2 2023===== https://wokwi.com/projects/379096280288198657 =====sauvegarde TD pour promo 2 2023===== https://wokwi.com/projects/379101194322659329 =====Solution du début du TD===== https://wokwi.com/projects/345940987705557586 projet de abaté qui foirait et resolu: https://wokwi.com/projects/348671540256047699 Sauvegarde prof: https://wokwi.com/projects/345943311321662034 et cliquer sur la petite flêche à droite de Save, puis "save a copy" pour obtenir une autre URL! =====TD 2 Informatique Embarquée CESI===== Explication fournies lors du td en distanciel (prise de note): https://bvdp.inetdoc.net/files/cesi/cesitd2exp ====Informations perso pour le TD==== TODO: mettre les commentaires au format doxygen (ou dire aux étudiants de le faire) https://www.doxygen.nl/manual/docblocks.html Nous allons continuer à utiliser Wokwi pour ce TD, les consignes données en [[cesitd1]] et en [[cesitp1]] sont donc toujours valables. Commencez par vous authentifier sur https://wokwi.com/ puis créez un nouveau projet pour la carte Arduino UNO R3. Vous démarrerez ce TD avec ce fichier de code que vous remplierez au fur et à mesure du TD. ====Schéma du montage utilisé dans le TD==== {{https://bvdp.inetdoc.net/files/cesi/td2/schemawokwi_td2cesi.png}} Pour reproduire cette schématique dans votre projet, copier coller le contenu du fichier suivant dans l'onglet diagram.json en écrasant le contenu antérieur: { "version": 1, "author": "Bertrand Vandeportaele", "editor": "wokwi", "parts": [ { "type": "wokwi-arduino-uno", "id": "uno", "top": 128, "left": 20, "attrs": {} }, { "type": "wokwi-logic-analyzer", "id": "logic1", "top": 165.29, "left": 365.03, "attrs": {} }, { "type": "wokwi-led", "id": "leda", "top": 380.34, "left": 236.43, "attrs": { "color": "red", "label": "canal A" } }, { "type": "wokwi-slide-switch", "id": "swa", "top": 400.11, "left": 288.25, "rotate": 90, "attrs": {} }, { "type": "wokwi-led", "id": "ledb", "top": 430.34, "left": 236.43, "attrs": { "color": "red", "label": "canal B" } }, { "type": "wokwi-slide-switch", "id": "swb", "top": 450.11, "left": 288.25, "rotate": 90, "attrs": {} }, { "type": "wokwi-ky-040", "id": "encoder1", "top": 346.34, "left": 23.32, "attrs": {} } ], "connections": [ [ "logic1:GND", "uno:GND.2", "black", [ "h0" ] ], [ "swa:3", "leda:C", "black", [ "v10", "h0" ] ], [ "swa:2", "leda:A", "green", [ "h0" ] ], [ "uno:GND.3", "leda:C", "black", [ "v0" ] ], [ "swa:1", "uno:5V", "red", [ "h0" ] ], [ "swa:2", "uno:A4", "green", [ "h0" ] ], [ "swb:3", "ledb:C", "black", [ "v10", "h0" ] ], [ "swb:2", "ledb:A", "green", [ "h0" ] ], [ "uno:GND.3", "ledb:C", "black", [ "v0" ] ], [ "swb:1", "uno:5V", "red", [ "h0" ] ], [ "swb:2", "uno:A5", "green", [ "h0" ] ], [ "logic1:D0", "uno:A4", "green", [ "h0" ] ], [ "logic1:D1", "uno:A5", "green", [ "h0" ] ], [ "encoder1:CLK", "uno:A0", "green", [ "h0" ] ], [ "encoder1:DT", "uno:A1", "green", [ "h0" ] ], [ "encoder1:SW", "uno:A2", "green", [ "h0" ] ], [ "encoder1:VCC", "uno:5V", "red", [ "h0" ] ], [ "encoder1:GND", "uno:GND.3", "black", [ "h0" ] ], [ "logic1:D5", "uno:A0", "green", [ "h0" ] ], [ "logic1:D6", "uno:A1", "green", [ "h0" ] ], [ "logic1:D7", "uno:A2", "green", [ "h0" ] ] ] } Ce schéma comporte une carte Arduino UNO R3 et un analyseur logique comme utilisé en TP. Il comporte également 2 interrupteurs (reliés aux broches A4 et A5 à des LEDs pour facilement voir leurs états) qui vont nous permettre de simuler finement un composant appelé Encodeur en quadrature. Le schéma comporte également un vrai composant encodeur (reliés aux broches A0 et A1) contenant également un bouton poussoir (relié à la broche A2). ====Analyse des chronogrammes et présentation du modèle de machine à états==== Analysons les chronogrammes sur cette image tirée de la documentation du composant encodeur simulé sur Wokwi ( https://docs.wokwi.com/parts/wokwi-ky-040 ): {{https://bvdp.inetdoc.net/files/cesi/td2/wokwi-ky-040-timing-cd6fe446378352c1416ef8817f1e5abf.png}} Dans la suite nous appellerons A la valeur du signal **CLK** et B la valeur du signal **DT**. Nous proposons d'utiliser une "machine à états" (abrégée MAE) pour effectuer le traitement des signaux générés par l'encodeur afin de déterminer la position angulaire (par INCrémentations et DECrémentations d'une variable) de ce dernier (et plus tard la vitesse de rotation). Le modèle graphique de cette MAE est visible ci-dessous et nous allons dans le TD l'implémenter, d'abord en langage C, puis en C++ avec l'approche Orientée Objet, puis nous l'améliorerons. Vous devrez sauver les différentes versions pour pouvoir les comparer à posteriori. Image tirée de https://hal.archives-ouvertes.fr/hal-02021357/document où vous pourrez trouver l'implémentation VHDL de ce modèle pour comparaison: {{https://bvdp.inetdoc.net/files/cesi/td2/MAE.png}} ====Implémentation en C==== Dans cette première implémentation, nous utiliserons des variables globales pour stocker l'état en cours et les valeurs lues sur les entrées. Les périphériques ainsi que variables globales devront être initialisées dans la fonction **setup()**. L'implémentation de la MAE se fera directement dans la fonction **loop()** (au bon endroit vis à vis des commentaires fournis) et la valeur de position angulaire mesurée sera affichée à l'aide de **Serial.println()** vers la console série. byte etat; //numéro de l'état actif byte entreeA; //valeur lue sur l'entrée A byte entreeB; //valeur lue sur l'entrée B int brocheA; //numéro de broche Arduino utilisée pour l'entrée A int brocheB; //numéro de broche Arduino utilisée pour l'entrée B int position; //position angulaire mesurée void setup() { Serial.begin(115200); //configurer et initialiser ce qui doit l'être } void loop() { //lecture et mémorisation des entrées //actions sur transition //évolution de l'état switch (etat){ case 0: if ... etat=...; else if .... etat=....; break; default: } //actions sur état //affichage de la valeur mesurée 10 fois par seconde unsigned int periodiciteTache1=100; static unsigned long timerTache1 = millis(); if (millis() - timerTache1 >= periodiciteTache1) { timerTache1 += periodiciteTache1; Serial.println(position); } } Dessiner la vue composant du système et l'implémenter. Vérifier le bon fonctionnement à l'aide de wokwi2gtkwave sur le jeu de 2 interrupteurs puis sur le vrai encodeur. Analyser les différences et en tirer une conclusion sur la robustesse du système vis à vis des rebonds mécaniques. Solution: https://wokwi.com/arduino/projects/316900946787435074 byte etat; //numéro de l'état actif byte entreeA; //valeur lue sur l'entrée A byte entreeB; //valeur lue sur l'entrée B int brocheA; //numéro de broche Arduino utilisée pour l'entrée A int brocheB; //numéro de broche Arduino utilisée pour l'entrée B int position; //position angulaire mesurée void setup() { Serial.begin(115200); //configurer et initialiser ce qui doit l'être brocheA=A4; brocheB=A5; pinMode(brocheA, INPUT_PULLUP); pinMode(brocheB, INPUT_PULLUP); entreeA=digitalRead(brocheA); entreeB=digitalRead(brocheB); etat=0; position=0; } void loop() { //lecture et mémorisation des entrées entreeA=digitalRead(brocheA); entreeB=digitalRead(brocheB); //actions sur transition if ((etat==0) && (entreeA==1)){ position++; } if ((etat==1) && (entreeA==0)){ position--; } //évolution de l'état switch (etat){ case 0: if (entreeA==1) etat=1; else if (entreeB==1) etat=3; break; case 1: if (entreeB==1) etat=2; else if (entreeA==0) etat=0; break; case 2: if (entreeA==0) etat=3; else if (entreeB==0) etat=1; break; default: case 3: if (entreeB==0) etat=0; else if (entreeA==1) etat=2; break; } //actions sur état //affichage de la valeur mesurée 10 fois par seconde unsigned int periodiciteTache1=100; static unsigned long timerTache1 = millis(); if (millis() - timerTache1 >= periodiciteTache1) { timerTache1 += periodiciteTache1; Serial.println(position); } } ====Implémentation en C++ avec l'approche objet==== Vous avez dû préalablement visionner la vidéo de cours présentant les concepts de la Programmation Orientée Objet: https://youtu.be/5Y9P4MIkTIs Et lire le polycopié du cours correspondant pour comprendre les termes utilisés ici: https://bvdp.inetdoc.net/files/iut/cours_POO_intro_complet_2021_numbered.pdf Sauvez le projet sous un nouveau nom. Nous allons maintenant adapter le code pour le transformer en une **Classe** et en faire une librairie **Objet**. ===Interface d'interaction de la classe=== L'interface d'interaction de la classe permet de créer une instance de la classe et de lui demander de faire des actions ou bien d'informer de son état. ===Attributs internes=== L'état interne du composant doit être protégé par le mécanisme de l'encapsulation afin qu'il ne puisse être modifié ou lu uniquement lors d'un usage prévu par le concepteur de la classe. ===Classe CStateMachineRotary=== Discutons sur la classe suivante: //////////////////////////////////////////////////////////////////// class CStateMachineRotary //déclaration de la classe { public: //membres accessibles depuis l'extérieur de la classe, il s'agit de l'interface d'interaction de la classe CStateMachineRotary(int brocheAinit, int brocheBinit); //Constructeur avec paramètres pour régler les 2 broches Arduino utilisées void clock(); //méthode pour cadencer la machine à état (faire 1 coup d'horloge) int getPosition()//méthode accesseur pour accéder à l'attribut privé position { return position;} //on parle de méthode inlinée car l'implémentation est faite dans la déclaration de la classe private: //membres privés pour réaliser l'encapsulation: ces attributs sont inacessibles directement depuis l'extérieur de la classe byte etat; //numéro de l'état actif byte entreeA; //valeur lue sur l'entrée A byte entreeB; //valeur lue sur l'entrée B int brocheA; //numéro de broche Arduino utilisée pour l'entrée A int brocheB; //numéro de broche Arduino utilisée pour l'entrée B virtual void frontDetecte(){}; //methode abstraite, sera implémentée dans la classe fille virtual void actionSurTousLesEtats(){}; //methode abstraite, sera implémentée dans la classe fille protected: //ces membres peuvent être accédés dans les classes filles int position; //position angulaire mesurée }; //ne pas oublier le ; //////////////////////////////////////////////////////////////////// //Implémentation de la méthode constructeur CStateMachineRotary::CStateMachineRotary(int brocheAinit, int brocheBinit){ // à compléter } //Implémentation de la méthode clock void CStateMachineRotary::clock(){ // à compléter } Vous allez maintenant devoir implémenter le constructeur de la classe ainsi que la méthode **clock()**. Rassurez vous, le travail a été déjà réalisé à l'exercice précédent et il s'agit principalement de faire du couper/coller. Vous allez également devoir construire une instance de cette classe de portée globale à l'aide d'une déclaration: CStateMachineRotary encoder1(A4,A5); et adapter le programme précédent pour qu'il utilise la classe. Pour rappel l'invocation des méthodes d'encoder1 se fait à l'aide de l'opérateur "." ===Instanciation multiple de CStateMachineRotary=== Nous illustrons ici une des notions de "ré-utilisabilité" permise par l'approche objet: l'instanciation multiple. Après avoir testé le bon fonctionnement de l'objet encoder1 puis ajouté au programme une instance encoder2 utilisant les broches A0 et A1. Utiliser le code suivant pour afficher la position des 2 encodeurs: Serial.print(encoder1.getPosition()); Serial.print(" "); Serial.print(encoder2.getPosition()); Serial.println(); ====Extension pour réaliser la mesure de vitesse angulaire==== Nous illustrons ici une autre notion de "ré-utilisabilité" permise par l'approche objet: l'héritage qui va permettre de créer une nouvelle classe (classe fille) bénéficiant des fonctionnalités de la classe dont elle hérite (classe mère). //////////////////////////////////////////////////////////////////// class CStateMachineRotaryWithSpeed: public CStateMachineRotary { public: CStateMachineRotaryWithSpeed(int brocheAinit, int brocheBinit); float getSpeed(); //méthode accesseur pour accéder à l'attribut privé speed private: float speed; //vitesse mesurée unsigned long lastPulseTime; //horodatage du dernier front void frontDetecte(); //implémentation pour effectuer des traitements lorsqu'un front est detecté }; //////////////////////////////////////////////////////////////////// //Implémentation du constructeur de la classe fille, exécutée après avoir exécuté le constructeur de la classe mère CStateMachineRotaryWithSpeed::CStateMachineRotaryWithSpeed(int brocheAinit, int brocheBinit): CStateMachineRotary(brocheAinit, brocheBinit){ lastPulseTime=micros(); //initialisation de la date initiale pour l'horodatage des fronts speed=0; //initialise vitesse nulle au démarrage. Attention, en simu, l'encodeur génère des fronts parasites } //////////////////////////////////////////////////////////////////// //implémentation de la méthode frontDetecte() qui n'était pas implémentée dans la classe mère void CStateMachineRotaryWithSpeed::frontDetecte() { // à compléter } //implémentation de la mesure de vitesse float CStateMachineRotaryWithSpeed::getSpeed(){ //à faire: calculer la vitesse et l'affecter à l'attribut speed return speed; } Il faut maintenant implémenter les méthodes **frontDetecte()** et **getSpeed()** . Vous allez également ajouter deux appels à **frontDetecte();** dans l'implémentation de **CStateMachineRotary.clock()** lors des changements de position. Ceci n'affectera pas le fonctionnement de **CStateMachineRotary** car **frontDetecte()** est vide dans cette classe. Par contre, ceci permet d'ajouter facilement un traitement à chaque front détecté dans la classe fille. Solution: void CStateMachineRotaryWithSpeed::frontDetecte() { unsigned long newPulseTime=micros(); speed=1000000./(newPulseTime-lastPulseTime); lastPulseTime=newPulseTime; } //implémentation de la mesure de vitesse float CStateMachineRotaryWithSpeed::getSpeed(){ unsigned long newTime=micros(); //TODO donner un signe à la vitesse en fonction du sens de rotation if((newTime-lastPulseTime)>4000000) speed=0; //vitesse=0 si mise à jour de la vitesse depuis plus de 4 secondes TODO: faire dépendre ce délai de la vitesse en cours... return speed; } ===Utilisation de la classe fille=== Modifier la déclaration d'encoder2 pour qu'il devienne une instance de la classe CStateMachineRotaryWithSpeed. Utiliser le code suivant pour afficher la position des 2 encodeurs et la vitesse d'encoder2: Serial.print(encoder1.getPosition()); Serial.print(" ") Serial.println(encoder2.getPosition()); Serial.print(" ") Serial.println(encoder2.getSpeed()); ====Ajout de la fonction de gestion du bouton de l'encodeur==== Nous allons à nouveau créer une classe fille **CStateMachineRotaryWithSpeedAndReset** à partir de la classe **CStateMachineRotaryWithSpeed** cette fois-ci pour ajouter la réinitialisation de la position angulaire lors de l'appui sur le bouton indépendamment de l'état courant de la machine à état. Débrouillez vous, puis changer le type d'encoder2 en **CStateMachineRotaryWithSpeedAndReset** en utilisant la broche A2 pour la réinitialisation de la position angulaire et tester la nouvelle fonctionnalité. Solution: Il faut ajouter un appel à **actionSurTousLesEtats()** à la fin de **CStateMachineRotary::clock()** //ajoute une broche pour la remise à zero de position dans n'importe quel état class CStateMachineRotaryWithSpeedAndReset: public CStateMachineRotaryWithSpeed { public: CStateMachineRotaryWithSpeedAndReset(int brocheAinit, int brocheBinit,int brocheRinit); //Ce constructeur dispose d'un paramètre supplémentaire pour configurer la broche utilisée pour le bouton private: int brocheR; //un nouvel attribut pour stocker le numéro de la broche servant au reset void actionSurTousLesEtats(); //implémentation de la méthode permettant de gérer l'action "appui sur le bouton reset" indépendamment de l'état courant de la machine à état }; //////////////////////////////////////////////////////////////////// CStateMachineRotaryWithSpeedAndReset::CStateMachineRotaryWithSpeedAndReset(int brocheAinit, int brocheBinit,int brocheRinit): CStateMachineRotaryWithSpeed(brocheAinit, brocheBinit){ brocheR=brocheRinit; pinMode(brocheR,INPUT_PULLUP); } //////////////////////////////////////////////////////////////////// void CStateMachineRotaryWithSpeedAndReset::actionSurTousLesEtats(){ if (digitalRead(brocheR)==0){ //ici on dévie légèrement du modèle de la machine à état car il faudrait idéalement avoir lue cette entrée en même temps que les autres à l'étape lecture des entrées position=0; //Serial.println("raz position"); } } ====Ajout d'une fonctionnalité à l'ensemble des classes==== Nous souhaitons maintenant ajouter une nouvelle fonctionnalité à l'ensemble des classes, la capacité à régler la position angulaire en cours de fonctionnement. Pour cela, nous allons ajouter une méthode manipulateur **void setPosition(int newposition)** à la classe mère. Dans **loop()**, faire en sorte d'augmenter la position automatiquement de 4 toute les 10 secondes. class CStateMachineRotary //déclaration de la classe { public: ... void setPosition(int newposition)//méthode manipulateur pour modifier à l'attribut privé position { position=newposition;} //on parle de méthode inlinée car l'implémentation est faite dans la déclaration de la classe ... }; loop(...){ ... unsigned int periodiciteTache3=10000; //10s static unsigned long timerTache3 = millis(); if (millis() - timerTache3 >= periodiciteTache3) { timerTache3 += periodiciteTache3; encoder1.setPosition(encoder1.getPosition()+4); } .... } ====Solution complète en vrac fourni pour le td en distanciel==== //////////////////////////////////////////////////////////////////// class CStateMachineRotary //déclaration de la classe { public: //membres accessibles depuis l'extérieur de la classe, il s'agit de l'interface d'interaction de la classe CStateMachineRotary(int brocheAinit, int brocheBinit); //Constructeur avec paramètres pour régler les 2 broches Arduino utilisées void clock(); //méthode pour cadencer la machine à état (faire 1 coup d'horloge) int getPosition()//méthode accesseur pour accéder à l'attribut privé position { return position;} //on parle de méthode inlinée car l'implémentation est faite dans la déclaration de la classe private: //membres privés pour réaliser l'encapsulation: ces attributs sont inacessibles directement depuis l'extérieur de la classe byte etat; //numéro de l'état actif byte entreeA; //valeur lue sur l'entrée A byte entreeB; //valeur lue sur l'entrée B int brocheA; //numéro de broche Arduino utilisée pour l'entrée A int brocheB; //numéro de broche Arduino utilisée pour l'entrée B virtual void frontDetecte(){}; //methode abstraite, sera implémentée dans la classe fille virtual void actionSurTousLesEtats(){}; //methode abstraite, sera implémentée dans la classe fille protected: //ces membres peuvent être accédés dans les classes filles int position; //position angulaire mesurée }; //ne pas oublier le ; //////////////////////////////////////////////////////////////////// class CStateMachineRotaryWithSpeed: public CStateMachineRotary { public: CStateMachineRotaryWithSpeed(int brocheAinit, int brocheBinit); float getSpeed(); //méthode accesseur pour accéder à l'attribut privé speed private: float speed; //vitesse mesurée unsigned long lastPulseTime; //horodatage du dernier front void frontDetecte(); //implémentation pour effectuer des traitements lorsqu'un front est detecté }; //////////////////////////////////////////////////////////////////// //TODO par les étudiants CStateMachineRotaryWithSpeedAndReset //hérite de CStateMachineRotaryWithSpeed et //ajoute une broche pour la remise à zero de position dans n'importe quel état class CStateMachineRotaryWithSpeedAndReset: public CStateMachineRotaryWithSpeed { public: CStateMachineRotaryWithSpeedAndReset(int brocheAinit, int brocheBinit,int brocheRinit); //Ce constructeur dispose d'un paramètre supplémentaire pour configurer la broche utilisée pour le bouton private: int brocheR; //un nouvel attribut pour stocker le numéro de la broche servant au reset void actionSurTousLesEtats(); //implémentation de la méthode permettant de gérer l'action "appui sur le bouton reset" indépendamment de l'état courant de la machine à état }; //////////////////////////////////////////////////////////////////// CStateMachineRotaryWithSpeedAndReset::CStateMachineRotaryWithSpeedAndReset(int brocheAinit, int brocheBinit,int brocheRinit): CStateMachineRotaryWithSpeed(brocheAinit, brocheBinit){ brocheR=brocheRinit; pinMode(brocheR,INPUT_PULLUP); } //////////////////////////////////////////////////////////////////// void CStateMachineRotaryWithSpeedAndReset::actionSurTousLesEtats(){ if (digitalRead(brocheR)==0){ //ici on dévie légèrement du modèle de la machine à état car il faudrait idéalement avoir lue cette entrée en même temps que les autres à l'étape lecture des entrées position=0; //Serial.println("raz position"); } } //////////////////////////////////////////////////////////////////// //Implémentation du constructeur de la classe fille, exécutée après avoir exécuté le constructeur de la classe mère CStateMachineRotaryWithSpeed::CStateMachineRotaryWithSpeed(int brocheAinit, int brocheBinit): CStateMachineRotary(brocheAinit, brocheBinit){ lastPulseTime=micros(); //initialisation de la date initiale pour l'horodatage des fronts } //////////////////////////////////////////////////////////////////// //implémentation de la méthode frontDetecte() qui n'était pas implémentée dans la classe mère void CStateMachineRotaryWithSpeed::frontDetecte() { unsigned long newPulseTime=micros(); speed=1000000./(newPulseTime-lastPulseTime); /*Serial.print("ici "); Serial.print(lastPulseTime); Serial.print(" "); Serial.print(newPulseTime); Serial.print(" "); Serial.print(speed); Serial.println(" "); */ lastPulseTime=newPulseTime; } //////////////////////////////////////////////////////////////////// //implémentation de la mesure de vitesse float CStateMachineRotaryWithSpeed::getSpeed(){ unsigned long newTime=micros(); //TODO donner un signe à la vitesse en fonction du sens de rotation if((newTime-lastPulseTime)>4000000) speed=0; //vitesse=0 si mise à jour de la vitesse depuis plus de 4 secondes TODO: faire dépendre ce délai de la vitesse en cours... return speed; } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode constructeur CStateMachineRotary::CStateMachineRotary(int brocheAinit, int brocheBinit){ brocheA=brocheAinit; brocheB=brocheBinit; pinMode(brocheA,INPUT); pinMode(brocheB,INPUT); entreeA=digitalRead(brocheA); entreeB=digitalRead(brocheB); etat=0; position=0; } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode clock void CStateMachineRotary::clock(){ //lecture et mémorisation des entrées entreeA=digitalRead(brocheA); entreeB=digitalRead(brocheB); //actions sur transition if ((etat==0) && (entreeA==1)){ frontDetecte(); position++; } if ((etat==1) && (entreeA==0)){ frontDetecte(); position--; } //évolution de l'état (veiller à ne pas retester etat après l'avoir changé, sinon passer par une variable temporaire) //byte ancienetat=etat; switch (etat){ case 0: if (entreeA==1) etat=1; else if (entreeB==1) etat=3; break; case 1: if (entreeB==1) etat=2; else if (entreeA==0) etat=0; break; case 2: if (entreeA==0) etat=3; else if (entreeB==0) etat=1; break; default: case 3: if (entreeB==0) etat=0; else if (entreeA==1) etat=2; break; } /*if (ancienetat!=etat){ Serial.print("e"); Serial.println(etat); } */ //actions sur état actionSurTousLesEtats(); } //CStateMachineRotaryWithSpeed encoder1(A4,A5); CStateMachineRotaryWithSpeedAndReset encoder1(A4,A5,A1); ====Organisation des fichiers pour faire une librairie==== Il est temps d'organiser le code produit. Nous allons faire une paire de fichiers pour notre librairie afin de contenir les trois classes **CStateMachineRotary**, **CStateMachineRotaryWithSpeed** et **CStateMachineRotaryWithSpeedAndReset**. Pour ajouter au projet les 2 fichiers de librairie, cliquer sur la flèche à droite de diagram.json puis sur “New file” et saisir libencodeur.h puis faire de même pour libencodeur.cpp Pour éviter l'inclusion multiple (plusieurs #include du même fichier dans un projet, les macros suivantes sont utilisées pour encadrer les définitions dans le fichier .h: #ifndef LIB_ENCODER_H #define LIB_ENCODER_H //inclusion des libraires nécessaires au fonctionnement de notre librairie: #include // COUPER COLLER ICI Les déclarations des 3 classes #endif Le fichier cpp doit avoir cette forme: //inclusion de l'entête de notre libraire pour que les classes soient connues #include "libencodeur.h" // COUPER COLLER ICI Les implémentations des méthodes des 3 classes Finalement le programme Arduino devra également comprendre un #include "libencodeur.h" afin de pouvoir utiliser notre librairie. Cette librairie pourra dorénavant être utilisée dans d'autres projet simplement en important ces deux fichiers et en incluant le fichier .h Testez votre projet, bravo si tout fonctionne. Vous pourrez noter que nous n'avons utilisé aucun pointeur dans tout l'exercice:) .