Project 3: Hardware Timer and PWM — Bare Metal AVR

Master hardware timers to achieve microsecond-precision timing and generate PWM signals for LED dimming, motor control, and audio synthesis—the foundation of all real-time embedded systems.


Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1 week (20-30 hours)
Language C (alt: AVR Assembly for ISRs)
Platform Arduino Uno / ATmega328P
Prerequisites Projects 1-2 (GPIO, UART), basic interrupt concepts
Key Topics Timer/Counter hardware, PWM modes, prescalers, CTC mode, timer interrupts

1. Learning Objectives

By completing this project, you will:

  1. Understand timer/counter hardware architecture - Know how counters, compare registers, overflow flags, and prescalers form a complete timing subsystem
  2. Master timer modes - Configure Normal, CTC, Fast PWM, and Phase-Correct PWM modes from first principles
  3. Calculate prescaler and compare values - Derive exact register values for any desired frequency or period
  4. Generate PWM signals - Create variable duty cycle outputs for analog-like control
  5. Implement timer interrupts - Build non-blocking periodic tasks with microsecond precision
  6. Generate audio frequencies - Create precise square waves for tones and buzzer control
  7. Build real-time foundations - Understand how RTOS tick timers and schedulers work

What You Will Build

A comprehensive timer demonstration system that:

  • Generates a precise 1-second tick using Timer1 compare match interrupt
  • Fades an LED smoothly using Timer0 PWM (0% to 100% brightness)
  • Produces audio tones at exact frequencies (A4=440Hz, etc.) using Timer2
  • Maintains a millisecond-resolution uptime counter
  • Accepts UART commands to control PWM duty cycle and play tones
  • (Optional) Controls a servo motor with 1-2ms pulse width

2. Theoretical Foundation

2.1 Timer/Counter Hardware

What is a Hardware Timer?

A hardware timer is a specialized counter circuit built into the microcontroller that increments automatically with each clock pulse. Unlike software loops that consume CPU cycles and can be interrupted, hardware timers run independently of CPU execution, providing deterministic timing.

The Timer/Counter Subsystem:

                   F_CPU (System Clock)
                          │
                          │ 16 MHz on Arduino Uno
                          ▼
              ┌───────────────────────┐
              │      Prescaler        │
              │  ┌─────────────────┐  │
              │  │ /1 /8 /64 /256  │  │
              │  │    /1024        │  │
              │  └────────┬────────┘  │
              │           │           │
              │    Timer Clock        │
              └───────────┬───────────┘
                          │
                          ▼
              ┌───────────────────────┐
              │    Counter (TCNTn)    │
              │  ┌─────────────────┐  │
              │  │  8-bit: 0-255   │  │
              │  │  16-bit: 0-65535│  │
              │  └────────┬────────┘  │
              │           │           │
              │     ┌─────┴─────┐     │
              │     ▼           ▼     │
              │ ┌───────┐  ┌───────┐  │
              │ │ OCRnA │  │ OCRnB │  │   Compare Registers
              │ └───┬───┘  └───┬───┘  │
              │     │          │      │
              └─────┼──────────┼──────┘
                    │          │
          ┌─────────┴──────────┴─────────┐
          │                              │
          ▼                              ▼
    ┌──────────┐                  ┌──────────┐
    │ Compare  │                  │ Overflow │
    │  Match   │                  │   Flag   │
    │ Interrupt│                  │ Interrupt│
    └──────────┘                  └──────────┘
          │                              │
          ▼                              ▼
    ┌──────────┐                  ┌──────────┐
    │   OCnA   │                  │  Timer   │
    │   Pin    │                  │  Reset   │
    │  Toggle  │                  │  or Wrap │
    └──────────┘                  └──────────┘

The Count Register (TCNTn)

The heart of any timer is its count register. This register increments (or decrements in some modes) with each tick of the timer clock:

8-bit Counter Operation (Timer0, Timer2):

Time →
           ┌─────────────────────────────────────────────────────┐
     255 ──┤                              ▲                      │
           │                             ╱│╲                     │
     200 ──┤                            ╱ │ ╲                    │
           │                           ╱  │  ╲                   │
     150 ──┤                          ╱   │   ╲                  │
           │                         ╱    │    ╲                 │
TCNT 100 ──┤                        ╱     │     ╲                │
           │                       ╱      │      ╲               │
      50 ──┤                      ╱       │       ╲              │
           │                     ╱        │        ╲             │
       0 ──┴────────────────────▼─────────┴─────────▼────────────┘
           │◄──── One count cycle (256 ticks) ────►│
           │                                       │
           Overflow                           Overflow
           Interrupt                          Interrupt


16-bit Counter Operation (Timer1):

           ┌─────────────────────────────────────────────────────┐
   65535 ──┤                              ▲                      │
           │                             ╱ ╲                     │
   50000 ──┤                            ╱   ╲                    │
           │                           ╱     ╲                   │
   30000 ──┤                          ╱       ╲                  │
           │                         ╱         ╲                 │
TCNT1      │                        ╱           ╲                │
   15000 ──┤                       ╱             ╲               │
           │                      ╱               ╲              │
       0 ──┴─────────────────────▼─────────────────▼─────────────┘
           │◄──── One count cycle (65536 ticks) ──►│

Compare Match Mechanism

When the counter value equals a compare register (OCRnA or OCRnB), several things can happen depending on configuration:

Compare Match Operation:

Counter value:    0    50   100  150  200  OCR=200  255   0
                  │     │     │    │    │     ▼      │    │
                  ▼     ▼     ▼    ▼    ▼           ▼    ▼
TCNT:         ────┴─────┴─────┴────┴────┴──────────┴────┴────►

                                        │
                                        │ Compare Match!
                                        ▼
              ┌─────────────────────────────────────────────────┐
              │            What can happen:                      │
              │                                                  │
              │  1. Trigger Interrupt (ISR executes)             │
              │  2. Toggle/Set/Clear output pin (OCnA/OCnB)      │
              │  3. Reset counter to 0 (CTC mode)                │
              │  4. Change PWM output state                      │
              └─────────────────────────────────────────────────┘

ATmega328P Timer Summary

The ATmega328P has three timers with different capabilities:

┌─────────────────────────────────────────────────────────────────────┐
│                    ATmega328P Timer Overview                         │
├─────────────┬───────────┬───────────┬───────────────────────────────┤
│   Timer     │   Bits    │   Pins    │        Best Used For          │
├─────────────┼───────────┼───────────┼───────────────────────────────┤
│   Timer0    │   8-bit   │  OC0A=PD6 │  PWM output, millis() base,   │
│             │           │  OC0B=PD5 │  short delays                 │
│             │           │ (pin 6,5) │                               │
├─────────────┼───────────┼───────────┼───────────────────────────────┤
│   Timer1    │  16-bit   │  OC1A=PB1 │  Precise timing, servo PWM,   │
│             │           │  OC1B=PB2 │  input capture, long periods  │
│             │           │ (pin 9,10)│  (up to 4+ seconds)           │
├─────────────┼───────────┼───────────┼───────────────────────────────┤
│   Timer2    │   8-bit   │  OC2A=PB3 │  PWM, async operation,        │
│             │           │  OC2B=PD3 │  tone generation, RTC with    │
│             │           │(pin 11,3) │  external 32.768kHz crystal   │
└─────────────┴───────────┴───────────┴───────────────────────────────┘

2.2 Timer Modes (Normal, CTC, PWM)

Understanding timer modes is crucial. Each mode changes how the counter behaves when it reaches certain values.

Normal Mode

In Normal mode, the counter simply counts from 0 to MAX (255 for 8-bit, 65535 for 16-bit) and then overflows back to 0.

Normal Mode Operation (8-bit):

    255 ──┬─────────▲─────────────▲─────────────▲─────────────
          │        ╱│            ╱│            ╱│
    200 ──┼───────╱─┼───────────╱─┼───────────╱─┼─────────────
          │      ╱  │          ╱  │          ╱  │
    150 ──┼─────╱───┼─────────╱───┼─────────╱───┼─────────────
TCNT      │    ╱    │        ╱    │        ╱    │
    100 ──┼───╱─────┼───────╱─────┼───────╱─────┼─────────────
          │  ╱      │      ╱      │      ╱      │
     50 ──┼─╱───────┼─────╱───────┼─────╱───────┼─────────────
          │╱        │    ╱        │    ╱        │
      0 ──▼─────────└───▼─────────└───▼─────────└─────────────
          │  TOV0   │    TOV0     │    TOV0     │
          │interrupt│  interrupt  │  interrupt  │
          │         │             │             │
          │◄─ 256 ─►│◄─── 256 ───►│◄─── 256 ───►│
             ticks       ticks        ticks

Period = 256 * (1 / timer_clock)
At 16MHz with prescaler 1024: Period = 256 * (1024/16000000) = 16.384 ms

Normal Mode Use Cases:

  • Simple overflow interrupts at fixed intervals
  • Measuring elapsed time (read TCNT, calculate difference)
  • Base for software timers

Normal Mode Registers (Timer1):

TCCR1A = 0x00;              // Normal mode (WGM = 0000)
TCCR1B = (1 << CS12);       // Prescaler = 256
TIMSK1 = (1 << TOIE1);      // Enable overflow interrupt

// Period = 65536 * 256 / 16000000 = 1.048576 seconds

CTC Mode (Clear Timer on Compare Match)

CTC mode is the workhorse for precise timing. The counter resets to 0 when it matches OCRnA, giving you exact control over the period.

CTC Mode Operation (Timer1, OCR1A = 15624):

         OCR1A
   15624 ──┬───────▲───────────▲───────────▲───────────
           │      ╱│          ╱│          ╱│
   10000 ──┼─────╱─┼─────────╱─┼─────────╱─┼───────────
           │    ╱  │        ╱  │        ╱  │
    5000 ──┼───╱───┼───────╱───┼───────╱───┼───────────
TCNT1      │  ╱    │      ╱    │      ╱    │
    2500 ──┼─╱─────┼─────╱─────┼─────╱─────┼───────────
           │╱      │    ╱      │    ╱      │
       0 ──▼───────└───▼───────└───▼───────└───────────
           │ Compare│   Compare │   Compare │
           │ Match! │   Match!  │   Match!  │
           │Counter │   Counter │   Counter │
           │ clears │   clears  │   clears  │
           │        │           │           │
           │◄─────►│◄─────────►│◄─────────►│
           15625    15625       15625
           counts   counts      counts

Key Insight: You control the TOP value!

Period = (OCR1A + 1) * prescaler / F_CPU
Frequency = F_CPU / (prescaler * (OCR1A + 1))

Example: 1 Hz with prescaler 1024
  OCR1A = (16000000 / (1024 * 1)) - 1 = 15624
  Period = (15624 + 1) * 1024 / 16000000 = 1.000 second exactly!

CTC Mode for Square Wave Generation:

CTC Mode with Toggle OC1A:

         OCR1A
   15624 ──┬───────▲───────────▲───────────▲───────────
           │      ╱│          ╱│          ╱│
TCNT1      │     ╱ │         ╱ │         ╱ │
           │    ╱  │        ╱  │        ╱  │
       0 ──▼───────└───────▼───└───────▼───└───────────

OC1A Pin:
    HIGH ──┬───────┐           ┌───────────┐
           │       │           │           │
    LOW  ──┴───────└───────────┘           └───────────
           │◄─────►│◄─────────►│◄─────────►│
             T/2        T/2         T/2

Square wave frequency = F_CPU / (2 * prescaler * (OCR1A + 1))

CTC Mode Registers (Timer1 for 1 Hz):

TCCR1A = 0;                         // CTC mode (WGM12 in TCCR1B)
TCCR1B = (1 << WGM12) |             // CTC mode
         (1 << CS12) | (1 << CS10); // Prescaler = 1024
OCR1A = 15624;                      // (16000000/1024/1) - 1
TIMSK1 = (1 << OCIE1A);             // Compare match interrupt

Fast PWM Mode

Fast PWM counts from 0 to TOP (either 255/65535 or OCRnA) in a single slope. The output pin state changes based on compare match.

Fast PWM Mode (8-bit, non-inverting):

              TOP=255
    255 ──┬────▲────────────▲────────────▲────────────
          │   ╱│           ╱│           ╱│
    200 ──┼──╱─┼──────────╱─┼──────────╱─┼────────────
          │ ╱  │         ╱  │         ╱  │
OCRnA=150─┼╱───┼────────╱───┼────────╱───┼─── Compare
          │    │       ╱    │       ╱    │    Match
    100 ──┼────┼──────╱─────┼──────╱─────┼────────────
TCNT      │    │     ╱      │     ╱      │
     50 ──┼────┼────╱───────┼────╱───────┼────────────
          │    │   ╱        │   ╱        │
      0 ──▼────└──▼─────────└──▼─────────└────────────
          │    │              │           │
          │    │              │           │

OCnA Pin (non-inverting COM0A1=1, COM0A0=0):

    HIGH ──┬────────────────┬────────────────┬────────
           │                │                │
           │    ┌───────────┤    ┌───────────┤
    LOW  ──┴────┘  OCR/256  └────┘  OCR/256  └────────
           │◄─────────────►│◄─────────────►│
               One PWM           One PWM
                period            period

Duty Cycle = (OCRnA + 1) / 256   (for 8-bit)
At OCRnA = 150: Duty = 151/256 = 59%

PWM Frequency = F_CPU / (prescaler * 256)
At prescaler 64: PWM freq = 16000000 / (64 * 256) = 976.56 Hz

Non-Inverting vs Inverting PWM:

Non-Inverting (COM0A1=1, COM0A0=0):
- Pin HIGH from BOTTOM to Compare Match
- Pin LOW from Compare Match to TOP
- Higher OCRnA = higher duty cycle = brighter LED

    HIGH ──┬────────────┐                 ┌────────────
           │ 150 ticks  │   105 ticks     │
    LOW  ──┴────────────└─────────────────┘────────────
           │◄── ON ────►│◄──── OFF ──────►│


Inverting (COM0A1=1, COM0A0=1):
- Pin LOW from BOTTOM to Compare Match
- Pin HIGH from Compare Match to TOP
- Higher OCRnA = lower duty cycle

    HIGH ──┬            ┌─────────────────┐
           │            │   105 ticks     │
    LOW  ──┴────────────┘  150 ticks      └────────────
           │◄── OFF ───►│◄──── ON ───────►│

Fast PWM Registers (Timer0 for LED dimming):

DDRD |= (1 << PD6);                  // OC0A as output
TCCR0A = (1 << COM0A1) |             // Non-inverting PWM on OC0A
         (1 << WGM01) | (1 << WGM00); // Fast PWM mode
TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler = 64
OCR0A = 127;                          // 50% duty cycle
// PWM frequency = 16000000 / (64 * 256) = 976.56 Hz

Phase-Correct PWM Mode

Phase-Correct PWM counts up to TOP, then counts back down to 0 (dual-slope). This creates symmetric PWM pulses centered in the period, which is better for motor control.

Phase-Correct PWM Mode (8-bit):

    255 ──┬──────────/\──────────────────/\──────────
          │         /  \                /  \
    200 ──┼────────/────\──────────────/────\────────
          │       /      \            /      \
OCRnA=150─┼──────/────────\──────────/────────\──Compare
          │     /          \        /          \ Match
    100 ──┼────/────────────\──────/────────────\──(both
TCNT      │   /              \    /              \ ways)
     50 ──┼──/────────────────\──/────────────────\
          │ /                  \/                  \
      0 ──▼/────────────────────▼────────────────────
          │◄────── UP ────────►│◄───── DOWN ─────►│
          │◄────────── One Period ───────────────►│

OCnA Pin:

    HIGH ──┬──────┐                    ┌──────┐
           │      │                    │      │
    LOW  ──┴──────└────────────────────┘──────└────
           │◄────►│◄─────────────────►│◄────►│
             ON         OFF              ON

Note: PWM is symmetric around the peak!
      This reduces harmonic content and noise.

PWM Frequency = F_CPU / (prescaler * 510)  (not 512!)
At prescaler 64: PWM freq = 16000000 / (64 * 510) = 490.2 Hz

Why Phase-Correct for Motors?

Fast PWM (motor current):
    ┌────┐              ┌────┐              ┌────┐
    │    │              │    │              │    │
────┘    └──────────────┘    └──────────────┘    └────
│◄──►│                  │◄──►│
  Current spike           Current spike
  at same time            at same time

All PWM transitions align = large current spikes!


Phase-Correct PWM (motor current):
       ┌────┐            ┌────┐            ┌────┐
       │    │            │    │            │    │
───────┘    └────────────┘    └────────────┘    └─────
       │◄──►│
         Current pulses spread out
         Less electrical noise!

2.3 Prescaler Theory

The prescaler divides the system clock to slow down the timer, enabling longer periods with the same counter size.

Prescaler as a Clock Divider

Prescaler Operation:

System Clock (16 MHz):
────┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬────►
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
    16 million pulses per second

Prescaler = 1 (no division):
────┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬────►
    Timer clock = 16 MHz

Prescaler = 8:
────┬───────┬───────┬───────┬───────┬───────┬───────┬──►
    │       │       │       │       │       │       │
    Timer clock = 2 MHz (every 8th CPU clock)

Prescaler = 64:
────┬───────────────────────────────────────────────┬──►
    │                                               │
    Timer clock = 250 kHz (every 64th CPU clock)

Prescaler = 1024:
────┬─────────────────────────────────────────────────────────────────────────┬──►
    │                         (long gap between timer ticks)                   │
    Timer clock = 15,625 Hz (every 1024th CPU clock)

Prescaler Selection Table for 16 MHz

┌──────────────────────────────────────────────────────────────────────────────┐
│                    Prescaler Selection Guide (F_CPU = 16 MHz)                 │
├───────────┬────────────┬──────────────┬──────────────┬──────────────────────┤
│ Prescaler │Timer Clock │ 8-bit Max    │ 16-bit Max   │ Typical Use          │
│           │    (Hz)    │   Period     │   Period     │                      │
├───────────┼────────────┼──────────────┼──────────────┼──────────────────────┤
│     1     │ 16,000,000 │   16 us      │   4.096 ms   │ High-freq PWM,       │
│           │            │              │              │ microsecond timing   │
├───────────┼────────────┼──────────────┼──────────────┼──────────────────────┤
│     8     │  2,000,000 │  128 us      │  32.768 ms   │ Audio frequencies,   │
│           │            │              │              │ fast PWM             │
├───────────┼────────────┼──────────────┼──────────────┼──────────────────────┤
│    64     │    250,000 │ 1.024 ms     │ 262.144 ms   │ General PWM,         │
│           │            │              │              │ millisecond timing   │
├───────────┼────────────┼──────────────┼──────────────┼──────────────────────┤
│   256     │     62,500 │ 4.096 ms     │  1.048576 s  │ Second-resolution    │
│           │            │              │              │ timing               │
├───────────┼────────────┼──────────────┼──────────────┼──────────────────────┤
│  1024     │     15,625 │ 16.384 ms    │  4.194304 s  │ Long delays,         │
│           │            │              │              │ low-power timing     │
└───────────┴────────────┴──────────────┴──────────────┴──────────────────────┘

Calculation formulas:
  Timer Clock = F_CPU / Prescaler
  Max Period (8-bit) = 256 / Timer_Clock
  Max Period (16-bit) = 65536 / Timer_Clock

Choosing the Right Prescaler

Goal: Generate a 1 Hz (1 second) interrupt

Step 1: Determine required timer counts
  Counts_needed = F_CPU / (Prescaler * Frequency)

Step 2: Check if counts fit in timer
  8-bit timer: must be <= 256
  16-bit timer: must be <= 65536

Example calculations for 1 Hz:

Prescaler 1:    16,000,000 / (1 * 1) = 16,000,000 counts  (too large!)
Prescaler 8:    16,000,000 / (8 * 1) = 2,000,000 counts   (too large!)
Prescaler 64:   16,000,000 / (64 * 1) = 250,000 counts    (too large for 16-bit!)
Prescaler 256:  16,000,000 / (256 * 1) = 62,500 counts    (fits in 16-bit!)
Prescaler 1024: 16,000,000 / (1024 * 1) = 15,625 counts   (fits in 16-bit!)

Both 256 and 1024 work. Choose based on:
- Lower prescaler = finer resolution (can adjust in smaller steps)
- Higher prescaler = simpler math (15,625 is "rounder" than 62,500)
- With prescaler 1024: OCR1A = 15625 - 1 = 15624

2.4 PWM Fundamentals

Duty Cycle and Average Voltage

PWM creates the illusion of analog voltage by rapidly switching between HIGH and LOW:

PWM Duty Cycle Visualization:

100% Duty Cycle (OCRnA = 255):
HIGH ─────────────────────────────────────────────────────
LOW  _____________________________________________________
     Average voltage = 5V * 100% = 5.0V
     LED: Maximum brightness


75% Duty Cycle (OCRnA = 191):
HIGH ─────────────────────────┐
                              └───────────────────────────
LOW  _________________________
     │◄─────── 75% ON ───────►│◄──── 25% OFF ───►│
     Average voltage = 5V * 75% = 3.75V
     LED: Bright but not maximum


50% Duty Cycle (OCRnA = 127):
HIGH ───────────────┐                     ┌───────────────
                    └─────────────────────┘
LOW  _______________                      ________________
     │◄── 50% ON ──►│◄──── 50% OFF ─────►│
     Average voltage = 5V * 50% = 2.5V
     LED: Medium brightness


25% Duty Cycle (OCRnA = 63):
HIGH ────────┐
             └────────────────────────────────────────────
LOW  ________
     │◄ 25% ►│◄────────── 75% OFF ───────────────►│
     Average voltage = 5V * 25% = 1.25V
     LED: Dim


0% Duty Cycle (OCRnA = 0):
HIGH _____________________________________________________
LOW  ─────────────────────────────────────────────────────
     Average voltage = 5V * 0% = 0V
     LED: Off

PWM Frequency Considerations

PWM Frequency Trade-offs:

Too LOW frequency (< 100 Hz):
┌────────────────────────────────────────────────────────┐
│  Visible flicker with LEDs                             │
│  Motor "cogging" (jerky motion)                        │
│  Audible buzz with motors                              │
└────────────────────────────────────────────────────────┘


Too HIGH frequency (> 100 kHz):
┌────────────────────────────────────────────────────────┐
│  Increased switching losses (heat)                     │
│  EMI (electromagnetic interference)                    │
│  Driver transistor stress                              │
└────────────────────────────────────────────────────────┘


Sweet spots by application:

Application         │ Recommended Frequency │ Reason
────────────────────┼───────────────────────┼────────────────────
LED dimming         │ 500 Hz - 2 kHz        │ No flicker, efficient
DC motor control    │ 1 kHz - 20 kHz        │ Smooth torque, quiet
Servo motors        │ 50 Hz (standard)      │ Servo protocol spec
Switching power     │ 50 kHz - 500 kHz      │ Small inductors
Audio PWM           │ 40 kHz+               │ Above audible range

PWM Resolution

Resolution Trade-off:

8-bit PWM (256 steps):
└──────────────────────────────────────────────────────────┘
  0%   ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  100%
       ▲                                           ▲
       0                                          255

  Each step = 1/256 = 0.39% change
  At 50% brightness, next step = 50.39%
  Good for most LED applications


10-bit PWM (1024 steps):
└──────────────────────────────────────────────────────────┘
  0%   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  100%
       ▲                                           ▲
       0                                         1023

  Each step = 1/1024 = 0.098% change
  Smoother fades, better for video/display


16-bit PWM (65536 steps):
└──────────────────────────────────────────────────────────┘
  0%   ████████████████████████████████████████████  100%
       ▲                                           ▲
       0                                        65535

  Each step = 1/65536 = 0.0015% change
  Near-continuous, professional grade

2.5 Timer Interrupts

Timer interrupts allow you to execute code at precise intervals without polling.

Timer Interrupt Flow:

Normal Program Execution:
─────────────────────────────────────────────────────────────────►
        │                    │                    │
        │ Timer Match        │ Timer Match        │ Timer Match
        │ Interrupt          │ Interrupt          │ Interrupt
        ▼                    ▼                    ▼
    ┌───────┐            ┌───────┐            ┌───────┐
    │ ISR   │            │ ISR   │            │ ISR   │
    │ runs  │            │ runs  │            │ runs  │
    └───┬───┘            └───┬───┘            └───┬───┘
        │                    │                    │
────────┘                    └────────────────────┘─────────────►
                          Program resumes


ISR Execution Details:

1. Timer match occurs
   ┌──────────────────────────────────────────────────────────┐
   │ TCNT1 == OCR1A ? Yes!                                     │
   │ Set interrupt flag (OCF1A in TIFR1)                       │
   └──────────────────────────────────────────────────────────┘
                              │
2. CPU notices flag           ▼
   ┌──────────────────────────────────────────────────────────┐
   │ Is interrupt enabled (OCIE1A in TIMSK1)?                  │
   │ Is global interrupt enabled (I flag in SREG)?             │
   │ If both yes, save current PC to stack                     │
   │ Clear interrupt flag automatically                        │
   │ Disable global interrupts (prevent nesting)               │
   │ Jump to ISR vector                                        │
   └──────────────────────────────────────────────────────────┘
                              │
3. ISR executes               ▼
   ┌──────────────────────────────────────────────────────────┐
   │ ISR(TIMER1_COMPA_vect) {                                  │
   │     // Your code here                                      │
   │     uptime_seconds++;                                      │
   │     PORTB ^= (1 << PB5);  // Toggle LED                   │
   │ }                                                         │
   └──────────────────────────────────────────────────────────┘
                              │
4. RETI (return from interrupt)
   ┌──────────────────────────────────────────────────────────┐
   │ Restore SREG (including I flag)                           │
   │ Pop PC from stack                                         │
   │ Resume normal execution                                   │
   └──────────────────────────────────────────────────────────┘

Critical ISR Guidelines

ISR Best Practices:

DO:                               DON'T:
┌─────────────────────────────┐  ┌─────────────────────────────┐
│ Keep ISR short and fast     │  │ Call blocking functions     │
│ Use volatile for shared vars│  │ Perform floating point math │
│ Clear flags if needed       │  │ Call printf/uart_puts       │
│ Set flags for main to check │  │ Loop for long periods       │
│                             │  │ Allocate memory             │
└─────────────────────────────┘  └─────────────────────────────┘

Example - Good ISR:
ISR(TIMER1_COMPA_vect) {
    tick_flag = 1;        // Fast: just set a flag
    milliseconds += 1000; // Fast: simple arithmetic
}

Example - Bad ISR:
ISR(TIMER1_COMPA_vect) {
    uart_printf("Tick at %lu ms\n", milliseconds); // SLOW!
    _delay_ms(10);        // NEVER delay in ISR!
    float x = sin(angle); // Floating point is slow!
}

2.6 Common Misconceptions

Misconception Reality
“PWM creates analog voltage” PWM is digital switching; the load’s response (averaging) creates the effect
“Higher PWM frequency is always better” Higher frequency increases switching losses and EMI
“Timer prescaler affects CPU speed” Prescaler only divides the timer clock, not the CPU clock
“CTC and PWM are mutually exclusive” Some modes combine both (e.g., Fast PWM with variable TOP)
“8-bit timers can’t do long delays” Combine with overflow counting for arbitrary periods
“Phase-correct PWM is always better” It has half the frequency of Fast PWM; use Fast for LEDs
“OCRnA = 0 means 0% duty cycle” In Fast PWM, OCRnA=0 still gives one clock cycle HIGH
“Timer interrupts are instant” There’s latency; ISR execution takes time

3. Project Specification

3.1 What You Will Build

A multi-function timer demonstration that showcases all major timer capabilities:

Project Architecture:

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Main Application                                │
│                                                                              │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────────────────────┐ │
│  │    Timer1      │  │    Timer0      │  │           Timer2                │ │
│  │   (16-bit)     │  │   (8-bit)      │  │          (8-bit)               │ │
│  │                │  │                │  │                                │ │
│  │  ┌──────────┐  │  │  ┌──────────┐  │  │  ┌──────────────────────────┐ │ │
│  │  │ CTC Mode │  │  │  │ Fast PWM │  │  │  │ CTC Mode + OC2A Toggle  │ │ │
│  │  │          │  │  │  │          │  │  │  │                         │ │ │
│  │  │ 1 Hz     │  │  │  │ ~1 kHz   │  │  │  │ Variable frequency      │ │ │
│  │  │ interrupt│  │  │  │ LED fade │  │  │  │ tone generation         │ │ │
│  │  └────┬─────┘  │  │  └────┬─────┘  │  │  └───────────┬─────────────┘ │ │
│  │       │        │  │       │        │  │              │               │ │
│  │       ▼        │  │       ▼        │  │              ▼               │ │
│  │  uptime++      │  │   OC0A pin     │  │         OC2A pin             │ │
│  │  tick_flag=1   │  │   (PD6)        │  │         (PB3)                │ │
│  │                │  │                │  │                               │ │
│  └────────────────┘  └────────────────┘  └────────────────────────────────┘ │
│           │                  │                        │                      │
│           ▼                  ▼                        ▼                      │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                          Hardware Outputs                               │ │
│  │                                                                         │ │
│  │   Pin 13 (PB5)        Pin 6 (OC0A)           Pin 11 (OC2A)             │ │
│  │   ┌───────────┐      ┌───────────┐          ┌───────────┐              │ │
│  │   │ Onboard   │      │ External  │          │  Piezo    │              │ │
│  │   │ LED       │      │ LED       │          │  Buzzer   │              │ │
│  │   │ (1Hz blink│      │ (PWM fade)│          │  (tones)  │              │ │
│  │   └───────────┘      └───────────┘          └───────────┘              │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                            UART Interface                               │ │
│  │                                                                         │ │
│  │   Commands:                                                             │ │
│  │     pwm <0-255>    - Set LED brightness                                │ │
│  │     tone <Hz>      - Play tone at frequency                            │ │
│  │     stop           - Stop tone                                         │ │
│  │     melody         - Play demo melody                                  │ │
│  │     uptime         - Show elapsed time                                 │ │
│  │     status         - Show all timer states                             │ │
│  │     help           - Show available commands                           │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 Functional Requirements

ID Requirement Priority Acceptance Criteria
FR1 1-second precise timing Must LED blinks exactly 60 times per minute
FR2 Millisecond uptime counter Must Reported time within 0.1% of actual
FR3 PWM LED fade (0-100%) Must Smooth fade visible, no stepping
FR4 UART PWM control Must “pwm 128” sets 50% brightness
FR5 Tone generation Should A4 (440Hz) matches tuner app
FR6 Variable frequency tones Should User-specified Hz plays correctly
FR7 Simple melody playback Nice Sequence of notes plays in order
FR8 Multiple simultaneous timers Must All three timers work together

3.3 Non-Functional Requirements

ID Requirement Metric Test Method
NFR1 1-second accuracy < 0.1% error Count 60 blinks, compare to stopwatch
NFR2 PWM frequency > 500 Hz No visible LED flicker
NFR3 Tone accuracy < 2% error Verify with tuner/frequency counter
NFR4 Code size < 4KB Check with avr-size
NFR5 Non-blocking operation Main loop runs UART responsive during PWM/tone

3.4 Example Output

$ screen /dev/ttyACM0 115200

=== AVR Timer/PWM Demo ===
Build: Dec 28 2024 14:30:00
Timer1: CTC mode, 1 Hz interrupt
Timer0: Fast PWM, ~976 Hz
Timer2: CTC mode for tones

[0.000s] System started
[1.000s] Tick
[2.000s] Tick
[3.000s] Tick

> help
Available commands:
  pwm <0-255>   - Set LED brightness (0=off, 255=max)
  fade          - Start auto-fade animation
  tone <Hz>     - Play tone (100-10000 Hz)
  stop          - Stop tone
  melody        - Play demo melody
  uptime        - Show uptime in ms
  status        - Show timer configuration
  help          - This message

> pwm 0
PWM set to 0 (0%)
LED off

> pwm 128
PWM set to 128 (50%)

> pwm 255
PWM set to 255 (100%)
Maximum brightness

> fade
Starting fade animation...
  0% -> 100% -> 0% (3 second cycle)
Press any key to stop.
(LED smoothly fades in and out)

> tone 440
Playing 440 Hz (A4)
OCR2A = 70, Prescaler = 256
Actual frequency: 440.14 Hz

> stop
Tone stopped

> melody
Playing demo melody...
  C4 (262 Hz) 250ms
  E4 (330 Hz) 250ms
  G4 (392 Hz) 250ms
  C5 (523 Hz) 500ms
Done!

> uptime
Uptime: 45.237 seconds (45237 ms)

> status
Timer Configuration:
  Timer1: CTC mode, OCR1A=15624, prescaler=1024
          Interrupt rate: 1.000 Hz
          Uptime: 45 seconds
  Timer0: Fast PWM, OCR0A=128, prescaler=64
          PWM frequency: 976.56 Hz
          Duty cycle: 50.2%
  Timer2: CTC mode, OCR2A=70, prescaler=256
          Tone frequency: 440.14 Hz (A4)
          Status: Playing

3.5 Real World Outcome

Upon completion, you will have:

  1. Precise timing capability - Generate exact delays without burning CPU cycles
  2. PWM mastery - Control LED brightness, motor speed, or any analog-like output
  3. Frequency generation - Create audio tones, carrier signals, or timing references
  4. Multi-timer coordination - Run multiple independent timing operations
  5. Interview readiness - Explain timer architectures in technical interviews
  6. RTOS foundation - Understand how operating system tick timers work

4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────────────────────┐
│                                main.c                                        │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │ int main(void) {                                                       │ │
│  │     timer1_init();    // 1 Hz system tick                              │ │
│  │     pwm_init();       // Timer0 PWM for LED                            │ │
│  │     tone_init();      // Timer2 for audio                              │ │
│  │     uart_init(115200);                                                 │ │
│  │     sei();            // Enable interrupts                             │ │
│  │                                                                        │ │
│  │     while(1) {                                                         │ │
│  │         if (tick_flag) { handle_tick(); }                              │ │
│  │         if (fade_active) { update_fade(); }                            │ │
│  │         process_uart_commands();                                       │ │
│  │     }                                                                  │ │
│  │ }                                                                      │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────┬─────────────────────────────────────────┘
                                    │
          ┌─────────────────────────┼─────────────────────────┐
          │                         │                         │
          ▼                         ▼                         ▼
┌─────────────────────┐   ┌─────────────────────┐   ┌─────────────────────┐
│     timer.c         │   │       pwm.c         │   │      tone.c         │
│                     │   │                     │   │                     │
│ timer1_init()       │   │ pwm_init()          │   │ tone_init()         │
│ get_uptime_ms()     │   │ pwm_set_duty()      │   │ tone_play(freq_hz)  │
│ get_uptime_sec()    │   │ pwm_get_duty()      │   │ tone_stop()         │
│                     │   │ fade_start()        │   │ tone_is_playing()   │
│ ISR(TIMER1_COMPA)   │   │ fade_update()       │   │ melody_play()       │
│ {                   │   │                     │   │                     │
│   uptime_ms+=1000;  │   │ // Uses Timer0      │   │ // Uses Timer2      │
│   tick_flag=1;      │   │ // OC0A on PD6      │   │ // OC2A on PB3      │
│ }                   │   │                     │   │                     │
└─────────┬───────────┘   └─────────┬───────────┘   └─────────┬───────────┘
          │                         │                         │
          ▼                         ▼                         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                            Hardware Layer                                    │
│                                                                              │
│  ┌─────────────────┐   ┌─────────────────┐   ┌─────────────────┐           │
│  │     Timer1      │   │     Timer0      │   │     Timer2      │           │
│  │                 │   │                 │   │                 │           │
│  │ TCCR1A = 0x00   │   │ TCCR0A = 0x83   │   │ TCCR2A = 0x42   │           │
│  │ TCCR1B = 0x0D   │   │ TCCR0B = 0x03   │   │ TCCR2B = 0x06   │           │
│  │ OCR1A = 15624   │   │ OCR0A = duty    │   │ OCR2A = tone    │           │
│  │ TIMSK1 = 0x02   │   │                 │   │                 │           │
│  │                 │   │                 │   │                 │           │
│  │ Interrupt→ISR   │   │ PWM out→PD6     │   │ Toggle→PB3      │           │
│  └─────────────────┘   └─────────────────┘   └─────────────────┘           │
│                              │                         │                    │
│                              ▼                         ▼                    │
│                        ┌──────────┐              ┌──────────┐              │
│                        │   LED    │              │  Buzzer  │              │
│                        │ (fade)   │              │ (tones)  │              │
│                        └──────────┘              └──────────┘              │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 Timer Register Map

Timer1 Registers (16-bit, for system tick):
┌─────────────────────────────────────────────────────────────────────────────┐
│ Register │ Address │ Bits Used     │ Value  │ Purpose                       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR1A   │ 0x80    │ WGM11:10      │ 0x00   │ Normal port operation,        │
│          │         │ COM1A/B       │        │ WGM=0100 (CTC with OCR1A)     │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR1B   │ 0x81    │ WGM13:12      │ 0x0D   │ CTC mode (WGM12=1),           │
│          │         │ CS12:10       │        │ Prescaler=1024 (CS12,CS10=1)  │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR1C   │ 0x82    │ FOC1A/B       │ 0x00   │ Force Output Compare (unused) │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCNT1H/L │ 0x85/84 │ 16-bit counter│  0-TOP │ Current count value           │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR1AH/L │ 0x89/88 │ 16-bit compare│ 15624  │ Compare value for 1 Hz        │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR1BH/L │ 0x8B/8A │ 16-bit compare│  N/A   │ Second compare (unused)       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ ICR1H/L  │ 0x87/86 │ 16-bit capture│  N/A   │ Input capture (unused)        │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIMSK1   │ 0x6F    │ OCIE1A        │ 0x02   │ Enable compare A interrupt    │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIFR1    │ 0x36    │ OCF1A         │ (auto) │ Compare match flag (auto-clr) │
└──────────┴─────────┴───────────────┴────────┴───────────────────────────────┘


Timer0 Registers (8-bit, for PWM):
┌─────────────────────────────────────────────────────────────────────────────┐
│ Register │ Address │ Bits Used     │ Value  │ Purpose                       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR0A   │ 0x44    │ COM0A1:0      │ 0x83   │ Non-inverting PWM on OC0A,    │
│          │         │ WGM01:00      │        │ Fast PWM mode (WGM=011)       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR0B   │ 0x45    │ WGM02         │ 0x03   │ Prescaler=64 (CS01,CS00=1)    │
│          │         │ CS02:00       │        │                               │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCNT0    │ 0x46    │ 8-bit counter │ 0-255  │ Current count value           │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR0A    │ 0x47    │ 8-bit compare │ 0-255  │ PWM duty cycle                │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR0B    │ 0x48    │ 8-bit compare │  N/A   │ Second PWM channel (unused)   │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIMSK0   │ 0x6E    │ OCIE0A/TOIE0  │ 0x00   │ No interrupts needed for PWM  │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIFR0    │ 0x35    │ OCF0A/TOV0    │ (auto) │ Flags (not used)              │
└──────────┴─────────┴───────────────┴────────┴───────────────────────────────┘


Timer2 Registers (8-bit, for tones):
┌─────────────────────────────────────────────────────────────────────────────┐
│ Register │ Address │ Bits Used     │ Value  │ Purpose                       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR2A   │ 0xB0    │ COM2A1:0      │ 0x42   │ Toggle OC2A on compare,       │
│          │         │ WGM21:20      │        │ CTC mode (WGM=010)            │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCCR2B   │ 0xB1    │ WGM22         │ 0x06   │ Prescaler=256 (CS22,CS21=1)   │
│          │         │ CS22:20       │        │                               │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TCNT2    │ 0xB2    │ 8-bit counter │ 0-OCR  │ Current count value           │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR2A    │ 0xB3    │ 8-bit compare │ varies │ Sets tone frequency           │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ OCR2B    │ 0xB4    │ 8-bit compare │  N/A   │ Second channel (unused)       │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIMSK2   │ 0x70    │ OCIE2A/TOIE2  │ 0x00   │ No interrupts (pin toggles)   │
├──────────┼─────────┼───────────────┼────────┼───────────────────────────────┤
│ TIFR2    │ 0x37    │ OCF2A/TOV2    │ (auto) │ Flags (not used)              │
└──────────┴─────────┴───────────────┴────────┴───────────────────────────────┘

4.3 PWM Waveform Analysis

Fast PWM Waveform (Timer0, OCR0A = 191, 75% duty):

TCNT0:
    255 ─┬────────────▲────────────────────▲────────────────────
         │           ╱│                   ╱│
         │          ╱ │                  ╱ │
    191 ─┼─────────╱──┼─────────────────╱──┼─ OCR0A (compare)
         │        ╱   │                ╱   │
         │       ╱    │               ╱    │
    128 ─┼──────╱─────┼──────────────╱─────┼─────────────────────
         │     ╱      │             ╱      │
         │    ╱       │            ╱       │
     64 ─┼───╱────────┼───────────╱────────┼─────────────────────
         │  ╱         │          ╱         │
         │ ╱          │         ╱          │
      0 ─▼────────────└────────▼───────────└─────────────────────
         │◄─────── Period ─────►│◄─────── Period ─────►│
         │  256 timer ticks     │
         │  256 * 64 / 16MHz    │
         │  = 1.024 ms          │
         │  = 976.56 Hz         │


OC0A Pin (PD6) Output:

    HIGH ┬─────────────────────┐           ┌───────────────────────
         │                     │           │
         │     191 ticks       │ 64 ticks  │
         │     (74.6%)         │ (25.4%)   │
         │                     │           │
    LOW  ┴─────────────────────└───────────┘───────────────────────
         │◄─────── ON ────────►│◄── OFF ──►│


Different Duty Cycles Comparison:

OCR0A = 0 (0.4% duty - almost off but not quite!):
    HIGH ┬┐                               ┌┐
         ││                               ││
    LOW  ┴┴───────────────────────────────┴┴───────────────────────
         │1│◄──────── 255 ───────────────►│1│

OCR0A = 63 (25% duty):
    HIGH ┬─────────┐                       ┌─────────┐
         │         │                       │         │
    LOW  ┴─────────┴───────────────────────┴─────────┴─────────────
         │◄─ 64 ──►│◄────── 192 ──────────►│

OCR0A = 127 (50% duty):
    HIGH ┬─────────────────┐               ┌─────────────────┐
         │                 │               │                 │
    LOW  ┴─────────────────┴───────────────┴─────────────────┴─────
         │◄──── 128 ──────►│◄──── 128 ───►│

OCR0A = 255 (100% duty - always on):
    HIGH ┬─────────────────────────────────────────────────────────
         │
    LOW  ┴_________________________________________________________

4.4 Timing Calculations

System Tick (1 Hz) Calculation

Goal: 1 Hz interrupt (1 second period)

Given:
  F_CPU = 16,000,000 Hz
  Desired frequency = 1 Hz
  Using Timer1 (16-bit)

Step 1: Try each prescaler

  Prescaler 1:    Counts = 16,000,000 / 1 = 16,000,000  (exceeds 65536)
  Prescaler 8:    Counts = 16,000,000 / 8 = 2,000,000   (exceeds 65536)
  Prescaler 64:   Counts = 16,000,000 / 64 = 250,000    (exceeds 65536)
  Prescaler 256:  Counts = 16,000,000 / 256 = 62,500    (fits!)
  Prescaler 1024: Counts = 16,000,000 / 1024 = 15,625   (fits, smaller number)

Step 2: Choose prescaler 1024 for simplicity
  OCR1A = 15,625 - 1 = 15624

Step 3: Verify
  Actual frequency = 16,000,000 / (1024 * 15625)
                   = 16,000,000 / 16,000,000
                   = 1.000000 Hz (exact!)

Register settings:
  TCCR1A = 0;                           // Normal port operation
  TCCR1B = (1 << WGM12) |               // CTC mode
           (1 << CS12) | (1 << CS10);   // Prescaler 1024
  OCR1A = 15624;                        // Compare value
  TIMSK1 = (1 << OCIE1A);               // Enable compare interrupt

PWM Frequency Calculation

Goal: ~1 kHz PWM for LED dimming (no visible flicker)

Given:
  F_CPU = 16,000,000 Hz
  Using Timer0 (8-bit Fast PWM)
  TOP = 255 (fixed in this mode)

Formula: PWM_freq = F_CPU / (prescaler * 256)

Prescaler options:
  Prescaler 1:    16,000,000 / (1 * 256) = 62,500 Hz    (very high)
  Prescaler 8:    16,000,000 / (8 * 256) = 7,812.5 Hz  (good for motors)
  Prescaler 64:   16,000,000 / (64 * 256) = 976.56 Hz  (good for LEDs!)
  Prescaler 256:  16,000,000 / (256 * 256) = 244.14 Hz (visible flicker)
  Prescaler 1024: 16,000,000 / (1024 * 256) = 61.04 Hz (very visible flicker)

Choice: Prescaler 64 gives 976.56 Hz
  - Above flicker threshold (~100 Hz)
  - Low enough for efficient switching

Register settings:
  DDRD |= (1 << PD6);                   // OC0A as output
  TCCR0A = (1 << COM0A1) |              // Non-inverting PWM
           (1 << WGM01) | (1 << WGM00); // Fast PWM mode
  TCCR0B = (1 << CS01) | (1 << CS00);   // Prescaler 64
  OCR0A = 127;                          // 50% duty cycle

Tone Frequency Calculation

Goal: Generate 440 Hz (A4 musical note)

Given:
  F_CPU = 16,000,000 Hz
  Using Timer2 (8-bit) in CTC mode with toggle
  Square wave: toggle on each compare match

Formula (square wave with toggle):
  OCR2A = F_CPU / (2 * prescaler * frequency) - 1

Prescaler analysis for 440 Hz:
  Prescaler 1:    OCR = 16,000,000 / (2 * 1 * 440) - 1 = 18,180     (exceeds 255)
  Prescaler 8:    OCR = 16,000,000 / (2 * 8 * 440) - 1 = 2,272      (exceeds 255)
  Prescaler 32:   OCR = 16,000,000 / (2 * 32 * 440) - 1 = 567       (exceeds 255)
  Prescaler 64:   OCR = 16,000,000 / (2 * 64 * 440) - 1 = 283       (exceeds 255)
  Prescaler 128:  OCR = 16,000,000 / (2 * 128 * 440) - 1 = 141      (fits!)
  Prescaler 256:  OCR = 16,000,000 / (2 * 256 * 440) - 1 = 70       (fits!)

Note: Timer2 has prescalers 1, 8, 32, 64, 128, 256, 1024

Choice: Prescaler 256 with OCR2A = 70
  Actual frequency = 16,000,000 / (2 * 256 * 71) = 440.14 Hz
  Error = (440.14 - 440) / 440 * 100 = 0.032% (excellent!)

Register settings:
  DDRB |= (1 << PB3);                   // OC2A as output
  TCCR2A = (1 << COM2A0) |              // Toggle OC2A on compare
           (1 << WGM21);                // CTC mode
  TCCR2B = (1 << CS22) | (1 << CS21);   // Prescaler 256
  OCR2A = 70;                           // For 440 Hz


Frequency range with prescaler 256:
  Minimum freq (OCR2A = 255): 16,000,000 / (2 * 256 * 256) = 122 Hz
  Maximum freq (OCR2A = 1):   16,000,000 / (2 * 256 * 2) = 15,625 Hz

For higher frequencies, use smaller prescaler:
  Prescaler 64, OCR2A = 1: 16,000,000 / (2 * 64 * 2) = 62,500 Hz

5. Implementation Guide

5.1 Development Environment

Hardware required:

  • Arduino Uno or compatible ATmega328P board
  • LED with 220-330 ohm resistor (for PWM output on pin 6)
  • Piezo buzzer or small speaker (for tone output on pin 11)
  • USB cable
  • (Optional) Oscilloscope or logic analyzer for verification

Software setup:

# Verify toolchain (should already be installed from Projects 1-2)
avr-gcc --version
avrdude -?

# Project directory
mkdir -p timer_pwm && cd timer_pwm

5.2 Project Structure

timer_pwm/
├── main.c              # Main loop and command processing
├── timer.h             # Timer1 system tick declarations
├── timer.c             # Timer1 implementation and ISR
├── pwm.h               # PWM declarations
├── pwm.c               # Timer0 PWM implementation
├── tone.h              # Tone generator declarations
├── tone.c              # Timer2 tone implementation
├── uart.h              # From Project 2
├── uart.c              # From Project 2
└── Makefile            # Build automation

5.3 The Core Question You’re Answering

“How do I achieve precise timing and analog-like outputs in a bare metal system without wasting CPU cycles?”

The answer: Hardware timers run independently of CPU execution. Configure them once, and they:

  1. Count clock cycles automatically
  2. Trigger interrupts at exact intervals
  3. Control output pins for PWM without CPU intervention
  4. Provide the foundation for all real-time systems

5.4 Concepts You Must Understand First

Before implementing, answer these self-assessment questions:

Concept Question If Unsure, Review
Counter registers What does TCNT1 contain? Datasheet Section 16.3
Compare match What happens when TCNT equals OCR? Datasheet Section 16.9
Prescaler How does prescaler 64 affect a 16 MHz clock? Section 2.3 above
CTC mode Why does the counter reset in CTC mode? Datasheet Section 16.9.2
Fast PWM When does the output go HIGH/LOW? Section 2.2 above
Interrupts What must you do before ISRs can fire? sei() enables global interrupts

5.5 Questions to Guide Your Design

Timer Architecture Questions

  1. Which timer should handle the 1-second tick? Why?
  2. Why is 16-bit Timer1 better for long periods than 8-bit Timer0?
  3. What happens if you use the same timer for both PWM and CTC interrupt?

PWM Questions

  1. What PWM frequency eliminates visible LED flicker?
  2. If OCR0A = 127 in Fast PWM mode, what is the duty cycle percentage?
  3. Why does setting OCR0A = 0 not give 0% duty cycle in Fast PWM?

Tone Generation Questions

  1. Why do we toggle the pin rather than use PWM for tones?
  2. What OCR2A value gives 440 Hz with prescaler 256?
  3. What’s the maximum frequency achievable with Timer2?

System Integration Questions

  1. Can Timer1 interrupt while Timer0 PWM is running?
  2. How do you stop a tone without affecting the system tick?
  3. How would you add millisecond resolution to the uptime counter?

5.6 Thinking Exercise

Before coding, work through this exercise on paper:

Exercise: Calculate the register values for a 2 Hz LED blink

  1. Goal: Blink LED at 2 Hz (toggle every 500 ms)
  2. Choose timer: Timer1 (16-bit for longer periods)
  3. Calculate required counts for 0.5 second period:
    • With prescaler 256: counts = 16,000,000 / (256 * 2) = ?
    • With prescaler 1024: counts = 16,000,000 / (1024 * 2) = ?
  4. Which prescaler fits in 16 bits?
  5. What is OCR1A?
  6. Write the register configuration code.

Complete this before looking at the hints.

5.7 Hints in Layers

Hint 1: Starting Point (Conceptual)

Start with Timer1 CTC mode for the system tick. This is the most straightforward:

// Timer1 initialization skeleton
void timer1_init(void) {
    // Step 1: Set CTC mode (Clear Timer on Compare)
    // WGM12 bit in TCCR1B enables CTC mode

    // Step 2: Set prescaler to 1024
    // CS12 and CS10 bits in TCCR1B

    // Step 3: Set compare value for 1 Hz
    // OCR1A = (F_CPU / prescaler / frequency) - 1

    // Step 4: Enable compare match interrupt
    // OCIE1A bit in TIMSK1
}

// ISR for Timer1 compare match
ISR(TIMER1_COMPA_vect) {
    // Increment your counters here
    // Set a flag for main loop
    // Keep it SHORT!
}

Hint 2: Next Level (PWM Setup)

Add Timer0 Fast PWM after the system tick works:

// PWM initialization
void pwm_init(void) {
    // Set OC0A pin (PD6) as output
    DDRD |= (1 << PD6);

    // Configure TCCR0A:
    // - COM0A1=1, COM0A0=0: Clear OC0A on compare (non-inverting)
    // - WGM01=1, WGM00=1: Fast PWM mode

    // Configure TCCR0B:
    // - CS01=1, CS00=1: Prescaler 64

    // Set initial duty cycle
    OCR0A = 0;  // Start with LED off
}

void pwm_set_duty(uint8_t duty) {
    OCR0A = duty;  // 0=off, 255=full brightness
}

Hint 3: Technical Details (Tone Generation)

Add Timer2 CTC with toggle for tones:

// Calculate OCR2A for a given frequency
uint8_t freq_to_ocr(uint16_t freq_hz) {
    // Using prescaler 256
    // Formula: OCR = F_CPU / (2 * prescaler * freq) - 1
    uint32_t ocr = (F_CPU / (2UL * 256 * freq_hz)) - 1;
    if (ocr > 255) ocr = 255;
    if (ocr < 1) ocr = 1;
    return (uint8_t)ocr;
}

void tone_play(uint16_t freq_hz) {
    // Set OC2A (PB3) as output
    DDRB |= (1 << PB3);

    // CTC mode, toggle OC2A on compare
    TCCR2A = (1 << COM2A0) | (1 << WGM21);

    // Prescaler 256
    TCCR2B = (1 << CS22) | (1 << CS21);

    // Set frequency
    OCR2A = freq_to_ocr(freq_hz);
}

void tone_stop(void) {
    TCCR2A = 0;  // Disable timer output
    TCCR2B = 0;  // Stop timer
    PORTB &= ~(1 << PB3);  // Ensure pin is LOW
}

Hint 4: Verification Tools

# Verify 1 Hz timing with stopwatch
# Count LED blinks for 60 seconds - should be exactly 60

# Verify PWM duty cycle with multimeter
# Set OCR0A = 127 (50%), measure voltage
# Expected: approximately 2.5V average

# Verify tone with smartphone tuner app
# Play 440 Hz, app should show A4 note

# For precise verification, use oscilloscope or logic analyzer
# sigrok-cli can capture and analyze signals

5.8 The Interview Questions They’ll Ask

  1. “Explain PWM. How does switching create analog-like voltage?”
    • PWM rapidly switches between HIGH and LOW
    • The load (LED, motor) averages the voltage due to its time constant
    • Duty cycle controls the average: 50% duty = 50% average voltage
  2. “How do you calculate timer compare values for a specific frequency?”
    • Formula: OCR = (F_CPU / (prescaler * frequency)) - 1
    • The -1 accounts for the count starting at 0
    • Verify the result fits in the timer’s bit width
  3. “What’s the difference between CTC mode and Fast PWM mode?”
    • CTC: Counter clears on compare match, good for precise intervals
    • Fast PWM: Counter counts to TOP (255/65535), compare controls output state
    • CTC for timing/interrupts, PWM for analog output
  4. “Why use hardware timers instead of software delay loops?”
    • Precision: Hardware timers are crystal-accurate
    • Non-blocking: CPU can do other work while timer runs
    • Deterministic: Timing unaffected by code complexity
    • Power: Can run in low-power modes
  5. “How would you generate a 1 MHz signal on an AVR?”
    • Timer1 with prescaler 1, CTC mode
    • OCR1A = (16,000,000 / (1 * 1,000,000 * 2)) - 1 = 7
    • Toggle output on compare match
    • Actual: 16MHz / (2 * 8) = 1 MHz exactly
  6. “What determines PWM resolution?”
    • Number of distinct duty cycle steps
    • 8-bit timer: 256 steps (0.39% per step)
    • Trade-off: Higher resolution requires lower frequency (longer period)

5.9 Books That Will Help

Topic Book Chapter/Section
AVR Timers “Make: AVR Programming” - Elliot Williams Chapters 6, 9, 10
PWM Theory “Making Embedded Systems” - Elecia White Chapter 10
Timer Details ATmega328P Datasheet Sections 15 (Timer0), 16 (Timer1)
Interrupt Handling “AVR Workshop” - John Boxall Chapter 7
Motor Control “Arduino Cookbook” - Michael Margolis Chapter 8
Audio Generation “Arduino Cookbook” - Michael Margolis Chapter 9

5.10 Implementation Phases

Phase 1: System Tick (4 hours)

  1. Configure Timer1 in CTC mode
  2. Calculate and set OCR1A for 1 Hz
  3. Write ISR to increment counter and toggle LED
  4. Verify with stopwatch (60 blinks = 60 seconds)

Phase 2: Uptime Counter (2 hours)

  1. Add millisecond counter variable
  2. Modify ISR or add sub-tick counting
  3. Implement get_uptime_ms() function
  4. Test accuracy over 1 minute

Phase 3: PWM Output (4 hours)

  1. Configure Timer0 for Fast PWM
  2. Connect LED to pin 6 with resistor
  3. Implement pwm_set_duty() function
  4. Test multiple duty cycles with multimeter

Phase 4: Fade Animation (3 hours)

  1. Add fade state machine (up/down direction)
  2. Update PWM periodically from main loop
  3. Use system tick for timing
  4. Make fade speed adjustable

Phase 5: Tone Generator (4 hours)

  1. Configure Timer2 in CTC mode with toggle
  2. Implement freq_to_ocr() calculation
  3. Add tone_play() and tone_stop()
  4. Test with tuner app

Phase 6: Integration (3 hours)

  1. Add UART command parsing
  2. Integrate all features
  3. Test simultaneous operation
  4. Document and clean up code

5.11 Key Implementation Decisions

Decision Options Recommended Rationale
System tick timer Timer0/1/2 Timer1 16-bit allows exact 1 Hz
PWM timer Timer0/2 Timer0 OC0A on convenient pin
Tone timer Timer0/2 Timer2 Keeps Timer0 free for PWM
PWM prescaler 8/64/256 64 ~1 kHz, no flicker
Tick prescaler 256/1024 1024 Simple math (15625)
Tone prescaler 64/128/256 256 Good range for music

6. Testing Strategy

6.1 Unit Testing Timer Calculations

Before flashing, verify your calculations:

// On PC (not AVR), compile with gcc to test calculations
#include <stdio.h>
#include <stdint.h>

#define F_CPU 16000000UL

uint16_t calc_ocr_for_freq(uint32_t freq, uint16_t prescaler) {
    return (F_CPU / (prescaler * freq)) - 1;
}

float calc_actual_freq(uint16_t ocr, uint16_t prescaler) {
    return (float)F_CPU / (prescaler * (ocr + 1));
}

int main() {
    // Test 1 Hz with prescaler 1024
    uint16_t ocr = calc_ocr_for_freq(1, 1024);
    float actual = calc_actual_freq(ocr, 1024);
    printf("1 Hz: OCR=%u, Actual=%.6f Hz\n", ocr, actual);

    // Test 440 Hz with prescaler 256 (for tone)
    uint8_t ocr8 = (F_CPU / (2UL * 256 * 440)) - 1;
    float actual_tone = (float)F_CPU / (2 * 256 * (ocr8 + 1));
    printf("440 Hz: OCR=%u, Actual=%.2f Hz\n", ocr8, actual_tone);

    return 0;
}

6.2 Hardware Verification Tests

Test Method Expected Result Pass Criteria
1 Hz accuracy Count blinks vs stopwatch 60 blinks in 60.0s +/- 0.5 blinks
PWM 0% Set OCR0A=0 LED very dim/off Nearly no light
PWM 50% Set OCR0A=127 Medium brightness ~2.5V average
PWM 100% Set OCR0A=255 Maximum brightness ~5V DC
Tone A4 Tuner app Shows A4 Within 5 cents
Multi-timer All running No interference All work simultaneously

6.3 Oscilloscope/Logic Analyzer Verification

Expected PWM waveform at 50% duty:

        ┌────────┐        ┌────────┐        ┌────────┐
   5V ──┤        │        │        │        │        │
        │        │        │        │        │        │
        │        │        │        │        │        │
   0V ──┴────────┴────────┴────────┴────────┴────────┴────────
        │◄─────►│◄─────►│
         ~0.5ms   ~0.5ms
        │◄── Period ──►│
            ~1.024ms

Expected tone waveform at 440 Hz:

        ┌──────────┐          ┌──────────┐
   5V ──┤          │          │          │
        │          │          │          │
        │          │          │          │
   0V ──┴──────────┴──────────┴──────────┴──────────
        │◄───────►│◄───────►│
           1.14ms    1.14ms
        │◄──── Period ────►│
              2.27ms = 440 Hz

7. Common Pitfalls & Debugging

7.1 Timer Not Running

Symptom Cause Fix
No interrupt Global interrupts disabled Add sei() after timer setup
No interrupt Interrupt not enabled Set OCIE1A in TIMSK1
Timer stuck at 0 No prescaler set Set CS bits in TCCRnB
Wrong frequency Calculation error Re-check formula, +1/-1

7.2 PWM Problems

Symptom Cause Fix
LED always off Wrong pin OC0A is PD6 (Arduino pin 6)
LED always on COM bits wrong Use COM0A1=1, COM0A0=0
LED flickers PWM freq too low Use prescaler 64 or lower
Only on/off, no dim Not in PWM mode Check WGM bits
Dim at OCR=0 Fast PWM quirk Normal; use OCR=0 carefully

7.3 Tone Problems

Symptom Cause Fix
No sound Wrong pin OC2A is PB3 (Arduino pin 11)
Wrong frequency Calculation error Verify OCR2A formula
Clicks between notes Glitch on change Stop timer before changing OCR
Tone doesn’t stop Register not cleared Set TCCR2A=0 AND TCCR2B=0

7.4 Debugging Code

// Add to your code for debugging
void debug_timers(void) {
    uart_printf("\n=== Timer Debug ===\n");

    uart_printf("Timer1:\n");
    uart_printf("  TCCR1A: 0x%02X\n", TCCR1A);
    uart_printf("  TCCR1B: 0x%02X\n", TCCR1B);
    uart_printf("  OCR1A:  %u\n", OCR1A);
    uart_printf("  TCNT1:  %u\n", TCNT1);
    uart_printf("  TIMSK1: 0x%02X\n", TIMSK1);

    uart_printf("Timer0 (PWM):\n");
    uart_printf("  TCCR0A: 0x%02X (expect 0x83)\n", TCCR0A);
    uart_printf("  TCCR0B: 0x%02X (expect 0x03)\n", TCCR0B);
    uart_printf("  OCR0A:  %u\n", OCR0A);

    uart_printf("Timer2 (Tone):\n");
    uart_printf("  TCCR2A: 0x%02X\n", TCCR2A);
    uart_printf("  TCCR2B: 0x%02X\n", TCCR2B);
    uart_printf("  OCR2A:  %u\n", OCR2A);
}

8. Extensions & Challenges

8.1 Easy Extensions

  1. Multiple LED fade patterns: Breathe, pulse, strobe, heartbeat
  2. RGB LED color mixing: Three PWM channels for full color control
  3. Button-controlled brightness: Increment/decrement with buttons
  4. Alarm clock: Set time via UART, beep at alarm time

8.2 Intermediate Challenges

  1. Servo motor control: Generate 1-2ms pulses at 50 Hz using Timer1
  2. Music player: Parse simple notation (C4, D4, etc.) and play melodies
  3. Frequency counter: Measure input signal frequency using input capture
  4. Software PWM: Use Timer1 interrupt to PWM multiple pins

8.3 Advanced Challenges

  1. Sine wave DAC: Use PWM + low-pass filter to generate analog sine waves
  2. DDS (Direct Digital Synthesis): Generate arbitrary waveforms
  3. Motor PID control: Use timer for control loop, PWM for motor output
  4. Real-time scheduler: Use system tick to implement cooperative multitasking

9. Real-World Connections

9.1 Industry Applications

Application Timer Use Why It Matters
Automotive ECU PWM for fuel injectors Precise fuel metering
Motor controllers PWM at 20-50 kHz Efficient BLDC control
LED lighting PWM dimming Energy-efficient brightness
Audio equipment Timer for sample rate CD-quality: 44.1 kHz exactly
Industrial PLCs System tick Deterministic control loops
Servo controllers 50 Hz PWM Hobby and industrial servos

9.2 How This Connects to RTOS

Your Timer Tick:                  RTOS SysTick:
┌─────────────────┐              ┌─────────────────────────────────┐
│ Timer1 interrupt│              │ SysTick_Handler()               │
│ every 1 second  │              │ {                               │
│                 │              │     os_tick++;                  │
│ ISR: uptime++   │              │     scheduler_update();         │
│      tick_flag  │              │     if (preempt_needed)         │
│                 │              │         context_switch();       │
│                 │              │ }                               │
└─────────────────┘              └─────────────────────────────────┘

Your foundation:                  Enables:
- Periodic code execution         - Task scheduling
- Time measurement               - Timeouts and delays
- Non-blocking design            - Preemptive multitasking

9.3 Professional Code Comparison

// Your bare metal PWM (~50 bytes)
DDRD |= (1 << PD6);
TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS01) | (1 << CS00);
OCR0A = brightness;

// Arduino analogWrite (~200 bytes overhead)
analogWrite(6, brightness);
// Calls into library that:
// - Looks up pin mapping
// - Determines which timer
// - Calls timer-specific function
// - Adds abstraction overhead

// STM32 HAL PWM (~500+ bytes)
TIM_HandleTypeDef htim;
HAL_TIM_PWM_Init(&htim);
HAL_TIM_PWM_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, brightness);

Your code is:

  • Smaller
  • Faster
  • More predictable
  • Fully understood

10. Resources

10.1 Essential References

Resource Purpose Location
ATmega328P Datasheet Section 15 Timer0 details Microchip website
ATmega328P Datasheet Section 16 Timer1 details Microchip website
ATmega328P Datasheet Section 18 Timer2 details Microchip website
AVR Libc Reference Interrupt macros nongnu.org/avr-libc

10.2 Online Resources

Resource URL Purpose
AVR Timer Tutorial maxembedded.com/avr-timers Step-by-step guide
PWM Guide protostack.com.au/pwm PWM theory
Music Note Frequencies pages.mtu.edu/~suits/notefreqs.html Note to Hz table
AVR Freaks Forum avrfreaks.net Community help

10.3 Musical Note Reference

Octave 4 (Middle C = C4):
Note    Frequency (Hz)    OCR2A (prescaler 256)
─────────────────────────────────────────────────
C4      261.63           119
C#4     277.18           112
D4      293.66           106
D#4     311.13           100
E4      329.63           94
F4      349.23           89
F#4     369.99           84
G4      392.00           79
G#4     415.30           74
A4      440.00           70
A#4     466.16           66
B4      493.88           62

Octave 5:
C5      523.25           59
D5      587.33           52
E5      659.26           47
G5      783.99           39
A5      880.00           34

11. Self-Assessment Checklist

Knowledge Verification

  • I can explain what a timer/counter register does
  • I understand the difference between Normal, CTC, and PWM modes
  • I can calculate OCR values for any frequency given clock and prescaler
  • I know how prescaler selection affects timing range and resolution
  • I understand why PWM creates analog-like voltage from digital output
  • I can explain timer interrupt flow (flag, enable, ISR, return)

Skill Verification

  • I can configure Timer1 for CTC mode from scratch
  • I can set up Timer0 for Fast PWM and control duty cycle
  • I can generate specific audio frequencies using Timer2
  • I can debug timer configuration by reading register values
  • I can calculate and verify timing accuracy

Confidence Check

  • I could add precise timing to any embedded project
  • I could implement motor control using PWM
  • I could troubleshoot timer-related bugs systematically
  • I could explain timer concepts in a technical interview
  • I understand how RTOS schedulers use timer interrupts

12. Completion Criteria

Required (Must complete all)

  1. 1-second LED blink using Timer1 interrupt
    • Not software delay
    • Exactly 60 blinks per minute (+/- 0.5)
  2. PWM LED control via UART
    • “pwm 0” turns LED off
    • “pwm 255” turns LED to maximum
    • “pwm 128” sets 50% brightness (verifiable with multimeter)
  3. Uptime counter
    • Displays elapsed seconds
    • Accurate within 0.1% over 1 minute
  4. Smooth LED fade animation
    • Visible breathing effect
    • No visible stepping or jumping

Bonus (Complete for deeper understanding)

  • Audio tone generation (verify 440 Hz with tuner)
  • Multiple tone frequencies / simple melody
  • Timer status display showing register values
  • Servo control demonstration (if hardware available)

Evidence of Completion

  • Video demonstrating LED fade and precise timing
  • Screenshot of UART output showing uptime
  • avr-size output showing code size (< 4KB)
  • (Optional) Oscilloscope capture of PWM waveform

Previous Project: P02 - UART Serial Communication

Next Project: P04 - Raspberry Pi Bare Metal


You now have precise timing, analog-like output, and audio capability - all without wasting CPU cycles! Hardware timers are fundamental to every embedded system, from simple toys to spacecraft. Next, we move to the ARM architecture with Raspberry Pi bare metal programming.