Voltmètre de précision avec Arduino, ADS1115, bluetooth HC05 et Processing Datalogging

Le schield pour Arduino UNO

ADS1115 et HC05 sur le schield Arduino UNO



Dessous du schield avec câblage visible





ADS1115 est un convertisseur 16-bit (15-bit utiles ou 32767 points), bon marché, qui offre bien plus de précision que celui de l'ATMega 328 de l'Arduino UNO qui lui est limité à 12-bit et est mono-canal. La lecture de la datasheet montre qu'il possède 4 canaux de mesures de tension (A0, A1, A2, A3) par rapport à la masse et que ces canaux peuvent fonctionner en mode différentiel, c'est-à-dire positionnés entre 4 points quelconque du circuit. Les 4 points de mesure A0-A3 doivent avoir un potentiel positif par rapport à la masse, sous peine d'endommagement du circuit. Le mode différentiel de mesure donne deux valeurs V1-V2 et V3-V4, il est intéressant pour, par exemple acquérir simultanément tension et intensité dans un circuit. De plus on peut le paramétrer au choix pour privilégier soit la précision soit la fréquence des mesures et ajuster l'échelle à l'aide du gain programmable. On peut utiliser la configuration par défaut du chip ADS1115 ou bien opter pour des valeurs différentes des paramètres de configuration, contenus dans les 16 bits (2 bytes) du registre de configuration, qu'il convient de programmer dans le programme d'utilisation Arduino. On se référera à la datasheet tableau 8 pour la signification des paramètres de configuration et pour les fonctions de configuration aux différentes libraries I2C disponibles pour l'ADS1115 (https://github.com/adafruit/Adafruit_ADS1X15, https://github.com/jrowberg/i2cdevlib.

HC06 est l'émetteur récepteur bluetooth qui assure la liaison série entre Arduino Uno et l'autre correspondant bluetooth comme un portable, une tablette ou un smartphone. Ses connexions à l'Arduino UNO sont les suivantes: Tx sur Rx (UNO) et Tx (UNO) par l'intermédiare d'un pont de 2 résistances (1K et 2K) pour ajuster la tension de 5V (UNO) vers 3,3V (HC05).

La précision est toujours inférieure au mV sur la plus grande échelle de mesure c'est-à-dire 5V. En théorie on devrait avoir 5000mV/32767 = 0,153 mV ce qui est très intéressant. Mais en pratique il se peut que du bruit electromagnétique influence les mesures. Toutefois on peut compter sur une bonne précision et comme on peut le constater sur les enregistrements plus bas, les courbes se présentent avec un taux de bruit pratiquement imperceptible. Les résultats sont donc encourageant pour un système à transmission bluetooth, c'est-à-dire sans fil.

Sketch Arduino
/****    UNO_ADS1115_Differential_DUAL_Bluetooth_SL01.ino    *****************************
 * celui-ci est pour la liaison série-Bluetooth seulement
 * slazare    2020 02 20
 */
#include <Wire.h>
#include <Adafruit_ADS1015.h>

Adafruit_ADS1115 ads;  // Declare an instance of the ADS1115


int16_t rawADCvalue[2];  // The is where we store the value we receive from the ADS1115

float scalefactor = 0.1875F; // This is the scale factor for the default +/- 6.144 Volt Range we will use
float volts = 0.0; // The result of applying the scale factor to the raw value

void setup(void)

{
  Serial.begin(9600); 
  ads.begin();
  ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV      0.1875mV (default)
  //ads.setGain(GAIN_ONE);        // 1x gain   +/- 4.096V  1 bit = 2mV      0.125mV
  // ads.setGain(GAIN_TWO);        // 2x gain   +/- 2.048V  1 bit = 1mV      0.0625mV
  // ads.setGain(GAIN_FOUR);       // 4x gain   +/- 1.024V  1 bit = 0.5mV    0.03125mV
  // ads.setGain(GAIN_EIGHT);      // 8x gain   +/- 0.512V  1 bit = 0.25mV   0.015625mV
  // ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV

}


void loop(void)

{  

  rawADCvalue[0] = ads.readADC_Differential_0_1();

  rawADCvalue[1] = ads.readADC_Differential_2_3();
  //volts = (rawADCvalue * scalefactor)/1000.0;
  
  //Serial.print("Raw ADC Value = "); 
  //Serial.print(rawADCvalue); 
  Serial.print(50000+rawADCvalue[0]); //50000 ajouté pour que le nombre ait constamment 5 digit et toujours positif
  //Serial.print(" ");
  Serial.print(10000+rawADCvalue[1]); //idem 
 // Serial.print("\tVoltage Measured = ");
  //Serial.println(volts,6);
  Serial.println();
  

  delay(1000);

}
//***********************************************************************************************************



Exemple de résultats acquis par le programme Processing


Mesures de tension programmée par DAC MCP4725 et Arduino UNO


La variation programmée de la tension  a été faite avec un module MCP4725 comme décrit plus bas. Le graphe a été enregistré à une mesure par seconde et il contient 2000 mesures de la gauche vers la droite, ce qui fait une durée d'enregistrement de 33 minutes. La fréquence des mesures et la durée d'enregistrement peut être ajustée par l'utilisateur. 

Ci-dessous en mode différentiel avec des mesures négatives sur l'une des voies. Par-contre les 4 broches de mesure doivent avoir un potentiel positif au dessus de la masse sous peine d'endommagement du chip ADS1115. 



Ce graphe de mesures de tension en fonction du temps est obtenu sur mon portable avec le sketch Processing ci-dessous selon le montage décrit dans le schéma ci-dessous. L'enregistrement comprend 1 mesure par seconde sur chacun des deux canaux soit au maximum 2000 points en 2000 secondes de gauche à droite. Les valeurs courantes en volt sont affichées en directe dans les rectangle en grisé. La précision et la stabilité des mesures sont excellentes. 

Sketch Processing pour l'acquisition de mesures ADS1115 en mode différentiel 


/*****  ReadADS1115_Differential_DUAL_SL33.pde *******************************************

* Convertisseur ADS1115 donne les valeurs différentielles de ADC0-ADC1 et ADC2-ADC3 dans le buffer "communication série" et 
* les enregistre 1 fois par 2 seconde dans le fichier xxxxxx.txt selon les réglages
* Sketch à mettre dans Arduino "****    UNO_ADS1115_Differential_DUAL_Bluetooth_SL01.ino    *****"
* ça fait un voltmètre bluetooth très précis 
* Paramètres ajustables de la fenêtre Vmin= 9V et Vmax= 15V par exemple  
* validé slazare 2020 03 13 
*/
import processing.serial.*;
PrintWriter output;
Serial myPort;
float val =20;
float val2 =10;
float voltage = 0 ;
float voltage2 = 0;
int tictime=0;      //arrêt du temps à chaque enregistrement 
int period = 999;    //détermine la fréquences des points de mesure et d'affichage 
int xPos = 1;         // horizontal position of the graph 

float scalefactor = 0.1875F; // This is the scale factor for the default +/- 6.144 Volt Range we will use

float Vmin = 0;    // en V
float Vmax = 5;    // en V 
          
//Variables to draw a continuous line.
int lastxPos=1;
int lastheight=500;
int lastheight2=500; 

void setup() {

  size(2000,1000);               //peut être adapté à 200, 1023 par exemple
  myPort = new Serial(this, Serial.list() [2], 9600); //HC05-2 c'est COM8 donc faut mettre [2] ou [3]        ESP32-GOUU COM19 c'est 11 ESP32-WROOMCOM20 c'est 12 
  printArray(Serial.list());
  output = createWriter("ReadADS1115_Differential_DUAL_SL02.txt");  //fichier et nom à changer 
  stroke(0,0,0);
  strokeWeight(1);
  line(0, height-height*(10-Vmin)/(Vmax-Vmin), width, height-height*(10-Vmin)/(Vmax-Vmin));  //ligne des 10V 
  stroke(255,0,0);
  line(0, height-height*(12-Vmin)/(Vmax-Vmin), width, height-height*(12-Vmin)/(Vmax-Vmin));  //ligne des 12V
  stroke(0,0,255);
  line(0, height-height*(14-Vmin)/(Vmax-Vmin), width, height-height*(14-Vmin)/(Vmax-Vmin));  //ligne des 14V
  //Date et heure imprimé en haut du fichier de sauvegarde
  int s = second();  // Values from 0 - 59  
  int m = minute();  // Values from 0 - 59
  int h = hour();    // Values from 0 - 23
  int d = day();
  int mo = month();
  int y = year();
      output.print(y); output.print(" ");
      output.print(mo); output.print(" ");
      output.print(d); output.print(" ");
      output.print(h);
      output.print('h');
      output.print(m);
      output.print('m');
      output.print(s);
      output.println('s');
      //rect(800, 1000, 300, 1000);
}

void draw()  {

  while (myPort.available() > 0) {
    String sb = myPort.readStringUntil('\n');
    if (sb != null)
    {
      String A0A1 = sb.substring(0,5);
      String A2A3 = sb.substring(5,10);
      val = float(A0A1)-50000;   //j'enlève 50000 aux valeurs transférées qui avait été ajouté pour que les 5 digits restent constants
      val2 = float(A2A3)-50000;  //idem
      //voltage = 3.53501*(val*scalefactor)/1000;     //le coeff 3.53501*   c'est pour l'échelle 0-17V seulement
      voltage = (val*scalefactor)/1000;
      voltage2 = (val2*scalefactor)/1000;
    }
  }
    textSize(32);
    
    if (millis()-tictime>period) {    //à chaque échéance "tick" reçoit, calcule, enregistre, trace et affiche 
      String sf2 = nf(voltage, 0, 5);    // transforme variable en string avec 5 après la virgule
      String sf3 = nf(voltage2, 0, 5);
      //String sf2 = nf(val, 0, 0);
      //Ecriture dans le fichier dont le nom est défini ci-dessus 
      output.print(sf2);  // Prints "2.51200" for example 
      output.print(" ");
      output.println(sf3);
      //output.println(voltage);   
      tictime=millis();

      //Draw a red line from Last voltage point to the new one.

    stroke(255,0,0);     //stroke color
    strokeWeight(4);        //stroke wider
    line(lastxPos, lastheight, xPos, height - height*(voltage-Vmin)/(Vmax-Vmin)); 
    lastheight= int(height-height*(voltage-Vmin)/(Vmax-Vmin));

     //Draw a green line from Last voltage point to the new one.

    stroke(0,200,100);     //stroke color green
    line(lastxPos, lastheight2, xPos, height - height*(voltage2-Vmin)/(Vmax-Vmin)); 
    lastxPos= xPos;
    lastheight2= int(height-height*(voltage2-Vmin)/(Vmax-Vmin));
    
    //renouvelle l'affichage des valeurs mesurées dans un rectangle 
    fill(#C1C1C1);      //255, 255, 255 
    noStroke();
    rect(100, 130, 260, 90);    //rectangle d'affichage des deux mesures 280x90
    rect(1000, 130, 260, 90);
    textSize(64);
    fill(0, 100, 153);  //définit la couleur des caractères RGB par défaut 
    text(nf(voltage, 0, 4), 100, 200);      //la fonction nf(val, 0, 4) format le float à 2 après virgule
    text(nf(voltage2, 0, 4), 1000, 200);
    //text(int(val), 100, 200);      //l
    //text(int(val2), 1000, 200);
    xPos++;
    }

  }     

  
  void keyPressed() {  // press any key or spacebar 
  output.print("FIN");
  output.flush();  // Writes the remaining data to the file
  output.close();  // Finishes the file
  exit();  // Stops the program
}
//*******************************************************************************************************

Quelques explications sur le sketch Processing



Le buffer recevant les données transmises par la communication série de l'arduino est lu par le sketch Processing comme une chaîne de caractères, ce qui veut dire que les nombres flottants ou entiers ne sont pas reconnus simplement. Il convient donc de lire octet par octet avec la fonction readStringUntil(\n) jusqu'à la fin signalée par la présence de \n et de définir les sous-chaînes de 5 digits qui vont définir les valeurs communiquées et de les convertir en nombres entiers avant calcul des tensions mesurées. Le convertisseur ADS1115  transmet des nombres compris entre 1 et 2^16=65536 (outre les valeurs de tension négatives qui sont possibles). C'est pour cela que les 5 digits transférés sont suffisants pour une bonne précision. Sur une échelle de 5V cela fait une imprécision inférieure au mV, ce qui très acceptable pour beaucoup d'applications. Les données transmises sont lues à la cadence de une fois par seconde dans l'exemple ci-dessus mais des fréquences plus rapides ou plus lentes sont possibles. Si on regarde de plus près, on voit que les deux nombres fournis par ADS1115 sont concaténés dans le buffer de communication série. Un problème surviendrait lorsque ces valeurs d'entier deviennent inférieures à 10000, 1000, 100 et 10 puisque le nombre de digits à ce moment-là passe de 5 à 4, puis à 3, 3 et 1. Alors on aurait un problème à la lecture des digits qui ne seraient pas en nombres constants. C'est pour cela que le sketch Arduino ajoute 50000 à chaque valeur avant la communication de manière à ne jamais avoir de valeur inférieure à 10000 et de maintenir le nombre de digit à 5 pour chaque nombre. Le buffer est donc lu en entier sur ses dix digits (variable sb), qui contiennent une valeur sur les 5 premiers et la deuxième sur les 5 autres. Avec les fonctions substring() les deux nombres sont séparés. Ensuite il suffit de retirer 50000 aux valeurs lues par le sketch Processing après transmission. Une fois les deux valeurs entières obtenues, on obtient les tensions en les multipliant par le facteur d'échelle appropriée qui dépend du gain de l'ADS1115. La fenêtre de traçage peut être ajustée à volonté ainsi que la fréquence des mesures. Les valeurs sont stockées dans un fichier text pour une éventuelle analyse ultérieure.

Exemple de mesures de tension "programmée" avec un DAC MCP4725

UNO plus MCP4725 génère une tension programmée qui est mesurée par ADS1115 puis transmise par bluetooth vers le programme Processing sur le portable
Avec un DAC (MCP4725) on peut programmer une tension et la faire varier dans le temps suivant un profil définit par une formule mathématique. Ici, dans l'exemple plus haut, c'est une sinusoïde. Pour que la tension mesurée soit dans la fenêtre de mesure possible, il convient de l'ajuster avec un pont de résistance R1, R2. Le rapport de d'atténuation est alors R1/(R1+R2).

Tension en dent de scie mesurée et transmise par le système mise au point dans ce projet




Commentaires