Generating PDF…
← Back

Understanding Timers, Counters, PWM and Measuring Temporal Events

DE6417 Microcontrollers 2, Week 2, Session 2

Deep dive into hardware timing mechanisms on the ATmega328P

Learning Objectives

  • Understand what timers and counters are in microcontrollers
  • Learn about the three timers on the ATmega328P (Arduino)
  • Understand how timers generate interrupts
  • Learn about PWM (Pulse Width Modulation)
  • Understand timer modes: Normal, CTC, Fast PWM, Phase Correct
  • Read and interpret timer-related registers
  • Recognize timer resource conflicts with Arduino APIs

Review: What is an Interrupt?

An interrupt is an external or internal event that:

  • Stops the processor from executing its current task
  • Jumps to an Interrupt Service Routine (ISR)
  • Performs some function/runs code
  • Returns to where it was interrupted
Arduino Interrupts: External interrupts, Pin Change interrupts, Timer interrupts, and many peripheral interrupts.

What is a Timer/Counter?

A timer is essentially an extremely sophisticated counter implemented in hardware.

  • Counts up or down based on clock pulses
  • Can generate interrupts at specific count values
  • Can toggle output pins automatically
  • Can measure external events (frequency, duration)
  • Operates independently of the CPU
Clock Input
Prescaler (÷1, ÷8, ÷64...)
Counter Register
Compare Match → Interrupt/Pin Toggle

Why Use Hardware Timers?

Remember: A microcontroller = microprocessor + peripherals


Software Approach Hardware Timer Approach
CPU busy counting/timing CPU free for other tasks
Blocking delays Non-blocking operation
Inaccurate timing Precise, clock-accurate timing
Can't multitask Parallel hardware operation
Key Insight: Set up the timer once, and it runs independently - like having parallel hardware!

ATmega328P Timer Overview

Timer Bits PWM Support Special Features Arduino Usage
Timer 0 8-bit Yes Basic timer delay(), millis(), micros()
Timer 1 16-bit Yes Input capture, More resolution Servo library, tone()
Timer 2 8-bit Yes Asynchronous operation tone() on some pins
⚠️ Warning: Timer 0 is used by delay(), millis(), micros() - modifying it will break these functions!

Timer Resource Conflicts

Arduino API functions use specific timers. Be careful of conflicts!

Function Timer Used Affected Pins
delay(), millis(), micros() Timer 0 Pin 5, 6
analogWrite() on pins 9, 10 Timer 1 Pin 9, 10
analogWrite() on pin 11 Timer 2 Pin 11
tone() Timer 1 or 2 Varies
Servo library Timer 1 Pin 9, 10
Tip: If you use a timer directly, avoid using Arduino functions that depend on it!

Timer Output Pins on Arduino Uno

Each timer can control specific pins only:

Timer Output A Output B
Timer 0 OC0A → Pin 6 (PD6) OC0B → Pin 5 (PD5)
Timer 1 OC1A → Pin 9 (PB1) OC1B → Pin 10 (PB2)
Timer 2 OC2A → Pin 11 (PB3) OC2B → Pin 3 (PD3)
Important: You cannot output timer waveforms on arbitrary pins! Only the designated OCnX pins.
ATmega328P Pin 6 (OC0A) Pin 5 (OC0B) Pin 9 (OC1A) Pin 10 (OC1B) Pin 11 (OC2A) Pin 3 (OC2B) Timer 0 Timer 1 Timer 2

How Timers Work

Basic timer operation:

  1. Clock drives the counter
  2. Prescaler divides clock frequency
  3. Counter increments each clock tick
  4. When counter = Compare Value:
    • Generate interrupt
    • Toggle output pin
    • Reset counter
  5. Repeat continuously
Time → Count 0 255 Compare Value Counter Value Current Count Compare Match

⟳ Timer counts up → reaches compare value → triggers interrupt → resets to 0

The Prescaler

The prescaler divides the system clock to slow down counting:

ATmega328P prescaler options for Timer 1:

CS12CS11CS10PrescalerTimer Freq @ 16MHz
000Timer OFF-
001116 MHz
01082 MHz
01164250 kHz
10025662.5 kHz
101102415.625 kHz
16 MHz System Clock Prescaler ÷1, ÷8, ÷64... Timer Clk 16 MHz (fast): ÷64 = 250 kHz: ÷1024 = 15.6 kHz:

Compare Match Operation

When the counter equals the compare value:

  • Interrupt flag is set
  • If enabled, ISR is called
  • Output pin can be set, cleared, or toggled
  • Counter may be reset to zero
Key Registers:
  • TCNT1 - Timer Counter (current count)
  • OCR1A/OCR1B - Output Compare Register (compare value)

Timer Terminology

Term Definition Value (16-bit)
BOTTOM Counter reaches its minimum value 0x0000
MAX Counter reaches its maximum value 0xFFFF (65,535)
TOP Highest value in count sequence (user-defined) OCR1A, ICR1, or fixed values
Fixed TOP values: 0x00FF (255), 0x01FF (511), 0x03FF (1023)

Note: TOP ≠ MAX! TOP is configurable; MAX is the absolute maximum the register can hold.

Timer Modes of Operation

Mode Description Use Case
Normal Counts from 0 to MAX, then overflows to 0 Simple counting, timing measurements
CTC Clear Timer on Compare match Generating interrupts at precise intervals
Fast PWM Single-slope PWM (count up only) High-frequency PWM, LED dimming
Phase Correct PWM Dual-slope PWM (count up and down) Motor control, audio, avoiding phase errors

Normal Mode

The simplest mode of operation:

  • Counting direction: Always UP
  • No automatic counter clear
  • Counter overflows at MAX (0xFFFF)
  • Restarts at BOTTOM (0x0000)
  • Sets TOV1 (Timer Overflow Flag)
WGM bits: WGM13:0 = 0000
Time Count MAX (0xFFFF) Overflow

CTC Mode (Clear Timer on Compare)

Counter clears when it matches OCR1A or ICR1:

  • Count from 0 to TOP (OCR1A value)
  • When TCNT1 = OCR1A → clear to 0
  • Generates Output Compare interrupt
  • Can toggle OC1A pin on each match
  • Perfect for precise timing intervals
WGM bits: WGM13:0 = 0100 (OCR1A as TOP)
Time TOP (OCR1A) OC1A Pin:

CTC Mode: Frequency Calculation

Rearranging to find OCR1A:

Example: Generate 1 Hz interrupt (1 second period)

  • fclk = 16,000,000 Hz
  • Prescaler = 1024
  • Desired frequency = 1 Hz
Result: Set OCR1A = 15624 for a 1 Hz interrupt with prescaler 1024
Note: This formula gives the interrupt frequency (how often the timer resets). In slide 15, the CTC output formula has an extra factor of 2 in the denominator because the OC1A pin only toggles on each compare match — it takes two toggles (HIGH → LOW → HIGH) to complete one full output cycle. So the pin's output frequency is half the interrupt frequency.

Pulse Width Modulation (PWM)

PWM creates an analog-like signal using digital output:

  • Rapidly switching between HIGH and LOW
  • Duty Cycle = % of time signal is HIGH
  • Average voltage proportional to duty cycle
Applications: LED dimming, motor speed control, audio generation, DAC simulation

PWM Waveform Anatomy

25% Duty Cycle: Low average voltage 50% Duty Cycle: Medium average voltage 75% Duty Cycle: High average voltage Period (T)

Fast PWM Mode

Single-slope operation:

  • Counter counts UP only (0 → TOP)
  • Clears to BOTTOM at TOP
  • Highest PWM frequency possible
  • Output set/cleared at compare match
CTC vs Fast PWM — What's the difference?
  • CTC mode toggles the output pin on each compare match. The counter resets at TOP (OCR1A), and the pin flips between HIGH and LOW — producing a 50% duty cycle square wave. You control frequency but not duty cycle.
  • Fast PWM mode sets the pin at BOTTOM and clears it at the compare match (or vice versa). The counter always runs from 0 to TOP, so you control both frequency and duty cycle by adjusting where the compare match occurs relative to TOP.
  • In short: CTC = frequency generation (fixed 50% duty), Fast PWM = duty cycle control (variable pulse width).
TOP OCR1A PWM Output:

Phase Correct PWM Mode

Dual-slope operation:

  • Counter counts UP then DOWN
  • Symmetric output pulses
  • Lower frequency than Fast PWM
  • No phase discontinuities when changing duty
  • Better for motor control & audio
TOP OCR1A PWM Output:

Fast PWM vs Phase Correct PWM

Feature Fast PWM Phase Correct PWM
Count Direction UP only (single slope) UP then DOWN (dual slope)
Frequency Higher (2× phase correct) Lower
Resolution Same Same
Phase Error Can have discontinuities Symmetric, no phase error
Best For LED dimming, power regulation Motor control, audio, precision

Timer 1 Block Diagram

Key components:

  • Clock Select - Choose prescaler
  • TCNT1 - 16-bit counter register
  • OCR1A/B - Compare registers
  • ICR1 - Input capture register
  • Waveform Generator - Controls output pins
  • Comparators - Hardware "if statements"
Timer 1 Block Diagram

Key Registers for Timer 1

Data Registers

Register Purpose Details
TCNT1 Current count value (16-bit) Readable/writable. Holds the live counter value (0–65535). Write to it to preset the counter; read it to snapshot the current count.
OCR1A Compare value, Channel A When TCNT1 == OCR1A, a compare-match event fires. Used as TOP in CTC & some PWM modes. Controls duty cycle on OC1A (Pin 9).
OCR1B Compare value, Channel B Independent second compare channel. Controls duty cycle on OC1B (Pin 10). Cannot define TOP.
ICR1 Input Capture Register Hardware copies TCNT1 into ICR1 on an edge at the ICP1 pin (Pin 8). Also usable as TOP in some PWM modes.

Control & Interrupt Registers

Register Purpose Details
TCCR1A Control Register A Sets compare-output mode (COM bits) and lower WGM bits. Determines what happens to OC1A/OC1B pins on a match.
TCCR1B Control Register B Sets clock source/prescaler (CS bits), upper WGM bits, and input-capture edge/noise settings.
TIMSK1 Interrupt Mask Bit-flags to enable specific interrupts: overflow (TOIE1), compare-match A/B (OCIE1A/B), input capture (ICIE1).
TIFR1 Interrupt Flags Hardware sets these when an event occurs. Cleared automatically when the ISR runs, or write a 1 to clear manually.
// --- Quick-reference examples ---

  // Read the current timer count
  uint16_t now = TCNT1;

  // Preset the counter to a known value
  TCNT1 = 0;

  // Set a compare value (1 Hz with prescaler 1024)
  OCR1A = 15624;

  // Set a second compare for 50 % duty
  OCR1B = 7812;

  // Read the captured timestamp after
  // an external edge on ICP1 (Pin 8)
  uint16_t stamp = ICR1;

  // Configure CTC mode, prescaler 1024
  TCCR1A = 0;                        // WGM11:10 = 00
  TCCR1B = (1 << WGM12)              // CTC mode
         | (1 << CS12) | (1 << CS10);// /1024

  // Enable compare-match A interrupt
  TIMSK1 = (1 << OCIE1A);

  // Check if overflow happened (polling)
  if (TIFR1 & (1 << TOV1)) {
    TIFR1 = (1 << TOV1); // clear flag
  }
Line-by-line explanation:
  • uint16_t now = TCNT1; — Read the timer's current count (like glancing at a stopwatch).
  • TCNT1 = 0; — Reset the counter back to zero.
  • OCR1A = 15624; — Tell the timer: "trigger an event when you count to 15624." With a 1024 prescaler and 16 MHz clock, this gives exactly 1 second.
  • OCR1B = 7812; — A second, independent trigger point (halfway to 15624 = 50% duty cycle on Channel B).
  • uint16_t stamp = ICR1; — Read the timestamp that was automatically saved when an external signal edge hit Pin 8.
  • TCCR1A = 0; — Clear Control Register A (sets lower waveform bits to 00).
  • TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10); — Two things at once: enable CTC mode (WGM12 bit) and set the prescaler to 1024 (CS12 + CS10 bits). The timer now counts in CTC mode, ticking once every 1024 CPU cycles.
  • TIMSK1 = (1 << OCIE1A); — Enable the compare-match A interrupt. Now when TCNT1 reaches OCR1A, the CPU will automatically jump to the ISR.
  • if (TIFR1 & (1 << TOV1)) — Check if the overflow flag is set (polling instead of using an interrupt).
  • TIFR1 = (1 << TOV1); — Clear the overflow flag by writing a 1 to it (this is how AVR flag clearing works — you write 1, not 0).

Register Walkthrough – Scenarios

Now that we know the registers, let's see how they work together in three common real-world situations. Each scenario shows a different Timer 1 capability: generating timed interrupts (CTC mode), measuring an external signal (Input Capture), and controlling two outputs at different duty cycles (Fast PWM). Pay attention to which registers are configured and why.

Scenario 1 — Periodic 10 ms Interrupt (100 Hz)

OCR1A = (16 000 000 / 64 / 100) − 1 = 2499
Prescaler 64, CTC mode → compare-match every 10 ms.
Time 2499 10 ms ISR ISR ISR ISR

Scenario 2 — Measure Pulse Width

Configure input capture on rising edge, read ICR1, then switch to falling edge and read again. The difference × tick period = pulse width.
Input: Rising edge ICR1 → riseTime Falling edge ICR1 → fallTime Pulse Width = (fallTime − riseTime) × tick

Scenario 3 — Two Independent PWM Channels

Use Fast PWM mode 14 (ICR1 = TOP) to set frequency, then write different duty-cycle values to OCR1A and OCR1B for pins 9 & 10 independently.
Pin 9 (OCR1A) 75% ON Pin 10 (OCR1B) 37.5% ON Period (ICR1 = TOP = 39999)
⚠️ 16-bit access: TCNT1, OCR1A/B, and ICR1 are 16-bit. The compiler handles byte ordering, but in ISRs or inline assembly you must write the high byte first and read the low byte first (the TEMP register mechanism).
// Scenario 1 – 100 Hz interrupt
  cli();
  TCCR1A = 0;
  TCCR1B = (1 << WGM12)            // CTC
         | (1 << CS11) | (1 << CS10); // /64
  OCR1A  = 2499;
  TIMSK1 = (1 << OCIE1A);
  sei();

  // Scenario 2 – Pulse width via Input Capture
  TCCR1A = 0;
  TCCR1B = (1 << ICES1)  // rising edge
         | (1 << CS11);  // /8  → 0.5 µs tick
  TIMSK1 = (1 << ICIE1); // capture interrupt

  volatile uint16_t riseTime, fallTime;
  ISR(TIMER1_CAPT_vect) {
    if (TCCR1B & (1 << ICES1)) {
      riseTime = ICR1;
      TCCR1B &= ~(1 << ICES1); // next: falling
    } else {
      fallTime = ICR1;
      TCCR1B |= (1 << ICES1);  // next: rising
    }
  }
  // pulseWidth_us = (fallTime-riseTime)*0.5;

  // Scenario 3 – Dual PWM on Pins 9 & 10
  TCCR1A = (1 << COM1A1) | (1 << COM1B1)
         | (1 << WGM11);       // Fast PWM mode 14
  TCCR1B = (1 << WGM13) | (1 << WGM12)
         | (1 << CS11);        // /8
  ICR1   = 39999;  // TOP → 50 Hz (servo)
  OCR1A  = 3000;   // Pin 9 duty
  OCR1B  = 1500;   // Pin 10 duty

Key Registers for Timer 1

Register Name Purpose
TCNT1 Timer/Counter 1 Current count value (16-bit)
OCR1A Output Compare Register 1A Compare value for channel A
OCR1B Output Compare Register 1B Compare value for channel B
ICR1 Input Capture Register 1 Captures timer value on external event
TCCR1A Timer/Counter Control Register A Waveform generation, compare output modes
TCCR1B Timer/Counter Control Register B Clock select, WGM bits
TIMSK1 Timer Interrupt Mask Enable/disable interrupts
TIFR1 Timer Interrupt Flag Interrupt status flags

TCCR1A Register

Timer/Counter1 Control Register A

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
COM1A1COM1A0COM1B1COM1B0--WGM11WGM10

COM1A[1:0] / COM1B[1:0] - Compare Output Mode:

COM1x1COM1x0Description (Non-PWM)
00Normal port operation, OC1x disconnected
01Toggle OC1x on Compare Match
10Clear OC1x on Compare Match (set low)
11Set OC1x on Compare Match (set high)

TCCR1B Register

Timer/Counter1 Control Register B

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
ICNC1ICES1-WGM13WGM12CS12CS11CS10

CS1[2:0] - Clock Select (Prescaler):

CS12CS11CS10Description
000No clock (Timer stopped)
001clk/1 (No prescaling)
010clk/8
011clk/64
100clk/256
101clk/1024

Waveform Generation Mode (WGM)

WGM bits are split across TCCR1A and TCCR1B:

ModeWGM13WGM12WGM11WGM10Mode NameTOP
00000Normal0xFFFF
40100CTC (OCR1A)OCR1A
50101Fast PWM, 8-bit0x00FF
60110Fast PWM, 9-bit0x01FF
70111Fast PWM, 10-bit0x03FF
141110Fast PWM (ICR1)ICR1
151111Fast PWM (OCR1A)OCR1A
For CTC mode: Set WGM13:0 = 0100 → WGM12 = 1, others = 0

Timer Interrupt Registers

TIMSK1 - Timer Interrupt Mask Register

BitNameDescription
5ICIE1Input Capture Interrupt Enable
2OCIE1BOutput Compare B Match Interrupt Enable
1OCIE1AOutput Compare A Match Interrupt Enable
0TOIE1Timer Overflow Interrupt Enable

TIFR1 - Timer Interrupt Flag Register

BitNameDescription
5ICF1Input Capture Flag
2OCF1BOutput Compare B Match Flag
1OCF1AOutput Compare A Match Flag
0TOV1Timer Overflow Flag

Setting Up Timer 1 CTC Interrupt

Here we configure Timer 1 to count up to a specific value and then automatically reset to 0, triggering an interrupt each time it matches. This creates a precise, repeating time interval — like a programmable alarm clock inside the chip.

Steps to configure a timer interrupt:

  1. Clear timer control registers
  2. Set CTC mode (WGM12 = 1)
  3. Set prescaler value
  4. Set compare match value (OCR1A)
  5. Enable compare match interrupt
  6. Enable global interrupts
Understanding |= (1 << BIT_NAME)

This pattern is how we set a single bit inside a register without changing the other bits. Let's break it down step by step:

  1. WGM12 is just a constant = 3 (it's bit number 3 in TCCR1B).
  2. (1 << WGM12) means "shift the number 1 left by 3 positions":
    00000001 → shift left 3 → 00001000  (this creates a mask with only bit 3 set)
  3. TCCR1B |= 00001000 means "OR the register with this mask":
    TCCR1B: 00000000
    mask:    00001000
    result:  00001000 — bit 3 is now ON, all others unchanged.

Why |= and not =? Using = would overwrite all 8 bits. Using |= (OR-equals) only turns ON the bits in the mask while leaving everything else untouched.

void setup() {
  // Disable interrupts during setup
  cli();
  
  // Reset Timer 1 control registers
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;  // Reset counter
  
  // Set compare match value for 1 Hz
  // OCR1A = (16MHz / (prescaler * freq)) - 1
  // OCR1A = (16000000 / (1024 * 1)) - 1 = 15624
  OCR1A = 15624;
  
  // Set CTC mode (WGM12 = 1)
  TCCR1B |= (1 << WGM12);
  
  // Set prescaler to 1024
  TCCR1B |= (1 << CS12) | (1 << CS10);
  
  // Enable compare match interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  // Enable global interrupts
  sei();
}

Timer ISR Example

Complete example: Blink LED at 1 Hz using Timer 1

  • ISR is called every 1 second
  • Toggle LED state in ISR
  • No code needed in loop()
ISR Macro: Use ISR(TIMER1_COMPA_vect) for Timer 1 Compare Match A interrupt
#define LED_PIN 13

volatile bool ledState = false;

// Timer 1 Compare Match A ISR
ISR(TIMER1_COMPA_vect) {
  ledState = !ledState;
  digitalWrite(LED_PIN, ledState);
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  
  cli();  // Disable interrupts
  
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  
  OCR1A = 15624;  // 1 Hz @ 1024 prescaler
  
  TCCR1B |= (1 << WGM12);  // CTC mode
  TCCR1B |= (1 << CS12) | (1 << CS10);  // 1024
  TIMSK1 |= (1 << OCIE1A);  // Enable int
  
  sei();  // Enable interrupts
}

void loop() {
  // Nothing here - timer handles everything!
}

Calculating Timer Values

Given: fCPU = 16 MHz, Desired interrupt rate

Example Calculations:

Desired Frequency Prescaler OCR1A Value Formula
1 Hz (1 sec) 1024 15624 (16M / 1024 / 1) - 1
10 Hz (100 ms) 1024 1561 (16M / 1024 / 10) - 1
100 Hz (10 ms) 64 2499 (16M / 64 / 100) - 1
1000 Hz (1 ms) 64 249 (16M / 64 / 1000) - 1
Note: OCR1A must be ≤ 65535 for 16-bit timer. Choose appropriate prescaler!

Double Buffering

Compare registers (OCR1A/B) are double buffered:

  • Two copies of the register exist in hardware
  • You write to the "buffer" register
  • Hardware uses the "active" register
  • Buffer copied to active at safe time (TOP/BOTTOM)
Benefits:
  • No glitches when updating compare values
  • Atomic updates even for 16-bit values
  • Safe to change values at any time
Buffer (Write) @ TOP Active (Compare) CPU Comparator

Input Capture Mode

Measure external events with hardware precision:

  • External event triggers capture
  • TCNT1 value copied to ICR1 instantly
  • Measure pulse width, frequency, period
  • Hardware timestamping - no software delay!
Use Cases:
  • Measuring signal frequency
  • Pulse width measurement
  • RPM sensing
  • Ultrasonic distance sensors
Input Signal (ICP1): Timer Count: ICR1 = T1 ICR1 = T2 Pulse Width = (T2 - T1) / f_timer

PWM as Digital-to-Analog Converter

Convert PWM to analog voltage with an RC filter:

  • PWM frequency >> RC cutoff frequency
  • Capacitor smooths the pulsed signal
  • Output voltage = VCC × Duty Cycle
Example: 5V PWM at 50% duty → 2.5V DC output
RC Low-Pass Filter: PWM R C V_out PWM Input: Filtered Output:

APIs vs. Direct Register Access

Arduino API Direct Register Access
Ease of Use Very easy Complex
Flexibility Limited Full control
Portability Works across Arduino boards Chip-specific
Performance Overhead from abstraction Maximum efficiency
Learning Quick start Deep understanding required
When to use direct access:
  • Need features not exposed by API
  • Performance-critical applications
  • Complex timing requirements
  • Custom PWM frequencies/resolutions

Third-Party Timer Libraries

Simplify timer usage without full register knowledge:

  • TimerOne - Easy Timer 1 interrupts
  • TimerThree - For boards with Timer 3
  • MsTimer2 - Millisecond-based Timer 2
  • FlexiTimer2 - Flexible timing
Best of both worlds: These libraries provide simple APIs while still offering more control than analogWrite() or tone().
// Using TimerOne library
#include <TimerOne.h>

void blinkLED() {
  digitalWrite(13, !digitalRead(13));
}

void setup() {
  pinMode(13, OUTPUT);
  
  // Initialize Timer1 to 1 second
  Timer1.initialize(1000000);  // microseconds
  
  // Attach interrupt function
  Timer1.attachInterrupt(blinkLED);
}

void loop() {
  // Your other code here
}

Interactive PWM Demonstration

Adjust the parameters to see how they affect the PWM signal:

Average Voltage: 2.50 V

Common Timer Mistakes

❌ Mistake 1: Using Timer 0 and expecting delay()/millis() to work
❌ Mistake 2: Forgetting to enable global interrupts with sei()
❌ Mistake 3: Using the wrong prescaler, causing OCR overflow
❌ Mistake 4: Long ISR code that takes longer than the timer period
❌ Mistake 5: Not making ISR variables volatile
❌ Mistake 6: Trying to use arbitrary pins for timer outputs

Timer Best Practices

✓ Keep ISRs short - Only set flags, let main loop handle complex tasks
✓ Use volatile - For all variables shared between ISR and main code
✓ Disable interrupts during setup - Use cli() and sei()
✓ Check for conflicts - Verify no libraries use your timer
✓ Calculate carefully - Double-check prescaler and OCR values
✓ Read the datasheet - It's the ultimate source of truth

Summary

  • Timers are hardware counters that operate independently of the CPU
  • ATmega328P has 3 timers: Timer 0 (8-bit), Timer 1 (16-bit), Timer 2 (8-bit)
  • Prescalers divide the clock to achieve longer timing periods
  • CTC mode is ideal for generating periodic interrupts
  • PWM modes create analog-like signals for motor/LED control
  • Key registers: TCNTn, OCRnA/B, TCCRnA/B, TIMSKn
  • Be aware of resource conflicts with Arduino library functions
  • Use direct register access for maximum control and flexibility

Questions?

🤔

Review the ATmega328P datasheet sections on Timer/Counter 1

Exercise: CTC Timer Setup

Task: Configure Timer 1 in CTC mode so that an ISR fires every 500 ms and prints "Hello!" to the Serial Monitor.
The CPU clock is 16 MHz. You must:
  1. Choose an appropriate prescaler.
  2. Calculate the OCR1A value (must fit in 16 bits: 0–65535).
  3. Set the correct bits in TCCR1A, TCCR1B, and TIMSK1.

CTC Interrupt Frequency Formula:

Register Reference Tables

TCCR1A — Timer/Counter1 Control Register A

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
COM1A1COM1A0COM1B1COM1B0WGM11WGM10

TCCR1B — Timer/Counter1 Control Register B

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
ICNC1ICES1WGM13WGM12CS12CS11CS10

Waveform Generation Mode (WGM) — CTC options

ModeWGM13WGM12WGM11WGM10DescriptionTOP
00000Normal0xFFFF
40100CTCOCR1A
121100CTCICR1

Clock Select (Prescaler)

CS12CS11CS10Description
000No clock (timer stopped)
001clk / 1 (no prescaling)
010clk / 8
011clk / 64
100clk / 256
101clk / 1024

TIMSK1 — Timer Interrupt Mask Register

BitNameDescription
5ICIE1Input Capture Interrupt Enable
2OCIE1BOutput Compare B Match Interrupt Enable
1OCIE1AOutput Compare A Match Interrupt Enable
0TOIE1Overflow Interrupt Enable
Hint: Fill in the blanks in the code on the next slide. Use the tables above to figure out which bits to set. Remember: OCR1A must be between 0 and 65535.

Exercise: Fill in the Code

Using the register tables from the previous slide, complete the code so that "Hello!" prints to Serial every 500 ms.

Your steps:
  1. Pick a prescaler so the OCR1A value fits in 16 bits.
  2. Calculate: OCR1A = (16,000,000 / (prescaler × 2)) − 1
  3. Set WGM bits for CTC mode (mode 4).
  4. Set CS bits for your chosen prescaler.
  5. Enable the compare-match A interrupt in TIMSK1.
volatile bool printFlag = false;

ISR(TIMER1_COMPA_vect) {
  printFlag = true;
}

void setup() {
  Serial.begin(9600);
  cli();

  // 1. Reset registers
  TCCR1A = ___;
  TCCR1B = ___;
  TCNT1  = ___;

  // 2. Set compare value for 500 ms (2 Hz)
  //    OCR1A = ???
  OCR1A = ___;

  // 3. Set CTC mode
  //    Which bit(s) go in TCCR1A?  ___
  //    Which bit(s) go in TCCR1B?  ___
  TCCR1B |= (1 << ___);

  // 4. Set prescaler
  //    Which CS bits do you need?
  TCCR1B |= (1 << ___) | (1 << ___);

  // 5. Enable compare match A interrupt
  TIMSK1 |= (1 << ___);

  sei();
}

void loop() {
  if (printFlag) {
    printFlag = false;
    Serial.println("Hello!");
  }
}