AVR Bare-Metal Blinking
No Arduino libraries. No HAL. Just registers.
An ATmega328P has DDRB, PORTB, and a 16-bit timer. We configure Timer1
in CTC mode with a 1 Hz compare match, toggle PB5 (the onboard LED pin)
in the ISR, and end up with a binary that fits in 176 bytes. The Makefile
uses avr-gcc and avrdude directly — no IDE required.
This exercise looks trivial, but it trains the exact muscle many developers skip: understanding cause and effect between register writes and hardware behavior. You do not “ask an API” to blink. You define direction bits, timer prescalers, compare values, and interrupt masks yourself.
Minimal mental model
DDRBconfigures PB5 as output.TCCR1A/TCCR1Bdefine timer mode and prescaler.OCR1Asets compare threshold.TIMSK1enables compare interrupt.- ISR toggles
PORTBbit for the LED.
When this chain is explicit, debugging gets faster. If timing is wrong, you inspect clock and prescaler. If the LED is dark, verify direction and pin. Each symptom maps to a small set of causes.
Why still do this in 2026
Bare-metal AVR is still a great teaching platform because feedback is fast and tooling is mature. You can compile, flash, and verify behavior in a few seconds, then iterate. Even if your production target is different, this discipline transfers directly to RISC-V, ARM, and RTOS-based projects.
Related reading: