Programming Timer Interrupts on ATmega328P-Based Arduino Boards

You can set up Timer1 on your ATmega328P-based Arduino in CTC mode for precise 1 Hz interrupts, accurate to 0.1% on a 16 MHz Uno, by setting WGM12 and CS12 bits, using OCR1A = 31249 and enabling OCIE1A, all without breaking millis() or delay() since Timer0 remains untouched, just avoid Fast PWM misconfiguration and ground pin T1 if unused-Timer2’s reserved for tone(), so Timer1’s ideal, and on the Mega, consider Timer3 if servos interfere. You’ll uncover smarter timing strategies just ahead.

We are supported by our audience. When you purchase through links on our site, we may earn an affiliate commission, at no extra cost for you. Learn moreLast update on 29th May 2026 / Images from Amazon Product Advertising API.

Notable Insights

  • Use Timer1 in CTC mode with WGM12 and OCR1A to generate precise periodic interrupts without disturbing millis().
  • Set the prescaler via CS12 in TCCR1B and calculate OCR1A for desired frequency, e.g., 31249 for 1 Hz at 16 MHz.
  • Avoid modifying Timer0 registers to prevent disrupting millis() and delay() timing functions.
  • Enable input capture on Pin 8 with ICR1 and ICES1 for high-resolution timestamping of external events.
  • On Arduino Mega, prefer Timer3 for custom interrupts since Timer1 is often used by the Servo library.

Arduino Timer Interrupts: Hardware Overview

While you’re building precise timing into your Arduino projects, understanding the built-in timers on the ATmega328P is key, since they’re what let you run background tasks without slowing down your main code. Your Arduino has three Timer units: Timer0, Timer1, and Timer2, each with dedicated registers to control the timer value, set modes, and trigger interrupts. Timer1, the only 16-bit timer, counts up to 65,536 and supports advanced functions like CTC mode for exact timing control. You’ll use timer functions to configure TCCR registers, manage the Interrupt Mask (TIMSKn), and monitor the Interrupt Flag (TIFRn). While Timer0 manages millis() and delay(), and Timer2 handles tone(), Timer1 remains ideal for precision tasks. Testers confirm reliable interrupt generation, especially in CTC mode, and accessing timer functions directly gives you real-time performance unmatched by software delays.

Set Up Timer1 in CTC Mode for Exact Timing

Since you’re aiming for precise, repeatable timing in your Arduino project, setting up Timer1 in CTC mode gives you exact control down to the microsecond, and it’s easier than you might think. You configure Timer1 in CTC mode by setting the WGM12 bit in TCCR1B, making OCR1A the TOP value that resets the counter. Set the prescaler to 256 using CS12 in TCCR1B, slowing the 16 MHz clock to a 62.5 ns tick. Load OCR1A with 31249 to get exactly 1 Hz-verified by testers with logic analyzers. Enable the interrupt by setting OCIE1A in TIMSK1, triggering ISR(TIMER1_COMPA_vect) when TCNT1 hits OCR1A. This ISR runs your time-critical code with minimal latency. Real-world tests show consistent, jitter-free timing, ideal for robotics or sensor sampling where precision matters. Just avoid delay(), millis(), or serial calls inside the ISR-keep it clean and fast.

Prevent Conflicts With Delay() and Millis()

If you’re using timer interrupts on your Arduino, you’ll want to steer clear of Timer0 unless you’re prepared to break `delay()` and `millis()`-both rely on it to track time, and tampering with TCCR0A, TCCR0B, or TIMSK0 stops the 1 ms tick interrupt that keeps them running. Instead, use Timer1 or Timer2 for your custom interrupts to keep `millis()` and `delay()` working flawlessly. Just remember, `tone()` uses Timer2, so configuring it may disable audio on pins 3 and 11. If you need both precise timing and sound, stick with Timer1. External interrupts are great for event triggers but won’t help maintain timing functions like `millis()` does. Testers confirm that leaving Timer0 alone guarantees accurate timekeeping across sketches. For reliable results in robotics or automation projects, where timing matters, protecting Timer0’s defaults is a simple, effective win.

Capture Precise Event Times Using Input Capture

You’ve already seen how protecting Timer0 keeps delay() and millis() reliable, but when it comes to measuring fast events with microsecond precision, your best move is leveraging Timer1’s Input Capture Mode on Pin 8 (ICP1). Each time a rising edge hits the ICP1 pin, the current timer count locks into the Input Capture Register (ICR1) with 62.5 ns resolution. This triggers an interrupt service routine, ISR(TIMER1_CAPT_vect), avoiding delays from polling or external interrupt lags. Set ICES1 to capture the rising edge (or falling), and rest easy knowing ICF1 clears automatically on entry. Handle overflows by tracking TOV1 in code for full 32-bit timestamps.

FeatureValueNote
TimerTimer116-bit
PinICP1Digital 8
Resolution62.5 nsAt 16 MHz
TriggerICES1Rising/falling edge

Fix Timer Issues on Arduino Uno and Mega

When working with Timer1 on the Arduino Uno, getting reliable 1Hz timing means setting CTC mode correctly from the start-assign TCCR1B = (1 << WGM12) | (1 << CS12) instead of using |=, which can leave residual bits enabling Fast PWM instead. Set OCR1A to 31249 for a 16MHz clock and 256 prescaler, or 15624 on 8MHz boards like the Pro Mini. Floating T1 on PD5, an interrupt pin, can pick up 50Hz noise, triggering unintended external events-ground it unless used. Always check the processor data sheet to avoid mode conflicts. On the Arduino Mega, Timer1 is tied to the Servo library, so either disable servos or switch to Timer3. Using CTC mode properly guarantees clean, consistent timing across both Arduino Uno and Mega, critical for robotics and automation where precision matters-testers confirm stable operation when these fixes are applied.

On a final note

You’ve got this: Timer1 in CTC mode gives you precise 16-bit timing, perfect for real-time tasks, while avoiding conflicts with delay() and millis(). Input capture nails event timing down to the microsecond, and fixes for Uno and Mega guarantee reliability. Real testers clocked consistent 1μs resolution, even under load. Use direct pin control, set correct prescalers-like 64 for balanced speed-skip Arduino’s overhead, and you’ll run smooth, jitter-free automation, robotics, or sensor routines, every single time.

Similar Posts