Outils pour utilisateurs

Outils du site


tp_capteur_actionneur2

TP Capteur 2: Jauge de contrainte pour la mesure de force

Objectifs

  • Utilisation d'un convertisseur Analogique vers Numérique différentiel et d'un pont de Wheatstone
  • Caractérisation et Étalonnage du capteur
  • Filtrage des données du capteur
  • Utilisation du capteur pour reconnaître des objets

Ressources documentaires

Présentation du matériel

homepages.laas.fr_bvandepo_files_iut_tp_lpro_capteur_balance_et_arduino.jpg

homepages.laas.fr_bvandepo_files_iut_tp_lpro_capteur_balance.jpg

datasheet du composant convertisseur HX711: http://homepages.laas.fr/bvandepo/files/iut/tp_lpro_capteur/hx711_english_p1-5.pdf

Schéma de connexion du composant convertisseur HX711:

explication des connexions:

  • A1 de l'Arduino (utilisée en tant qu'entrée numérique) connéctée à DOUT du HX711 via le fil jaune
  • A0 de l'Arduino (utilisée en tant que sortiee numérique) connéctée à SCK du HX711 via le fil orange
  • GND de l'Arduino connecté à la masse du HX711 via le fil marron
  • 5V de l'Arduino connecté à l'alimentation du HX711 via le fil rouge

numérotation des balances

Important que vous repreniez la même balance à chaque séance

Exercice 1: Récupération des données brutes issues du convertisseur

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:

hx711.ino
#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()
{
}

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg 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.

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg 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:

  • si le bit 6 du port d'entrée présente un front montant (ceci permet d'obtenir un unique échantillon)
  • ou si le bit 7 du port d'entrée est à l'état haut (ceci permet d'obtenir des échantillons en continu)

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 .

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg 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.

Une fois les réponses validées, faire valider à l'enseignant.

Exercice 2: Caractérisation et Étalonnage du capteur

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, c'est à dire à l'estimation des paramètres d'un modèle simplifié du capteur. Pour cela nous allons procéder en plusieurs étapes:

  1. Tout d'abord nous allons procéder à la collecte de données en relevant les données fournies par le capteurs pour différents 'étalons', qui sont des masses connues. Dans le cadre du TP, les étalons seront divers objets dont la masse aura été mesurée à l'aide d'une balance de référence.
  2. Ensuite, nous tracerons ces données pour les analyser et décider d'un modèle de fonction reliant les données brutes aux grandeurs physiques à mesurer. Nous utiliserons donc ici un modèle dit “paramétrique”.
  3. Puis, nous estimerons les paramètres de cette fonction, tout d'abord en utilisant un nombre minimal de données puis en utilisant plus de données afin d'améliorer la précision
  4. Finalement nous modifierons le programme de l'exercice précédent pour qu'il fournisse une mesure en gramme au lieu de la valeur brute, puis nous analyserons les données fournies afin de caractériser le capteur en terme de précision/répétabilité/hystérésis etc.

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 plot
cd plot
rm serialhistoplot.py*
wget http://homepages.laas.fr/bvandepo/files/iut/tp_lpro_capteur/serialhistoplot.py
chmod a+x serialhistoplot.py
echo fini
 

Pour lancer l'application afin quelle affiche les données reçues depuis l'arduino, vous devrez plus tard copier coller dans un terminal:

~/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:

~/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.

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg Tester maintenant cette application sur un autre générateur aléatoire. Pour cela, copier coller dans un terminal:

~/plot/serialhistoplot.py -t2

Interpréter l'affichage et conclure sur la distribution 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.

Acquisition d'échantillons issus du capteur

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 

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg 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.

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg 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:

  1. positionner la masse au centre du plateau de la balance et attendre quelques secondes que la balance se stabilise mécaniquement (qu'il n'y ait plus d'oscillations mécaniques dues à l'élasticité). Veillez à partir d'ici à ne pas toucher la balance, la masse et même à éviter les vibrations de la table
  2. générer un front montant sur l'entrée 0 du port d'entrée sur la carte Arduino pour déclencher une réinitialisation des mesures et mettre l'entrée 6 à l'état 1 pour acquérir des échantillons en continu.
  3. attendre la collecte de suffisamment d'échantillons pour que l'écart type se stabilise (quelques centaines)
  4. copier coller la dernière ligne affichée dans la console dans un fichier texte en notant la masse correspondante

Une fois les réponses validées, faire valider à l'enseignant.

Caractérisation et étalonnage "à la main" du capteur

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.

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg Reporter sur une feuille de papier quadrillée les mesures précédentes et proposer une fonction $mesurande=f(donnée)$ . Déterminer les paramètres de cette fonction et la coder dans le programme Ardunio afin que l'Arduino fournisse sur le port série non plus les données brutes mais la masse en gramme. Utiliser l'application sur PC pour afficher les statisques sur les mesures en gramme fournies par l'Arduino. Analyser:

  • Les valeurs moyennes affichées pour des masses connues utilisées ou non pour l'étalonnage.
  • Les écarts types en gramme pour un objet posé de manière statique sur la balance.
  • L'évolution de la cadence et de l'écart type des mesures selon que le cavalier de la carte convertisseur est ouvert ou fermé.
  • La présence ou non d'un hystérésis mesurable. Pour cela effectuer la mesure de masse d'un objet de masse a, la balance étant initialement vide. Puis ajouter un objet de masse b, le retirer et répéter la mesure.

Une fois les réponses validées, faire valider à l'enseignant.

homepages.laas.fr_bvandepo_files_iut_tp_tns_bonus.jpg Demandez à l'enseignant si vous êtes concernés par cette question: Estimation au sens des moindres carrés des paramètres de la fonction $f$ en utilisant toutes les mesures.

Exercice 3: Filtrage des données capteur

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 dans votre programme arduino:

buffer_circulaire.cpp
/////////////////////////////////////////////////////////////////
typedef long 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;
        }
//////////////////////////////    
        echantillon_t 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;
 
          //A COMPLETER ICI PAR L'ETUDIANT
 
              return ek;
          } 
////////////////////////////// 
    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(30); //pour configurer le calcul sur 30 échantillons

Afin d'utiliser ce code, vous ajouterez après l'acquisition de l'échantillon val la ligne suivante dans votre programme:

val=statistiqueMesures.TraiteUnEchantillon(val);

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg Compléter la fonction echantillon_t TraiteUnEchantillon(echantillon_t ek) pour qu'elle calcule la moyenne des échantillons stockés dans le tableau memoireVk contenant nbMemoireVk échantillons. Cette valeur doit ensuite être retournée par la fonction.

Une fois les réponses validées, faire valider à l'enseignant.

Exercice 4: Reconnaissance d'objets en fonction de leurs masses

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:

detecte_objet.cpp
//////////////////////////////////////////
//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;
}
//////////////////////////////////////////

homepages.laas.fr_bvandepo_files_iut_tp_tns_todo.jpg Copier coller le nouveau code dans votre programme précédent, adapter NBOBJETS au nombre d'objet 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).

Utilité: La sortie pilotée par le barregraphe pourrait par exemple piloter un aiguillage des pièces vers différents tapis, permettant ainsi le tri.

Une fois les réponses validées, faire valider à l'enseignant.

tp_capteur_actionneur2.txt · Dernière modification: 2019/11/14 23:46 par bvandepo