📊 Convertisseur ADC — ATmega328P

Lisez des capteurs analogiques en bare metal : registres ADMUX, ADCSRA, référence de tension et conversion continue

L'ADC de l'ATmega328P

L'ATmega328P intègre un convertisseur analogique-numérique (ADC) 10 bits à approximation successive. Il convertit une tension analogique (entre 0V et la tension de référence) en une valeur numérique de 0 à 1023. C'est l'équivalent bare metal de la fonction Arduino analogRead().

Caractéristiques principales :

📋 Les registres ADC

ADMUX — Sélection du canal et de la référence

BitsNomFonction
7:6REFS1:REFS0Référence de tension : 00=AREF, 01=AVCC (5V), 11=Interne 1.1V
5ADLARAlignement à gauche : si =1, les 8 bits MSB sont dans ADCH (lecture 8 bits rapide)
3:0MUX3:MUX0Sélection du canal : 0000=ADC0, 0001=ADC1, ..., 0101=ADC5, 1000=capteur temp., 1110=1.1V, 1111=GND

ADCSRA — Contrôle et statut

BitNomFonction
7ADENADC Enable — Active l'ADC
6ADSCADC Start Conversion — Lance une conversion
5ADATEADC Auto Trigger Enable — Mode conversion automatique
4ADIFADC Interrupt Flag — Passe à 1 quand la conversion est terminée
3ADIEADC Interrupt Enable — Active l'interruption ADC
2:0ADPS2:0Prescaler : 000=/2, 010=/4, 011=/8, 100=/16, 101=/32, 110=/64, 111=/128

⚠️ Prescaler ADC : L'ADC fonctionne de manière optimale avec une fréquence d'horloge ADC entre 50 kHz et 200 kHz. À 16 MHz, un prescaler de 128 donne 125 kHz (idéal). Un prescaler trop faible dégrade la résolution effective.

💻 Code : Lecture ADC simple

#include <avr/io.h>
#include <util/delay.h>

#define F_CPU 16000000UL

/* Initialisation ADC */
void adc_init(void) {
    // Référence = AVCC (5V), alignement à droite
    ADMUX = (1 << REFS0);

    // Activer ADC, prescaler = 128 (125 kHz à 16 MHz)
    ADCSRA = (1 << ADEN)
           | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}

/* Lire un canal ADC (0–5) */
uint16_t adc_read(uint8_t channel) {
    // Sélectionner le canal (conserver les bits hauts de ADMUX)
    ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);

    // Lancer la conversion
    ADCSRA |= (1 << ADSC);

    // Attendre la fin de la conversion (ADSC revient à 0)
    while (ADCSRA & (1 << ADSC));

    // Lire le résultat 10 bits (ADCL doit être lu en premier !)
    return ADC;  // Macro qui lit ADCL puis ADCH
}

/* Programme principal */
int main(void) {
    adc_init();

    // Configurer PB5 en sortie (LED)
    DDRB |= (1 << PB5);

    while (1) {
        uint16_t val = adc_read(0);  // Lire ADC0 (A0)

        // Allumer LED si la valeur dépasse 512 (mi-course potentiomètre)
        if (val > 512)
            PORTB |= (1 << PB5);
        else
            PORTB &= ~(1 << PB5);

        _delay_ms(100);
    }
}

🌡️ Lire le capteur de température interne

L'ATmega328P intègre un capteur de température interne accessible via le canal ADC 8. Il n'est pas très précis (±10°C) mais peut être utile pour détecter une surchauffe :

uint16_t adc_read_temp(void) {
    // Référence interne 1.1V + canal 8 (capteur temp.)
    ADMUX = (1 << REFS1) | (1 << REFS0) | (0x08);
    _delay_ms(5);              // Stabilisation référence
    ADCSRA |= (1 << ADSC);
    while (ADCSRA & (1 << ADSC));
    return ADC;
}

// Approximation : T(°C) ≈ (ADC − 324.31) / 1.22

📌 Comparaison analogRead() Arduino vs bare metal

ArduinoBare metal AVR
analogRead(A0)adc_read(0)
analogReference(INTERNAL)ADMUX = (1<
~112 µs par conversion~104 µs (identique, même prescaler)
Pas de mode Free RunningMode Free Running disponible via ADATE

En bare metal, l'avantage principal est l'accès au mode Free Running (conversion continue automatique) et aux interruptions ADC pour un traitement non bloquant — deux fonctionnalités non exposées par l'API Arduino standard.