🛠 Manipulation directe des registres PORT : le bit-banging à 16 MHz
Objectives
digitalWrite is convenient but slow on an 8-bit AVR: about ten cycles per call, limiting the pulse width to a few tens of kHz. To drive neopixels, multiplexed displays, or a custom protocol, you drop down to the register level: DDRx, PORTx, PINx. The official Arduino pin ↔ port bit mapping table (PinMapping168) is your navigation map. You will generate a pulse measurable with an oscilloscope and write to an entire port in a single write operation.
Hardware
- 1 Arduino Duemilanove (ATmega328)
- 1 oscilloscope or logic analyzer (otherwise, a frequency counter on the pin)
- No wiring required: the LED on pin 13 serves as a visible load
Complete code
// 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;
}
Writing to 8 LEDs at once: PORTD = B10101010; sets pins 0–7 to the desired state in a single instruction (note: pins 0/1 are the FTDI serial link — do not break the monitor during uploading).
Expected verification
On the oscilloscope connected to pin 13, you should see a pulse with a period measured in hundreds of nanoseconds (C loops run at around ~1–2 MHz switching speed, much faster with sbi/cbi in assembly) — compared to ~50 kHz maximum with digitalWrite. LED 13 remains dimly lit (natural PWM due to duty cycle). The "one write, 8 LEDs" test toggles an entire port simultaneously.
Common errors
- Writing to PORTD without thinking: bits 0 and 1 are RX/TX to the FTDI; an inadvertent
PORTD = 0breaks the serial monitor and can interfere with uploading. - Forgetting DDRx: writing to PORTx without setting the pin as output controls nothing (or just activates the internal pull-up, PORTx=1 while in input mode).
- Confusing PORTx and PINx: reading the actual pin state is done via PINx; reading PORTx reads back the output setting.
- Modifying a shared port via read-modify-write inside an interrupt: risk of race condition with the main code; use atomic bit operations (sbi/cbi) or disable interrupts.
- Forgetting that millis runs via timer0: the 1 ms interrupts add jitter to frequency measurements — hence
noInterruptsduring measurement. - Using registers on an ATmega168 chip assuming the same pins: the mapping is identical for standard pins, but the SRAM (1 KB) and flash (14,336 bytes) quickly limit large lookup tables.
- Verifying with a multimeter: at these frequencies, it displays a misleading average voltage — you need an oscilloscope.
Other tutorials for this board
- 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)
Code examples for this board
clignotante.inoArduinobouton-led.inoArduinopwm-respiration.inoArduinopotentiometre-plotter.inoArduinoservo-balayage.inoArduinobuzzer-melodie.inoArduino