🛠 Manipulation directe des registres PORT : le bit-banging à 16 MHz
Objectifs
digitalWrite est pratique mais lent sur un AVR 8 bits : une dizaine de cycles par appel, soit un créneau limité à quelques dizaines de kHz. Pour piloter des neopixels, un affichage multiplexé ou un protocole maison, on descend au niveau des registres : DDRx, PORTx, PINx. La table de correspondance officielle broches Arduino ↔ bits de port (PinMapping168) est votre carte de navigation. Vous allez générer un créneau mesurable à l'oscilloscope et écrire dans un port entier d'un seul coup d'écriture.
Matériel
- 1 Arduino Duemilanove (ATmega328)
- 1 oscilloscope ou analyseur logique (sinon, un compteur fréquence-mètre sur la broche)
- Pas de câblage nécessaire : la LED de la broche 13 sert de charge visible
Code complet
// Carre rapide sur la broche 13 = PORTB bit 5 (voir PinMapping168)
// Broches 8..13 = PORTB bits 0..5 ; broches 0..7 = PORTD bits 0..7 ;
// broches analogiques A0..A5 = PORTC bits 0..5.
void setup {
DDRB |= (1 << DDB5); // broche 13 en sortie (registre DDR du port B)
// Equivalence : pinMode(13, OUTPUT);
}
void loop {
noInterrupts; // le timer0 du millis perturbe les mesures
PORTB |= (1 << PORTB5); // broche 13 a 1 -> 1 cycle CPU
PORTB &= ~(1 << PORTB5); // broche 13 a 0 -> 1 cycle CPU
// NB : |= et &= font 2 ecritures ; en assembleur, sbi/cbi font 1 cycle.
interrupts;
}
Écrire 8 LED d'un coup : PORTD = B10101010; place les broches 0–7 dans l'état voulu en une seule instruction (attention : les broches 0/1 sont la liaison série FTDI — ne pas casser le moniteur en plein téléversement).
Vérification attendue
À l'oscilloscope sur la broche 13, un créneau dont la période se compte en centaines de nanosecondes (les boucles C tournent autour de ~1–2 MHz de commutation, bien plus avec sbi/cbi en assembleur) — contre ~50 kHz maximum avec digitalWrite. La LED 13 reste faiblement allumée (PWM naturel par rapport de service). Le test « une écriture, 8 LED » fait basculer tout un port simultanément.
Erreurs fréquentes
- Écrire dans PORTD sans réfléchir : les bits 0 et 1 sont RX/TX vers la FTDI ; un
PORTD = 0intempestif casse le moniteur série et peut gêner le téléversement. - Oublier DDRx : écrire dans PORTx sans avoir mis la broche en sortie ne pilote rien (ou active juste le pull-up interne, PORTx=1 en entrée).
- Confondre PORTx et PINx : lire l'état réel des broches passe par PINx ; lire PORTx relit la consigne de sortie.
- Modifier un port partagé en lecture-modification-écriture dans une interruption : risque de race condition avec le code principal ; utiliser les bits atomiques (sbi/cbi) ou couper les interruptions.
- Oublier que millis tourne via le timer0 : les interruptions de 1 ms ajoutent du jitter aux mesures de fréquence — d'où
noInterruptspendant la mesure. - Utiliser les registres sur une puce 168 en croyant aux mêmes broches : la correspondance est identique pour les broches standard, mais la SRAM (1 Ko) et la flash (14 336 octets) limitent vite les gros tableaux de tables.
- Vérifier avec un multimètre : à ces fréquences il affiche une tension moyenne trompeuse — il faut l'oscilloscope.
Autres tutoriels pour cette carte
- Faire clignoter la LED 13 d'une Duemilanove (et choisir le bon ATmega)
- Bouton poussoir et moniteur série sur la Duemilanove (avec ses 0,5 s de silence)
- Potentiomètre et variation LED : l'ADC 10 bits et le PWM 8 bits version AVR
- Ajouter un deuxième port série avec SoftwareSerial (GPS sur broches 10/11)
- Régler la référence de mesure avec AREF et analogReference
- Persister une calibration dans l'EEPROM de l'ATmega (512 octets ou 1 Ko)
Exemples de code pour cette carte
clignotante.inoArduinobouton-led.inoArduinopwm-respiration.inoArduinopotentiometre-plotter.inoArduinoservo-balayage.inoArduinobuzzer-melodie.inoArduino