Video summary

Level Up Your Arduino Code: Timer Interrupts

Main summary

Key takeaways

Educational

Main ideas / concepts

  • Timer interrupts let an ATmega328P microcontroller run tasks at precise, regular times by using its hardware timers instead of software timing loops.
  • Timers are like kitchen timers: they keep counting in the background while the CPU does other work, then interrupt the CPU when they reach certain conditions.
  • The ATmega328P (used in Arduino Uno) provides three timers:
    • Timer0 → TCNT0 (8-bit)
    • Timer1 → TCNT1 (16-bit, uses 2 bytes)
    • Timer2 → TCNT2 (8-bit)
  • Timers count based on the system clock (typically 16 MHz on Arduino Uno/ATmega328P).
  • A prescaler can divide the clock frequency to slow timer counting.

How timers count (system clock + prescaler)

  • Each timer can be driven by the system clock or a divided version.
  • Without prescaler:
    • The timer register increments once per system clock pulse.
    • For 16 MHz: clock period = 1 / 16,000,000 = 62.5 ns
  • With prescaler:
    • The timer increments less frequently (e.g., every 8, 64, 256, or 1024 clock cycles depending on configuration).
    • Changing the prescaler changes the rate at which timer count increases.

Timer size and rollover (important for timing)

  • Timer0 and Timer2 are 8-bit:
    • Count 0 → 255, then roll over to 0
  • Timer1 is 16-bit:
    • Count 0 → 65,535, then roll over to 0

How interrupts are generated

Each timer can generate interrupts in multiple ways:

  • Compare match interrupt
    • Set an output compare value in a register (e.g., OCR1A).
    • When the timer count equals that value, the corresponding compare match interrupt fires.
  • Overflow interrupt
    • Fires when the timer rolls over from its maximum back to 0.
  • Input capture interrupt (Timer1 only)
    • When a signal level changes on a dedicated input pin (e.g., ICP1), Timer1 captures the counter value into ICR1 and triggers an input capture interrupt.
    • Useful for measuring time between pulses or frequency.

Arduino compatibility warning (what breaks if you change timers)

Several Arduino functions rely on timers:

  • Using Timer0 can break delay(), millis(), and micros().
  • Using Timer1 or Timer2 can interfere with Servo and tone().
  • analogWrite() uses all three timers on the Uno; which timer it uses depends on the pin.

Example guidance:

  • If manually using Timer1, avoid analogWrite() on pins 9 and 10 (commonly tied to Timer1 on Uno).

Method / instruction set: Timer1 “Blinky” using compare match interrupts

Goal

  • Blink the onboard LED (Arduino pin 13, which corresponds to ATmega328P port B bit 5 / PB5) at 1 Hz (500 ms on, 500 ms off) using Timer1 compare match interrupts.

Step-by-step setup

  • Hardware target
    • Arduino board with ATmega328P (e.g., Uno or RedBoard).
  • LED mapping
    • Arduino pin 13 = ATmega328P Port B, Pin 5 → use PB5.

1) Replace pinMode() / digitalWrite() with direct register I/O

  • Set LED pin as output (LED is on Port B):
    • DDRB |= (1 << LED_PIN);

2) Demonstrate baseline blinking (non-interrupt version)

  • In loop():
    • Toggle the LED by flipping the bit in PORTB:
      • PORTB ^= (1 << LED_PIN);
    • Then delay:
      • delay(500);

This confirms basic behavior before moving to interrupts.

3) Configure Timer1 registers for interrupts

  • Use Timer1 (chosen because it can count high and the video notes it’s not used by delay()/millis()/micros()).
  • Datasheet-based actions:

    • Reset timer control register A:
      • TCCR1A = 0;
    • Set prescaler (desired timing):
      • Choose prescaler 256
      • Set CS12 CS11 CS10 = 100
    • Load timer counter:
      • TCNT1 = t1Load; (where t1Load = 0)
    • Set compare match value:
      • OCR1A = t1Comp; (where t1Comp = 31250)
    • Enable the Timer1 Compare Match A interrupt:
      • TIMSK1 = (1 << OCIE1A);
    • Enable global interrupts:
      • sei();

4) Implement the ISR (interrupt service routine)

  • Enable/define ISR for compare match A:
    • ISR(TIMER1_COMPA_vect) { ... }
  • Inside the ISR:

    • Toggle LED:
      • PORTB ^= (1 << LED_PIN);
    • Reset timer counter (in the non-CTC version):
      • TCNT1 = t1Load;

5) Determine the compare value for 500 ms timing

  • Clock period with no prescaler:
    • 1 / 16 MHz = 62.5 ns
  • Convert 0.5 seconds to counts:
    • No prescaler would require too many counts for 16-bit.
  • Prescaler experiments:

    • Prescaler 8 → 1,000,000 (too big)
    • Prescaler 64 → 125,000 (too big)
    • Prescaler 256 → 31,250 (fits)
    • Prescaler 1024 → 7,812.5 (not a whole number → slight timing error)
  • Final choice:

    • Prescaler = 256
    • OCR1A = 31250

6) Main loop behavior when using interrupts

  • Remove the LED toggle from loop().
  • Keep something harmless so the CPU appears active, e.g.:
    • delay(500); inside loop() (LED timing is handled by interrupts).

Expected result

  • LED continues to blink at ~1 Hz.
  • The video notes an oscilloscope check showing ~1 second between rising edges.

Optimization method: Use CTC mode to reduce ISR work

What changes

  • Switch Timer1 to CTC (Clear Timer on Compare) mode:
    • Hardware automatically resets the timer to 0 at each compare match.
  • Result:
    • The ISR no longer needs to manually reset TCNT1.

Step-by-step changes for CTC mode

  • Set CTC mode bits in TCCR1B:
    • Configure mode 4
    • Do:
      • Clear WGM13
      • Set WGM12
  • Modify ISR:
    • Remove the line resetting TCNT1 = t1Load;
  • Expected behavior:
    • Still blinks at ~1 Hz, but with a slightly shorter ISR.

Why it matters

  • CTC saves one instruction.
  • Shorter ISRs reduce latency and timing jitter.

Speakers / sources featured

  • Video creator / presenter: The narrator introduces and explains timer interrupts and provides the code example (no specific name given in the subtitles).
  • ATmega328P documentation (datasheet): Referenced as the source for register details and timer mode selection.
  • Background music: Mentioned as [background music] (no specific track/artist named).

Original video