Lisez des capteurs analogiques en bare metal : registres ADMUX, ADCSRA, référence de tension et conversion continue
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 :
| Bits | Nom | Fonction |
|---|---|---|
| 7:6 | REFS1:REFS0 | Référence de tension : 00=AREF, 01=AVCC (5V), 11=Interne 1.1V |
| 5 | ADLAR | Alignement à gauche : si =1, les 8 bits MSB sont dans ADCH (lecture 8 bits rapide) |
| 3:0 | MUX3:MUX0 | Sélection du canal : 0000=ADC0, 0001=ADC1, ..., 0101=ADC5, 1000=capteur temp., 1110=1.1V, 1111=GND |
| Bit | Nom | Fonction |
|---|---|---|
| 7 | ADEN | ADC Enable — Active l'ADC |
| 6 | ADSC | ADC Start Conversion — Lance une conversion |
| 5 | ADATE | ADC Auto Trigger Enable — Mode conversion automatique |
| 4 | ADIF | ADC Interrupt Flag — Passe à 1 quand la conversion est terminée |
| 3 | ADIE | ADC Interrupt Enable — Active l'interruption ADC |
| 2:0 | ADPS2:0 | Prescaler : 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.
#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);
}
}
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
| Arduino | Bare 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 Running | Mode 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.