// // 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; } }