=====TD 3 Informatique Embarquée CESI===== Document de référence de Mr Nketsa à lire chez vous sur les machines à états: https://bvdp.inetdoc.net/files/cesi/td3/architecture_logicielle_mae_info_indust_avancee_2020_2021-p1-7.pdf ====Objectifs du TD===== Nous avons vu précédemment un exemple de codage d'une 'petite' Machine A Etats. Cela nous a permis d'illustrer les concepts de Programmation Orientée Objet. Nous allons maintenant plus loin pour permettre de piloter des systèmes plus complexes. Nous allons privilégier la généricité (capacité à traiter un large panel de cas) à la performance (occupation mémoire et temps CPU). Pour gagner en performance, il serait possible d'intriquer le code de changement de l'état et celui du pilotage des sorties mais cela rendrait compliqué la gestion de certains cas, que la méthode proposée ici permet de traiter. Dans ce TD nous aborderons également une notion importante: LE TEST UNITAIRE. Ceci consiste à écrire un programme dédié permettant de vérifier le bon fonctionnement d'un composant logiciel. Après avoir défini la procédure de test, nous montrerons comment faire en sorte que le composant logiciel puisse être testé sur une autre cible que celle sur laquelle il devra s'exécuter en exploitation. Finalement, nous verrons comment il est aisé d'intégrer ce composant logiciel sur la cible (microcontrôleur Arduino) une fois que tout aura bien été préparé en amont. =====Cadencement de la MAE==== Dans le TD précédent, le code de la MAE était exécuté lorsque le processeur avait le temps, c'est-à-dire que la cadence d’échantillonnage des entrées, d'évolution de l'état et de pilotage des sorties n'était pas garantie. On parle alors d'une MAE **asynchrone**. Nous allons, dans ce TD, cadencer la MAE pour (tenter d') imposer une fréquence de fonctionnement. Ceci peut être réalisé soit à l'aide d'un timer par scrutation ou bien par le mécanisme d'interruption. Le code suivant illustre l'utilisation du timer de l'Arduino par scrutation pour le cadencement, avec une détection de retard trop important, qui permet au système de se rendre compte si la cadence souhaitée n'est pas obtenue. void loop(){ ..... unsigned int periodicite=10; static unsigned long timer = millis(); if (millis() - timer >= (periodicite*1.2)) digitalWrite(LEDRETARD,HIGH); //allume une LED dès que la MAE est en //retard de plus de 20% d'une période d'horloge if (millis() - timer >= periodicite) { timer += periodicite; <<<<<<<<<>>>>>>>> } ..... } =====Interaction de la MAE avec l’extérieur via ses Entrées/Sorties===== Contrairement au TD précédent, le code de la MAE de ce TD ne va contenir aucun code d'accès aux périphériques tels que les entrées/sorties. Au lieu de cela, ces E/S seront échangées avec la MAE via des méthodes manipulateurs et accesseurs, et le code de la MAE manipulera uniquement les attributs correspondants comme des variables. Le gros avantage de cette approche est que le code de la MAE ne contient pas de code dépendant du matériel : il est possible de le compiler et de le tester hors ligne, et même pour différentes cibles (PC, µc, SOC). ====Règles de nommage==== Nous allons donc utiliser des attributs pour les E/S. Il est décidé ici d'utiliser le suffixe du nom d'une entrée pour indiqué le numéro de bit correspondant. Dans le cas ou plusieurs bus d'entrées ou sorties sont nécessaires, il est décidé d'utiliser une lettre en fin de nom pour identifier le bus dans le cas ou plusieurs sont nécessaires. Par exemple : * EntreeC7 désigne le bit 7 de EntreeC. * Sortie2 désigne le bit 2 de l'unique bus de sortie. Les valeurs des entrées et sorties sont fournies et récupérées de la MAE avec un code tel que : .... mae.setEntree(e) .... s=mae.getSortie() .... Si plusieurs attributs d'E/S sont nécessaires : * soit utiliser plusieurs accesseurs/manipulateurs ( ex: setEntreesA(), ..., setEntreesN()) * soit passer par des types sur plus de bits (ex: unsigned long int mae.getSortie(); ) * soit passer les paramètres en E/S via pointeurs (ex: mae.getSorties(&sortiesA,&sortiesB....) ) Dans le code, l'exploitation des entrées et sorties se fait à l'aide des opérateurs de masquage et de décalage et avec des valeurs numériques. Par exemple, il est possible : * de tester si (Entree3 et Entree2 et not Entree0) est Vrai en faisant (Entree&0xD)==0xA * de tester si EntreeB6..4>2 en faisant ((EntreeB>>4)&0x7)>2 * de piloter une sortie à partir d'une entrée, SortieC4=EntreeB2 s'écrit: SortieC=(SortieC&0xEF) | ((EntreeB&0x4)<<2); * de piloter plusieurs sorties à partir de plusieurs entrées, SortieC7..4=EntreeB3..0 s'écrit: SortieC=(SortieC&0x0F) | ((EntreeB&0xF)<<4); Si besoin (car la complexité du système le requière), la MAE peut utiliser et calculer des variables temporaires : char EntreeC3=(EntreeC>>3)&1; SortieB= (SortieB7<<7) | .... | (SortieB0<<0); Il est également possible d'utiliser des méthodes manipulateurs et accesseurs pour agir individuellement sur les bits des bus d'entrées et sorties, par exemple : void setEntreeBit(unsigned char nbit,unsigned char entreeval) unsigned char getSortieBit(unsigned char nbit) ====Types de sorties==== Les sorties d'un système type microprocesseur (que ce soit les variables ou même les sorties physiques GPIO) sont mémorisées par construction. Nous allons décrire des actions du modèle de MAE qui sont soit à mise à zéro implicite (c'est à dire non mémorisée) soit à mise à zéro explicite (c'est à dire mémorisée) et nous montrons ici comment coder ces comportements. Une sortie est soit AMZI soit AMZE et conserve son status dans toute la MAE. ===Actions A Mise à Zéro Implicite (AMZI)=== Exemple : 0 -> 1 ?CONDITION1: Sortie1; 1->2; 2:Sortie1=Entree3; {{https://bvdp.inetdoc.net/files/cesi/td3/exocesi1amzi.gif}} Le I précédent l'action indique qu'il s'agit d'une AMZI. Cet exemple montre la nécessité de décrire les sorties une par une et de mélanger le codage des actions sur états et sur transitions : exercice !!! // exemple de codage d'une AMZI: (indiqué par un I sur le dessin de la MAE) if ((etat==0) && (Condition1)) Sortie1=1; else if (etat=2) Sortie1=Entree3; else Sortie1=0; //Il s'agit d'une AMZI, on la met à 0 par défaut Autre méthode pour initialiser l'AMZI à la déclaration: unsigned char Sortie1=0; .... // exemple de codage d'une AMZI: (indiqué par un I sur le dessin de la MAE) if ((etat==0) && (Condition1)) Sortie1=1; else if (etat=2) Sortie1=Entree3; //else Sortie1=0; //Inutile car la variable était déjà initialisée à 0 ===Actions A Mise à Zéro Explicite (AMZE)=== Exemple: 0 -> 1 ?CONDITION1: S,Sortie0=Entree3; 1->2; 2:R,Sortie0; 2->3; 3->4:M,Sortie0=Entree4; {{https://bvdp.inetdoc.net/files/cesi/td3/exocesi1amze.gif}} Le S,R ou M précédent l'action indique qu'il s'agit d'une AMZE: * S indique "Set": mise à 1 de la sortie (éventuellement conditionnée) * R indique "Reset": mise à 0 de la sortie (éventuellement conditionnée) * M indique "Mémorisation": sauvegarde la valeur de quelque chose jusqu'à nouvel ordre Sur l'exemple: * sur la transition de l'état 0 à 1, Sortie0 mise à 1 si entree3 est à 1, sinon Sortie1 est inchangée * sur l'état 2, Sortie0 est mise à 0 * sur la transition de l'état 3 à 0, Sortie0 mémorise la valeur de entree4 Exercice ! // exemple de codage d'une AMZE: (indiqué par un S,R ou M sur le dessin de la MAE) if ((etat==0) && (Condition1)) if (Entree3==1) Sortie0=1; else if (etat=2) Sortie0=0; else if ((etat==3) && (True)) Sortie0=Entree4; //Il s'agit d'une AMZE, on ne la met pas à 0 par défaut, donc pas de else =====Classe pour les MAE===== Nous proposons ici une classe pour implémenter des MAE telles que décrites: //////////////////////////////////////////////////////////////////// class CStateMachine //déclaration de la classe { //! membres accessibles depuis l'extérieur de la classe, il s'agit de l'interface d'interaction de la classe public: //! Constructeur CStateMachine(); //! méthode pour redémarrer la machine à état void reset(); //! méthode pour cadencer la machine à état (faire 1 coup d'horloge) void clock(); //! méthode manipulateur pour fournir les entrées void setEntree(unsigned char entreeval) { entree=entreeval;} //! méthode manipulateur pour fournir les entrées individuellement void setEntreeBit(unsigned char nbit,unsigned char entreeval) { entree= (entree&~(1<>nbit)&1;} //! méthode accesseur pour accéder à l'état courant unsigned char getEtat() { return etat;} //! membres privés pour réaliser l'encapsulation: ces attributs sont inacessibles directement depuis l'extérieur de la classe private: //! numéro de l'état actif unsigned char etat; //! valeur des entrées unsigned char entree; //! valeur des sorties unsigned char sortie; }; //////////////////////////////////////////////////////////////////// =====Exemple d'implémentation d'une MAE===== Implémentons l'exemple de MAE suivant: 0->1? ENTREE0 AND NOT ENTREE1:M,SORTIE2=ENTREE2; 1:SORTIE1=ENTREE1; 1->2? NOT ENTREE2:M,SORTIE3=ENTREE0; 2->3? ENTREE0; 2->0? ENTREE2 AND NOT ENTREE0:R,SORTIE3; //AND NOT ENTREE0 ajouté pour rentre plus prioritaire 2->3 3->0? NOT ENTREE0:SORTIE1=ENTREE2; 3:SORTIE0=ENTREE1; #pragma_dot_global_directive{ rankdir=TB; ranksep=0.1; nodesep=0.1; }#pragma {{https://bvdp.inetdoc.net/files/cesi/td3/exocesi1.gif}} L'implémentation de cette MAE peut être : Exercice ! //////////////////////////////////////////////////////////////////// //Implémentation de la méthode constructeur CStateMachine::CStateMachine(){ // à compléter si besoin, allocation etc... reset(); } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode reset, tout ce qui est fait pour (re)démarrer la machine à états void CStateMachine::reset(){ etat=0; sortie=0; //mettre des valeurs initiales correctes } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode clock void CStateMachine::clock(){ //variables intérmédiaires pour les entrées unsigned char entree0=(entree>>0)&1; unsigned char entree1=(entree>>1)&1; unsigned char entree2=(entree>>2)&1; unsigned char entree3=(entree>>3)&1; //variables intérmédiaires pour les sorties //codage des sorties //Sortie0 est une AMZI unsigned char sortie0; if (etat==3) sortie0=entree1; else sortie0=0; //Sortie1 est une AMZI unsigned char sortie1; if (etat==1) sortie1=entree1; else if ((etat==3)&& (entree0==0)) sortie1=entree2; else sortie1=0; //Sortie2 est une AMZE unsigned char sortie2=(sortie>>2)&1; //récupère la valeur précédente if ((etat==0) && ( (entree0==1) && (entree1==0)) ) sortie2=entree2; //Sortie3 est une AMZE unsigned char sortie3=(sortie>>3)&1; //récupère la valeur précédente if ((etat==1) && (entree2==0) ) sortie3=entree0; else if ((etat==2) && (entree2==1) && (entree0==0) ) sortie3=0; //codage de l'évolution de l'état switch (etat){ default: case 0: if ( (entree0==1) && (entree1==0)) etat=1; break; case 1: if (entree2==0) etat=2; break; case 2: if (entree0==1) etat=3; else if (entree2==1) //entree0==0 implicite par le else etat=0; break; case 3: if (entree0==0) etat=0; break; } //reconstruction de sortie à partir des variables intermédiaires sortie=(sortie3<<3) | (sortie2<<2) | (sortie1<<1) | (sortie0<<0) ; } //////////////////////////////////////////////////////////////////// La déclaration de la classe et son implémentation peuvent être rangés dans 2 fichiers .h et .cpp comme nous l'avons fait au TD précédent afin de permette d'inclure cette MAE en tant que librairie dans un programme. =====Programme de test===== Nous allons maintenant procéder au test de notre composant MAE, comme vous l'avez fait pour des composants VHDL. Vous l'avez fait via des chronogrammes, mais la méthode habituelle consiste plutôt à écrire un programme de test appelé testbench. Nous allons ici écrire un programme de test qui va valider le bon fonctionnement (au moins au niveau logiciel) de notre MAE et permettre de reproduire des tests à l'identique pour pouvoir reproduire des erreurs et les traiter. Ces tests permettent notamment de s'assurer qu'un composant logiciel conserve son fonctionnement nominal après que des modifications aient été appliquées. Pour faire ce test, nous allons générer des valeurs sur les entrées de la MAE via le programme pour faire évoluer l'état et les sorties, et vérifier que les valeurs obtenues sont bien conformes à celle attendues. Ce programme doit être le plus exhaustif possible et dans le cas du test d'une MAE cela se traduit par le fait qu'il doit faire apparaître: * toutes les transitions entre états * tous les maintiens dans les états * toutes les combinaisons d'entrées possibles lorsque l'évolution ou les sorties en dépendent (Nous verrons deux moyens pour réaliser cela). Afin de nous aider dans l'écriture du programme de test, je vous propose d'utiliser cette fonction: //////////////////////////////////////////////////////////////////// //! fonction d'affichage d'un message de debug bien pratique, //! qui affiche un message texte, le nom du fichier et le numéro de la ligne //! depuis où la fonction a été appelée void debugMessage(const char * chaineMsg,const char * chaineFile,const unsigned int line){ Serial.print("DEBUG "); Serial.print(chaineFile); Serial.print(" : l "); Serial.print(line); Serial.print(" : "); Serial.print(chaineMsg); Serial.println(); } //////////////////////////////////////////////////////////////////// Cette fonction sera utilisée de la manière suivante pour afficher facilement un message et identifier automatiquement la ligne et le fichier dans lequel le problème s'est produit: if ( Quelque chose n'est pas normal) debugMessage("Erreur: explication", __FILE__, __LINE__); Voici le programme de test qui peut être utilisé pour tester la MAE: //////////////////////////////////////////////////////////////////// //! Un programme pour tester notre composant (logiciel) void programmeDeTest1(){ //! Le composant à tester CStateMachine mae; Serial.print("Test lancé le "); Serial.print(__DATE__ ); Serial.print(" à "); Serial.println(__TIME__ ); Serial.print("Fonction de test: "); Serial.println(__func__); //boucle pour gérer les différents cas à tester for (unsigned int ntest=0;ntest<8;ntest++){ Serial.print("début du test numéro: "); Serial.println(ntest); mae.reset(); mae.setEntree(0); /* mae.setEntreeBit(0,1); mae.setEntreeBit(1,1); mae.setEntreeBit(2,1); mae.setEntreeBit(3,1); mae.setEntreeBit(0,0); mae.setEntreeBit(2,0); mae.setEntreeBit(1,0); mae.setEntreeBit(3,0); */ //test du maintien dans l'état 0 mae.clock(); //ici on teste la mae sans considération de timing, donc clock() n'est pas conditionné à un timer mae.clock(); //la MAE doit être dans l'état 0 if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(0,1); //équivalent ici à mae.setEntree(1); mae.clock(); //transition de 0 à 1 if (mae.getEtat()!=1) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(2)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.reset(); //ici pour faire le test en fonction des 2 valeurs de entree2 je reset la mae //on verra plus bas une manière plus générale de tester 2 cas if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(2,1); //met entree2 à 1 pour tester la mémorisation d'un 1 sur sortie2 mae.clock(); //transition de 0 à 1 if (mae.getSortieBit(2)!=1) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //test du maintien dans l'état 1 //bloqué dans l'état 1 tant que entree2 est à 1 //verifie si sortie1 recopie bien entree1 mae.setEntreeBit(1,1); mae.clock(); if (mae.getSortieBit(1)!=1) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.setEntreeBit(1,0); mae.clock(); if (mae.getSortieBit(1)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //transition de etat 1 vers état 2 // avec mémorisation de entree0 qui vaut 0 ou 1 dans sortie3, //il faut faire 2 tests, donc j'utilise le bit 0 de ntest pour cela mae.setEntreeBit(0,((ntest>>0)&1)); //pour régler la valeur à mémoriser mae.setEntreeBit(2,0); //pour déclencher la transition mae.clock(); if (mae.getSortieBit(3)!=((ntest>>0)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //test du maintien dans l'état 2 mae.setEntreeBit(2,0); mae.setEntreeBit(0,0); mae.clock(); mae.clock(); if (mae.getEtat()!=2) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); //transition de etat 2 vers etat3 mae.setEntreeBit(0,1); mae.clock(); if (mae.getEtat()!=3) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); //vérifie si sortie0 recopie bien entree1 //il faut faire 2 tests, donc j'utilise le bit 1 de ntest pour cela mae.setEntreeBit(1,((ntest>>1)&1)); mae.clock(); //test du maintien dans l'état 3 //la mae doit être maintenue dans l'état 3 et sortie0 doit être égale à entree1 if (mae.getEtat()!=3) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(0)!=((ntest>>1)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //transition de etat 3 vers etat 0, on teste les 2 cas de recopie de sortie1 , avec entree2= bit 2 de ntest mae.setEntreeBit(0,0); mae.setEntreeBit(2,((ntest>>2)&1)); mae.clock(); if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(1)!=((ntest>>2)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.clock(); //sortie1 est une AMZI, elle doit revenir à 0 if (mae.getSortieBit(1)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //revenons à l'état 2 pour tester la transition vers l'état 0, //durant cette transition, il faut remettre à 0 la sortie3, donc on va s'arranger pour la mémoriser à 1 dans la //transition de 1 vers 2 mae.setEntreeBit(0,1); mae.setEntreeBit(1,0); mae.clock(); mae.setEntreeBit(2,0); mae.setEntreeBit(0,1); //on mémorise un 1 dans sortie 3 mae.clock(); if (mae.getEtat()!=2) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(0,0); mae.setEntreeBit(2,1); mae.clock(); if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(3)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //Ce test n'est même pas exhaustif car nous n'avons pas testé si les AMZI reviennent bien toutes à 0 //et si les AMZE mémorisent bien //et si les entrées autres que celle écrites sur le modèle n'ont pas un impact (par exemple que fait l'entree1 dans l'état 2) } Serial.println("fin du test"); //vidage de la FIFO d'affichage: //flush(std::cout); } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// void setup() { Serial.begin(115200); //Normalement le programme de test est un programme à part programmeDeTest1(); } //////////////////////////////////////////////////////////////////// void loop() { } =====Utilisation de la MAE sur le composant cible==== #include "fichierdelamae.h" //le code de la MAE #include "lib_io_tp.h" //le code de la MAE ne dépend pas des librairies d'entrées/sorties //fournit les fonctions unsigned char readPort(void); // et void writePort(unsigned char value) {} //////////////////////////////////////////////////////////////////// CStateMachine maeUtile; //instanciation de la MAE //////////////////////////////////////////////////////////////////// void setup() { Serial.begin(115200); } //////////////////////////////////////////////////////////////////// void loop() { unsigned int periodicite=10; static unsigned long timer = millis(); if (millis() - timer >= periodicite) { timer += periodicite; maeUtile.setEntree(readPort()); maeUtile.clock(); writePort(maeUtile.getSortie()); } } =====Solution complète===== Projet Wokwi: https://wokwi.com/arduino/projects/317529091041395266 // B. Vandeportaele 9/12/2021 //////////////////////////////////////////////////////////////////// class CStateMachine //déclaration de la classe { //! membres accessibles depuis l'extérieur de la classe, il s'agit de l'interface d'interaction de la classe public: //! Constructeur CStateMachine(); //! méthode pour redémarrer la machine à état void reset(); //! méthode pour cadencer la machine à état (faire 1 coup d'horloge) void clock(); //! méthode manipulateur pour fournir les entrées void setEntree(unsigned char entreeval) { entree=entreeval;} //! méthode manipulateur pour fournir les entrées individuellement void setEntreeBit(unsigned char nbit,unsigned char entreeval) { entree= (entree&~(1<>nbit)&1;} //! méthode accesseur pour accéder à l'état courant unsigned char getEtat() { return etat;} //! membres privés pour réaliser l'encapsulation: ces attributs sont inacessibles directement depuis l'extérieur de la classe private: //! numéro de l'état actif unsigned char etat; //! valeur des entrées unsigned char entree; //! valeur des sorties unsigned char sortie; }; //////////////////////////////////////////////////////////////////// //Implémentation de la méthode constructeur CStateMachine::CStateMachine(){ // à compléter si besoin, allocation etc... reset(); } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode reset, tout ce qui est fait pour (re)démarrer la machine à états void CStateMachine::reset(){ etat=0; sortie=0; //mettre des valeurs initiales correctes } //////////////////////////////////////////////////////////////////// //Implémentation de la méthode clock void CStateMachine::clock(){ //variables intérmédiaires pour les entrées unsigned char entree0=(entree>>0)&1; unsigned char entree1=(entree>>1)&1; unsigned char entree2=(entree>>2)&1; unsigned char entree3=(entree>>3)&1; //variables intérmédiaires pour les sorties //codage des sorties //Sortie0 est une AMZI unsigned char sortie0; if (etat==3) sortie0=entree1; else sortie0=0; //Sortie1 est une AMZI unsigned char sortie1; if (etat==1) sortie1=entree1; else if ((etat==3)&& (entree0==0)) sortie1=entree2; else sortie1=0; //Sortie2 est une AMZE unsigned char sortie2=(sortie>>2)&1; //récupère la valeur précédente if ((etat==0) && ( (entree0==1) && (entree1==0)) ) sortie2=entree2; //Sortie3 est une AMZE unsigned char sortie3=(sortie>>3)&1; //récupère la valeur précédente if ((etat==1) && (entree2==0) ) sortie3=entree0; else if ((etat==2) && (entree2==1) && (entree0==0) ) sortie3=0; //codage de l'évolution de l'état switch (etat){ default: case 0: if ( (entree0==1) && (entree1==0)) etat=1; break; case 1: if (entree2==0) etat=2; break; case 2: if (entree0==1) etat=3; else if (entree2==1) //entree0==0 implicite par le else etat=0; break; case 3: if (entree0==0) etat=0; break; } //reconstruction de sortie à partir des variables intermédiaires sortie=(sortie3<<3) | (sortie2<<2) | (sortie1<<1) | (sortie0<<0) ; } //////////////////////////////////////////////////////////////////// //! fonction d'affichage d'un message de debug bien pratique, //! qui affiche un message texte, le nom du fichier et le numéro de la ligne //! depuis où la fonction a été appelée void debugMessage(const char * chaineMsg,const char * chaineFile,const unsigned int line){ Serial.print("DEBUG "); Serial.print(chaineFile); Serial.print(" : l "); Serial.print(line); Serial.print(" : "); Serial.print(chaineMsg); Serial.println(); } //////////////////////////////////////////////////////////////////// //! Un programme pour tester notre composant (logiciel) void programmeDeTest1(){ //! Le composant à tester CStateMachine mae; Serial.print("Test lancé le "); Serial.print(__DATE__ ); Serial.print(" à "); Serial.println(__TIME__ ); Serial.print("Fonction de test: "); Serial.println(__func__); //boucle pour gérer les différents cas à tester for (unsigned int ntest=0;ntest<8;ntest++){ Serial.print("début du test numéro: "); Serial.println(ntest); mae.reset(); mae.setEntree(0); /* mae.setEntreeBit(0,1); mae.setEntreeBit(1,1); mae.setEntreeBit(2,1); mae.setEntreeBit(3,1); mae.setEntreeBit(0,0); mae.setEntreeBit(2,0); mae.setEntreeBit(1,0); mae.setEntreeBit(3,0); */ //test du maintien dans l'état 0 mae.clock(); //ici on teste la mae sans considération de timing, donc clock() n'est pas conditionné à un timer mae.clock(); //la MAE doit être dans l'état 0 if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(0,1); //équivalent ici à mae.setEntree(1); mae.clock(); //transition de 0 à 1 if (mae.getEtat()!=1) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(2)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.reset(); //ici pour faire le test en fonction des 2 valeurs de entree2 je reset la mae //on verra plus bas une manière plus générale de tester 2 cas if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(2,1); //met entree2 à 1 pour tester la mémorisation d'un 1 sur sortie2 mae.clock(); //transition de 0 à 1 if (mae.getSortieBit(2)!=1) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //test du maintien dans l'état 1 //bloqué dans l'état 1 tant que entree2 est à 1 //verifie si sortie1 recopie bien entree1 mae.setEntreeBit(1,1); mae.clock(); if (mae.getSortieBit(1)!=1) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.setEntreeBit(1,0); mae.clock(); if (mae.getSortieBit(1)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //transition de etat 1 vers état 2 // avec mémorisation de entree0 qui vaut 0 ou 1 dans sortie3, //il faut faire 2 tests, donc j'utilise le bit 0 de ntest pour cela mae.setEntreeBit(0,((ntest>>0)&1)); //pour régler la valeur à mémoriser mae.setEntreeBit(2,0); //pour déclencher la transition mae.clock(); if (mae.getSortieBit(3)!=((ntest>>0)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //test du maintien dans l'état 2 mae.setEntreeBit(2,0); mae.setEntreeBit(0,0); mae.clock(); mae.clock(); if (mae.getEtat()!=2) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); //transition de etat 2 vers etat3 mae.setEntreeBit(0,1); mae.clock(); if (mae.getEtat()!=3) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); //vérifie si sortie0 recopie bien entree1 //il faut faire 2 tests, donc j'utilise le bit 1 de ntest pour cela mae.setEntreeBit(1,((ntest>>1)&1)); mae.clock(); //test du maintien dans l'état 3 //la mae doit être maintenue dans l'état 3 et sortie0 doit être égale à entree1 if (mae.getEtat()!=3) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(0)!=((ntest>>1)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //transition de etat 3 vers etat 0, on teste les 2 cas de recopie de sortie1 , avec entree2= bit 2 de ntest mae.setEntreeBit(0,0); mae.setEntreeBit(2,((ntest>>2)&1)); mae.clock(); if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(1)!=((ntest>>2)&1)) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); mae.clock(); //sortie1 est une AMZI, elle doit revenir à 0 if (mae.getSortieBit(1)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //revenons à l'état 2 pour tester la transition vers l'état 0, //durant cette transition, il faut remettre à 0 la sortie3, donc on va s'arranger pour la mémoriser à 1 dans la //transition de 1 vers 2 mae.setEntreeBit(0,1); mae.setEntreeBit(1,0); mae.clock(); mae.setEntreeBit(2,0); mae.setEntreeBit(0,1); //on mémorise un 1 dans sortie 3 mae.clock(); if (mae.getEtat()!=2) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); mae.setEntreeBit(0,0); mae.setEntreeBit(2,1); mae.clock(); if (mae.getEtat()!=0) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); if (mae.getSortieBit(3)!=0) debugMessage("Erreur: La sortie n'est pas à la valeur prévue", __FILE__, __LINE__); //Ce test n'est même pas exhaustif car nous n'avons pas testé si les AMZI reviennent bien toutes à 0 //et si les AMZE mémorisent bien //et si les entrées autres que celle écrites sur le modèle n'ont pas un impact (par exemple que fait l'entree1 dans l'état 2) } Serial.println("fin du test"); } //////////////////////////////////////////////////////////////////// CStateMachine maeUtile; //#include "lib_io_tp.h" //le code de la MAE ne dépend pas des librairies d'entrées/sorties //code vide pour les fonctions pour simuler unsigned char readPort(void) {} void writePort(unsigned char value) {} //////////////////////////////////////////////////////////////////// void setup() { // put your setup code here, to run once: Serial.begin(115200); //Normalement le programme de test est un programme à part programmeDeTest1(); //ici on enchaine avec l'utilisation sur site } //////////////////////////////////////////////////////////////////// void loop() { unsigned int periodicite=10; static unsigned long timer = millis(); if (millis() - timer >= periodicite) { timer += periodicite; maeUtile.setEntree(readPort()); maeUtile.clock(); writePort(maeUtile.getSortie()); } } ////////////////////////////////////////////////////////////////////