Programme de chargeur intelligent AVR avec régulation PID (ATmega16/ATmega328)

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(&regulateur_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(&regulateur_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

  1. Sécurité avant tout : D'abord déconnecter la batterie, simuler avec une charge variable
  2. Vérification progressive : Contrôler la sortie PWM → Valider l'ADC → Régler le PID
  3. Tests de protection : Provoquer volontairement des conditions de surtension/surcourant
  4. Refroidissement : Dissipation thermique adéquate pour MOSFET et parcours fort courant

Étiquettes: AVR ATmega16 ATmega328 PID chargeur batterie

Publié le 3 juillet à 09h22