//
// PROGRAMME POUR GESTION DE CHARGEUR DE BATTERIE AU PLOMB
// Pascal Chour - 08/2017
// Le schéma du chargeur se trouve sur www.pascalchour.fr
//
// Carte Arduino pro micro pour la partie processeur.
//
//#define TEST true
//
// Affectation des pins du processeur
//
const int FaibleCourant = 19; // Pin pour commande faible courant. Commande la LED Orange et commute le relais "Courant faible"
const int FortCourant = 18; // Pin pour commande fort courant. Commande une LED Rouge et commute le relais "Courant fort"
const int BatterieCharge = 20; // Pin pour commander la LED verte indiquant que la batterie est chargée.
const int ADCALire = 4; // N° de l'ADC à lire (ADC4)
const int Erreur = 10; // Pin pour comander une LED rouge qui sert à signaler les erreurs
const byte VREF_INTERNE = 1; // Constante à utiliser si on veut mesurer par rapport à la référence de tension interne (2,56V). Voir InitADC
const byte Vcc_EXTERNE = 2; // constante à utiliser si on veut mesurer par rapport à Vcc. Voir InitADC
//
// Quelques constantes concernant les tensions (toutes en mv)
//
const unsigned long VLimiteBasse = 7000; // Valeur de tension en dessous de laquelle on considère qu'il n'y a pas de batterie connectée => Err
const unsigned long VLimiteHaute = 15000; // Valeur de tension au dessus de laquelle on considère que la tension de batterie est trop élevée => Err
//
// ---- CONSTANTES A REGLER ---- //
//
const unsigned long Facteur = 585; // Facteur multiplicatif pour calculer la tension réelle par rapport à la tension mesurée.
// // Est en lien avec la valeur du diviseur de tension du montage (pont de résistance plus diode en série).
// // Tension réelle = (valeur ADC) * (Tension de référence) * Facteur) / 102300 où
// // Valeur ADC est la valeur lue dans le convertisseur, Tension de référence est la tension
// // d'alimentation ou la tension de référence ARef en mV selon la référence choisie dans InitADC.
const unsigned long VAlimArduino = 4800; // tension d'alimentation de l'Arduino réellement mesurée
//
// ---FIN CONSTANTES A REGLER--- //
//
const unsigned long VAref = 2560; // Tension ARef interne (2.56 sur 32U4)
const unsigned long VBasculeFaibleFort = 13000; // Tension de basculement entre charge à courant élevé et charge à courant faible
const unsigned long VLimiteBatterieChargee = 13500; // Tension à partir de laquelle on considère la batterie comme chargée
const unsigned long Hysteresis = 200; // valeur de l'hystérésis
//
// Constantes de temps
//
const unsigned long PeriodeMesure = 600000; // 600000 = Période de mesure de la tension réelle de la batterie (10 mn)
//
// Etat automate chargeur
//
const byte EtatInitial = 1; // Etat initial de l'automate. Normalement, on n'y reste pas, sauf si erreur de programme !
const byte EtatErrFaible = 2; // Tension trop faible. Clignotement lent
const byte EtatErrEleve = 3; // Tension trop forte. Cligontement rapide
const byte EtatChargeFaible = 4; // Charge faible de la batterie
const byte EtatChargeForte = 5; // Charge forte de la batterie
const byte EtatBatterieCharge = 6; // Batterie chargée
//
// Variables
//
byte EtatAutomate; // Etat courant automate
boolean Transition; // Indique si on est dans la transition d'un état vers l'autre (vrai) ou si on est dans l'état établi (faux)
boolean LEDAllume; // Utilisé pour le clignotement de la LED d'erreur. Vrai si la LED est allumée
unsigned long DureeClignotement; // Pour mesurer la temps pour le clignotement de la LED d'erreur
unsigned int LEDDelai; // Pour indiquer la période du clignotement. 0 = Pas de clignotement.
//
unsigned long DureeMesure; // pour mesurer le temps entre deux mesures avec batterie déconnectée.
//-------------------------------------- INITIALISE ADCi
// Lecture de la tension d'alimentation selon la référence.
// En entrée : N° de l'ADC à lire
// Reference de tension à utiliser
// En sortie : valeur lue
//
void InitADC(byte ADCAlire, byte Reference)
{
ADMUX = ADCAlire; // n° de l'ADC
if (Reference == VREF_INTERNE) {
ADMUX |= (1 << REFS0); // Utilisation référence interne
ADMUX |= (1 << REFS1); //
} else {
ADMUX |= (1 << REFS0); // Utilisation référence Vcc
ADMUX &= ~(1 << REFS1); //
}
ADMUX &= ~(1 << ADLAR); // Résolution de 10 bits
delay(2);
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescale for 8Mhz
ADCSRA |= (1 << ADEN); // Active l'ADC
}
//-------------------------------------- LECTURE ADCi
// Lecture de la tension d'alimentation selon la référence.
// InitADC doit avoir été appelé avant
// En sortie : valeur lue
//
word LectureADC()
{
word ADCval;
ADCSRA |= (1 << ADSC); // Démarre la conversion
while (ADCSRA & (1 << ADSC)); // Attente que la conversion soit terminée
ADCval = ADCL; // Lecture d'ADCL en premier
ADCval = (ADCH << 8) + ADCval; // Lecture d'ADCH. ADC peut de nouveau être utilisé
return ADCval;
}
//-------------------------------------- SETUP
// Initialisation du programme
void setup() {
pinMode(FaibleCourant, OUTPUT);
pinMode(FortCourant, OUTPUT);
DesactiveCharge(); // Ouvre tous les relais
pinMode(BatterieCharge, OUTPUT);
IndicateurBatterieChargee(false); // Eteint l'indicateur de batterie chargée
pinMode(Erreur, OUTPUT);
RAZErreur(); // Pas d'erreur. Eteint la diode de signalement d'erreur
InitADC(ADCALire, Vcc_EXTERNE); // Par précaution, on initialise la référence de tension pour la mesure à Vcc. Le montage est ainsi protégé jusqu'à environ 30V avant diviseur de tension.
EtatAutomate = EtatInitial; // Etat de l'automate au lancement
Transition = false; // Pas de changement d'état en cours
// pour tests
#if defined(TEST)
Serial.begin(9600);
#endif
}
//-------------------------------------- MESURE TENSION
// Mesure de la tension.
// En entrée : MesureBatterie = vrai, mesure tension batterie avec déconnexion préalable du chargeur
// MesureBatterie = faux, mesure tension batterie connectée ou déconnectée du chargeur
// En sortie : V = Valeur mesurée
// EtatAutomate = EtatErrFaible si tension trop faible. La fonction rend faux
// EtatAutomate = EtatErrFort si tension trop forte. La fonction rend faux
// EtatAutomate inchangé si autre cas. La fonction rend vrai.
//
// NOTE : si le chargeur a été déconnecté, il n'est pas reconnecté. C'est à l'appelant de savoir ce qu'il veut faire.
// Le fait de ne pas reconnecter le chargeur dans cette fonction permet d'éviter d'éventuelles oscillations des relais
//
boolean MesureTension(unsigned long *V, boolean MesureBatterie) {
int i;
unsigned long moyenne;
//
// On commence par tester les tension grossièrement (par rapport à l'alimentation = 5V)
//
InitADC(ADCALire, Vcc_EXTERNE); // La référence de tension est celle de l'alimentation (environ 5V)
for (i = 1; i < 5; i++) { // On fait quelques lectures bidons
LectureADC();
}
moyenne = 0; // On fait une moyenne sur 5 mesures de la lecture de l'ADC
for (i = 1; i <= 5; i++) { //
moyenne = moyenne + LectureADC();
}
moyenne = moyenne / 5; // on espère que le compilo n'optimise pas cette opération
*V = (VAlimArduino * moyenne * Facteur) / 102300 ; // V contient le résultat de la mesure
#if defined(TEST)
Serial.print(" TVcc ");
Serial.print(*V);
Serial.print(" - Vlu ");
Serial.print(moyenne);
Serial.print(" - Tension lue ");
Serial.print((moyenne * VAlimArduino) / 1023);
#endif
if (*V <= VLimiteBasse) { // V inférieur à la limite basse de tension acceptée ?
Transition = (EtatAutomate != EtatErrFaible); // Oui ? Transition n'est positionné à vrai que s'il y a changement d'état
EtatAutomate = EtatErrFaible; // Alors on passe dans l'état d'erreur (tension faible).
return false;
}
if (*V >= VLimiteHaute) { // V inférieur à la limite haute de tension acceptée ?
Transition = (EtatAutomate != EtatErrEleve); // Oui ? Transition n'est positionné à vrai que s'il y a changement d'état
EtatAutomate = EtatErrEleve; // Alors on passe dans l'état d'erreur (tension élevée).
return false;
}
// Il n'y a pas d'erreur. La tension peut être mesurée préciséement.
if (MesureBatterie) { // Si on doit mesurer la tension réelle de la batterie
DesactiveCharge(); // alors, il faut déconnecter le chargeur
};
InitADC(ADCALire, VREF_INTERNE); // La référence de tension est ARef (cf. doc Atmel)
for (i = 1; i < 5; i++) {
LectureADC();
}
moyenne = 0;
for (i = 1; i <= 5; i++) {
moyenne = moyenne + LectureADC();
}
moyenne = moyenne / 5; // on espère que le compilo n'optimise pas cette opération
*V = (VAref * moyenne * Facteur) / 102300; // V contient le résultat de la mesure
#if defined(TEST)
Serial.print(" - TVaref ");
Serial.print(*V);
Serial.print(" - Vlu ");
Serial.print(moyenne);
Serial.print(" - Tension lue ");
Serial.print((moyenne * VAref) / 1023);
#endif
InitADC(ADCALire, Vcc_EXTERNE); // Par précaution, on remet la référence de tension à celle de l'alimentation.
return true;
}
//-------------------------------------- INDICATEUR BATTERIE CHARGEE
// Allume/éteint la diode d'indication de batterie chargée
// En entrée : True, allume la diode, false, éteint la diode
// En sortie : Néant
//
void IndicateurBatterieChargee(boolean B) {
if (B) {
digitalWrite(BatterieCharge, LOW);
} else {
digitalWrite(BatterieCharge, HIGH);
}
}
//-------------------------------------- ACTIVE COURANT FORT
// Active la Charge courant fort
// En entrée : néant
// En sortie : Néant
//
void ActiveCourantFort() {
digitalWrite(FaibleCourant, HIGH); // désactive courant faible
IndicateurBatterieChargee(false); // désactive batterie chargée
delay(200); // Attend que le relai commute
digitalWrite(FortCourant, LOW); // active courant fort
}
//-------------------------------------- ACTIVE VOURANT FAIBLE
// Active la Charge courant faible
// En entrée : néant
// En sortie : Néant
//
void ActiveCourantFaible() {
digitalWrite(FortCourant, HIGH); // désactive courant fort
IndicateurBatterieChargee(false); // désactive batterie chargée
delay(200); // Attend que le relai commute
digitalWrite(FaibleCourant, LOW); // active courant faible
}
//-------------------------------------- DESACTIVE CHARGE
// Désactive courant Fort et faible
// En entrée : néant
// En sortie : Néant
//
void DesactiveCharge() {
digitalWrite(FortCourant, HIGH); // désactive courant fort
digitalWrite(FaibleCourant, HIGH); // désactive courant faible
delay(200); // attend que les relais commutent
}
//-------------------------------------- INIT DIODE ERREUR
// Initialisation de l'allumage et de la durée de clignotement de la diode
// de signalement d'erreur
// En entrée : RAPIDE = True => clignotement rapide => 100mS
// RAPIDE = False => clignotement lent => 500ms
//
void InitDiodeErreur(boolean Rapide) {
digitalWrite(Erreur, LOW); // on allume la diode qui signale les erreurs
LEDAllume = true; // on se souvient de l'état de la diode
if (Rapide) { // si clignotement rapide,
LEDDelai = 100; // la durée du timer est initialisé pour 100ms
} else { // sinon
LEDDelai = 700; // pour 700ms
}
DureeClignotement = millis(); // on initialise le timer (variable globale DureeClignotement)
}
// La fonction ci-dessous mesure le temps en prenant en compte un éventuel bouclage de la fonction millis
// On lui donne la valeur de départ de la valeur à mesurer et la valeur courante de millis. Elle rend la durée écoulée en milliseondes.
//
unsigned long DureeMilli(unsigned long derniere, unsigned long courante) {
if (courante < derniere) { // on est passé par zéro
return (0xffffffffu - derniere) + courante;
} else {
return courante - derniere;
}
}
//-------------------------------------- DIODE ERREUR
// Gère le clignotement de la LED de signalement d'erreur
// En entrée : néant
// néant
// Un appel à InitDiodeErreur doit avoir été effectué pour initialiser les variables globales
// si LEDDelai = 0, on considère qu'il n'y a pas de clignotement.
//
void DiodeErreur() {
unsigned long i;
if (LEDDelai == 0) { // Si le timer n'est pas initialisé, on sort
return;
}
i = millis(); // valeur courante de l'horloge systeme
if (DureeMilli(DureeClignotement, i) >= LEDDelai) { // calcule si délai timer dépassé
if (LEDAllume) { // si oui, alors, si la diode est allumée
digitalWrite(Erreur, HIGH); // on l'éteint
} else { // sinon,
digitalWrite(Erreur, LOW); // on l'allume
}
LEDAllume = !LEDAllume; // on mémorise l'état de la diode
DureeClignotement = i; // et on réinitialise le timer
}
}
//-------------------------------------- RAZ ERREUR
// Arrête le clignotement
//
void RAZErreur() {
digitalWrite(Erreur, HIGH); // Eteint la diode signalant les erreurs
LEDDelai = 0; // LEDDelai = 0 => timer non initialisé
}
//-------------------------------------- INIT TIMER MESURE
// Initialise le timer pour la mesure de tension (variable globale DureeMesure)
//
void InitTimerMesure() {
DureeMesure = millis();
}
//-------------------------------------- TIMER MESURE
// Vrai si le temps écoulé depuis le dernier InitTimer est supérieur à la période prévues pour la mesure
// En entrée : néant
//
boolean TimerMesure() {
if (DureeMilli(DureeMesure, millis()) >= PeriodeMesure) { // si le timer est échu
InitTimerMesure(); // alors, on le redémarre
return true; // et on retourne True (Timer échu)
} else { // sinon
return false; // on retourne faux (timer non échu)
}
}
//-------------------------------------- LOOP
// Pour faire simple, dès que l'on n'est pas dans une condition d'erreur, on
// démarre par l'état charge faible. L'état de charge faible peut être considéré comme l'aiguillage de l'automate
// pour les changements d'états hors cas d'erreur.
//
void loop() {
unsigned long TensionCourante;
int ADCvalue;
#if defined(TEST)
Serial.print(" - Etat Automate ");
Serial.println(EtatAutomate);
delay(500);
#endif
switch (EtatAutomate) {
case EtatErrFaible: // Erreur, tension trop faible
if (Transition) { // si transition d'automate en cours (passage d'un état à l'autre)
DesactiveCharge(); // on désactive le chargeur (au cas où)
IndicateurBatterieChargee(false); // l'indicateur de batterie chargée est éteint (au cas où)
InitDiodeErreur(false); // On initialise le timer de la diode avec un clignotement lent
Transition = false; // on indique que la transition est terminée
} else { // On est dans l'état (transition terminée)
DiodeErreur(); // DiodeErreur gère le clignotement de la diode
if (MesureTension(&TensionCourante, false)) { // on mensure la tension au cas où.
EtatAutomate = EtatChargeFaible; // Si une tension valide est mesurée, alors, on passe dans l'état Charge Faible
Transition = true; // et on positione l'indicateur de transition
}
}
break;
case EtatErrEleve: // Erreur, tension trop élevée. Pour la suite, voir commentaires de l'état EtatErrFaible
if (Transition) {
DesactiveCharge();
IndicateurBatterieChargee(false);
InitDiodeErreur(true); // On initialise le timer de la diode avec un clignotement rapide
Transition = false;
} else {
DiodeErreur();
if (MesureTension(&TensionCourante, false)) {
EtatAutomate = EtatChargeFaible;
Transition = true;
}
}
break;
case EtatChargeForte: // Etat charge forte (5A par exemple)
if (Transition) { // Si le changement d'état est en cours
ActiveCourantFort(); // On active le relai qui commande le courant fort
RAZErreur(); // On efface les conditions d'erreurs (au cas où)
IndicateurBatterieChargee(false); // On éteint la diode qui indique la batterie est chargée (au cas où)
Transition = false; // On indique que la transition est terminée
InitTimerMesure; // On initialise le timer pour la mesure de la tension sur la batterie
} else { // Si on est dans l'état établi (pas dans la transition)
if (TimerMesure()) { // On vérifie si on doit mesurer la tension avec le chargeur déconnecté
if (MesureTension(&TensionCourante, true)) { // En sortie, le chargeur est déconnecté
if (TensionCourante >= VBasculeFaibleFort + Hysteresis) { // Doit on passer en charge de maintien ?
EtatAutomate = EtatChargeFaible; // Si oui, on change d'état
Transition = true; // Transition en cours
break; // On sort
}
ActiveCourantFort(); // on reconnecte le chargeur uniquement si on n'a pas changé d'état
}
} else { // On mesure la tension pour vérifier s'il n'y a rien d'anormal (tension trop basse ou tension trop élevée)
MesureTension(&TensionCourante, false);
}
}
break;
case EtatChargeFaible: // Etat charge faible. C'est l'aiguillage de l'automate lorsqu'il n'y a pas d'erreur
if (Transition) { // Si le changement d'état est en cours
RAZErreur(); // On efface les conditions d'erreurs (au cas où)
IndicateurBatterieChargee(false); // on éteint l'indicateur de batterie chargée (au cas où)
if (MesureTension(&TensionCourante, true)) { // on fait une mesure de la tension de batterie pour voir si on doit rester dans l'état courant
if (TensionCourante >= VLimiteBatterieChargee + Hysteresis) {
EtatAutomate = EtatBatterieCharge;
break;
}
if (TensionCourante <= VBasculeFaibleFort - Hysteresis) {
EtatAutomate = EtatChargeForte;
break;
}
}
ActiveCourantFaible(); // On arrive ici si on était bien dans le bon état
Transition = false; // La transition est terminée
InitTimerMesure; // On initialise le timer pour la prise de mesure de la tension de la batterie
}
if (TimerMesure()) { // Si le timer pour la prise de mesure est échu
if (MesureTension(&TensionCourante, true)) { // Alors, on mesure la tension de la batterie et on voit si on doit rester dans cet état.
if (TensionCourante >= VLimiteBatterieChargee + Hysteresis) {
EtatAutomate = EtatBatterieCharge;
Transition = true;
break;
}
if (TensionCourante <= VBasculeFaibleFort - Hysteresis) {
EtatAutomate = EtatChargeForte;
Transition = true;
break;
}
ActiveCourantFaible(); // on reconnecte le chargeur
}
} else {
MesureTension(&TensionCourante, false);
}
break;
case EtatBatterieCharge:
if (Transition) {
DesactiveCharge();
RAZErreur();
IndicateurBatterieChargee(true); // active batterie chargée
Transition = false;
}
if (MesureTension(&TensionCourante, false)) { // la batterie est déja déconnectée
if (TensionCourante <= VLimiteBatterieChargee - Hysteresis) {
EtatAutomate = EtatChargeFaible;
Transition = true;
}
}
break;
default: // état initial ou erreur d'automate !
DesactiveCharge();
RAZErreur();
IndicateurBatterieChargee(false);
if (MesureTension(&TensionCourante, false)) {
EtatAutomate = EtatChargeFaible;
Transition = true;
}
break;
}
}