Pour faire le TP, vous devrez bien lire le sujet, ne pas sauter de parties, ne pas inventer de prototypes de fonctions mais bien respecter ce qui est demandé, car les fonctions que vous écrirez devront pouvoir être substituées à d'autres fonctions ayant les mêmes prototypes. Les marges de manœuvre dont vous disposez sont au niveau de l'implémentation de ces fonctions, mais les prototypes font partie du cahier des charges et ne sont pas négociables! Le TP se déroulera sur plusieurs séances, il est de votre responsabilité de conserver les fichiers que vous aurez produits; pour cela, vous pourrez vous envoyer par mail ou via clef USB une archive zip de votre dossier de projet à chaque fin de séance pour éviter toute perte. De plus cette archive devra être fournie à l'enseignant pour évaluation en fin de projet.
Vous allez devoir réaliser un système de commande d'un dispositif de tri industriel doté de capteurs et d'actionneurs. Ce dispositif est visible sur la figure suivante:
Les informations fournies par les capteurs sont accessibles pour votre système de commande sur différents bits d'un port d'entrée 8 bits tel qu'indiqué sur la figure, les bits étant positionnés à 0 lorsque le capteur s'active.
Les commandes des actionneurs sont réalisées par votre système de commande sur différents bits d'un port de sortie 8 bits tel qu'indiqué sur la figure, les bits étant positionnés à 1 pour activer un actionneur.
L'interaction entre votre système de commande et la maquette à piloter se résume donc à un port d'entrée et un port de sortie, comme lors du TD1: cesitd1. De plus le système de commande intègre un écran LCD à deux lignes qui vous permettra d'afficher vos noms et des messages pendant l'exécution du programme.
Dans le TP, nous réaliserons le système de commande en plusieurs étapes:
Les trois capteurs permettant de détecter les pièces sont positionnés en différents lieux sur le tapis roulant, et afin de déterminer si une pièce est Noire, Rouge ou Metallique, il est nécessaire de scruter l'activation successive des différents capteurs. Le tableau suivant récapitule la séquence d'activation des capteurs pour les différentes pièces:
Type de pièce | capteur NON_NOIR | capteur METAL | capteur PRESENT |
---|---|---|---|
Noire | X | ||
Rouge | X | X | |
Métal | X | X | X |
Nous proposons d'utiliser une machine à états pour trier les pièces, afin de piloter les différents actionneurs du dispositif en fonction des informations fournies par les capteurs et du comportement souhaité:
Il est vivement recommandé de raisonner sur l'activation des capteurs en utilisant des variables qui seront affectées au complément des valeurs lues sur les entrées correspondantes car les capteurs fournissent des signaux actifs à 0. Voir avec l'enseignant une machine à état (sans les actions) permettant de préparer ce comportement. Vous devrez par la suite réaliser vous même les actions.
Copier coller dans un terminal les commandes suivantes A LA PREMIERE SEANCE UNIQUEMENT, afin de récupérer le projet et de le compiler:
cd ~ wget https://bvdp.inetdoc.net/files/cesi/tp2/tcp_server.zip unzip tcp_server.zip rm tcp_server.zip cd tcp_server rm -rf build* cd tcp_server qmake make clean make ./tcp_server &
Par la suite, pour lancer le simulateur, il vous suffira de saisir dans un terminal:
~/tcp_server/tcp_server/tcp_server &
Vous devez voir la fenêtre suivante s'ouvrir, qui vous permet de piloter l'état des entrées et de visualiser l'état des sorties. Elle permet également l'affichage de votre nom ainsi que de différents messages pendant l’exécution de votre programme.
Copier coller dans un terminal les commandes suivantes A LA PREMIERE SEANCE UNIQUEMENT, afin de récupérer le projet et de le compiler:
cd ~ wget https://bvdp.inetdoc.net/files/cesi/tp2/tcp_server_simulator.zip unzip tcp_server_simulator.zip rm tcp_server_simulator.zip cd tcp_server_simulator rm -rf build* .git tcp_server_simulator.pro.user station_rempli.png station_vide2.png station_vide.png 'switch*$.png' qmake make clean make ./tcp_server_simulator &
Par la suite, pour lancer le simulateur, il vous suffira de saisir dans un terminal:
cd ~/tcp_server_simulator/ && ./tcp_server_simulator &
Copier coller dans un terminal les commandes suivantes A LA PREMIERE SEANCE UNIQUEMENT, afin de récupérer le projet de départ et d'initialiser l'outils de gestion de versions:
echo commence cd ~ wget https://bvdp.inetdoc.net/files/cesi/tp2/mae_etudiant_vide.zip unzip mae_etudiant_vide.zip rm mae_etudiant_vide.zip mv mae_etudiant_vide mae_etudiant cd mae_etudiant/tcp_client git init git add * git commit -m'Version initiale fournie par B.Vandeportaele' qtcreator tcp_client.pro & gitk & echo fini
A l'ouverture de Qtcreator, cliquer sur le bouton “Configurer le projet”, le choix de kit proposé étant normalement correct.
Le projet que vous avez récupéré comporte plusieurs fichiers permettant de générer un exécutable pour le PC:
Voici la description des fonctions disponibles dans la libraire pour piloter les E/S:
/*! \file lib_io_tp.h \brief Librairie E/S via TCP pour le CESI. \author Bertrand Vandeportaele IUT GEII \date 14/02/2022 */ #ifndef LIB_IO_TP_H #define LIB_IO_TP_H // Bertrand Vandeportaele 2022 // inspiré d'un exemple de client TCP simple sans QT: // https://riptutorial.com/cplusplus/example/24000/hello-tcp-client #include "Arduino.h" #include <cstring> #include <iostream> #include <string> #include <arpa/inet.h> #include <netdb.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> /*! * \brief setupES Configure les entrées sorties émulées ou télé-opérées vers l'hôte ipAddressInit via le port TCP portNumInit * \param ipAddressInit * \param portNumInit * \return */ int setupES(char *ipAddressInit,char *portNumInit); /*! * \brief closeES Libère les ressources de communications utilisées par la librairie */ void closeES(); /*! * \brief readPort Lit l'état du port d'entrée 8 bits * \return l'état du port d'entrée 8 bits */ unsigned char readPort(void); /*! * \brief writePort Pilote l'état du port de sortie 8 bits * \param value l'état du port de sortie 8 bits */ void writePort(unsigned char value); /*! /*! * \brief setName Règle votre nom à afficher sur l'écran LCD ou dans la zone de texte de l'émulateur /*! * \param value la chaine de caractères contenant votre nom /*! */ void setName(char *value); /*! * \brief setMsg Règle un message à afficher sur l'écran LCD ou dans la zone de texte de l'émulateur * \param value la chaine de caractères contenant le message */ void setMsg(char *value); // formats de requetes TCP: // g \n pour lecture port // s val\n pour ecriture port // n nom\n pour affichage nom // m message\n pour affichage message #endif // LIB_IO_TP_H
Pour l'émulation des E/S sur votre PC en local, vous utilisez l'IP de la boucle locale: 127.0.0.1 et le numéro de port: 4242.
Pour la télé-opération des E/S sur la carte microcontrôleur, vous utilisez l'IP: 172.16.6.60 et le numéro de port: 4242. L'adaptateur Ethernet du microcontrôleur est connecté sur une prise VLAN bleue.
Une fois le projet ouvert, l'utilisateur peut choisir entre plusieurs mode d'affichages, notamment “Editer” et “Debogage” à l'aide des boutons sur la gauche de la fenêtre.
La fenêtre Projets fait apparaître les fichiers du (des) projet(s) ouvert(s), triés selon leur type. Sur la droite, la fenêtre affiche le contenu d'un fichier sélectionné.
CTRL+Click souris gauche sur un mot du programme permet de se déplacer à la déclaration d'une variable, d'un objet, d'une fonction, d'un type ou autre. Cela sera très utile pour connaître par exemple les méthodes utilisables sur un objet d'une certaine classe.
CTRL+B permet de compiler l'application, la fenêtre Sortie de compilation permet alors d'observer les éventuelles erreurs, sur lesquelles vous pourrez cliquer pour aller à la ligne correspondante dans le code.
CTRL+R permet d’exécuter le programme. Le programme devra être fermé en cliquant sur le bouton croix de la fenêtre avant de le recompiler ou de l’exécuter à nouveau.
F5 permet d'exécuter le programme en mode DEBUG. L'utilisateur passe alors en mode Debug, ce qui ajoute à l'affichage une zone à droite dans laquelle l'utilisateur pourra observer les variables et leurs valeurs. L'utilisateur pourra placer ou retirer des points d'arrêt en cliquant à gauche du numéro de ligne d'un fichier. Il pourra exécuter en pas à pas sans rentrer dans les fonctions à l'aide de F10 ou bien en entrant dans les fonctions à l'aide de F11. Il pourra ajouter une variable particulière à la liste des variables affichées en Debug en selectionant le nom de la variable dans le fichier programme et en cliquant droit dessus puis clic gauche sur ajouter un évaluateur d'expression.
Pour créer une version, au moins à chaque validation, vous devrez cliquer sur Outils→Git→Dépot local→Commit, cliquer sur Fichiers→Select all, et saisir un texte descriptif dans le champ Description. Ensuite cliquer sur Soumettre.
Pour visualiser les différentes versions, vous devrez cliquer sur Outils→Git→Outils Git→gitk
Compiler et exécuter le projet de départ, alors que l'application tcp_server est en cours d'exécution. Jouer sur les entrées et observer le comportement de l'application. Relier ce comportement au code du projet de départ pour en comprendre le fonctionnement.
Implémenter la machine à états dans le fichier sketch.cpp comme cela a été montré en cesitd3 (s'identifier en login:au , mot de passe: au pour voir les solutions du td3). Modifier le code de la fonction loop() pour que votre machine soit reliée aux entrées/sorties et soit cadencée à 200Hz. Faire en sorte d'afficher votre nom et un message indiquant l'état actif de la machine à état à chaque évolution. Tester complètement le bon fonctionnement de votre programme en jouant sur les entrées et en déduire des séquences de tests.
Appeler un enseignant pour valider votre travail et sauver une version.
A partir des séquences de tests déterminées à l'exercice précédent, écrire et exécuter un programme de test complet void programmeDeTest1() faisant apparaître le maintien dans chaque état et toutes les transitions comme montré en cesitd3. Pour cela, vous devrez adapter le code pour que l'affichage se fasse sur la console du PC au lieu de celle de l'arduino. Vous utiliserez donc la fonction debugMessage suivante:
//////////////////////////////////////////////////////////////////// //! 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){ printf("DEBUG %s : l %d : %s\n",chaineFile,line,chaineMsg); flush(std::cout); }
et vous remplacerez les affichages réalisés avec Serial.print par des appels à la fonction printf.
Vous pourrez utiliser ce squelette de programme de test et le compléter. Cette fonction devra être appelée par la fonction setup():
void programmeDeTest1(){ //! Le composant à tester CStateMachine mae; printf("\nTest lancé le "); printf("%s",__DATE__ ); printf(" à "); printf("%s",__TIME__ ); printf("\nFonction de test: "); printf(__func__); //boucle pour gérer les différents cas à tester int ntest=1; printf("\ndébut du test numéro: "); printf("%d\n",ntest); mae.reset(); mae.setEntree(0xF); mae.clock(); //ici on teste la mae sans considération de timing, donc clock() n'est pas conditionné à un timer mae.clock(); //Compléter le test ici: if (mae.getEtat()!=......) debugMessage("Erreur: La MAE n'est pas dans l\'état prévu", __FILE__, __LINE__); //fin du test debugMessage("\nFin du test\n", __FILE__, __LINE__); exit(0); //commenter cette ligne pour ne pas quitter le programme après la fin du test } //////////////////////////////////////////////////////
Appeler un enseignant pour valider votre travail et sauver une version.
Adapter le programme pour le tester en mode télé-opéré sur la maquette réelle. Vous veillerez à indiquer au tableau le nom du binôme utilisant la maquette à un instant donné pour éviter qu'un autre binôme n'interfère.
Appeler un enseignant pour valider votre travail et sauver une version.
Insérer le code de vos fonctions setup() et loop() dans le projet du TD1: cesitd1 et tester avec wokwi le bon fonctionnement en embarqué dans le simulateur.
Vous devrez dans setup(), après avoir appelé setupES(), appeler 1 fois readPort() et writePort(0);
Vous aurez besoin d'ajouter à la librairie du TD1 les fonctions suivantes:
/////////////////////////////////////////////////////// void setName(char *value) { Serial.println(value); } /////////////////////////////////////////////////////// void setMsg(char *value) { Serial.println(value); } ///////////////////////////////////////////////////////
Appeler un enseignant pour valider votre travail et sauver une version (sous Wokwi).
Programmer une vraie carte Arduino dotée d'entrées/sorties telle que visible sur la carte tdcom2 en utilisant la librairie d'entrées/sortie suivante et tester:
/*! \file lib_io_tp.h \brief Premier TD pour le CESI. \author Bertrand Vandeportaele IUT GEII \date 28/10/2021 */ #include <Arduino.h> void SetupES(void); unsigned char readPort(void); void writePort(unsigned char value); void setName(char *value); void setMsg(char *value);
/*! \file lib_io_tp.cpp \brief Premier TD pour le CESI. \author Bertrand Vandeportaele IUT GEII \date 28/10/2021 */ #include "lib_io_tp.h" bool SIMULATION=false; //si cette variable est true, alors la librairie utilise les composants registres //à décalage en simulation sur Wokwi, sinon elle utilise les composants PCF8574 de la carte de TP //Pour les composants I2C: #include <Wire.h> #define SLAVE_ADDR_8574_A (0x38+6) #define SLAVE_ADDR_8574_B (0x38+7) //Pour les composants en simulation: //Librairie pour registres à décalage sur Wokwi.com /*mix de https://wokwi.com/arduino/projects/313005664351814210 et de https://wokwi.com/arduino/projects/301188813482361352 */ // Pin definitions: const int datapin_in = 9; /* Q7 */ const int clockpin_in = 8; /* CP */ const int latchpin_in = 4; /* PL */ const int datapin_out = 5; const int clockpin_out = 6; const int latchpin_out = 7; /////////////////////////////////////////////////////// /*! * \brief Fonction d'initialisation des ressources matérielles pour accéder aux ports d'entrée/sortie */ void SetupES(void){ Wire.begin(); delay(100); //teste si un composant 8574 est présent sur la carte for (byte addr=0x38;addr<=0x3f;addr++){ Wire.requestFrom(addr, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique if (Wire.available()==1) { Wire.read(); SIMULATION=false; //Utilisation des composants I2C plutot que des registres à décalage //Serial.print("premier composant i2c trouvé a:");Serial.println(addr,HEX); break; } } if (SIMULATION==false){ delay(100); Wire.beginTransmission((byte)SLAVE_ADDR_8574_B);Wire.write((byte)0xff);Wire.endTransmission();//configure le composant B en entrée }else{ pinMode(datapin_in, INPUT); pinMode(clockpin_in, OUTPUT); pinMode(latchpin_in, OUTPUT); // Set the three SPI pins to be outputs: pinMode(datapin_out, OUTPUT); pinMode(clockpin_out, OUTPUT); pinMode(latchpin_out, OUTPUT); } } /////////////////////////////////////////////////////// /*! * \brief Fonction de lecture du port d'entrée * @return valeur 8 bits lue sur le port */ unsigned char readPort(void){ if (SIMULATION==false){ Wire.requestFrom((byte)SLAVE_ADDR_8574_B, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique if (Wire.available()==1) //si l'octet est disponible return Wire.read(); // lire l'octet else return 0; }else{ unsigned char val=0; // Step 1: Sample digitalWrite(latchpin_in, LOW); digitalWrite(latchpin_in, HIGH); for (int i = 0; i < 8; i++) { int bit = digitalRead(datapin_in); val=(val<<1)|bit; digitalWrite(clockpin_in, HIGH); // Shift out the next bit digitalWrite(clockpin_in, LOW); } return val; } } /////////////////////////////////////////////////////// /*! * \brief Fonction d'écriture vers le port de sortie * @param value valeur 8 bits à écrire sur le port */ void writePort(unsigned char value){ //cette fonction pilote les 8 leds avec la valeur 8bits fournie dans le paramètre valeur if (SIMULATION==false){ Wire.beginTransmission((byte)SLAVE_ADDR_8574_A);//démarre la transmission avec l'adresse du pérpiphérique Wire.write(~(byte)value); //envoie la donnée complémentée car les LEDs s'allument à l'état 0 Wire.endTransmission(); }else{ shiftOut(datapin_out, clockpin_out, MSBFIRST, value); //Send "data" to the shift register //Toggle the latchpin_out to make "data" appear at the outputs digitalWrite(latchpin_out, HIGH); digitalWrite(latchpin_out, LOW); } } /////////////////////////////////////////////////////// void setName(char *value) { Serial.println(value); } /////////////////////////////////////////////////////// void setMsg(char *value) { Serial.println(value); } ///////////////////////////////////////////////////////
Appeler un enseignant pour valider votre travail et sauver une version.
Installer la librairie LiquidCrystal_I2C dans Arduino. Pour cela, cliquer dans l'IDE arduino sur Croquis→Inclure une Bibliothèque→Gérer les Bibliothèques et copier coller “LiquidCrystal_I2C” dans le champ “Filtrer votre recherche”. Selectionner dans la liste “LiquidCrystal_I2C by Frank Brabander” en version 1.1.2 et cliquer sur Installer.
Avec l'enseignant, programmer LA carte Arduino raccordée à la station de tri en utilisant la librairie d'entrées/sortie suivante et tester:
/*! \file lib_io_tp.h \brief Librairie IO+LCD pour FESTO \author Bertrand Vandeportaele IUT GEII \date 14/02/2022 */ #include <Arduino.h> #include <LiquidCrystal_I2C.h> void SetupES(void); unsigned char readPort(void); void writePort(unsigned char value); void setName(char *value); void setMsg(char *value);
/*! \file lib_io_tp.cpp \brief Librairie IO+LCD pour FESTO \author Bertrand Vandeportaele IUT GEII \date 14/02/2022 */ #include "lib_io_tp.h" //pour utiliser carte d'interface et gérer inversion des quartets + E et S inversées A/B par rapports aux inter et led.. #define FESTO #include <Wire.h> #define SLAVE_ADDR_8574_A (0x38+6) #define SLAVE_ADDR_8574_B (0x38+7) LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display //doc sur https://github.com/johnrickman/LiquidCrystal_I2C/blob/master/LiquidCrystal_I2C.h #ifdef FESTO ////////////////////////////////////////// char swapNibles(char in) { //le port de sortie est cablé en inversant les 4 bits MSB avec les LSB return (( (in & 0x0f) << 4) | ((in >> 4) & 0x0f)); } ////////////////////////////////////////// #endif /////////////////////////////////////////////////////// /*! \brief Fonction d'initialisation des ressources matérielles pour accéder aux ports d'entrée/sortie */ void SetupES(void) { Wire.begin(); delay(100); #ifdef FESTO Wire.beginTransmission((byte)SLAVE_ADDR_8574_A); Wire.write((byte)0xff); Wire.endTransmission(); //configure le composant A en entrée Wire.beginTransmission((byte)SLAVE_ADDR_8574_B); Wire.write((byte)0x00); Wire.endTransmission(); //configure le composant B en sortie #else Wire.beginTransmission((byte)SLAVE_ADDR_8574_A); Wire.write((byte)0x00); Wire.endTransmission(); //configure le composant A en sortie Wire.beginTransmission((byte)SLAVE_ADDR_8574_B); Wire.write((byte)0xff); Wire.endTransmission(); //configure le composant B en entrée #endif // Wire.beginTransmission((byte)SLAVE_ADDR_8574_B); Wire.write((byte)0xff); Wire.endTransmission(); //configure le composant B en entrée lcd.init(); // initialize the lcd lcd.backlight(); lcd.print("Nom:"); lcd.setCursor(0, 1); lcd.print("Msg:"); } /////////////////////////////////////////////////////// /*! \brief Fonction de lecture du port d'entrée @return valeur 8 bits lue sur le port */ unsigned char readPort(void) { #ifdef FESTO Wire.requestFrom((byte)SLAVE_ADDR_8574_A, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique #else Wire.requestFrom((byte)SLAVE_ADDR_8574_B, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique #endif if (Wire.available() == 1) //si l'octet est disponible return Wire.read(); // lire l'octet else return 0; } /////////////////////////////////////////////////////// /*! \brief Fonction d'écriture vers le port de sortie @param value valeur 8 bits à écrire sur le port */ void writePort(unsigned char value) { //cette fonction pilote les 8 leds avec la valeur 8bits fournie dans le paramètre valeur #ifdef FESTO Wire.beginTransmission((byte)SLAVE_ADDR_8574_B);//démarre la transmission avec l'adresse du pérpiphérique Wire.write((byte)swapNibles(value)); //envoie la donnée non complémentée #else Wire.beginTransmission((byte)SLAVE_ADDR_8574_A);//démarre la transmission avec l'adresse du pérpiphérique Wire.write(~(byte)value); //envoie la donnée complémentée car les LEDs s'allument à l'état 0 #endif Wire.endTransmission(); } /////////////////////////////////////////////////////// void printlcd(char *value, int nligne) { lcd.setCursor(4, nligne); int i; for (i = 0; i < 12; i++) { if ((value[i] == '\0') || (value[i] == '\n')) break; else lcd.write( value[i]); } //si i<12; on efface avec des ' ' while (i < 12) { lcd.write(' '); i++; } } /////////////////////////////////////////////////////// void setName(char *value) { printlcd(value, 0); } /////////////////////////////////////////////////////// void setMsg(char *value) { printlcd(value, 1); }
Appeler un enseignant pour valider votre travail et sauver une version.
Proposer une adaptation de la machine à états permettant de réaliser:
Réaliser la démarche de test comme précédemment.
Appeler un enseignant pour valider votre travail et sauver une version.
Pour illustrer le parallélisme de tâches, vous allez scinder la machine à états en 2 machines à états séparées afin de permettre au système de gérer l'arrivée d'une pièce en amont du tapis alors qu'une pièce est en cours de transit vers une glissière:
Les deux machines a états doivent communiquer: La première doit informer la deuxième du type de pièce détectée et attendre que la deuxième lui demande d'envoyer la pièce.
Proposer un dessin des deux machines à états et après validation par l'enseignant, réaliser la démarche de test comme précédemment.
Appeler un enseignant pour valider votre travail et sauver une version.