BMS, Etat de charge et santé de batterie lithium, banc cyclage (arduino)

Bonjour,

J’ai apporté quelques modifications concernant mon dernier post du 29/04

Voici l’algorithme :

clignoter la led13
mesurer la tension
mesurer le courant 
calculer la capacité
si temps = 1mn 
                 alors désactiver le relai 0
                 mesurer la tension électrochimique E
                 si temps = 4s
                                  alors réactiver relai 0
si on appuie charge_decharge 
                                          alors autoriser la charge ou la décharge
calcul de la résistance interne de la batterie 
afficher la tension en volt
afficher le courant en ampère
afficher la capacité en mAh
afficher le temps

Le code est le suivant :

#include <Wire.h>
#include <SoftwareSerial.h> 
#include <TimerOne.h>
#include <LiquidCrystal.h>
#define led13   13 
#define relays1 12          
#define relays0 11   
#define charge_decharge    9        
#define on_off 10
LiquidCrystal lcd(7,6,2,3,4,5); // (RS=7, E=6, D4=2, D5=3, D6=4, D7=5)     
/******************************************************************/
float tension,tensionACS=0;       // Initialisation 
float courant=0;
float sensible=0.185;           // Sensibilité du capteur 
float ACSoffset=2.5;           // Valeur de tension de sortie du capteur lorsque le courant=0
float capacite=0;  
float R;
float E=1.2 ; // tension électrochimique
int heure=0,minute=0,seconde=0; // le temps
/*****************************************************************/    
void setup()
{
  Timer1.initialize(1000000);          // initialize timer1, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
pinMode(led13, OUTPUT);
pinMode(A0, INPUT); // On défini A0 en entrée
pinMode(relays1, OUTPUT);           // On défini relay1 en sortie
pinMode(relays0, OUTPUT);           // On défini relay0 en sortie
Serial.begin(9600);
lcd.begin(20,4);         //et pas lcd begin comme cetraine biblio
lcd.clear();      // Effacer le l'écran
digitalWrite(relays0,HIGH);
} // fin setup
// Interruptions  tous les 1s fait par le timer1****************
void callback()  
{
 seconde++;
 if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);} else {digitalWrite(13,HIGH);}
 tension=analogRead(A0); // Lecture de la valeur récupèrée de A0
 tension=tension*(5.0/1023.0); // Conversion de cette valeur en une tension comprise entre 0 et 5V (Batterie)
 tensionACS=analogRead(A1); // Lecture de la valeur récupèrée de A1
 tensionACS=(tensionACS*5.0)/1023; // Conversion de cette valeur en une tension comprise entre 0 et 5V (Capteur ASC)
 courant=(tensionACS-ACSoffset)/sensible; // Calcul du courant 
 capacite=(courant/3.6)+capacite; // (I(A)*1000)/(1s/3600)+capacite   
} // fin void callback
void temps()
{ if(seconde>=60){seconde=0;
                  minute++;
                  digitalWrite(relays0,LOW);
                  if(minute>=60){minute=0;
                                 heure++;
                                }
                  if(heure>=24){heure=0;
                                lcd.clear();
                               }
                 }
  if(seconde==4){digitalWrite(relays0,HIGH);}            
} // fin void temps
/*********************************************************/
void loop()
{
 temps();
 
 if (digitalRead(charge_decharge)==0)  
 {digitalWrite(relays1,LOW);R=(tension-E)/courant;} // charge     
 else
 {digitalWrite(relays1,HIGH);R=E/courant;} // décharge
/*************Affichage sur l'écran LCD****************/
 lcd.setCursor(0,0);                 
 lcd.print(tension,2);
 lcd.print("V");
 lcd.print("  "); 
 lcd.print(courant,2);
 lcd.print("A");
 lcd.setCursor(0,1);
 lcd.print(R,2);
 lcd.print("ohm");
 lcd.print("  ");
 lcd.print(capacite,0);
 lcd.print("mAh   ");
lcd.setCursor(5,2);
lcd.print(heure);
lcd.print("h ");
lcd.print(minute);
lcd.print("m ");
lcd.print(seconde);
lcd.print("s");
    }//fin loop ;

Vérification et simulation Isis :

En charge :
Pour être en charge, il faut désactiver l’interrupteur charge_decharge de l’entrée. On a alors l’alimentation de 1.5V de recharge la batterie à 1A.
En effet, le courant batterie correspond à l’équation suivante : I=(U-E)/R
Sur la figure suivante, on peut observer que le courant de charge est bien à 1A et que la tension de la batterie correspond bien à 1.3V. la mesure de la résistance interne affiche 0.09Ω presque égale à la valeur paramétrée dans la simulation.
Lorsque la tension de la batterie est à 1.5V et que le courant est de 0.1A alors il y a l’arrêt de la charge.
De plus, si le temps de charge est plus grand que 3 heures, la charge s’arrête.

En décharge :
Pour la décharge, on active l’interrupteur charge_decharge. La batterie se décharge sur la résistance de décharge (0.9ohm). La tension de décharge est de 1.2V et le courant de décharge est 1A.
Le courant de batterie en décharge correspond à l’équation suivante : I=E/(R+r(0.9))
Dans la simulation Isis, représentée sur la figure ci-dessous, on observe que le courant de charge est égal à 1A et que la tension de la batterie correspond bien à 1.1V.
La résistance interne affichée est de 1.12Ω : ce qui est presque égale à la valeur paramétrée dans la simulation.

Voici le lien pour télécharger le fichier Isis :
https://drive.google.com/file/d/1aHmKApdtJsQLtyKSUddmVvACPW5-L8TQ/view?usp=sharing

Pour plus d’information concernant le projet, je vous invite à télécharger le dossier dont le lien
est le suivant : batterie.docx - Fichier Doc

Perspectives :
Notre testeur NiMH fonctionne bien, mais pour sécuriser le fonctionnement, un capteur de température LM35 va permettre de mesurer la température externe de la batterie.
Si celle-ci est supérieure à 50°C, alors la charge ou la déchargé sera bloquée.
Dans un deuxième temps, de réaliser un testeur de deux batteries NiMH de type AA et AAA à la fois en parallèle avec une Nano.

Merci.

J’ai ajouté dans mon dernier post du 16/05 une autre batterie NiMH de type AAA.
J’ai changé les logicstates avec de vrais interrupteurs pour la gestion de la charge et de la décharge de chaque batterie, et un autre interrupteur pour imposer à l’afficheur écran LCD les informations sur quelle batterie sont à afficher.
J’ai aussi utilisé la liaison série pour pouvoir récupérer les informations concernant chaque batterie dans le but d’une étude ultérieure.
Deux thermomètres Lm35 (un pour chaque batterie) ont été rajoutés pour sécuriser le fonctionnement, il va permettre de mesurer la température externe des batteries.
Si celle-ci est supérieure à 50°C, alors la charge ou la déchargé sera bloquée.

Voici le code :

#include <Wire.h>
#include <SoftwareSerial.h> 
#include <TimerOne.h>
#include <LiquidCrystal.h>
#define led   13 
#define R1 12          
#define R0 11  
#define R2 8
#define R3 9
//#define on_off 10   
LiquidCrystal lcd(7,6,2,3,4,5); // (RS=7, E=6, D4=2, D5=3, D6=4, D7=5)     
/******************************************************************/
float tension1=0,tension2=0;       // Initialisation 
float courant1=0,courant2=0;
float sensible=0.185;           // Sensibilité du capteur 
float ACSoffset=2.5;           // Valeur de tension de sortie du capteur lorsque le courant=0
float capacite1=0,capacite2=0;  
float Rb1,Rb2;
float E1=1.2,E2=1.2; // tension électrochimique
int heure=0,minute=0,seconde=0; // le temps
float temperature;
/*****************************************************************/    
void setup()
{
  Timer1.initialize(1000000);          // initialize timer1, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
pinMode(led, OUTPUT);
pinMode(A0, INPUT); // On défini A0 en entrée
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(A4, INPUT);
pinMode(A5, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
pinMode(0, INPUT_PULLUP);
pinMode(R0, OUTPUT);pinMode(R1, OUTPUT);pinMode(R2, OUTPUT);pinMode(R3, OUTPUT); // On définit les relaies en sortie
Serial.begin(9600);
lcd.begin(20,4);         //et pas lcd begin comme cetraine biblio
lcd.clear();      // Effacer l'écran
digitalWrite(R0,HIGH);
digitalWrite(R2,HIGH);
} // fin setup
// Interruptions  tous les 1s fait par le timer1****************
void callback()  
{
 seconde++;
 if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);} else {digitalWrite(13,HIGH);}
 tension1=analogRead(A0)*(5.0/1023.0); tension2=analogRead(A3)*(5.0/1023.0); // Récupération et conversion de cette valeur en une tension comprise entre 0 et 5V (Batterie)
 courant1=((analogRead(A1)*(5.0/1023.0))-ACSoffset)/sensible; courant2=((analogRead(A2)*(5.0/1023.0))-ACSoffset)/sensible; // Calcul du courant 
 capacite1=(courant1/3.6)+capacite1; capacite2=(courant2/3.6)+capacite2; // (I(A)*1000)/(1s/3600)+capacite   
 temperature=((analogRead(A4)*5.0)/1023.0)*(1/0.01); // la tension de sortie est de : 10mV/°C
} // fin void callback
void temps()
{ if(seconde>=60){seconde=0;
                  minute++;
                  if(analogRead(A5)*(5.0/1023.0)>=4.99)
                  {digitalWrite(R0,LOW);}
                  else
                  {digitalWrite(R2,LOW);}
                  if(minute>=60){minute=0;
                                 heure++;
                                }
                  if(heure>=24){heure=0;
                                lcd.clear();
                               }
                 }
  if(seconde==4)
  {if(analogRead(A5)*(5.0/1023.0)>=4.99){digitalWrite(R0,HIGH);}
   else {digitalWrite(R2,HIGH);}
  }           
} // fin void temps
void affichage_temps()
{
 lcd.setCursor(5,2);
 lcd.print(heure);
 lcd.print("h ");
 lcd.print(minute);
 lcd.print("m ");
 lcd.print(seconde);
 lcd.print("s "); 
}
/*********************************************************/
void affichage_bat1()
{
 lcd.setCursor(0,0);      
 lcd.print("Bat1 :");  
 lcd.setCursor(7,0);          
 lcd.print(tension1,2);
 lcd.print("V ");
 lcd.print(" "); 
 lcd.print(courant1,2);
 lcd.print("A ");
 lcd.setCursor(0,1);
 lcd.print(Rb1,2);
 lcd.print("ohm");
 lcd.print("  ");
 lcd.print(capacite1,0);
 lcd.print("mAh   ");
 lcd.setCursor(0,3);
 lcd.print("temperature : ");
 lcd.print(temperature);
 Serial.println("Bat1 : ");
 Serial.print("Temps : ");Serial.print(heure);Serial.print("h ");Serial.print(minute);Serial.print("m ");Serial.print(seconde);Serial.println("s ");
 Serial.print(tension1,2);Serial.print("V   ");Serial.print(courant1,2);Serial.print("A   ");
 Serial.print(capacite1,0);Serial.print("mAh   ");Serial.print(temperature);Serial.println("deg   ");
}
void affichage_bat2()
{
 lcd.setCursor(0,0);      
 lcd.print("Bat2 :");  
 lcd.setCursor(7,0);                 
 lcd.print(tension2,2);
 lcd.print("V ");
 lcd.print(" "); 
 lcd.print(courant2,2);
 lcd.print("A ");
 lcd.setCursor(0,1);
 lcd.print(Rb2,2);
 lcd.print("ohm");
 lcd.print("  ");
 lcd.print(capacite2,0);
 lcd.print("mAh   ");
 lcd.setCursor(0,3);
 lcd.print("temperature : ");
 lcd.print(temperature);
 Serial.println("Bat2 : ");
 Serial.print("Temps : ");Serial.print(heure);Serial.print("h ");Serial.print(minute);Serial.print("m ");Serial.print(seconde);Serial.println("s ");
 Serial.print(tension2,2);Serial.print("V   ");Serial.print(courant2,2);Serial.print("A   ");
 Serial.print(capacite2,0);Serial.print("mAh   ");Serial.print(temperature);Serial.println("deg   ");
}
void loop()
{
 temps();
if (digitalRead(10)==1)  
{digitalWrite(R1,LOW);Rb1=(tension1-E1)/courant1;} // charge     
else
{digitalWrite(R1,HIGH);Rb1=E1/courant1;} // décharge
if (digitalRead(0)==1)  
{digitalWrite(R3,LOW);Rb2=(tension2-E2)/courant2;} // charge     
else
{digitalWrite(R3,HIGH);Rb2=E2/courant2;} // décharge
/*************Affichage sur l'écran LCD****************/
if(analogRead(A5)*(5.0/1023.0)>=4.99){affichage_bat1();}
else{affichage_bat2();}
affichage_temps();

    }//fin loop ;

Quelques captures d’écran :

Sur des deux images suivantes, on observe que l’écran affiche les valeurs de la batterie AAA (Bat1).
En charge :
On a alors l’alimentation de 1.5V et un courant de charge de à 1C avec C=1000mAh.
En effet, le courant batterie correspond à l’équation suivante : I=(U-E)/R
On peut observer que le courant de charge est bien à 1A et que la tension de la batterie correspond bien à 1.3V. La mesure de la résistance interne affiche 0.09Ω presque égale à la valeur paramétrée dans la simulation.

En décharge :
La batterie se décharge sur la résistance de décharge (0.9ohm). La tension de décharge est de 1.2V et le courant de décharge est 1A.
Le courant de batterie en décharge correspond à l’équation suivante : I=E/(R+r(0.9))
Dans la simulation Isis, représentée sur la figure ci-dessous, on observe que le courant de charge est égal à 1A et que la tension de la batterie correspond bien à 1.1V.
La résistance interne affichée est de 1.12Ω : ce qui est presque égale à la valeur paramétrée dans la simulation.

Sur des deux images suivantes, on observe que l’écran affiche les valeurs de la batterie AA (Bat2).
En charge :
On a une alimentation de 1.5V et un courant de charge de à C/2 avec C=2000mAh.
Comme la batterie AAA, on observe bien que le courant de charge est bien à 1A et que la tension de la batterie correspond bien à 1.3V. La mesure de la résistance interne affiche 0.09Ω presque égale à la valeur paramétrée dans la simulation.

En décharge :
La batterie se décharge avec un courant de décharge à C/5 sur la résistance de décharge (2.9ohm). La tension de décharge est de 1.2V et le courant de décharge est 0.4A.
Sur la figure suivante, on observe que le courant de charge est égal à 0.38A et que la tension de la batterie correspond bien à 1.16V.
La résistance interne affichée est de 3.13Ω : ce qui est presque égale à la valeur paramétrée dans la simulation.

Voici le lien pour télécharger le schéma isis :

1 Like

Quelles sont tes perspectives Mr Touré ???

Lorsqu’on n’a pas beaucoup d’entrée et sortie comme sur la nano, on peut rajouter beaucoup d’esclave relier par 4 fils avec le bus de communication I2C comme sur la figure suivante si l’on a des systèmes assez lent.

Mais il y a un conflit entre la routine d’interruption timerOne et la communication de la bibliothèque « wire ».
Mais il est possible d’utiliser un real time clock qui ferrait la même chose que la routine d’interruption time par l’intermédiaire de la routine d’interruption extérieure de 1seconde

Hors la charge et décharge des batteries sont assez lente cela dure environ 1 heure, donc une routine d’interruption extérieur 1 seconde sur le Sout du DS1307 peut faire très bien l’affaire

Voici les adresses des composants utilisé par notre câblage
I2C device found at address 0x20 PCF8574
I2C device found at address 0x21 PCF8575
I2C device found at address 0x27 LCD PCF8574
I2C device found at address 0x60 DAC MCP4725 A0=0
I2C device found at address 0x68 DS1307
I2C device found at address 0x50 EEPROM AT4C1024

Voici le code qui utilise différente bibliothèque juste pour le DS1307, le LCD, ainsi que les PCF857X

#include <Wire.h>     // // Join i2c bus
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"      // Bibliothèque pour le module RTC


LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter
RTC_DS1307 rtc;   //minuscule    minuscule 


#define led13   13 
byte x;
unsigned int n;
byte data;


DateTime datetime;

void setup()
{  // initialise l'afficheur LCD
pinMode(led13, OUTPUT); 
//pinMode(A4, OUTPUT); //resistance de pull-up inutile
//pinMode(A5, OUTPUT); 


lcd.init();  //et pas lcd begin comme cetraine biblio
  lcd.display();     // activer l'affichage
  lcd.backlight();   // allumer retroeclairage

  lcd.setCursor(0,0);
  lcd.print("IUT GENIE elect&info");
  lcd.setCursor(5,1);
  lcd.print("Soissons");
  //Serial.begin(57600);

 Wire.begin();
   
rtc.begin();       //adress fixe 0x68
rtc.adjust(DateTime(2020, 05, 10, 23, 59, 35));    //annee, mois, date, heure

pf575_write(word(B00000000,B00000000));

//https://www.idreammicro.com/post/Utilisation-du-Square-Wave-Output-du-DS1307
    Wire.beginTransmission(0x68);   //dS1307  adress
    Wire.write(0x07);     //DS1307_CONTROL_Regitre 
    Wire.write(B00010000);   //frequence 1Hz
    Wire.endTransmission();
  

  attachInterrupt(0, Rexterieur2, RISING);   // broche2
}

void Rexterieur2() // la fonction appelée par l'interruption externe n°0
{
//digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption                                    
//digitalWrite(LED13,LOW); 
if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
}

void pf575_write(uint16_t data)
{
  Wire.beginTransmission(0x21); //adresse PCF8575
  Wire.write(lowByte(data));
  Wire.write(highByte(data));
  Wire.endTransmission();
}



void loop()
{

 DateTime now=rtc.now();
/*
lcd.setCursor(0,2);
lcd.print(now.day(),DEC); 
lcd.print("/" ); 
lcd.print(now.month(), DEC);
lcd.print("/" ); 
lcd.print(now.year(), DEC);
lcd.print("   ");
*/
  
  lcd.setCursor(0,3);
   lcd.print(now.hour(), DEC);
   lcd.print(':');
   lcd.print(now.minute(), DEC);
   lcd.print(':');
   lcd.print(now.second(), DEC);
   lcd.print("   ");

x++;      //variable 
Wire.beginTransmission(0x20);   // transmit to device #20  PCF8574  A0=0, A1=0, A2=0
Wire.write(x);                  // sends value byte 
Wire.endTransmission();         // stop transmitting  

 Wire.requestFrom (0x20, 1);   //lecture information
 if (Wire.available ()) {
   data = Wire.read ();  }
   
pf575_write(word(x,x));

delay(100); 

}

Une capture d’écran qui prouve que cela fonctionne

Suite à mon dernier post du 21 mai, vu que la carte n'a plus assez de pins disponibles, j'ai donc décidé d'utiliser la liaison I2C pour la gestion de l'écran LCD, prenant broches à l'Arduino.

Le composant principal de l'écran LCD I2C est l'extenseur d'E/S PCF8574, avec seulement deux broches SDA et SCL, nous obtenons un maximum de 8 broches de P0 à P7. Le PCF8574A peut également être utilisé mais il a une adresse différente. Toutes les broches de données LCD sont connectées au PCF8574 où : RS, RW, E, D4, D5, D6 et D7 sont connectés à P0, P1, P2, P4, P5, P6 et P7 respectivement.

Un problème se pose : il y a un conflit entre la routine d’interruption TimerOne et la communication I2C. Pour résoudre cette problématique, j'ai utilisé un Real Time Clock à la place du TimerOne.

Il fait la même chose que la routine d’interruption TimerOne par l’intermédiaire de la routine d’interruption extérieure de 1 seconde.

Je vous le lien de mon dossier pour plus d'explication sur la communication I2C et l'interruption extérieure : https://www.fichier-doc.fr/2020/06/03/batterie-toure-v4/

Dans la partie suivante, je vais récupérer les valeurs du Virtual Terminal pour l’estimation de l’état de santé de chaque batterie.

pourriez vous m'aider pour l'estimation de la regression linéaire pour programmation en Arduino et avec Excel?? et faire un exemple simple

Merci d'avance

1 Like

Pour estimer le SOH d’une batterie, la méthode par régression linéaire peut se faire à partir de 2 points. mais Plus, il y aura de points et plus l’estimation sera précise si la courbe se rapporche d'une droite ???? La régression linéaire est bien expliquée sur Excel ici https://support.office.com/fr-fr/article/droitereg-droitereg-fonction-84d7d0d9-6e50-4101-977a-fa7abf772b6d https://support.microsoft.com/fr-fr/office/coefficient-determination-coefficient-determination-fonction-d7161715-250d-4a01-b80d-a8364f2be08f La méthode des moindres carres, régression, ajustement, équations ou courbes de tendances ont tous le même nom

Pour faire un exemple simple : La tension de la batterie diminue =5-0,1*B2 donc a=-0.1 et b=5 A la place d’un nuage de points, la tension de la batterie oscillera =C2-0,2*SIN(B2) L’estimation de la tension sera =a*capacité+b avec a=pente de la droite Pour avoir la valeur de la pente a de la régression linéaire E3 =INDEX(DROITEREG($D$2:D3;$B$2:B3;VRAI;FAUX);1) Puis, à chaque mesure E4 une nouvelle pente est déterminée E4= =INDEX(DROITEREG($D$2:D4;$B$2:B4;VRAI;FAUX);1)

Pour avoir la valeur de la pente de la régression linéaire b =INDEX(DROITEREG($D$2:D3;$B$2:B3;VRAI;FAUX);2)

On peut observer sur la capture d’écran Les données déterminées par les fonctions Excel avec le coefficient de dermination R^2 qui est assez proche de 1 mais la sinusoïde provoque des écarts de détermination. Heureusement, que la diminution de la tension des batteries |500x160

Mais comme tu as dit, il faut mieux faire le calcul par l’Arduino de la regression lineaire par tableau D’ailleurs de nombreux softs (Matlab, C++, python, Mathcad), les fonctions de la regression lineaire existent qui peuvent donner directement ces données ici regression polynomiale en C avec le code source https://github.com/dani2442/Polynomial-Regression Sur Arduino, des bibliothèques existent, mais il faudrait les tester https://github.com/cubiwan/LinearRegressino/blob/master/README.md https://github.com/Rotario/arduinoCurveFitting

pour ma part, mon code est noyé avec tout le programme de test de batterie et ce n'est pas trop explicite

Le calcul des coefficients de la regression lineaire sont les suivantes Ce qui donne pour l'exemple |500x216

Tu peux telecharger le fichier de l'exemple ci dessus Excel ici https://drive.google.com/file/d/1rWsyEtMK7Orjirq5xmjrkUy3sXB87jnx/view?usp=sharing

Donc l’algo Arduino somme X, somme des Y, sommeX^2, Somme des X*Y calcul de a et b

Attention, pour chaque chimie de batterie, la variation de la tension en fonction de la capactité energetique est différente donc l'estimation de la regression lineaire et viable ou pas ? De plus, l'estimation du SOH est viable à partir d'une certaine capacité energetique dépensée. pour le Ni mh, il faudrait faire des essais avant

1 Like

Pour les lithiums LTO, LIPO l’estimation de la régression linéaire est très satisfaisante, mais seulement à partir d’une certaine valeur de capacité énergétique dépensée.
D’ailleurs, pour les batteries LTO, le 27/02/2020 la courbe expérimentale de l’estimation a été présenté sur ce post et elle est viable à partir de 15% de la capacité énergétique
Pour les éléments lithium NCM 18650 Samsung, on peut observer sur la figure suivante que l’estimation est viable seulement à partir de 20% de la capacité énergétique qui est ici de 2.5A.h

Est-ce qu’avec une régression polynomiale, on pourrait améliorer l’estimation ?
La, il faut me laisser du temps

1 Like

Pas encore réaliser le programme ???

Est-ce qu’il faut des bibliothèques sur la régression linéaire alors que 7 lignes de programmes suffisent ?

Dans l’exemple ci-dessous et posté precedement, la pente est de -0.01 et b=5 et le programme la retrouve bien pour chaque mesure effectuée.
D’ailleurs la régression linéaire demande un temps de calcul et l’envoie des données en USB de 1.6ms.
L’estimation du SOH donne bien 20A.h pour une tension d’arrêt de décharge de 3V

Voici le code qui mesure et calcul la régression linéaire toutes les sondes toutes les 1 secondes.

include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>

#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;
 
float  n=0;                 //nombre d'echnatillon
float  X=0;    //varaible abscisse
float Y=0;     //ordonnée image de l'abcisse
float SommeX=0;
float SommeXsquare=0;  
float SommeY=0;
float SommeXY=0;

float a=0;    //pende de la courbe  Y=a*X+b
float b=0;    //constante

float estimateur; 
           
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4 
  Serial.begin(9600); 
//  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
}




// Interruptions  tous les 1 fait par le timer1    fe=1Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
X=n;                   // capacity energetique batttery
Y=5-0.1*X-0.2*sin(n);            //Y=voltage=analogRead(A0);   for battery
SommeX=SommeX+X;     //for (byte i = 0; i < M; i++) {SommeX=X[i]+SommeX;} 
SommeXsquare=SommeXsquare+(X*X);
SommeY=SommeY+Y;
SommeXY=SommeXY+(X*Y);
n++;
if(n>=2) {             // 2 echantillon pour une regression lineaire    
a=(((n*SommeXY)-(SommeX*SommeY))/((n*SommeXsquare)-(SommeX*SommeX)));
b=((SommeY-(a*SommeX))/n);
estimateur=(3-b)/a;          //estimateur x lorsque Y devrait atteontre  la valeur 3
}
/*
Serial.print(X,1);Serial.print(";");
Serial.print(Y,1);Serial.print(";");
Serial.print(SommeX,1);Serial.print(";");
Serial.print(SommeY,1);Serial.print(";");
Serial.print(SommeXsquare,1);Serial.print(";");
Serial.print(SommeXY,1);Serial.print(";  ");
*/
Serial.print(a,3);Serial.print(";");
Serial.print(b,3);Serial.print(";");
Serial.print(estimateur,3);Serial.println(";");

digitalWrite(LED13,LOW);      //calcul et envoie en 1.6ms
}//fin routine



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {
 
lcd.setCursor(0,0);    
lcd.print("Regression Lineaire");
lcd.setCursor(0,1);    
lcd.print("nbr echantillon=");
lcd.print(n);
lcd.setCursor(0,2);
lcd.print("a="); 
lcd.print(a,3);                 //coefficient a
lcd.print(" b="); 
lcd.print(b,3);                 //coefficient b
lcd.setCursor(0,3);
lcd.print("estimateur(3)="); 
lcd.print(estimateur,3); 

} // fin loop

Avec le bruit du sinus de l’exemple donné par le fichier Excel précèdent, on peut observer que la régression linéaire donne les même valeurs que dans excel, et qu’il y a un écart entre la valeur théorique de 20A.h et celle donné par l’estimateur.

L’Arduino pourrait faire le calcul du coefficient de corrélation d’échantillonnage de Pearson, pour savoir si la courbe estimée peut être une droite ou pas.
Si c’est le cas la corrélation ou de détermination de Pearson doit être proche de -1 avec une pente negative

bizarre il y a des petites différence dans les données fournies par Excel si l’on prend la fonction « Pearson » et « coeficient.determination »

j’ai oublié de le faire dans le code précédent
Il est possible de le déterminer par les equations suivantes


Il suffit de rajouter

SommeYsquare=SommeYsquare+(Y*Y);

Rpearson=(n*SommeXY-SommeX*SommeY)/((sqrt((n*SommeXsquare)-sq(SommeX))*sqrt((n*SommeYsquare)-sq(SommeY))));

Serial.print(Rpearson,3);Serial.print(";");

Le virtual terminal donne a, b, Rpearson, l’estimateur

cela devrait t aider à comprendre

1 Like

Merci beaucoup iutgeiisoissons, c'est très clair.

Pendant que j’y suis autant que j’explique la régression polynomiale…
D’ailleurs, elle est plus adaptée pour estimer la capacité énergétique d’une batterie. Mais le coefficient du second ordre et troisième peuvent parfois être négligé pour certaine chimie.

Pas facile de comprendre comment on détermine les coefficients d’une régression polynomiale par les encyclopédies.

Le calcul des ajustements de courbes, n’est pas très explicite non plus

Pourtant, la programmation est relativement simple mais demande un calcul matricielle ou des équations à rallongent
La régression linéaire est une régression polynomiale d’ordre 1 avec x^1, puis l’on peut faire des polynomes ordre 2 avec x^2, et d’ordre supérieur……
il y a une library pour les matrice pour arduino

voici la méthode pour bien comprendre la programmation

Voici le résultat des équations pour un polynome d’ordre 2 seulement

Pour vérifier que le programme Arduino fonctionne bien ou pour utiliser la méthode par l’intermédiaire d’Excel
En effet, à la place que ce soit le processeur qui fasse le calcul de la régression polynomiale, les données peuvent être envoyées dans Excel pour que le PC fasse les données.

Mais comment avoir les données dans Excel ?

Dans Excel, pour avoir une régression polynomiale d’ordre 2 et fait par calcul en matriciel [3,3], donc va retourner 3 valeurs avec la fonction suivante. {1.2} demande un second ordre. {1.2.3} demande un troisième ordre.
DROITEREG($B$31:B33;$A$31:A33^{1.2})
C’est très bien expliqué et présenté ici pour avoir les données matricielles dans Excel

Sur la figure suivante, on peut observer les valeurs de la courbe de tendances du second ordre, qui est bien déterminée par Excel par la fonction précédente.
On peut observer que la pente avec la régression linéaire de la courbe, va diminuer et fournir la tangente à la courbe. Donc, avec la regression linaire, si l’on prend les points au début de la courbe qui est d’ordre 2, l’estimation de la capacité énergétique sera complètement décalée.

attention : dans excel, a0 est le poids forts alors que dans arduino, c’est le poids faible

Nous allons utiliser ces mêmes équations dans le programme Arduino
Sur le Virtual terminal, est affiché après 3 mesures, le vecteur a de la courbe de tendance……

Puis à chaque mesure, toutes les secondes, la régression polynomiale est recalculée…
L’estimation pour un seuil de tension de 3.1V est calculé, on retrouve bien les 7.2A.h qui est dans Excel precedement

Voici le code, ou les cases de la matrice 3*3 pour l’ordre 2, est remplie valeur par valeur

//#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>
#include <MatrixMath.h>   //https://github.com/eecharlie/MatrixMath

#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;

float n=0;   //nombre d'echantillons
float X=0;    //varaible abscisse
float Y=0;     //ordonnée image de l'abcisse

#define N  (2+1)      //N ordre
float SommeX [N*2];    //sommeX0, sommeX1, sommeX2
float SommeXY [N];
mtx_type S[N][N];
mtx_type v[N];      // This is a colonn vector
mtx_type a[N];      // This is a colonn vector a[0]coeficient poids faible  X^0,  a[1]coeficient  X^1=X, a[2] constante   X^2   

float a0;        //valeur de a0 moins le seuil de la tension atteinte ou la battery est dechargé à 100%
float estimateur=0;
 
           
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4 
  Serial.begin(9600); 
//  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
}




// Interruptions  tous les 1 fait par le timer1    fe=1Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
X=n;                   // capacity energetique batttery
Y=5-0.43*X+0.023*sq(X);            //Y=voltage=analogRead(A0);   for battery
SommeX [0]=SommeX [0]+1   ;        //SommeX [0]=SommeX [0]+pow(X, 0)
SommeX [1]=SommeX [1]+X   ;        //SommeX [1]=SommeX [1]+pow(X, 1)
SommeX [2]=SommeX [2]+X*X ;        //SommeX [2]=SommeX [2]+pow(X, 2)
SommeX [3]=SommeX [3]+pow(X, 3);
SommeX [4]=SommeX [4]+pow(X, 4);

SommeXY [0]=SommeXY [0]+Y;
SommeXY [1]=SommeXY [1]+Y*X;
SommeXY [2]=SommeXY [2]+Y*X*X;
n++;
if(n>=4)   {
 S[0][0] = SommeX [0];
 S[0][1] = SommeX [1];
 S[0][2] = SommeX [2];
  
 S[1][0] = SommeX [1];
 S[1][1] = SommeX [2];
 S[1][2] = SommeX [3];  

 S[2][0] = SommeX [2];
 S[2][1] = SommeX [3];
 S[2][2] = SommeX [4];  

 v[0]=SommeXY [0];
 v[1]=SommeXY [1];
 v[2]=SommeXY [2];

//Serial.println("\n S:");
//Matrix.Print((mtx_type*)S, N, N, "S");
//Serial.println("\n v:");
//Matrix.Print((mtx_type*)v, N, 1, "v");

 Matrix.Invert((mtx_type*)S, N);
// Serial.println("\nInverted S:");
// Matrix.Print((mtx_type*)S, N, N, "S");


Matrix.Multiply((mtx_type*)S, (mtx_type*)v, N, N, 1, (mtx_type*)a);
Matrix.Print((mtx_type*)a, N, 1, "a");
Serial.println("");
a0=a[0]-3.1;       //3.1
estimateur=((sqrt(sq (a[1])-(4*a[2]*a0))+a[1])/(2*a[2]));    //resolution second ordre premiere valeur de x=capcity energy
}

Serial.print(X,1);Serial.print(";");
Serial.print(Y,1);Serial.print(";");
Serial.print(estimateur,1);Serial.println(";");

digitalWrite(LED13,LOW);      //calcul et envoie en
}//fin routine



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {
 
lcd.setCursor(0,0);    
lcd.print("Regression polyno");
lcd.setCursor(0,1);    
lcd.print("ordre");
lcd.print(N-1);
lcd.setCursor(0,2);
lcd.print("a0="); 
lcd.print(a[0],3);               
lcd.print(" a1="); 
lcd.print(a[1],3);                 
lcd.setCursor(0,3);
lcd.print("a2="); 
lcd.print(a[2],3);    


} // fin loop

Evidement, pour un ordre 3, ou la matrice est de 4.4, avec 2 boucles FOR, la matrice peut etre rempli automatiquement.
donc, une regression polynomiale est un recette qu’il faut juste savoir utiliser
Le temps de calcul et d’estimation est de 3.8ms avec l’envoie des données sur l’USB

1 Like

Si l’on désire prendre en compte la décroissance rapide de la tension en fin de décharge, une régression polynomiale troisième ordre, voir du quatrième ordre devrait être utilisé pour certaine chimie de battery.
D’ailleurs, à chaque valeur supplémentaire, il est possible d’augmenter l’ordre.
Si le quatrième ordre est utilisé alors que la courbe de tendance ne correspond pas à cet ordre, alors le coefficient sera négligeable.
D’ailleurs, on peut l’observer sur la figure suivante sur Excel ou la variation de la tension est un troisième ordre comme on peut le voir sur la courbe de tendance

On peut observer sur la figure suivante que l’Arduino retrouve bien l’équation précédente

Voici le code ou il suffit juste de rentrer l’ordre N pour la régression polynomiale désirée

//#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>
#include <MatrixMath.h>   //https://github.com/eecharlie/MatrixMath

#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;

float n=0;   //nombre d'echantillons
float X=0;    //varaible abscisse
float Y=0;     //ordonnée image de l'abcisse
float z=0;
#define N  (3+1)      //polynome 3 ordre
float SommeX [2*N];    //2*ordre   ,sommeX0, sommeX1, sommeX2
float SommeXY [N];
mtx_type S[N][N];
mtx_type v[N];      // This is a colonn vector
mtx_type a[N];      // This is a colonn vector a[0]coeficient poids faible  X^0,  a[1]coeficient  X^1=X, a[2] constante   X^2   

float a0;        //valeur de a0 moins le seuil de la tension atteinte ou la battery est dechargé à 100%
float estimateur=0;

          
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4 
  Serial.begin(9600); 
//  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
}

// Interruptions  tous les 1 fait par le timer1    fe=1Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
X=n;                   // capacity energetique batttery
Y=(5-0.71*X+0.145*pow(X, 2)-0.013*pow(X, 3));            //Y=voltage=analogRead(A0);   for battery

for (byte i = 0; i <= (N-1)*2; i++) {SommeX [i]=SommeX [i]+pow(X, i);}     
for (byte i = 0; i < (N); i++) {SommeXY [i]=SommeXY [i]+Y*pow(X, i);}


n++;
if(n>=4)   {
for (byte i = 0; i < N; i++) {
for (byte j = 0; j < N; j++)   { S[i][j] =SommeX [j+i] ;  }
                             }
for (byte i = 0; i < N; i++)  { v[i]=SommeXY [i];}

//Serial.println("\n S:");
//Matrix.Print((mtx_type*)S, N, N, "S");
//Serial.println("\n v:");
//Matrix.Print((mtx_type*)v, N, 1, "v");

 Matrix.Invert((mtx_type*)S, N);
// Serial.println("\nInverted S:");
// Matrix.Print((mtx_type*)S, N, N, "S");


Matrix.Multiply((mtx_type*)S, (mtx_type*)v, N, N, 1, (mtx_type*)a);
Matrix.Print((mtx_type*)a, N, 1, "a");
Serial.println("");
a0=a[0]-3.1;       //3.1
//estimateur=((sqrt(sq (a[1])-(4*a[2]*a0))+a[1])/(2*a[2]));    
}
Serial.print(X,1);Serial.print(";");
Serial.print(Y,1);Serial.println(";");
//Serial.print(estimateur,1);Serial.println(";");

digitalWrite(LED13,LOW);      //calcul et envoie en
}//fin routine



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {
 
lcd.setCursor(0,0);    
lcd.print("Regression polyno");
lcd.setCursor(0,1);    
lcd.print("ordre");
lcd.print(N-1);
lcd.setCursor(0,2);
for (byte i = 0; i < (N/2); i++) {lcd.print("a");lcd.print(i);lcd.print("=");lcd.print(a[i],3);lcd.print(i);lcd.print(" ");}
lcd.setCursor(0,3);
for (byte i = N/2; i < (N); i++) {lcd.print("a");lcd.print(i);lcd.print("=");lcd.print(a[i],3);lcd.print(i);lcd.print(" ");}
} // fin loop

résoudre l’équation d’un troisième ordre pour faire l’estimation n’est pas si facile car l’équation est relativement grande.

l’equation est encore plus grande, si l’on a des ordres supérieurs,
Mais il y a des méthodes par dichotomie, Newton……par programmation pour faire la résolution d’une fonction

1 Like

Pour avoir l’estimateur avec une équation polynomiale
La méthode de la dichotomie ou « bissection method » est bien expliquée dans wikipedia avec meme un algo

Pour la methode de Newton, des exemples bien fait ici
http://math.univ-lille1.fr/~bodin//fichiers/ch_zeros.pdf

Voici le code de l’estimateur avec la bissection method avec la régression polynomial

#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>
#include <MatrixMath.h>   //https://github.com/eecharlie/MatrixMath

#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;

float n=0;   //nombre d'echantillons
float X=0;    //varaible abscisse
float Y=0;     //ordonnée image de l'abcisse

#define N  (3+1)      //polynome 3 ordre
float SommeX [2*N];    //2*ordre   ,sommeX0, sommeX1, sommeX2
float SommeXY [N];
mtx_type S[N][N];
mtx_type v[N];      // This is a colonn vector
mtx_type a[N];      // This is a colonn vector a[0]coeficient poids faible  X^0,  a[1]coeficient  X^1=X, a[2] constante   X^2   

float a0;        //valeur de a0 moins le seuil de la tension atteinte ou la battery est dechargé à 100%
float estimateur=0;
float x2=15;
float x1=0;
float Yxes, Yx1;
byte z=0;
          
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4 
  Serial.begin(9600); 
//  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
}

// Interruptions  tous les 1 fait par le timer1    fe=1Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
X=n;                   // capacity energetique batttery
Y=(5-0.71*X+0.145*pow(X, 2)-0.013*pow(X, 3));            //Y=voltage=analogRead(A0);   for battery

//determination des coefficients de la courbe de tendance
for (byte i = 0; i <= (N-1)*2; i++) {SommeX [i]=SommeX [i]+pow(X, i);}     
for (byte i = 0; i < (N); i++) {SommeXY [i]=SommeXY [i]+Y*pow(X, i);}

n++;
if(n>=4)   {
for (byte i = 0; i < N; i++) {
for (byte j = 0; j < N; j++)   { S[i][j] =SommeX [j+i] ;  }
                             }
for (byte i = 0; i < N; i++)  { v[i]=SommeXY [i];}

Matrix.Invert((mtx_type*)S, N);

Matrix.Multiply((mtx_type*)S, (mtx_type*)v, N, N, 1, (mtx_type*)a);
//Serial.print(a[0],3);serial.print(";");Serial.print(a[1],3);serial.print(";");Serial.print(a[2],3);serial.print(";");Serial.println(a[3],3);

//bissection methode
a0=a[0]-3;       //3 seuil de tension que l'estimateur doit decouvrir, mais il est possible de prendre 2,5V
while ((x2-x1)>=0.01) {estimateur=(x2+x1)/2;
                      Yxes=(a0+a[1]*estimateur+a[2]*pow(estimateur, 2)+a[3]*pow(estimateur, 3));
                      Yx1=(a0+a[1]*x1+a[2]*pow(x1, 2)+a[3]*pow(x1, 3));
                      if ((Yxes*Yx1)<0) {x2=estimateur;}  else {x1=estimateur;}           
                    z++;
                    //if (z==1) {Serial.println("bissection methode");}
                    Serial.print(x1,2);Serial.print(";");Serial.print(x2,2);Serial.print(";");Serial.print(estimateur,2);Serial.println(";");
                      }
x2=15; x1=0; z=0; //remise à zero des plages de l'estimateurs
} //fin if n>4
//Serial.println("X,Y,estimation");
//Serial.print(X,1);Serial.print(";");
//Serial.print(Y,1);Serial.print(";");
//Serial.print(estimateur,1);Serial.println(";");

digitalWrite(LED13,LOW);      //calcul et envoie en
}//fin routine



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {
 
lcd.setCursor(0,0);    
lcd.print("Regression polyno");
lcd.setCursor(0,1);    
lcd.print("ordre");
lcd.print(N-1);
lcd.setCursor(0,2);
lcd.print("bissection methode");
lcd.setCursor(0,3);
lcd.print("estimation=");
lcd.print(estimateur,2);
} // fin loop

avec une precision de 2 chiffres après la virgule, N=2 suivant les equations suivantes

la convergence de la valeur se fait en 11 itérations, à partir de l’équation suivante
nbr iteration=(2+Log(15-0))/log2=11.28
la valeur 15 provient de la capacity energetique de la battery maximale initialisé dans le programme

avec une precision de 3 chiffres après la virgule, N=3 suivant les equations suivantes, le nombre d’iteration augmente legerement, mais ce n’est pas problematique
nbr iteration=(3+Log(15-0))/log2=14

Chaque ligne donne les A.h, la tension, et l’estimation, comme on peut l’observer sur la figure suivante avec le nombre d’iteration sur le virtual terminal. On retrouve le nombre d’iteration

Le calcul de la regression lineaire du 3eme ordre et la methode de bissection dure 14ms sans l’envoie des données,
donc ce n’est pas un problème.

j’ai fait un maximum pour la transparence des codes et des methodes

1 Like

j’ai réalisé le montage du projet sur testeur et cycleur batterie NiMH,
et j’ai fait des tests sur une batterie de 2300mA.h pour tracer la courbe de décharge sur résistance de 1.8Ω, sur deux résistances de 1.8Ω en parallèle et sur deux résistances de 1.8Ω en série,
de la tension, du courant, de la résistance interne et de la température en fonction de la capacité énergétique, afin de faire des comparaisons.

Le programme est le suivant :

#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
#define PWM3       3      //   timer2 
#define LED13     13       
#define R2     12
#define R1     11

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
float voltage;
float courant;
float ACSoffset=2.5; // tension pour un courant I=0A
float sensibilite=0.100;
float capacite; // capacité énergétique
float R; // résistance interne
float E; // tension électrochimique
float temperature;
int seconde=0;
int BP1,BP2,BP3,BP4;
int flag1,flag2;

  void setup() {
  Timer1.initialize(1000000);           //  
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  pinMode(A7,INPUT);pinMode(A1,INPUT);pinMode(A2,INPUT);
  pinMode(LED13, OUTPUT);
  pinMode(PWM3,OUTPUT);
  pinMode(R2, OUTPUT);
  pinMode(R1, OUTPUT);

  flag1=1; // OFF
  flag2=0; // CHARGE
  
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  Serial.begin(9600); 
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

}

void callback()
{
if (digitalRead(13)== 1 ) 
{digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
voltage=analogRead(A7);  // valeur numérique de la tension
voltage=(voltage*5)/1024;// valeur analogique de la tension
courant=analogRead(A1);  
courant=(courant*5)/1024; 
courant=courant-ACSoffset; // tension image du courant en valeur analogique - ACSoffset(2.5V)
courant=courant*10;
if(digitalRead(R2)==0){courant=courant*(-1);} // CHARGE
else{courant=courant;}                        // DECHARGE
capacite=(courant/3.6)+capacite;
temperature=analogRead(A3);
temperature=(temperature*500)/1024;

seconde++; // temps

if(seconde==292){ // si le temps=292s, récupèrer sur le terminal les valeurs suivantes
Serial.print(capacite,0);Serial.print(";");Serial.print(voltage,2);Serial.print(";");
Serial.print(courant,2);Serial.print(";");Serial.print(temperature,1);Serial.print(";");
}
if(seconde==294){flag1=1;} // OFF, si le temps=294s,desactiver le relais 1
if(seconde==296){E=analogRead(A7);E=(E*5)/1024;Serial.print(E,2);Serial.print(";");} // si le temps=296s, mesurer l'OCV
if(seconde==298){flag1=0;} // ON, si le temps=298s, réactiver le relais 1
if(seconde==300){if(flag2==1){R=(E-voltage)/courant;} // DECHARGE, si le temps=5mn, mesurer la résistance interne en décharge
                if(flag2==0){R=(voltage-E)/courant;} //  CHARGE, si le temps=5mn, mesurer la résistance interne en charge 
                Serial.println(R,2);seconde=0;}

lcd.setCursor(0,0); 
lcd.print("V:");
lcd.print(voltage,2);
lcd.print("  I:" );
lcd.print(courant,2); 
lcd.setCursor(0,1);
lcd.print("Q:" );
lcd.print(capacite,0);
lcd.print("  T:" );
lcd.print(temperature,1);
lcd.setCursor(0,2);
lcd.print("R:" );
lcd.print(R,2);
lcd.print(" ");
lcd.print(seconde);
}//fin routine

void loop() { 
BP1=digitalRead(A2);
BP2=digitalRead(A5);
BP3=digitalRead(A4);
BP4=digitalRead(10);
if(BP1==1 && BP2==0){flag1=0;if(BP1==0 && BP2==0){flag1=0;}} 
if(BP1==0 && BP2==1){flag1=1;if(BP1==0 && BP2==0){flag1=1;}}
if(flag1==0){digitalWrite(R1,HIGH);} // ON
if(flag1==1){digitalWrite(R1,LOW);}  // OFF
if(BP3==1 && BP4==1){flag2=0;if(BP3==0 && BP4==1){flag2=0;}}
if(BP3==0 && BP4==0){flag2=1;if(BP3==0 && BP4==1){flag2=1;}}
if(flag2==0){digitalWrite(R2,LOW);} // CHARGE
if(flag2==1){digitalWrite(R2,HIGH);}  // DECHARGE            
} // fin loop

Vérification du fonctionnement du programme en simulation sur Isis:

Sur le fichier suivant, on décharge la batterie sur une résistance de 1.8Ω, j’ai ajouté en plus des 0.1Ω, résistance de chaque relais, un potentiomètre réglé à 81%2Ω=1.62Ω, et du coup la résistance externe
r=0.1Ω+0.1Ω+1.62Ω+1.8Ω=3.62Ω et la résistance interne de la batterie (en simulation Isis) R=0.1Ω.
Le courant de décharge est : I=(E-V)/R=E/(R+r)=0.3A; V=r
I
On voit sur le fichier que le courant calculé avec la formule ci-dessus dans le code (0.34A) est différent de celui mesuré par l’ampèremètre (0.32A) ; en pratique, cela pourrait être par les perturbations extérieures d’où la précision=((0.34-0.32)/0.32)*100=6.25%
On remarque que l’intensité du courant obtenue est quasiment égale à celle prévue, donc le programme fonctionne bien.

Le fichier de simulation sur Isis est téléchargeable sur le lien suivant :

Voici le montage :
Sur cette image, le relais 1 est désactivé (état Off), le courant est nul, enfin presque.
J’alimente l’arduino avec le 5V de la source de tension car avec l’alim du PC, la tension de sortie est environ 4.2V et du coup les mesures ne seront pas exactes.

Les courbes de décharge sont les suivantes :
Analyse et Comparaison:
On observe dans les figgures 1 et 2 que la résistance interne augmente considérablement plus la capacité énergetique diminue.
Cela fait chuter fortement le courant de sortie.
Si on observe bien les trois figures, on remarque que la chute de tension est plus rapide et plus importante en décharge sur les résistances en paralléle que sur une résistance et sur les deux résistances en série.
Cela s’explique par le fait que la résistance équivalente de décharge (0.9Ω) sur la figure 2 est plus petite que la résistance (1.8Ω) de décharge sur la figure 1 et encore plus petite que la somme des deux résistances en série (3.6Ω) et que d’après la loi d’Ohm,
moins la résistance est petite et plus la demande de courant est importante, et cela fait chuter la tension.
Plus la tension diminue, plus le courant chute.


Le fichier Excel est téléchargeable sur le lien suivant:

Il faut préciser que le temps d’échantillonnage pour l’écriture des valeurs n’est pas le même.
Pour la décharge sur les deux résistances en parallèle c’est 30s puisque la décharge est très rapide, 1mn sur une résistance et 5mn pour deux résistance en série.

1 Like

Dans votre programme, je ne vois pas l’arrêt de la charge ????

Différentes possibilités de recharger des batteries NiMh sont possibles qui vont jouer sur la durée de vie de la batterie. Voici quelques liens https://lygte-info.dk/info/batteryChargingNiMH%20UK.html http://bricolsec.canalblog.com/archives/2012/11/02/25480956.html https://espace.etsmtl.ca/id/eprint/120/1/DEKKICHE_Abdelillah.pdf

La charge à C/10 sans danger, pour un temps 10heures est bien trop longue. La détection du dV/dt de la tension de la batterie n’est pas si facile à détecter car très faible. Mais vous mesurez la température de la batterie. Donc, avec un générateur qui limite le courant à une certaine valeur (exemble C/2 avec une limitation à 2V ; dans isis, c’est possible à configurer un courant constant) et lorsque la température atteint environ 38°C, alors arrêter la charge avec un flag. En effet, en fin de charge, la batterie ne transforme plus l’énergie reçue en chimie mais en chaleur et cette énergie se transforme en chaleur (divergence) Sur la figure suivante, on peut observer, ce mode de charge |500x298 Très peu de chargeur vendu utilise cette méthode à cause à des problèmes de contact entre la batterie et le capteur de température.

Mais, ces dernières années, le capteur de température IR sans contact GY-906 MLX90614ESF est devenu bon marché à moins de 7€ peut être utilisé.

1 Like

Voici en dessous le nouveau programme modifié qui prend en compte de l’arrêt de la charge et de la décharge.
La charge est arrêté si et seulement si la température de la batterie atteint 38°C ou
si la tension de la batterie V = E + r.I >= 1.6V.
Et on arrête la décharge si la tension de la batterie V = E - r.I <= 0.5V.
Pour arrêter la charge ou la décharge, il suffit de mettre le relais 1 en état Off.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
#define PWM3       3      //   timer2 
#define LED13     13       
#define R2     12
#define R1     11

float voltage;
float courant;
float ACSoffset=2.5; // tension pour un courant I=0A
float sensibilite=0.185;
float capacite; // capacité énergétique
float R; // résistance interne
float E; // tension électrochimique
float temperature;
int seconde=0;
int BP1,BP2,BP3,BP4;
int flag1,flag2;
LiquidCrystal lcd(8,9,4,5,6,7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
 
  void setup() {
  Timer1.initialize(1000000);           //  
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  pinMode(A7,INPUT);pinMode(A1,INPUT);pinMode(A2,INPUT);
  pinMode(LED13, OUTPUT);
  pinMode(PWM3,OUTPUT);
  pinMode(R2, OUTPUT);
  pinMode(R1, OUTPUT);
  
  flag1=0; // OFF
  flag2=1; // CHARGE
  
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  Serial.begin(9600); 
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
  ACSoffset=analogRead(A1);
  }// fin setup

void callback()
{
if (digitalRead(13)== 1 ) 
{digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
voltage=analogRead(A7);  // valeur numérique de la tension
voltage=(voltage*5)/1024;// valeur analogique de la tension
courant=analogRead(A1);  
courant=courant-ACSoffset; // tension image du courant - ACSoffset
courant=(courant*5)/1024; // Conversion en valeur analogique
courant=courant/sensibilite;
capacite=(courant/3.6)+capacite;
temperature=analogRead(A3);
temperature=(temperature*500)/1024;

seconde++; // temps
if(seconde==60){  
Serial.print(capacite,0);Serial.print(";");Serial.print(voltage,2);Serial.print(";");
Serial.print(courant,2);Serial.print(";");Serial.print(temperature,1);Serial.print(";");
}
if(seconde==61){flag1=1;} 
if(seconde==62){E=analogRead(A7);E=(E*5)/1024;Serial.print(E,2);Serial.print(";");} 
if(seconde==63){flag1=0;} 
if(seconde==64){if(flag2==1){R=(E-voltage)/courant;} 
                if(flag2==0){R=(voltage-E)/courant;} 
                Serial.println(R,2);
                if(voltage<=0.5){flag1=1;seconde=65;} 
                else if(temperature>=38||E>=1.6){flag1=1;seconde=65;} 
                else {seconde=0;}}

lcd.setCursor(0,0); 
lcd.print("V:");
lcd.print(voltage,2);
lcd.print("  I:" );
lcd.print(courant,2); 
lcd.setCursor(0,1);
lcd.print("Q:" );
lcd.print(capacite);
lcd.print("  T:" );
lcd.print(temperature,1);
lcd.setCursor(0,2);
lcd.print("R:" );
lcd.print(R,2);
lcd.print(" ");
lcd.print(seconde);
}//fin callback

void loop() { 
BP1=digitalRead(A2);
BP2=digitalRead(A5);
BP3=digitalRead(A4);
BP4=digitalRead(10);
if(BP1==1 && BP2==0){flag1=0;if(BP1==0 && BP2==0){flag1=0;}} 
if(BP1==0 && BP2==1){flag1=1;if(BP1==0 && BP2==0){flag1=1;}}
if(flag1==0){digitalWrite(R1,HIGH);} // ON
if(flag1==1){digitalWrite(R1,LOW);}  // OFF
if(BP3==1 && BP4==1){flag2=0;if(BP3==0 && BP4==1){flag2=0;}}
if(BP3==0 && BP4==0){flag2=1;if(BP3==0 && BP4==1){flag2=1;}}
if(flag2==0){digitalWrite(R2,LOW);} // CHARGE
if(flag2==1){digitalWrite(R2,HIGH);}  // DECHARGE          
}// fin loop
1 Like

Encore un contact relais Songle (relais chinois) défaillant, après 800 commutations sur le testeur LTO.

Le relais peut être remplacé par un Omron G5LE 1DC5 qui devrait supporter pour 8A 100 000 commutations suite à leurs docs http://www.farnell.com/datasheets/2182434.pdf?_ga=2.136815667.2060745107.1593329096-627363803.1593329096&_gac=1.14804866.1593329117.EAIaIQobChMIz_WRtv2j6gIVycmyCh1ztQ15EAAYASAAEgJAWfD_BwE

Mais, il vaut mieux remplacer les relais par des transistors avec optocoupleur cela coute moins cher en plus. En plus, les courants pour le NiMh sont faibles. les transistors sont surdimensionnés mais à ce prix, on s'en moque les transistors canal P aurait pu etre utilisé mais ils sont moins abondants que les canals N

|500x168 Mais avec les transistors, la configuration électrique change comme on peut le voir sur la figure suivante |500x287

En mettant un certain nombre de résistances en parallèles pour décharger la batterie, il est possible de commuter les différentes résistances et d’avoir une régulation de courant constant pour décharger. Pourquoi, le TIP 122, car j’en ai toute une boite de chaussure de ce composant.

1 Like

Voici un nouveau programme avec toutes les conditions possibles d’arrêt de charge et de décharge avec 2 relais indépendant pour la charge et la décharge.
Les nombreuses conditions avec la gestion de l’écran auraient pu être programmées avec un switch case pour bien les identifier.
Mais cela n’a pas été fait.
Avec un bouton charge ou décharge, la résistance est testé au début de chaque mode
Mais, il y a un bouton test résistance qui fait la mesure en mode charge ou décharge quand on l’active.
Un bouton test qui réalise une près charge, puis une décharge à 100% pour tester la capacité énergétique jusque ce la tension de l’élément NiMH attient 0.5V et l’affiche Soc. Puis il y a une recharge à 100%.
On peut comparer la capacité en décharge et en charge.

On peut observer sur la capture d’écran le mode du test pour la dernière charge

La routine d’interruption pour l’affichage est de 1s et le programme de la routine dure 25ms, donc cela ne pose pas de problème
Le test de la résistance attend que le temps de commutation du relais soit effectué d’où l’utilisation des délais de 10ms

Etant donné que les transistors vont permettre une meilleure durée de vie du système par rapport aux relais. Mais les transistors vont permettre une régulation du courant constante de décharge ou de la charge grâce à l’inductance et une PWM de 32kHz.
En effet, en décharge la constante de temps L/R=1mH/1ohms est bien supérieure à 1/32kHz=31µs. Donc avec une résistance de batterie de 0.1ohm, le filtre L/R attenue encore plus la variation du courant.

La régulation de décharge à courant constant sera avec un correcteur « intégral unitaire »
if (I>0.51) {PWM–; } //erreur positive
if (I<0.49) {PWM++; }
if (PWM>253) {PWM=253; }
if (PWM<1) {PWM=1; }

Cette régulation est très stable ; mais étant donné que, la PWM s’incrémente ou se décrémente de 1 toutes les secondes, la PWM est initialisé à une valeur proche de la valeur désirée.
Le courant de décharge correspond en l’équation suivante : Idecharge(A)=(Ubatt-VCEsat)PWM/(Rdecharge255)

On peut observer sur la figure suivante la régulation de la décharge

le programme en PJ

De la même manière, il est possible de réguler le courant de charge
Mais, pour quelques euros il vaut mieux une alimentation avec CI XL4015 pour la charge qui permet de limiter le courant à 1A et limiter la tension à 2.5V

Evidement pour un élément PP3 de 8.4V, il faut modifier les valeurs pour l’arrêt de la décharge, la valeur du courant de charge et de décharge. De même pour les boitiers type AAA.
Pour ma part, j’ai utilisé un vieux chargeur pour mettre l’Arduino à l’intérieur……
J’ai gardé le transformateur à point milieu de 2*11.5V pour alimenter l’Arduino et l’alimentation à courant constant, mais rien que l’afficheur me mange tout l’intérieur du chargeur.

Evidement un chargeur tel que le BTY V407 pour 17€ est bien plus compacte.
Mais si celui-ci donne les informations des capacités énergétiques de la charge. Il ne la test pas en décharge et ne donne pas l’info de la résistance interne.
Commercialement, il y a d’autres types de chargeur et testeur tel que le lien suivant pour environ 130 € depuis 2016

https://www.skyrc.com/Charger/MC3000_Charger
Ce chargeur testeur permet d’avoir une analyse par bluetooth sur smartphone et permet d’éviter l’écran LCD

Cela pourrait être une perspective pour ce chargeur analyseur avec Arduino qui est facile à réparer s’il y a un problème par rapport à l’électronique propriétaire précèdent.

Une publication d’environ 8 pages sur les chargeurs et les testeurs des NiMH arrive pour faire la synthèse du travail
mais voici le rapport
https://www.fichier-pdf.fr/2020/08/05/nimh-testeur-chargeur-arduino–sivert/

nimh_PWM__transi.ino.ino (7.43 KB)

2 Likes

J’ai récupéré de vieux chargeurs chez Emmaüs exactement comme dans mon boitier chargeur précèdent. Mais, l’afficheur LCD 4 lignes 20 caractères prend vraiment beaucoup de place. Or, j’ai un ESP32 Heltec qui traine avec un écran oled 128x64 c’est petit comme afficheur (2.5cmx1.25cm) mais en plus, il peut afficher des courbes donc pourquoi pas. voilà le programme juste pour faire une routine d’interruption toutes les 1 seconde et l’affichage d’une courbe.

#include "Arduino.h"
#include "heltec.h"
int Te=1000000  ; //periode d'echantillonnage micro seconde  1s

DRAM_ATTR  int Time;
DRAM_ATTR  float  Y[127];          //declaration des Y  
DRAM_ATTR  float  phase=0;
DRAM_ATTR byte a=0;

byte i=0;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
uint32_t cp0_regs[18];


void IRAM_ATTR onTimer() {      //IRAM et DRAM
digitalWrite(33,HIGH); 
  portENTER_CRITICAL_ISR(&timerMux);

xthal_set_cpenable(1);  xthal_save_cp0(cp0_regs);   // // Save FPU registers
//if ( digitalRead(33)== 1 ) {digitalWrite(33,LOW);}  else {digitalWrite(33,HIGH);}

Time++;
Y[Time]=31+10*sin(1.5*2*PI*Time/127+phase);           //calcul la courbe
if (Time>127) {Time=0;phase=phase+0.78;}               //remet à zero, le calcul et cree un dephasage de PI/4
     
  xthal_restore_cp0(cp0_regs);  xthal_set_cpenable(0);  // and turn it back off
  portEXIT_CRITICAL_ISR(&timerMux);
  digitalWrite(33,LOW);
}

void setup() {

  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
//  Serial.begin(9600); 
  Serial.begin(115200);
//  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_10); //22 caracteres possible 
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, Te, true);     //200000 0.2s 
  timerAlarmEnable(timer);

   Heltec.display->clear();   
   Heltec.display->drawString(3, 0, "IUT GEII Soissons");          // afficher 17 caractéres 5ms;  1 seul caractere 3.6ms       
 //   Heltec.display->drawString(0, 11, "Time "+String(Time));
 //   Heltec.display->setPixel(0, 0);  // (X,Y) en haut à gauche
 //   Heltec.display->setPixel(63,31 );  //milieu
 //   Heltec.display->setPixel(127,63);  // en bas à droite
 Heltec.display->drawHorizontalLine(0,31,120);  //(x,y,length)      //trace les axes
 Heltec.display->drawVerticalLine(0,0,63);      //(x,y,length)
 Heltec.display->display();  
}

void loop() { 

Heltec.display->setPixel(Time, Y[Time]);
Heltec.display->display();
            }

|500x375 Mais impossible d’utiliser l’afficheur OLED dans la routine d’interruption car la bibliothèque de OLED bloque les interruptions Donc, il faut refaire tout le programme qui était valable du post précédent ou la gestion de l’afficheur est exclusivement dans la routine d'interruption. Il y a le même problème si on veut utiliser un afficheur LCD I2C. c’est là où on voit qu’un programme ne peut être utiliser d’un processeur à un autre, meme avec le meme compilateur, à cause du choix de matos..

Il y avait une publication dans elecktor pour utiliser un afficheur OLED, J’ai voulu m’en inspirer, mais c’est immangeable.... https://www.elektormagazine.fr/articles/enregistreur-de-temprature-avec-arduino-nano

1 Like

Sur le OLED 128x64 (2.5cmx1.25cm) de l’ESP32, la gestion de l’affichage et des courbes demande de faire des translations et de gerer les echelles…
Avec la taille 10 et la police Arial d’écriture, il est possible de faire 6 lignes de 21 caractères donc 2 lignes supplémentaires par rapport aux LCD précèdent.
La tension est enregistrée toutes les minutes dans une table et elle est affichée dans le menu 2 correspondant pour le « case 2. »
Etant donné que le (x,y) zéro correspond, en haut à gauche de l’écran OLED, une translation des points doit se faire en fonction de l’échelle choisie.
Ici l’échelle est figée pour être habitué à sa lecture. Mais, une échelle automatique aurait pu être programmée en fonction des écarts de tension et du temps du tableau en identifiant les valeurs min et max.
L’effacement de l’écran se fera qu’une seule fois lors du changement de menu pour ne pas avoir l’effet de clignotement de l’écran.
De même toutes les données de la table des mesures seront envoyé qu’une seule fois sur le monitor

Pour que ce soit plus compréhensible, seul le programme de la gestion de l’écran et de la mesure de tension et présent dans le programme suivant.

#include "Arduino.h"
#include "heltec.h"   //use SSD1306
int Te=1000000  ; //periode d'echantillonnage micro seconde  1s

DRAM_ATTR  int Time;
DRAM_ATTR  float  tension;
DRAM_ATTR  float  courant=0.7;
DRAM_ATTR  float  temperature=35;
DRAM_ATTR  byte  heure=0;
DRAM_ATTR  byte  minute=0;
DRAM_ATTR  byte  seconde=0;
DRAM_ATTR  byte  capacity=200;
DRAM_ATTR  float  resist=0.15;
DRAM_ATTR  float  Y[127];          //declaration des tensions  
DRAM_ATTR byte  a=0;


byte i=0;
byte var=1;
bool flagenvoie=1;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
uint32_t cp0_regs[18];


void IRAM_ATTR onTimer() {      //IRAM et DRAM   routine d'interruption toutes les secondes
digitalWrite(33,HIGH); 
  portENTER_CRITICAL_ISR(&timerMux);

xthal_set_cpenable(1);  xthal_save_cp0(cp0_regs);   // // Save FPU registers
//if ( digitalRead(33)== 1 ) {digitalWrite(33,LOW);}  else {digitalWrite(33,HIGH);}

//mesure toutes les secondes
tension=analogRead(36);
tension=(tension*3.3)/4096;      
//courant=courant-2048; // tension image du courant - ACSoffset
//courant=(courant*3.3)/4096; // Conversion en valeur analogique
//courant=courant/0.185;
resist=0.654;

//enregistrement toutes les minutes
seconde++;
if (seconde>=60) {seconde=0;                //60
minute++;
if (minute>127)   {minute=127;}
}//fin time>60
     
  xthal_restore_cp0(cp0_regs);  xthal_set_cpenable(0);  // and turn it back off
  portEXIT_CRITICAL_ISR(&timerMux);
  digitalWrite(33,LOW);
}//fin routine interrupt


 
void setup() {
pinMode(0, INPUT_PULLUP);   //bouton prg carte heltec ESP32

Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
Serial.begin(115200);
  Heltec.display->setFont(ArialMT_Plain_10);       //21 caracteres possible et 6 lignes 
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
//  Heltec.display->display.setTextColor(WHITE);      // Draw white text
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, Te, true);     //200000 0.2s 
  timerAlarmEnable(timer);
           
for (byte i = 0; i <= 127; i++) {     //tets initialisation des valeurs de la mesure tension
//tension=1.5-(0.5*i/120);
tension=-0.0000007*pow(i,3)+0.00008*pow(i,2)-0.005*i+1.3;
Y[i]=tension;                    }
  
}


 
void loop() { 

if (digitalRead(0)==0)   {var++;delay(100); }    //utilisation du bouton prg de la carte ESP32 heltec
if (var>=3)   {var=1; }



//gestion de l'afficheur 
switch (var) {
  case 1:        //affichage des données
Heltec.display->clear();
Heltec.display->drawString(0, 0, "U="+String(tension,2));   //max  21 chiffres   donc 6 points par lettres
Heltec.display->drawString(45,0, "I="+String(courant,2));
Heltec.display->drawString(80,0, "T="+String(temperature,0)+"°C");
Heltec.display->drawString(0,10, String(heure)+"h"+String(minute)+"m"+String(seconde)+"s");
Heltec.display->drawString(0,20, String(capacity)+"mah  R="+String(resist,3)  );     //   "Ω"
Heltec.display->drawString(0,30, "charge on");
Heltec.display->drawString(0,40, "T");
Heltec.display->drawString(0,50, "T");
Heltec.display->display(); 
flagenvoie=1;
    break;
 
  
  
  case 2:       //affichage curve 

 if (flagenvoie==1)    {flagenvoie=0; Heltec.display->clear(); 
 for (byte i = 0; i <= 127; i++) {    // affichage de la courbe
 Heltec.display->setPixel(i, (94-Y[i]*63));   //a=(0-31.5)/(1.5V-1V)     b=-1.5*63=94
 Heltec.display->display();  }

for (byte i = 0; i <= 127; i++) {    // envoie la courbe de la mesure de tension sur le monitor
Serial.print( Y[i]);Serial.print(";");
 }
Serial.println(";");            }

 Heltec.display->drawHorizontalLine(0,63,127);  //(x,y,length)      //trace les axes
 Heltec.display->drawHorizontalLine(0,48,5);
  Heltec.display->drawHorizontalLine(0,32,5);
   Heltec.display->drawString(5, 27, "1V");     //ordonnées courbes de tension
  Heltec.display->drawHorizontalLine(0,16,5);
    Heltec.display->drawString(5, 42, "0.75V");    
 Heltec.display->drawVerticalLine(0,0,63);      //(x,y,length)
 Heltec.display->drawVerticalLine(15,58,5); 
 Heltec.display->drawVerticalLine(30,58,5);
 Heltec.display->drawVerticalLine(45,58,5);
   Heltec.display->drawString(54, 48, "1h");
 Heltec.display->drawVerticalLine(60,58,5);  
  Heltec.display->drawVerticalLine(75,58,5); 
   Heltec.display->drawVerticalLine(90,58,5);
    Heltec.display->drawVerticalLine(105,58,5);
    Heltec.display->drawVerticalLine(120,58,5);
     Heltec.display->drawString(116, 48, "2h");    //120 correspond à 120 minutes
     Heltec.display->display(); 

       
    break;  //case 2
}


} //fin loop

Évidemment, il est possible de tracer la tension en fonction de la capacité énergétique qui est plus représentative, que la tension en fonction du temps.

1 Like

Ayant remplacé les relais par des transistors M0SFET D’ailleurs, j’ai testé la carte IRF540 avec optocoupleur |500x322 |500x281 https://www.robotics.org.za/IRF540-MOD IRF540 Rdson=0.07Ω avec une résistance thermique sans dissipateur de 62.5°C/W Imax=(TJ-Tab)/RTH*Rdson=(175-25)/(62.5*0.07)= 27A Ayant, besoin que de petit courant, je n’ai pas testé, l’échauffement du transistor. Il faut l’alimenter pour une tension supérieure 5V car V threshold 2V à 4V pour avoir une saturation Vgatesaturation mini=V threshold+Idrain/gfs=4+1A/8A/V=5V Donc, il faut séparer l’alimentation de la commande est celle de la batterie, si l’on a une tension d’alimentation inferieure à 5V. Donc pour remplacer les relais, cela fonctionne très bien

Puis, j’ai voulu programmé l’asservissement de décharge de courant avec une PWM à 20kHz, en 12 bits pour une batterie en 12V. Si le temps pour rendre passant le transistor est de 1 µs, le temps de blocage du transistor est de 19µs. car la décharge de la capacité d’entrée du MOSFET, ce fait dans la résistance de 10KΩ, comme on peut l’observer sur la figure suivante |370x500 l’0ptocoupleur peut supporter 50mA, donc la résistance de 10Kohms a été remplacée par 1KΩ donc un courant de 0.005A et le temps de blocage est passé à 5µs. |370x500

Le programme de test de différentes sorties PWM sur ESP32 est très simple

//PWM     https://api.riot-os.org/group__boards__esp32__heltec-lora32-v2.html
const int ledPin22 = 22;       //32,2,17,22 ok   35,34,39 ne fonctionne pas     26 amplitude 1V ???
const int ledChannel = 2;      //de 0 à 15
ledcSetup(ledChannel, 20000, 12);           //ledcSetup(ledChannel, freq, resolution); 
ledcAttachPin(ledPin22, ledChannel);        //ledcAttachPin(ledPin, ledChannel);
ledcWrite(ledChannel, 200);                    //ledcWrite(ledChannel, dutyCycle);

Pas facile d’avoir un shield compatible avec des tenions d’alimentation de 5V jusque 100V D’ailleurs, c’est grâce à la LED qui sature la tension de gate qu’il est possible d’avoir des tensions d’alimentation de plus de 20V. Par conséquent pour faire de la PWM une alimentation à découpage pour alimenter la gate serait préférable avec des optocoupleurs genre Si8261.

1 Like

J’ai voulu commandé la carte des transistors MOSFET précédente avec un PCF8574 mais cela ne marche pas ? en plus, lorsque je fais la lecture des données sur le PCF8574, tout est à zero et losque je debranche la carte mosfet, la lecture fonctionne et au multimetre les tensions sont correctes. Pourtant sous isis mon prgramme fonctionne Pourriez-vous m’aider ? Pas facile de reprendre votre programme de condition d’arrêt de charge de la batterie, il faudrait faire un fichier avec une table de condition via une fonction Case J’ai appris plein de choses grâce à ce post et du coup j’ai réalisé aussi un chargeur avec votre programme Je suis en train d’essayer de faire le programme avec une nano et 4 batteries a recharger en meme temps en utilisant la liaison I2C. merci d’avance