Le cœur de l'Arduino Uno décortiqué — architecture AVR, registres et programmation bas niveau en C
L'ATmega328P est un microcontrôleur 8 bits de la famille AVR, conçu par Atmel (racheté par Microchip Technology en 2016). C'est le microcontrôleur qui équipe l'Arduino Uno et l'Arduino Nano, ce qui en fait le MCU le plus utilisé au monde pour le prototypage.
Cependant, l'ATmega328P est bien plus qu'un « chip Arduino ». C'est un microcontrôleur complet et puissant que l'on peut programmer sans la couche d'abstraction Arduino, en accédant directement aux registres avec AVR-GCC (le compilateur C officiel pour AVR). Cette approche « bare metal » offre un contrôle total sur le matériel, des performances optimales et une compréhension profonde du fonctionnement interne.
| Caractéristique | Valeur |
|---|---|
| Architecture | AVR 8 bits, Harvard modifiée, RISC (131 instructions) |
| Fréquence max | 20 MHz (16 MHz sur Arduino Uno) |
| Mémoire Flash | 32 Ko (programme) |
| SRAM | 2 Ko (variables, pile) |
| EEPROM | 1 Ko (stockage persistant) |
| GPIO | 23 broches (3 ports : B, C, D) |
| ADC | 6 canaux, 10 bits |
| Timers | Timer0 (8 bits), Timer1 (16 bits), Timer2 (8 bits) |
| PWM | 6 canaux |
| Communication | USART, SPI, I2C (TWI) |
| Interruptions | 26 vecteurs (INT0, INT1, PCINT, Timer, ADC, USART…) |
| Tension de fonctionnement | 1.8V – 5.5V |
| Boîtier | DIP-28, TQFP-32, QFN-32 |
L'ATmega328P utilise une architecture Harvard modifiée avec des bus séparés pour les instructions (Flash) et les données (SRAM). Cela permet au processeur de lire une instruction et d'accéder aux données simultanément, améliorant les performances.
Le cœur AVR dispose de 32 registres 8 bits (R0 à R31) qui servent à effectuer les opérations arithmétiques et logiques. Les registres R26-R31 peuvent être utilisés par paires comme pointeurs 16 bits (X, Y, Z) pour l'adressage indirect. La plupart des instructions s'exécutent en un seul cycle d'horloge, ce qui rend l'AVR très efficace.
L'ATmega328P organise ses 23 broches GPIO en 3 ports :
| Port | Broches | Arduino Uno | Fonctions spéciales |
|---|---|---|---|
| PORTB | PB0–PB5 (6 bits utiles) | D8–D13 | SPI (PB2-PB5), Quartz (PB6-PB7), PWM (PB1,PB2,PB3) |
| PORTC | PC0–PC5 (6 bits utiles) | A0–A5 | ADC (PC0-PC5), I2C (PC4=SDA, PC5=SCL), Reset (PC6) |
| PORTD | PD0–PD7 (8 bits) | D0–D7 | USART (PD0=RX, PD1=TX), INT0/INT1 (PD2,PD3), PWM (PD3,PD5,PD6) |
Chaque port est contrôlé par 3 registres :
1 = sortie, 0 = entrée.Voici l'équivalent du Blink Arduino, mais en accès direct aux registres :
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// PB5 = broche 13 Arduino = LED intégrée
DDRB |= (1 << PB5); // PB5 en sortie
while (1) {
PORTB |= (1 << PB5); // PB5 = HIGH (LED ON)
_delay_ms(500);
PORTB &= ~(1 << PB5); // PB5 = LOW (LED OFF)
_delay_ms(500);
}
return 0;
}
Ce code fait exactement la même chose que le Blink Arduino, mais en ~178 octets de Flash contre ~900 octets pour la version Arduino. La différence : pas de bootloader, pas de framework, juste du pur C et des registres.
💡 Compilation en ligne de commande :
avr-gcc -mmcu=atmega328p -DF_CPU=16000000UL -Os -o blink.elf blink.c
avr-objcopy -O ihex blink.elf blink.hex
avrdude -c usbasp -p m328p -U flash:w:blink.hex
PORTB |= (1 << PB5) s'exécute en 2 cycles d'horloge (~125 ns à 16 MHz) contre ~60 cycles pour digitalWrite().