ATmega328P Timer Configuration and Programming
Example: COM1A1 and COM1A0
| Part | Meaning | Example |
|---|---|---|
| COM | Compare Output Mode | Function type |
| 1 | Timer Number | Timer 1 |
| A | Channel | Channel A (or B) |
| 1 or 0 | Bit Number | Bit 1 or Bit 0 |
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| COM1A1 | COM1A0 | COM1B1 | COM1B0 | - | - | WGM11 | WGM10 |
// For normal operation with
// output pins disconnected:
// Set all bits to 0
TCCR1A = 0b00000000;
// Or equivalently:
TCCR1A = 0;
Timer1 has 16 different modes (0-15) controlled by WGM13:10 bits
| Mode | WGM13 | WGM12 | WGM11 | WGM10 | Description | TOP Value |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | Normal | 0xFFFF |
| 4 | 0 | 1 | 0 | 0 | CTC (Clear Timer on Compare) | OCR1A |
| 5 | 0 | 1 | 0 | 1 | Fast PWM, 8-bit | 0x00FF |
| 14 | 1 | 1 | 1 | 0 | Fast PWM | ICR1 |
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 |
// For CTC mode with 256 prescaler:
// WGM12 = 1 (bit 3)
// CS12 = 1 (bit 2)
TCCR1B = 0b00001100;
// Binary breakdown:
// Bit 3 (WGM12) = 1 → CTC mode
// Bit 2 (CS12) = 1 → 256 prescaler
| CS12 | CS11 | CS10 | Description | Division Factor |
|---|---|---|---|---|
| 0 | 0 | 0 | No clock (Timer stopped) | - |
| 0 | 0 | 1 | clk/1 (No prescaling) | 1 |
| 0 | 1 | 0 | clk/8 | 8 |
| 0 | 1 | 1 | clk/64 | 64 |
| 1 | 0 | 0 | clk/256 | 256 |
| 1 | 0 | 1 | clk/1024 | 1024 |
Essential for timing calculations with very large or small numbers:
| Number | Scientific Notation | Rule |
|---|---|---|
| 16,000,000 | 1.6 × 107 | Move decimal 7 places left |
| 125,000 | 1.25 × 105 | Move decimal 5 places left |
| 0.000000001 | 1 × 10-9 | Move decimal 9 places right |
T = 1 / f
T = 1 / 16,000,000 Hz
T = 1 / (16 × 106) s
T = 62.5 × 10-9 s = 62.5 ns
1 ÷ 16E6 =
6.25E-8
= 62.5 ns
Goal: Generate an interrupt every 500 ms (0.5 seconds)
Number of Clocks = 500 ms / 62.5 ns
= (500 × 10-3) / (62.5 × 10-9)
= 8,000,000 clocks
We need 8,000,000 clocks. Let's try different prescalers:
| Prescaler | Calculation | Result | Fits in 16-bit? |
|---|---|---|---|
| 1 | 8,000,000 ÷ 1 | 8,000,000 | ❌ Too large |
| 64 | 8,000,000 ÷ 64 | 125,000 | ❌ Too large |
| 256 | 8,000,000 ÷ 256 | 31,250 | ✅ Perfect! |
| 1024 | 8,000,000 ÷ 1024 | 7,812.5 | ⚠️ Not integer |
void setup() {
pinMode(8, OUTPUT);
Serial.begin(9600);
// Step 1: Disable interrupts
cli();
// Step 2: Configure TCCR1A
TCCR1A = 0b00000000;
// Step 3: Configure TCCR1B
// WGM12=1 (CTC), CS12=1 (256)
TCCR1B = 0b00001100;
// Step 4: Set compare match
OCR1A = 31250;
// Step 5: Reset counter
TCNT1 = 0;
// Step 6: Enable interrupt
TIMSK1 |= (1 << OCIE1A);
// Step 7: Enable interrupts
sei();
}
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| - | - | ICIE1 | - | - | OCIE1B | OCIE1A | TOIE1 |
// Enable Output Compare A interrupt
// OCIE1A is bit 1
// Method 1: Using bit shift
TIMSK1 |= (1 << OCIE1A);
// Method 2: Binary literal
TIMSK1 |= 0b00000010;
// Method 3: OR to preserve others
TIMSK1 = TIMSK1 | 0b00000010;
// ✅ GOOD: Set only bit 1
TIMSK1 |= (1 << OCIE1A);
// ✅ GOOD: Clear only bit 1
TIMSK1 &= ~(1 << OCIE1A);
// ❌ BAD: Overwrites everything
TIMSK1 = 0b00000010;
// This destroys any other bits!
// If you need to set bit 3:
// Shift 1 left by 3 positions
// Binary: 00001000
uint8_t mask = (1 << 3);
// Volatile for ISR-modified variables
volatile int ledState = LOW;
volatile unsigned int intCount = 0;
// Timer1 Compare Match A ISR
ISR(TIMER1_COMPA_vect) {
// Toggle LED state
ledState = !ledState;
// Write to LED pin
digitalWrite(8, ledState);
// Count interrupts
intCount++;
}
// Vector names from avr/iom328p.h:
// TIMER1_COMPA_vect
// TIMER1_COMPB_vect
// TIMER1_OVF_vect
Common Bug: Confusing interrupt rate with toggle rate!
Must manually reset: TCNT1 = 0;
Counter resets automatically! ✅
// Enable CTC mode
// Set WGM12 (bit 3) in TCCR1B
// Without CTC - need manual reset:
TCCR1B = 0b00000100; // Just prescaler
ISR(TIMER1_COMPA_vect) {
TCNT1 = 0; // Manual reset needed!
// ... rest of ISR
}
// With CTC - automatic reset:
TCCR1B = 0b00001100; // CTC + prescaler
ISR(TIMER1_COMPA_vect) {
// No reset needed!
// Counter resets automatically
}
Blink LED every 500ms using Timer1 interrupt:
#define LED_PIN 8
volatile int ledState = LOW;
volatile unsigned int intCount = 0;
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
cli(); // Disable interrupts
TCCR1A = 0; // Normal port operation
TCCR1B = 0; // Clear register
// Set CTC mode + 256 prescaler
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS12);
OCR1A = 31250; // Compare value
TCNT1 = 0; // Reset counter
TIMSK1 |= (1 << OCIE1A); // Enable int
sei(); // Enable interrupts
}
void loop() {
Serial.print("Interrupts: ");
Serial.println(intCount);
delay(1000);
}
ISR(TIMER1_COMPA_vect) {
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
intCount++;
}
| OCR1A Value | Interrupt Rate | LED Blink Rate |
|---|---|---|
| 31,250 | Every 500ms | 1 Hz |
| 3,125 | Every 50ms | 10 Hz |
| 312 | Every 5ms | 100 Hz |
// Original: 500ms interrupt
OCR1A = 31250;
// 10x faster: 50ms interrupt
OCR1A = 31250 / 10; // = 3125
// 100x faster: 5ms interrupt
OCR1A = 31250 / 100; // = 312
// Formula:
// OCR1A = (F_CPU / Prescaler)
// * (delay_ms / 1000) - 1
// For 500ms:
// = (16000000 / 256) * 0.5 - 1
// = 62500 * 0.5 - 1 = 31249 ≈ 31250
| Register | Purpose | Key Bits |
|---|---|---|
| TCCR1A | Timer Control Register A | COM1A1:0, COM1B1:0, WGM11:10 |
| TCCR1B | Timer Control Register B | WGM13:12, CS12:10 (prescaler) |
| TCNT1 | Timer Counter (16-bit) | Current count value |
| OCR1A | Output Compare Register A | Compare match value |
| TIMSK1 | Timer Interrupt Mask | OCIE1A, OCIE1B, TOIE1 |
| TIFR1 | Timer Interrupt Flag | OCF1A, OCF1B, TOV1 |
// ❌ WRONG: Non-volatile variable
int counter = 0;
// ✅ CORRECT: Volatile for ISR
volatile int counter = 0;
// ❌ WRONG: Long ISR
ISR(TIMER1_COMPA_vect) {
delay(100); // Never!
Serial.println(); // Avoid!
}
// ✅ CORRECT: Short ISR
ISR(TIMER1_COMPA_vect) {
ledState = !ledState;
PORTB ^= (1 << PB0); // Fast!
}
Don't want to manipulate registers directly? Use libraries!
// Using TimerOne library
#include <TimerOne.h>
void setup() {
pinMode(8, OUTPUT);
// Initialize timer with 500ms period
Timer1.initialize(500000); // microseconds
// Attach interrupt function
Timer1.attachInterrupt(blinkLED);
}
void loop() {
// Empty - all done by timer!
}
void blinkLED() {
digitalWrite(8, !digitalRead(8));
}
// Debug: Print register values
void printRegisters() {
Serial.print("TCCR1A: ");
Serial.println(TCCR1A, BIN);
Serial.print("TCCR1B: ");
Serial.println(TCCR1B, BIN);
Serial.print("OCR1A: ");
Serial.println(OCR1A);
Serial.print("TIMSK1: ");
Serial.println(TIMSK1, BIN);
}
// Debug: Verify ISR timing
ISR(TIMER1_COMPA_vect) {
PORTB |= (1 << PB1); // Pin high
// ... your code ...
PORTB &= ~(1 << PB1); // Pin low
}
From precise timing to analog-like output control
Pulse Width Modulation rapidly switches a digital pin ON and OFF to simulate an analog voltage.
Timer1 supports several PWM modes via the WGM13:10 bits:
| Mode | WGM13:10 | Name | TOP | Best For |
|---|---|---|---|---|
| 5 | 0101 | Fast PWM, 8-bit | 0x00FF | Quick & simple |
| 6 | 0110 | Fast PWM, 9-bit | 0x01FF | More resolution |
| 7 | 0111 | Fast PWM, 10-bit | 0x03FF | Smooth fading |
| 14 | 1110 | Fast PWM | ICR1 | Custom frequency ⭐ |
| 15 | 1111 | Fast PWM | OCR1A | Custom freq (1 channel) |
| 8 | 1000 | Phase Correct | ICR1 | Motor control |
| 10 | 1010 | Phase Correct | OCR1A | Smooth motor |
TCNT1) counts 0 → TOP (value in ICR1)TCNT1 == OCR1A: output pin cleared (goes LOW)TCNT1 reaches TOP: counter resets to 0 and pin is set (goes HIGH)| Bit | Register | Value |
|---|---|---|
| WGM13 | TCCR1B bit 4 | 1 |
| WGM12 | TCCR1B bit 3 | 1 |
| WGM11 | TCCR1A bit 1 | 1 |
| WGM10 | TCCR1A bit 0 | 0 |
Plus COM1A1:0 = 10 for non-inverting output on OC1A (pin 9).
// Fast PWM Mode 14 — ICR1 as TOP
// Non-inverting output on OC1A (pin 9)
TCCR1A = (1 << COM1A1) // Non-inverting
| (1 << WGM11); // WGM11 = 1
TCCR1B = (1 << WGM13) // WGM13 = 1
| (1 << WGM12) // WGM12 = 1
| (1 << CS11); // Prescaler = 8
// WGM13:12:11:10 = 1 1 1 0 → Mode 14 ✓
// CS12:11:10 = 0 1 0 → clk/8 ✓
// Set frequency via ICR1 (TOP)
ICR1 = 39999; // example
// Set duty cycle via OCR1A
OCR1A = 19999; // 50% duty
In Fast PWM mode, the COM1A1:0 bits control the OC1A pin:
| COM1A1 | COM1A0 | OC1A Behaviour |
|---|---|---|
| 0 | 0 | Normal port operation (OC1A disconnected) |
| 0 | 1 | Toggle on Compare Match (Mode 15 only) |
| 1 | 0 | Non-inverting: clear on match, set at BOTTOM ⭐ |
| 1 | 1 | Inverting: set on match, clear at BOTTOM |
Rearranging to find TOP (the value we put in ICR1):
TOP = (16 000 000 / (8 × 50)) − 1 = 39 999
→ ICR1 = 39999;
| Desired | OCR1A value |
|---|---|
| 0 % (off) | 0 |
| 25 % | ICR1 / 4 |
| 50 % | ICR1 / 2 |
| 75 % | ICR1 * 3 / 4 |
| ~100 % | ICR1 |
// Example: 50% duty cycle
ICR1 = 39999; // TOP → sets frequency
OCR1A = ICR1 / 2; // 50% duty
// Fade an LED from 0% to 100%
for (uint16_t i = 0; i <= ICR1; i += 400) {
OCR1A = i;
delay(20);
}
Drag the OCR1A slider to see how it changes the duty cycle and output waveform:
analogWrite(9, value) — same result but we control the exact frequency!
#define LED_PIN 9 // OC1A
void setup() {
pinMode(LED_PIN, OUTPUT);
// Fast PWM Mode 14, non-inverting, prescaler 8
TCCR1A = (1 << COM1A1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12)
| (1 << CS11);
ICR1 = 39999; // ~50 Hz
OCR1A = 0; // Start at 0% duty
}
void loop() {
// Fade up
for (uint16_t i = 0; i <= ICR1; i += 400) {
OCR1A = i;
delay(20);
}
// Fade down
for (int32_t i = ICR1; i >= 0; i -= 400) {
OCR1A = (uint16_t)i;
delay(20);
}
}
Standard tuning — 4th octave (middle C upward):
| Note | Frequency (Hz) | ICR1 (prescaler 8) | OCR1A (50 %) |
|---|---|---|---|
| C4 | 261.63 | 7644 | 3822 |
| D4 | 293.66 | 6810 | 3405 |
| E4 | 329.63 | 6067 | 3033 |
| F4 | 349.23 | 5726 | 2863 |
| G4 | 392.00 | 5102 | 2551 |
| A4 | 440.00 | 4544 | 2272 |
| B4 | 493.88 | 4049 | 2024 |
| Note | Calculation | ICR1 |
|---|---|---|
| C4 (261.63 Hz) | 16 000 000 / (8 × 261.63) − 1 | 7644 |
| E4 (329.63 Hz) | 16 000 000 / (8 × 329.63) − 1 | 6067 |
| G4 (392.00 Hz) | 16 000 000 / (8 × 392.00) − 1 | 5102 |
For 50 % duty cycle: OCR1A = ICR1 / 2
playNote() — start a tonestopNote() — silence#define BUZZER_PIN 9 // OC1A
void playNote(uint16_t top) {
// Fast PWM Mode 14, non-inverting OC1A
TCCR1A = (1 << COM1A1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12)
| (1 << CS11); // prescaler 8
ICR1 = top; // Set frequency
OCR1A = top / 2; // 50% duty cycle
}
void stopNote() {
// Disconnect OC1A (normal port operation)
TCCR1A = 0;
TCCR1B = 0;
digitalWrite(BUZZER_PIN, LOW);
}
Using our helper functions, we can play C4 → E4 → G4:
| Note | Frequency | ICR1 | Duration |
|---|---|---|---|
| C4 | 261.63 Hz | 7644 | 500 ms |
| E4 | 329.63 Hz | 6067 | 500 ms |
| G4 | 392.00 Hz | 5102 | 500 ms |
stopNote() + delay) prevents them from blurring together.
#define BUZZER_PIN 9
#define NOTE_C4 7644
#define NOTE_E4 6067
#define NOTE_G4 5102
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
}
void loop() {
playNote(NOTE_C4); // C4
delay(500);
stopNote();
delay(100); // short silence
playNote(NOTE_E4); // E4
delay(500);
stopNote();
delay(100);
playNote(NOTE_G4); // G4
delay(500);
stopNote();
delay(800); // pause before repeat
}
// playNote() and stopNote() defined below
void playNote(uint16_t top) {
TCCR1A = (1 << COM1A1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12)
| (1 << CS11);
ICR1 = top;
OCR1A = top / 2;
}
void stopNote() {
TCCR1A = 0;
TCCR1B = 0;
digitalWrite(BUZZER_PIN, LOW);
}
Build a 3-note piano using Timer1 Fast PWM and a piezo buzzer
⏱️ Estimated time: 1.5 – 2 hours
📋 3 tasks + 1 bonus challenge
| Component | Arduino Pin | Notes |
|---|---|---|
| Buzzer (+) | Pin 9 (OC1A) | Timer1 PWM output |
| Buzzer (−) | GND | |
| Button 1 (C4) | Pin 2 | INPUT_PULLUP |
| Button 2 (E4) | Pin 3 | INPUT_PULLUP |
| Button 3 (G4) | Pin 4 | INPUT_PULLUP |
Configure Timer1 in Fast PWM Mode 14 to play C4 (261.63 Hz) on a piezo buzzer connected to pin 9.
Fill in the blanks in the code template →
#define BUZZER 9 // OC1A pin
void setup() {
pinMode(BUZZER, OUTPUT);
// Fast PWM Mode 14, non-inverting OC1A
TCCR1A = (1 << _____) | (1 << _____);
TCCR1B = (1 << _____) | (1 << _____)
| (1 << _____); // prescaler 8
// Set TOP for C4 (261.63 Hz)
ICR1 = _____; // ← calculate this!
// 50% duty cycle
OCR1A = _____; // ← half of ICR1
}
void loop() {
// Nothing needed — hardware generates
// the tone continuously!
}
Write a function playNote(float frequency) that:
Then play C4 → E4 → G4 in loop(), 500 ms each note, 100 ms silence between.
stopNote() to silence the buzzer between notes!
| Note | Hz |
|---|---|
| C4 | 261.63 |
| E4 | 329.63 |
| G4 | 392.00 |
#define BUZZER 9
void playNote(float freq) {
uint16_t top = _____________ ; // calculate!
// Configure Timer1 Mode 14...
// Set ICR1 and OCR1A...
}
void stopNote() {
// Stop the timer and silence buzzer
}
void setup() {
pinMode(BUZZER, OUTPUT);
}
void loop() {
playNote(261.63); // C4
delay(500);
stopNote();
delay(100);
playNote(329.63); // E4
delay(500);
stopNote();
delay(100);
playNote(392.00); // G4
delay(500);
stopNote();
delay(800); // longer pause
}
Connect 3 push buttons (pins 2, 3, 4) — each button plays a different note:
| Button | Pin | Note |
|---|---|---|
| Button 1 | 2 | C4 (261.63 Hz) |
| Button 2 | 3 | E4 (329.63 Hz) |
| Button 3 | 4 | G4 (392.00 Hz) |
Behaviour:
#define BUZZER 9
#define BTN_C 2
#define BTN_E 3
#define BTN_G 4
void setup() {
pinMode(BUZZER, OUTPUT);
pinMode(BTN_C, INPUT_PULLUP);
pinMode(BTN_E, INPUT_PULLUP);
pinMode(BTN_G, INPUT_PULLUP);
}
void loop() {
// INPUT_PULLUP → LOW when pressed
if (digitalRead(BTN_C) == LOW) {
playNote(261.63);
}
else if (digitalRead(BTN_E) == LOW) {
playNote(329.63);
}
else if (digitalRead(BTN_G) == LOW) {
playNote(392.00);
}
else {
stopNote();
}
}
// Reuse your playNote() & stopNote()
// from Task B!
Using your playNote() function, play the first phrase of the melody:
| Beat | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Note | C4 | C4 | G4 | G4 | A4 | A4 | G4 |
| Hz | 261.63 | 261.63 | 392.00 | 392.00 | 440.00 | 440.00 | 392.00 |
| Duration | 400 ms | 400 ms | 400 ms | 400 ms | 400 ms | 400 ms | 800 ms |
Hints:
volatile for ISR variables?Key Takeaways: