Architecture matérielle du système
1. Interconnexion des composants
AVR ATmega16
├── ADC0 (PC0) → Mesure tension batterie (diviseur résistif)
├── ADC1 (PC1) → Mesure courant de charge (shunt + ampli-op)
├── OC1A (PB1) → Sortie PWM → Pilote MOSFET → Contrôle charge
├── INT0 (PD2) → Protection thermique (CTN)
├── PC4/PC5 → Interface I2C → Affichage OLED (optionnel)
└── PD0/PD1 → UART → Supervision PC (optionnel)
Bloc d'alimentation :
Adaptateur 12V/2A → Convertisseur Buck synchrone → Batterie Li-ion/Plomb
2. Spécifications techniques clés
| Paramètre |
Valeur |
Description |
| Fréquence de commutation |
31.25 kHz |
Division PWM, réduction pertes |
| Échantillonnage tension |
ADC 12 bits |
Ratio diviseur 1/10, résolution 10mV |
| Échantillonnage courant |
50mV/10A |
Résistance shunt 0.005Ω, gain ampli-op 20 |
| Période de régulation |
10 ms |
Cycle calcul PID, réponse adaptée |
Implémentation du régulateur PID (arithmétique entière pour AVR)
1. Structure et initialisation du PID
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
// Structure du régulateur PID (entiers, évite calcul flottant)
typedef struct {
int16_t gain_P; // Coefficient proportionnel (x100)
int16_t gain_I; // Coefficient intégral (x10000)
int16_t gain_D; // Coefficient dérivé (x100)
int16_t consigne; // Valeur cible (tension ou courant)
int16_t mesure; // Valeur mesurée
int32_t terme_integral; // Accumulateur intégral (x100)
int16_t erreur_precedente;// Erreur cycle précédent
int16_t commande; // Sortie (0-1000 pour 0-100% duty)
int16_t limite_basse; // Sortie minimum
int16_t limite_haute; // Sortie maximum
} RegulateurPID_t;
// États possibles de la charge
typedef enum {
ARRET = 0, // Charge désactivée
COURANT_CONSTANT, // Mode CC
TENSION_CONSTANT, // Mode CV
CHARGE_ENTRETIEN, // Charge d'entretien
CHARGE_TERMINEE // Cycle complet
} EtatCharge_t;
// Variables globales
RegulateurPID_t regulateur_tension;
RegulateurPID_t regulateur_courant;
EtatCharge_t etat_courant = ARRET;
uint16_t tension_batterie = 0; // en mV
uint16_t intensite_charge = 0; // en mA
2. Algorithme de calcul PID optimisé pour 8 bits
// Calcul PID avec entiers (compatible AVR 8-bit)
int16_t Calculer_PID(RegulateurPID_t *reg) {
int16_t ecart = reg->consigne - reg->mesure;
int32_t sortie;
// Terme proportionnel (gain_P déjà multiplié par 100)
int32_t P = (int32_t)reg->gain_P * ecart; // Résultat x100
// Terme intégral (gain_I x10000, accumulateur x100)
reg->terme_integral += (int32_t)ecart * 100;
// Anti-windup : limitation de l'intégrateur
int32_t limite_integral = (int32_t)reg->limite_haute * 10000 / reg->gain_I;
if (reg->terme_integral > limite_integral)
reg->terme_integral = limite_integral;
if (reg->terme_integral < -limite_integral)
reg->terme_integral = -limite_integral;
int32_t I = (reg->gain_I * reg->terme_integral) / 10000; // Résultat x100
// Terme dérivé (gain_D x100)
int32_t D = (int32_t)reg->gain_D * (ecart - reg->erreur_precedente); // x100
reg->erreur_precedente = ecart;
// Somme des termes (tous à l'échelle x100)
sortie = P + I + D;
sortie /= 100; // Échelle réelle
// Saturation de la sortie
if (sortie > reg->limite_haute) sortie = reg->limite_haute;
if (sortie < reg->limite_basse) sortie = reg->limite_basse;
reg->commande = (int16_t)sortie;
return reg->commande;
}
3. Fonctions d'acquisition analogique
// Configuration du convertisseur ADC
void Initialiser_ADC(void) {
ADMUX = (1<<REFS0); // Référence AVCC, alignement droit
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Fréquence ADC 125kHz
}
// Lecture d'une voie ADC
uint16_t Lire_ADC(uint8_t voie) {
ADMUX = (ADMUX & 0xF0) | (voie & 0x0F);
ADCSRA |= (1<<ADSC);
while (ADCSRA & (1<<ADSC));
return ADC;
}
// Conversion tension batterie (mV)
uint16_t Mesurer_Tension_Batterie(void) {
uint16_t val_adc = Lire_ADC(0);
uint32_t tension = (uint32_t)val_adc * 5000 / 1023; // Conversion mV
return (uint16_t)(tension * 10); // Compensation diviseur
}
// Conversion courant de charge (mA)
uint16_t Mesurer_Courant_Charge(void) {
uint16_t val_adc = Lire_ADC(1);
uint32_t tension = (uint32_t)val_adc * 5000 / 1023; // mV
// Calcul : courant = tension / (gain_amp * résistance_shunt)
uint32_t courant = tension * 1000 / (20 * 5);
return (uint16_t)courant;
}
4. Contrôle de modulation de largeur d'impulsion
// Configuration Timer1 en mode PWM 10 bits
void Configurer_PWM(void) {
// Mode Fast PWM 10 bits, sortie OC1A
TCCR1A = (1<<COM1A1) | (1<<WGM11) | (1<<WGM10);
TCCR1B = (1<<WGM12) | (1<<CS11); // Prescaler 8 → 31.25kHz
OCR1A = 0; // Cycle de service initial nul
DDRB |= (1<<PB1); // Pin PB1 en sortie
}
// Réglage du rapport cyclique (0-1000 → 0-100%)
void Regler_Duty_Cycle(uint16_t valeur) {
if (valeur > 1000) valeur = 1000;
OCR1A = valeur; // Valeur 10 bits (0-1023)
}
5. Automate de gestion de la charge
// Gestion des transitions d'état
void Machine_Etat_Charge(void) {
static uint16_t objectif_cc = 1000; // 1000mA en CC
static uint16_t objectif_cv = 4200; // 4.2V pour Li-ion
static uint16_t seuil_entretien = 100; // 100mA pour mode entretien
switch (etat_courant) {
case ARRET:
Regler_Duty_Cycle(0);
break;
case COURANT_CONSTANT:
regulateur_courant.consigne = objectif_cc;
regulateur_courant.mesure = intensite_charge;
Regler_Duty_Cycle(Calculer_PID(®ulateur_courant));
// Transition vers CV si tension atteinte
if (tension_batterie >= objectif_cv) {
etat_courant = TENSION_CONSTANT;
}
break;
case TENSION_CONSTANT:
regulateur_tension.consigne = objectif_cv;
regulateur_tension.mesure = tension_batterie;
Regler_Duty_Cycle(Calculer_PID(®ulateur_tension));
// Passage en entretien quand courant diminue
if (intensite_charge <= seuil_entretien) {
etat_courant = CHARGE_ENTRETIEN;
}
break;
case CHARGE_ENTRETIEN:
Regler_Duty_Cycle(50); // Maintien faible courant
// Détection fin de charge (courant < 50mA pendant 10min)
static uint16_t compteur_fin = 0;
if (intensite_charge < 50) {
compteur_fin++;
if (compteur_fin >= 6000) { // 10 minutes
etat_courant = CHARGE_TERMINEE;
}
} else {
compteur_fin = 0;
}
break;
case CHARGE_TERMINEE:
Regler_Duty_Cycle(0);
break;
}
}
6. Boucle principale
int main(void) {
// Initialisation paramètres PID
regulateur_tension.gain_P = 800; // Coeff P: 8.0
regulateur_tension.gain_I = 200; // Coeff I: 0.02
regulateur_tension.gain_D = 100; // Coeff D: 1.0
regulateur_tension.limite_basse = 0;
regulateur_tension.limite_haute = 1000;
regulateur_courant.gain_P = 600; // Coeff P: 6.0
regulateur_courant.gain_I = 150; // Coeff I: 0.015
regulateur_courant.gain_D = 50; // Coeff D: 0.5
regulateur_courant.limite_basse = 0;
regulateur_courant.limite_haute = 1000;
// Initialisation périphériques
Initialiser_ADC();
Configurer_PWM();
// Démarrage charge
etat_courant = COURANT_CONSTANT;
while (1) {
// Acquisition des grandeurs physiques
tension_batterie = Mesurer_Tension_Batterie();
intensite_charge = Mesurer_Courant_Charge();
// Vérification des limites de sécurité
if (tension_batterie > 4300 || intensite_charge > 1500) {
etat_courant = ARRET;
}
// Exécution de l'automate
Machine_Etat_Charge();
// Temporisation (10ms → 100Hz)
_delay_ms(10);
}
}
Méthodologie d'ajustement des paramètres
1. Valeurs recommandées pour charge Li-ion
| Paramètre |
Mode CC |
Mode CV |
Rôle |
| Kp |
600-800 |
800-1000 |
Temps de réponse |
| Ki |
100-200 |
150-250 |
Erreur statique |
| Kd |
30-80 |
50-100 |
Dépassement/osclilations |
2. Procédure d'optimisation
1. Régler d'abord le mode CC : consigne 1000mA, augmenter progressivement Kp
2. Ajouter Ki pour éliminer l'erreur statique, éviter le saturage intégral
3. Ajuster le mode CV : consigne 4200mV, utiliser Kd pour limiter les dépassements
4. Tester les transitions : vérifier la fluidité CC→CV
Fonctionnalités additionnelles
1. Compensation thermique de la tension
// Adaptation tension selon température batterie
uint16_t Tension_Compensee_Temp(uint16_t temperature) {
// Coefficient Li-ion : -3mV/°C/cellule, référence 25°C
int16_t correction = (25 - temperature) * 3;
return 4200 + correction;
}
2. Identification du type de batterie
// Détection type batterie (Li-ion vs Plomb)
TypeBatterie_t Identifier_Batterie(void) {
uint16_t tension = Mesurer_Tension_Batterie();
if (tension < 2000) return BATTERIE_LIION; // <2V : Li-ion
else if (tension < 3000) return BATTERIE_PLOMB; // 2-3V : Plomb
return BATTERIE_INCONNU;
}
Conseils pour la mise au point
- Sécurité avant tout : D'abord déconnecter la batterie, simuler avec une charge variable
- Vérification progressive : Contrôler la sortie PWM → Valider l'ADC → Régler le PID
- Tests de protection : Provoquer volontairement des conditions de surtension/surcourant
- Refroidissement : Dissipation thermique adéquate pour MOSFET et parcours fort courant