The ATmega328P on the classic Arduino Uno R3 contains three hardware timers: Timer0 (8-bit, reserved for millis() and delay()), Timer1 (16-bit, often claimed by the Servo library), and Timer2 (8-bit, used by tone()). To use Arduino timers for custom non-blocking interrupts without breaking core functions, you must configure Timer1 or Timer2 registers (TCCR, OCR) directly while leaving Timer0 untouched. This guide walks through a dual-timer build, exact register math, and how to fix the most common compiler faults.

The Hardware Reality of Arduino Timers

Beginners often treat delay() as a timing mechanism, but it halts the CPU. True multitasking on a microcontroller requires hardware timers—dedicated silicon counters that tick independently of your main code. When a counter matches a target value you set, it triggers an Interrupt Service Routine (ISR).

Here is the exact silicon layout for the ATmega328P (the chip on the Uno R3 and Nano v3):

Timer Resolution Default Arduino Core Usage Safe for Custom ISR?
Timer0 8-bit millis(), micros(), delay(), PWM on pins 5 & 6 No. Touching this breaks the Arduino timekeeping API.
Timer1 16-bit Servo.h library, PWM on pins 9 & 10 Yes. Best for low-frequency, high-precision tasks (1Hz to 10kHz).
Timer2 8-bit tone() function, PWM on pins 3 & 11 Yes. Best for high-frequency tasks (up to 31kHz), but limited range.

Assumption check: All math and register names in this guide assume a 16 MHz crystal oscillator (standard Uno/Nano) and the 5V logic ATmega328P-PU variant. If you are using a 3.3V 8MHz Pro Mini, all prescaler calculations below must be halved.

Build Spec: Non-Blocking Dual-Timer Metronome

We will build a circuit that uses Timer1 to blink an LED at exactly 0.5 Hz (1 full cycle every 2 seconds) using Clear Timer on Compare (CTC) mode, while Timer2 handles a 100 Hz heartbeat LED. The main loop() will remain entirely free to read a potentiometer and update a serial dashboard.

Difficulty Rating: 4/5 (Intermediate - requires bitwise register manipulation)
Estimated Time: 45 minutes
Target Board: Arduino Uno R3 Rev3 (ATmega328P)

Parts List

  • 1x Arduino Uno R3 (ATmega328P DIP-28 or SMD variant)
  • 2x 5mm Standard LEDs (Red and Green)
  • 2x 220Ω or 330Ω carbon film resistors (1/4W)
  • 1x 10kΩ linear potentiometer (B10K taper)
  • Breadboard and male-to-male jumper wires

Pin Mapping Table

Component Arduino Pin ATmega328P Port/Function Notes
Red LED (Anode via 220Ω) D9 PB1 / OC1A Hardware Timer1 Compare Output A
Green LED (Anode via 220Ω) D11 PB3 / OC2A Hardware Timer2 Compare Output A
Potentiometer Wiper A0 PC0 / ADC0 Analog read in main loop
Potentiometer Legs 5V & GND VCC & GND Standard voltage divider

Wiring and Register Configuration Steps

Safety & Hardware Note: While this is a 5V low-voltage build, never connect timer PWM outputs (pins 9, 10, 11, 3) directly to inductive loads like DC motors or relay coils without a flyback diode and a logic-level MOSFET driver. The ATmega328P GPIO pins can only source/sink 20mA safely (40mA absolute max).
  1. Disconnect Power: Unplug the USB cable from the Arduino Uno.
  2. Wire the LEDs: Connect the anode (long leg) of the Red LED to Pin 9 via a 220Ω resistor, and the Green LED to Pin 11 via a 220Ω resistor. Connect both cathodes to the GND rail.
  3. Wire the Potentiometer: Connect the left leg to 5V, the right leg to GND, and the center wiper to A0.
  4. Configure Timer1 (16-bit): We want a 1 Hz interrupt (which toggles the pin to create a 0.5 Hz square wave).
    • Formula: OCR1A = (Clock_Speed / (Prescaler * Target_Frequency)) - 1
    • Math: (16,000,000 / (1024 * 1)) - 1 = 15624.
    • Since 15624 fits inside a 16-bit register (max 65535), this works perfectly.
  5. Configure Timer2 (8-bit): We want a 100 Hz interrupt.
    • Math: (16,000,000 / (1024 * 100)) - 1 = 155.25. We truncate to 155.
    • Since 155 fits inside an 8-bit register (max 255), this is valid.
  6. Verify Connections: Use a multimeter in continuity mode to ensure the GND rail is properly bonded to the Arduino GND pin before applying power.

The Complete Compilable Code

This code targets the Arduino Uno R3 (ATmega328P). It bypasses the Arduino analogWrite() abstraction to set the hardware registers directly. It includes basic error handling to verify that interrupts are actually enabled before entering the main loop.

#include <avr/io.h>
#include <avr/interrupt.h>

// Pin Definitions
#define LED_TIMER1 9   // OC1A (Red LED)
#define LED_TIMER2 11  // OC2A (Green LED)
#define POT_PIN A0     // Analog Input

// Volatile flags for ISR communication
volatile bool timer1_flag = false;
volatile uint16_t isr_tick_count = 0;

void setup() {
  Serial.begin(115200);
  
  // Set pins as outputs
  pinMode(LED_TIMER1, OUTPUT);
  pinMode(LED_TIMER2, OUTPUT);
  
  // --- TIMER 1 CONFIGURATION (16-bit, CTC Mode) ---
  // Target: 1 Hz interrupt (15624 ticks at 1024 prescaler)
  cli(); // Disable global interrupts during setup
  
  TCCR1A = 0; // Clear control register A
  TCCR1B = 0; // Clear control register B
  TCNT1  = 0; // Initialize counter value to 0
  
  OCR1A = 15624; // Set compare match register for 1Hz increments
  TCCR1B |= (1 << WGM12); // Turn on CTC mode (Clear Timer on Compare)
  TCCR1B |= (1 << CS12) | (1 << CS10); // Set prescaler to 1024
  TIMSK1 |= (1 << OCIE1A); // Enable timer compare interrupt
  
  // --- TIMER 2 CONFIGURATION (8-bit, CTC Mode) ---
  // Target: 100 Hz interrupt (155 ticks at 1024 prescaler)
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  
  OCR2A = 155; // Set compare match register for 100Hz
  TCCR2A |= (1 << WGM21); // Turn on CTC mode
  TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20); // Prescaler 1024
  TIMSK2 |= (1 << OCIE2A); // Enable timer compare interrupt
  
  sei(); // Re-enable global interrupts
  
  // Error Handling / Verification
  if (SREG & (1 << SREG_I)) {
    Serial.println("SUCCESS: Global interrupts enabled.");
  } else {
    Serial.println("CRITICAL ERROR: Interrupts failed to enable. Check sei() macro.");
    while(1); // Halt execution
  }
}

// Timer1 ISR: Fires at 1 Hz
ISR(TIMER1_COMPA_vect) {
  // Toggle Red LED directly via PORT register for speed
  PORTB ^= (1 << PB1); 
  timer1_flag = true;
}

// Timer2 ISR: Fires at 100 Hz
ISR(TIMER2_COMPA_vect) {
  isr_tick_count++;
  // Toggle Green LED every 50 ticks (2 Hz visual blink from 100Hz ISR)
  if (isr_tick_count >= 50) {
    PORTB ^= (1 << PB3);
    isr_tick_count = 0;
  }
}

void loop() {
  // Main loop is entirely free for non-blocking tasks
  if (timer1_flag) {
    timer1_flag = false;
    int potVal = analogRead(POT_PIN);
    Serial.print("Timer1 Tick | Pot Value: ");
    Serial.println(potVal);
  }
}

Debugging: 'multiple definition of __vector_11' and Timer Faults

When working with Arduino timers, the most infamous compiler error you will encounter is:

multiple definition of `__vector_11'
collect2: error: ld returned 1 exit status

On the ATmega328P, __vector_11 is the internal interrupt vector name for TIMER1_COMPA_vect. This error means two different pieces of code are trying to claim the exact same hardware interrupt.

Ranked Causes and Fixes

  1. Library Conflict (Most Likely): You included a library that uses Timer1 under the hood. The classic culprits are Servo.h, IRremote.h, and TimerOne.h.
    Fix: Check the documentation for your libraries. If you need IR decoding and custom Timer1 interrupts simultaneously, you must edit the library's source code to reassign it to Timer2, or switch to an ESP32 which has 4 hardware timers.
  2. Duplicate ISR Definitions: You accidentally pasted ISR(TIMER1_COMPA_vect) twice in your sketch, or defined it in both the .ino file and an included .cpp file.
    Fix: Use your IDE's search function to ensure the vector name only appears once as an ISR definition.
  3. Wrong Board Selected in IDE: You wrote code for the Uno (ATmega328P) but the IDE is set to compile for the Mega2560. Vector numbers shift between chips.
    Fix: Go to Tools > Board and ensure 'Arduino Uno' is selected.

The First Three Things to Check When a Timer Fails Silently

If the code compiles but your LED doesn't blink or your serial output freezes:

  1. Did you call sei()? Without the global interrupt enable macro at the end of your setup, the silicon will count ticks but never execute the ISR.
  2. Is your prescaler math overflowing the register? If you try to set OCR2A = 30000 on an 8-bit timer, it will truncate to 8 bits (30000 % 256 = 48), resulting in a wildly incorrect frequency. Always verify your math against the bit-width limit.
  3. Are you doing too much inside the ISR? An ISR must execute in microseconds. If you put Serial.println() or delay() inside an ISR, the microcontroller will lock up. Set a volatile flag and handle the logic in the main loop().

Extending and Simplifying the Build

To Simplify: If bitwise register manipulation (TCCR, OCR) feels too opaque, use the TimerOne library. It wraps the ATmega328P Timer1 registers into simple functions like Timer1.initialize(1000000) and Timer1.attachInterrupt(callback). Note that this library abstracts away the CTC mode math but still claims __vector_11.

To Extend: If your project requires more than two independent timers (e.g., driving three stepper motors at different microstepping intervals), the ATmega328P is the wrong silicon. Upgrade to the ESP32-WROOM-32. The ESP32 features four 64-bit hardware timers accessible via the Arduino IDE using hw_timer_t. Because the ESP32 runs an RTOS, timer interrupts are handled via timerAttachInterrupt() and do not suffer from the same library vector collisions as the AVR architecture.

Arduino Timers FAQ

Can I use Arduino timers to generate PWM on any digital pin?

No. Hardware timer PWM outputs are hardwired in silicon to specific pins. On the Uno R3, Timer0 handles pins 5 and 6, Timer1 handles pins 9 and 10, and Timer2 handles pins 3 and 11. If you need high-speed PWM on Pin 12, you cannot use a hardware timer directly; you must use a software-bit-banging library (like SoftPWM), which consumes CPU cycles and introduces jitter, or use an external hardware PWM driver IC like the PCA9685 over I2C.

Why does my delay() stop working when I configure a timer?

If your delay() and millis() functions suddenly fail, return 0, or cause the program to hang, you have accidentally modified Timer0. The Arduino core library relies on Timer0's overflow interrupt to increment the internal millisecond counter. Never alter TCCR0A, TCCR0B, or TIMSK0 unless you are intentionally writing a bare-metal program that does not rely on the Arduino API.

How do I calculate the OCR value for a specific interrupt frequency?

Use the CTC (Clear Timer on Compare) formula: OCR = (Clock_Speed / (Prescaler * Target_Frequency)) - 1. The ATmega328P runs at 16,000,000 Hz. Available prescalers for Timer1 are 1, 8, 64, 256, and 1024. Always choose the smallest prescaler that keeps your resulting OCR value below the register's maximum limit (65535 for 16-bit, 255 for 8-bit) to maintain the highest possible timing resolution. For a deep dive into AVR clock math, the official Microchip ATmega328P Datasheet (Section 15.11) is the definitive authority.