How to use a timer interrupt to blink an LED at a fixed rate?


Here’s a real-world pattern: use a hardware timer interrupt to blink an LED at a fixed rate while the main loop keeps doing other work (e.g., reading sensors, talking over serial). I’ll show an Arduino Uno (AVR) version first, then quick notes for ESP32/RP2040.
1) Arduino Uno/Nano (ATmega328P) — Timer1 interrupt + non-blocking main loop
What it does
Timer1 fires every 500 ms (2 Hz).
ISR just sets a flag (keeps ISR short).
loop()
toggles the LED when it sees the flag, while still reading a sensor and printing data.
// --- Pins ---
const uint8_t LED_PIN = 13; // On-board LED
const uint8_t SENSOR_PIN = A0;
// --- Shared state between ISR and loop ---
volatile bool tick500ms = false; // set by ISR, read/cleared in loop
// Optional: data accumulation in loop
unsigned long lastPrintMs = 0;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(SENSOR_PIN, INPUT);
Serial.begin(115200);
// -------- Timer1 setup (CTC mode) --------
// Goal: 500 ms interrupt (2 Hz)
// F_CPU = 16 MHz, prescaler = 256
// OCR1A = (16e6 / (256 * 2 Hz)) - 1 = 31249
noInterrupts();
TCCR1A = 0; // normal port operation
TCCR1B = 0;
TCCR1B |= (1 << WGM12); // CTC mode (clear timer on compare)
TCCR1B |= (1 << CS12); // prescaler = 256
OCR1A = 31249; // compare match value for 500 ms
TCNT1 = 0; // reset counter
TIMSK1 |= (1 << OCIE1A); // enable compare A match interrupt
interrupts();
}
void loop() {
// --- Do regular work (non-blocking) ---
int sensor = analogRead(SENSOR_PIN); // e.g., potentiometer or sensor
unsigned long now = millis();
// Print sensor every 250 ms (independent of LED blink)
if (now - lastPrintMs >= 250) {
lastPrintMs = now;
Serial.print(F("Sensor="));
Serial.println(sensor);
}
// --- Handle the 500 ms "tick" generated by the timer ISR ---
if (tick500ms) {
tick500ms = false; // consume the event
// Toggle LED
static bool led = false;
led = !led;
digitalWrite(LED_PIN, led ? HIGH : LOW);
}
// The CPU is free to do more things here (parse serial, handle state machines, etc.)
}
// -------- Interrupt Service Routine --------
ISR(TIMER1_COMPA_vect) {
// Keep it short: just set a flag
tick500ms = true;
}
Why this is good practice
ISR is tiny (sets a flag only) → minimal jitter, safer for complex systems.
loop() stays responsive (reads sensors, prints, handles state machines).
Timing is rock solid (true hardware timer, not
delay()
).
2) ESP32 quick version (hardware timer)
#include <driver/timer.h>
const uint8_t LED_PIN = 2;
volatile bool tick500ms = false;
hw_timer_t* timer0 = nullptr;
void IRAM_ATTR onTimer() {
tick500ms = true;
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
// 80 MHz APB clock / 80 = 1 MHz timer -> 500,000 ticks = 500 ms
timer0 = timerBegin(0, 80, true);
timerAttachInterrupt(timer0, &onTimer, true);
timerAlarmWrite(timer0, 500000, true); // 500 ms periodic
timerAlarmEnable(timer0);
}
void loop() {
// Do other work...
if (tick500ms) {
tick500ms = false;
static bool led = false;
led = !led;
digitalWrite(LED_PIN, led);
}
}
3) Raspberry Pi Pico (RP2040) quick version (hardware alarm)
#include "pico/stdlib.h"
#include "hardware/timer.h"
const uint LED_PIN = 25;
volatile bool tick500ms = false;
bool on_timer(repeating_timer_t *t) {
tick500ms = true;
return true; // keep repeating
}
int main() {
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
repeating_timer_t timer;
add_repeating_timer_ms(500, on_timer, nullptr, &timer);
bool led = false;
while (true) {
// Do other work here (read sensors, UART, etc.)
if (tick500ms) {
tick500ms = false;
led = !led;
gpio_put(LED_PIN, led);
}
}
}
Pro tips
Keep ISRs short: set flags, push bytes to ring buffers, acknowledge hardware — nothing heavy.
No
delay()
in ISRs, avoidSerial.print()
there.Share data via
volatile
variables; for multi-byte data, disable interrupts or use atomic access.Use hardware timers for precise periodic tasks; use
millis()
for coarse scheduling inloop()
.
Subscribe to my newsletter
Read articles from ampheo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
