https://fr.wikipedia.org/wiki/Jauge_de_d%C3%A9formation
https://en.wikipedia.org/wiki/Strain_gauge
https://en.wikipedia.org/wiki/Wheatstone_bridge
https://fr.wikipedia.org/wiki/Fonction_de_r%C3%A9partition
https://fr.wikipedia.org/wiki/Variable_al%C3%A9atoire_%C3%A0_densit%C3%A9
https://fr.wikipedia.org/wiki/Loi_normale
http://www.bibmath.net/dico/index.php?action=affiche&quoi=./r/reglin.html
http://www.bibmath.net/dico/index.php?action=affiche&quoi=./r/reglin.html
https://euler.ac-versailles.fr/baseeuler/lexique/notion.jsp?id=29
https://euler.ac-versailles.fr/baseeuler/lexique/notion.jsp?id=5
https://euler.ac-versailles.fr/baseeuler/lexique/notion.jsp?id=26
http://serge.mehl.free.fr/anx/meth_carr.html
https://fr.wikipedia.org/wiki/%C3%89cart_type#Intervalle_de_fluctuation
datasheet du composant convertisseur HX711: https://bvdp.inetdoc.net/files/iut/tp_lpro_capteur/hx711_english_p1-5.pdf
Schéma de connexion du composant convertisseur HX711:
explication des connexions:
Les balances sont numérotées, il est important que vous repreniez la même balance à chaque séance!
Une version allégée de la librairie HX711 permettant à l'Arduino d'avoir accès aux échantillons convertis par le convertisseur différentiel HX711 est fournie dans le squelette d'application suivant:
#include <Wire.h> #define SLAVE_ADDR_8574_A 0x3e #define SLAVE_ADDR_8574_B 0x3f #include "Arduino.h" ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Code issu de la librairie HX711 pour Arduino de https://github.com/bogde/HX711.git class HX711 { private: byte PD_SCK; // Power Down and Serial Clock Input Pin byte DOUT; // Serial Data Output Pin byte GAIN; // amplification factor public: // define clock and data pin, channel, and gain factor // channel selection is made by passing the appropriate gain: 128 or 64 for channel A, 32 for channel B // gain: 128 or 64 for channel A; channel B works with 32 gain factor only HX711(byte dout, byte pd_sck, byte gain = 128){ begin(dout, pd_sck, gain); } HX711() { }; virtual ~HX711() {} // Allows to set the pins and gain later than in the constructor ////////////////////////////// void begin(byte dout, byte pd_sck, byte gain = 128){ PD_SCK = pd_sck; DOUT = dout; pinMode(PD_SCK, OUTPUT); pinMode(DOUT, INPUT); set_gain(gain); } ////////////////////////////// // check if HX711 is ready // from the datasheet: When output data is not ready for retrieval, digital output pin DOUT is high. Serial clock // input PD_SCK should be low. When DOUT goes to low, it indicates data is ready for retrieval. bool is_ready() { return digitalRead(DOUT) == LOW; } // set the gain factor; takes effect only after a call to read() // channel A can be set for a 128 or 64 gain; channel B has a fixed 32 gain // depending on the parameter, the channel is also set to either A or B void set_gain(byte gain = 128){ switch (gain) { case 128: GAIN = 1; break; // channel A, gain factor 128 case 64: GAIN = 3; break; // channel A, gain factor 64 case 32: GAIN = 2; break; // channel B, gain factor 32 } digitalWrite(PD_SCK, LOW); read(); } ////////////////////////////// // waits for the chip to be ready and returns a reading long read() { // wait for the chip to become ready while (!is_ready()) { // Will do nothing on Arduino but prevent resets of ESP8266 (Watchdog Issue) yield(); } long value = 0; uint8_t data[4] = { 0,0,0,0 }; // pulse the clock pin 24 times to read the data // https://www.arduino.cc/reference/en/language/functions/advanced-io/shiftin/ data[2] = shiftIn(DOUT, PD_SCK, MSBFIRST); data[1] = shiftIn(DOUT, PD_SCK, MSBFIRST); data[0] = shiftIn(DOUT, PD_SCK, MSBFIRST); // set the channel and the gain factor for the next reading using the clock pin for (unsigned int i = 0; i < GAIN; i++) { digitalWrite(PD_SCK, HIGH); digitalWrite(PD_SCK, LOW); } // Replicate the most significant bit to pad out a 32-bit signed integer if ( (data[2] & 0x80) !=0) { data[3] = 0xFF; } else { data[3] = 0x00; } // Construct a 32-bit signed integer value = ( static_cast<unsigned long>(data[3]) << 24 | static_cast<unsigned long>(data[2]) << 16 | static_cast<unsigned long>(data[1]) << 8 | static_cast<unsigned long>(data[0]) ); return value; } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// HX711 scale(A1, A0); // DOUT, SCK #include <math.h> #include <stddef.h> ////////////////////////////////////////// bool detecteFrontMontantPortB(unsigned char numerobit) { static char ancien[8]={-1,-1,-1,-1,-1,-1,-1,-1}; bool ret=false; unsigned char pb=LirePortB(); if (ancien[numerobit]==-1){ for (int i=0;i<8;i++) ancien[i]=(pb>>i)&1; }else{ char actuel=(pb>>numerobit)&1; if ( (actuel==1) && (ancien[numerobit]==0)) ret=true; else ret=false; ancien[numerobit]=actuel; } return ret; } ////////////////////////////////////////// unsigned char LirePortB() { 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; } ////////////////////////////////////////// void EcrirePortA(unsigned char valeur) //cette fonction pilote les 8 leds avec la valeur 8bits fournie dans le paramètre valeur { Wire.beginTransmission((byte)SLAVE_ADDR_8574_A);//démarre la transmission avec l'adresse du pérpiphérique Wire.write(~(byte)valeur); //envoie la donnée complémentée car les LEDs s'allument à l'état 0 Wire.endTransmission(); } ////////////////////////////////////////// void setup() { Serial.begin(9600); Serial.println("reset"); //scale.set_scale(250.f); //// this value is obtained by calibrating the scale with known weights //scale.tare(); Wire.begin(); // joindre le bus i2c en tant que maître delay(100); Wire.beginTransmission((byte)SLAVE_ADDR_8574_B); Wire.write((byte)0xff); Wire.endTransmission();//configure le composant B en entrée pinMode(3,OUTPUT); } ////////////////////////////////////////// void loop() { }
Lire et essayer de comprendre les différentes parties de ce squelette d'application. Demander à l'enseignant en cas de difficulté, APRES avoir tout lu. Compléter la fonction loop() pour déclencher la lecture d'un échantillon et écrire en ASCII sa valeur sur le port série, suivi d'un retour à la ligne. Programmer l'Arduino et vérifier dans la console série que l'affichage des valeurs fluctue lorsque vous appuyez (ou tirez) légèrement sur la balance.
Ajouter dans la fonction loop() le nécessaire (en vous aidant des fonctions fournies dans le squelette bool detecteFrontMontantPortB(unsigned char numerobit) et unsigned char LirePortB()) pour que l'échantillon ne soit acquis que:
De plus, en cas d'une détection d'un front montant sur le bit 0 du port d'entrée, votre programme devra envoyer la chaîne “reset” suivi d'un retour à la ligne sur le port série.
Les numéros de bits du port d'entrée à utiliser sont ceux inscrits sur le circuit imprimé, et non pas sur les interrupteurs. Il en est de même pour les niveaux logiques de ces entrées.
Vérifier sur la console que les fonctionnalités demandées sont bien ajoutées .
Lancer l'outils de visualisation de courbes d'arduino (via Outils→Traceur série), et visualiser le signal issus du capteur en fonction du temps (positionner l'interrupteur 6 en position 1 pour faire l'acquisition en continu). Vous devriez voir un tracé tel que celui ci si vous exercez des pressions variables sur le capteur:
Piloter le barregraphe pour allumer une seule LED indiquant une valeur de 0 à 7 qui est fonction affine par rapport au données brutes du capteur dont vous devrez mémoriser les valeurs minimales et maximales. Ainsi pour la valeur minimum mesurée par le capteur, vous devrez allumer la led 0 et pour la valeur maximum mesurée par le capteur, vous devrez allumer la led 7.
Pour pouvoir exploiter les données brutes issues du convertisseur afin d'obtenir une masse (pour rappel le capteur utilisée est une jauge de contrainte, qui mesure une force, telle un poids et non pas une masse…), il est nécessaire de procéder à l'étalonnage. L'étalonnage consiste en l'estimation des paramètres d'un modèle simplifié du capteur et il peut être réalisé en plusieurs étapes:
Si la carte convertisseur de votre balance dispose d'un cavalier, assurez vous qu'il est en position fermée pour cette partie de l'exercice.
Pour procéder à la collecte et à l'analyse des données, une application fonctionnant sur le PC est fournie. Cette application décode les chaines de caractères émises par l'Arduino (sur le port /dev/ttyUSB0 ou /dev/ttyACM0), chaque ligne correspondant à une valeur numérique (avec éventuellement une partie fractionnaire) codée en ASCII, et terminée par un retour à la ligne. Vous devriez remarquer que c'est le format qui vous a été demandé à l'exercice précédent, votre application Arduino doit donc être capable de communiquer directement avec cette nouvelle application.
Pour récupérer l'application, copier coller dans un terminal:
echo commence cd ~/ mkdir -p plot cd plot rm serialhistoplot.py* wget --no-check-certificate https://bvdp.inetdoc.net/files/iut/tp_lpro_capteur/serialhistoplot2022.py -O ./serialhistoplot.py chmod a+x serialhistoplot.py echo fini
Pour lancer l'application afin qu'elle affiche les données reçues depuis l'arduino, vous devrez plus tard copier coller dans un terminal alors que les fenêtres du moniteur et du traceur série Arduino sont fermées.:
~/plot/serialhistoplot.py
Dans un premier temps, nous allons tester cette application en utilisant des données issues non pas de l'arduino mais de l'application elle même, générées à l'aide de différents générateurs aléatoires. Pour cela, copier coller dans un terminal la ligne suivante:
~/plot/serialhistoplot.py -t1
Vous devriez voir l'affichage se stabiliser vers:
et dans la console obtenir les informations statistiques:
... nb mesures: 1342 cadence: 90.69 moyenne: 101.134 écart type: 30.678 nb mesures: 1343 cadence: 90.69 moyenne: 101.127 écart type: 30.667 nb mesures: 1344 cadence: 90.70 moyenne: 101.135 écart type: 30.657 nb mesures: 1345 cadence: 90.70 moyenne: 101.115 écart type: 30.655 nb mesures: 1346 cadence: 90.70 moyenne: 101.135 écart type: 30.652 ...
Les bâtons bleus correspondent à un histogramme ( https://fr.wikipedia.org/wiki/Histogramme ). La notion d'histogramme sera largement réutilisée plus tard dans l'année lorsque nous traiterons des images: https://perso.esiee.fr/~perretb/I5FM/TAI/histogramme/index.html
L'histogramme affiché par l'application fournie contient au maximum 10 intervalles de valeurs (axe horizontal) pour lesquels la fréquence relative d’occurrence est représentée (sur l'axe vertical). Ceci permet d'avoir une représentation graphique de la distribution des valeurs et de les interpréter ( https://fr.wikipedia.org/wiki/Histogramme#Interpr%C3%A9tation ). L'application fournie calcule la moyenne et l'écart type ( https://fr.wikipedia.org/wiki/%C3%89cart_type#Intervalle_de_fluctuation ) et affiche fonction appelée “densité de probabilité de la loi normale” ( https://fr.wikipedia.org/wiki/Loi_normale ) correspondant à ces moyennes et écarts types. Cet affichage permet de comparer l'histogramme issu des données avec une distribution normale (aussi appelée gaussienne) et de vérifier si les données suivent ou non une distribution normale. Pour l'exemple précédent, l'histogramme et la fonction de densité de probabilité sont assez proches, indiquant que les échantillons suivent une distribution de loi normale.
Pour fermer l'application, cliquer sur la croix fermant la fenêtre ou taper CTRL+C dans la console ayant servi à la lancer.
Tester maintenant cette application sur un autre générateur aléatoire. Pour cela, copier coller dans un terminal:
~/plot/serialhistoplot.py -t2
Faites de même avec:
~/plot/serialhistoplot.py -t5
Interpréter l'affichage et conclure sur les distributions des valeurs aléatoires générées. Interrogez vous sur le sens donné à l'écart type sur la page https://fr.wikipedia.org/wiki/%C3%89cart_type#Intervalle_de_fluctuation pour une distribution ne suivant pas la loi normale.
Alors que la carte Arduino a été chargée avec le programme de l'exercice précédent, lancer l'application sur le PC par copier coller dans un terminal:
~/plot/serialhistoplot.py
Jouez sur les interrupteurs 0,6 et 7 du port d'entrée de la carte et observer l'évolution de l'affichage sur le PC. Analyser la distribution des valeurs lorsque la masse posée sur la balance ne change pas. Suit elle une loi normale? Faire de même en changeant la masse alors que l'application intègre les échantillons et analyser la distribution.
Procéder maintenant à l'acquisition des mesures pour les masses de référence (fournies par l'enseignant). Pour cela, au lieu d'utiliser une seule mesure brute, nous allons utiliser l'application sur le PC pour calculer la moyenne d'un grand nombre de mesures, afin de minimiser l'influence du bruit de mesure. Pour chacune des masses de référence procéder suivant la séquence suivante:
Nous allons maintenant analyser les mesures réalisées à l'exercice précédent et déterminer la fonction (et ses paramètres) qui relie les données brutes à la grandeur physique mesurée.
echo commence cd ~/ mkdir -p plot cd plot rm ajustedroite2.py* wget --no-check-certificate https://bvdp.inetdoc.net/files/iut/tp_lpro_capteur/ajustedroite22021.py -O ./ajustedroite2.py chmod a+x ajustedroite2.py echo fini
Pour lancer l'application afin qu'elle affiche les données, vous devrez saisir dans un terminal en utilisant les valeurs que vous avez collectées une commande de la forme:
~/plot/ajustedroite2.py masse1 mesurebrute1 ecarttype1 masse2 mesurebrute2 ecarttype2 ...
par exemple:
~/plot/ajustedroite2.py 100 1000 25 500 7000 40
Le programme visualise alors dans une fenêtre les données fournies ainsi que les fonctions correspondantes:
Les valeurs des paramètres calculées par le programme s'affichent dans le terminal sous la forme:
2 correspondances saisies liste_masse <=> liste_mesure_brute +- ecart_type 100.00000 +1000.00000 +25.00000 500.00000 +7000.00000 +40.00000 ajuste aux sens des Moindres Carrés: évaluation de la fonction de cout: 0.0 ; résidus: [0.0, 0.0] ajuste sur 2 points seulement: évaluation de la fonction de cout: 0.0 ; résidus: [0.0, 0.0] Données brutes en sortie du capteur=f(Grandeur physique mesurée) coefficients de la droite ajustée y=a.x+b a=15.00000 b=-500.00000 Grandeur physique mesurée=f(Données brutes en sortie du capteur) coefficients de la droite ajustée y=a.x+b a=0.06667 b=33.33333
Dans cet exercice, vous allez devoir intégrer sur l'Arduino le calcul de moyenne des échantillons afin de minimiser l'influence du bruit de mesure.
Le calcul de moyenne sera réalisé sur les $n$ échantillons les plus récents, qui seront disponibles dans un tableau. Le code permettant la gestion de ce tableau en tant que buffer circulaire est fourni ci dessous et vous devrez le copier coller au début de votre programme arduino:
#include <Arduino.h> ///////////////////////////////////////////////////////////////// typedef double echantillon_t; ///////////////////////////////////////////////////////////////// class Filtre{ public: Filtre(int nbMemoireVkInit=0, echantillon_t *memoireVkInit=NULL){ nbMemoireVk = nbMemoireVkInit; if (memoireVkInit!=NULL){ memoireVk = memoireVkInit; }else{ memoireVk= new echantillon_t[ nbMemoireVk]; // alloue un tableau de nbMemoireVk cases pour les échantillons } int i; indice_ecr=0; for (i=0;i<nbMemoireVk;i++) memoireVk[i]=0; } ////////////////////////////// double TraiteUnEchantillon(echantillon_t ek){ //rangement de la valeur dans tableau à l'indice indice_ecr memoireVk[indice_ecr]=ek; //incrémentation du pointeur d'écriture pour la prochaine itération indice_ecr = (indice_ecr + 1); if (indice_ecr>=nbMemoireVk) indice_ecr=0; double moyenne=0; //A COMPLETER ICI PAR L'ETUDIANT return moyenne; } ////////////////////////////// protected: //attributs accessibles dans les classes dérivées int nbMemoireVk; //nombre de cases du buffer circulaire pour stocker les valeurs de vk echantillon_t * memoireVk;//buffer circulaire pour stocker les valeurs de vk int indice_ecr; //indice d'écriture dans le buffer rotatif memoireVk }; ///////////////////////////////////////////////////////////////// Filtre statistiqueMesures(10); //pour configurer le calcul sur 10 échantillons
val=statistiqueMesures.TraiteUnEchantillon(val);
Nous souhaitons maintenant utiliser le capteur (la balance) pour reconnaître des objets. Ceci peut permettre par exemple à identifier des pièces défectueuses sur une chaîne d'assemblage automatisée. Pour cela, nous allons devoir définir une plage de masses correspondant à chaque objet, car une égalité stricte de masse n'est pas adaptée (à cause du bruit de mesure, des perturbations extérieures et des variations de masse des objets quasi identiques).
Nous proposons pour traiter ce problème une ébauche de solution qu'il vous faudra compléter.
Une structure de donnée est utilisée pour stocker les différentes informations correspondant à un objet: sa masse maximale et minimale ainsi que son nom.
Un tableau contenant plusieurs éléments de la structure précédente est ensuite utilisé pour stocker les informations de tous les objets. En langage C, l'initialisation de ces éléments doit être faite dans une fonction void initListeObjets() dont un exemple est fourni ci dessous:
////////////////////////////////////////// //Structure pour un objet struct Objet { float masse_max; //masse max pour reconnaitre l'objet float masse_min; //masse min pour reconnaitre l'objet char nom[20]; //chaine de caractères pour le nom de l'objet, 20 caractères maximum }; ////////////////////////////////////////// //Tableau pour stocker les différentes informations sur les objets #define NBOBJETS 3 Objet listeObjets[NBOBJETS]; ////////////////////////////////////////// //fonction pour remplir le tableau de structure avec les informations sur les différents objets, à //appeler dans la fonction setup(). Cette fonction doit être adaptée selon les objets que vous voulez //pouvoir reconnaitre void initListeObjets(){ strcpy(listeObjets[0].nom,"Trousse"); //la fonction strcpy permet de copier tous les caractères de la chaine listeObjets[0].masse_max=301.35; listeObjets[0].masse_min=272.65; strcpy(listeObjets[1].nom,"Souris"); listeObjets[1].masse_max=73.5; listeObjets[1].masse_min=66.5; strcpy(listeObjets[2].nom,"Calculatrice"); listeObjets[2].masse_max=222.6; listeObjets[2].masse_min=201.4; } //////////////////////////////////////////
Copier coller le nouveau code dans votre programme précédent, adapter NBOBJETS au nombre d'objets que vous souhaitez reconnaître ainsi que la fonction void initListeObjets(). Compléter les fonctions void setup() et void loop() pour effectuer la reconnaissance d'objets. Piloter le barregraphe pour allumer la led correspondant au numéro de l'objet détecté et les éteindre toutes dans le cas où la masse mesurée ne correspond à aucun objet connu. Afficher sur la console Serial le nom de l'objet détecté. Expliquer pourquoi il peut arriver qu'un objet soit temporairement détecté à tort et proposer une solution pour résoudre ce problème (sans forcément l'implémenter).
Un cas d'usage du système que vous devez réaliser est le suivant: La sortie pilotée par le barregraphe pourrait par exemple piloter un aiguillage des pièces vers différents tapis, permettant ainsi le tri.
Faire valider vos réponses à l'enseignant.
—————————————————————-