Lab 3: Timers, Pulse Width Modulation (PWM)
Helpful chapters from the ATmega324P Datasheet
-
- Pin Configurations – section 1.1, page 15
-
- 8-bit Timer/Counter0 with PWM – section 16.9, page 140
-
- 16-bit Timer/Counter1 with PWM – section 17.14, page 173
-
- 8-bit Timer/Counter2 with PWM and asynchronous operation – section 19.11, page 202
1. Pulse Width Modulation
PWM (Pulse Width Modulation) is a technique used to control the voltage applied to an electronic device by quickly switching it between ON and OFF states.
This rapid switching results in an average voltage determined by the time the signal stays ON versus the total cycle time — this ratio is called the duty cycle.
PWM allows digital control over analog signals — for instance:
- Dimming an LED
- Changing the color of RGB LEDs
- Setting the pitch of a buzzer
- Adjusting the speed of a motor
1.1. Operating Principle
The duty cycle is expressed as a percentage of the ON time relative to the total cycle:
The average voltage received by a device is:
PWM signals are typically generated by digital circuits and microcontrollers. The ATmega324 uses a counter that resets periodically and compares its value with a reference (OCRn
). When the counter exceeds the reference value, the PWM output toggles its state.
🛠️ PWM via software?
PWM can also be implemented in software using methods like:
- Bit-banging
- Timers with ISR
But for high-frequency PWM (e.g., kHz range for motors), hardware PWM is far more efficient, avoiding CPU overhead from frequent interrupts.
2. PWM on AVR Microcontrollers
In the previous lab, we saw that the ATmega324 has three timers:
Timer0
(8-bit)Timer1
(16-bit)Timer2
(8-bit)
Each timer can be configured via the control registers TCCRnA
and TCCRnB
, using the WGMnx
bits to select the mode:
- Normal
- CTC (Clear Timer on Compare Match)
- Fast PWM (used today!)
- Phase Correct PWM, etc.
Each timer has two output compare channels: OCnA
and OCnB
. These are tied to specific physical pins. Check the Pin Configurations chapter of the datasheet for exact mappings:
Today we’ll focus on Fast PWM, a common mode suitable for general applications like LED dimming and motor speed control.
Fast PWM
In Fast PWM mode, the timer counts only on the rising edge of the clock signal. The duty cycle changes take effect immediately, but the waveform is not centered — a shift or glitch may appear when changing the duty cycle significantly.
Several Fast PWM modes are available for different timers. For Timer1, you have:
- Fast PWM, 8-bit: TOP = 0x00FF
- Fast PWM, 9-bit: TOP = 0x01FF
- Fast PWM, 10-bit: TOP = 0x03FF
- Fast PWM with TOP in
ICR
- Fast PWM with TOP in
OCRnA
In this lab, we’ll use only Fast PWM 8-bit mode. Refer to the datasheet for other timer-specific modes and features.
Suppose Timer1 is configured in Fast PWM mode. This mode has a fixed frequency and allows the duty cycle (threshold) to be modified during execution.
- With mode
10
forCOM1A1:COM1A0
, the signal on pinOC1A
stays HIGH during counting up to the threshold, and LOW afterwards until the end of the cycle. - To get a duty cycle of
x%
, set:
OCR1A = x * TOP / 100
whereTOP
is 255 (8-bit), 511 (9-bit), or 1023 (10-bit), depending on configuration.
Example: Timer1 in Fast PWM 8-bit, non-inverting mode with 1024 prescaler
PWM frequency ≈ 12 MHz / 1024 / 255 ≈ 45 Hz
/* OC1A is PD5, must be configured as OUTPUT */
DDRB |= (1 << PD5);
/* Select Fast PWM 8-bit mode: WGM[3:0] = 0b0101 */
/* WGM10 and WGM11 -> TCCR1A; WGM12 and WGM13 -> TCCR1B */
TCCR1A = (1 << WGM10);
TCCR1B = (1 << WGM12);
/* Set non-inverting mode on OC1A: COM1A[1:0] = 0b10 */
TCCR1A |= (1 << COM1A1);
/* Set prescaler to 1024: CS1[2:0] = 0b101 */
TCCR1B |= (1 << CS12) | (1 << CS10);
/* Set 50% duty cycle (TOP = 255, so OCR1A = 127) */
OCR1A = 127;
Phase Correct PWM
Although we won’t use this in the lab, it’s useful to understand how Phase Correct PWM differs from Fast PWM — it offers better accuracy, often required for BLDC motors or audio.
Key difference: the counter counts up from BOTTOM to TOP, then back down to BOTTOM.
- Output is set based on comparison with
OCRnx
, just like Fast PWM. - However,
OCRnx
remains constant during the full cycle, resulting in stable ON durations and cleaner signals. - This avoids frequency shifts (jitter) — important for motors, which can lose efficiency due to inconsistent PWM.
Comparison: Fast PWM vs Phase Correct PWM
3. Exercises
- Lab Work
- Homework
The goal of these exercises is to control the color of an RGB LED using PWM. You can produce any color by adjusting the brightness of each diode (Red, Green, Blue) independently!
The RGB LED pins are connected as follows:
- Red: pin
PD5
, functionOC1A
(linked to Timer1) - Green: pin
PD7
, functionOC2A
(linked to Timer2) - Blue: pin
PB3
, functionOC0A
(linked to Timer0)
The RGB LED is wired with common anode / "active-low" configuration — the LED turns ON when the pin is set to LOW, and OFF when set to HIGH.
Task 0
- Download and run the starter project ZIP.
- What do you observe on the board?
- Check the serial monitor — do you remember how timing was handled in the previous lab?
- How does the LED behave? Which timer is used and in what mode?
- Where is the intensity updated? What input does the formula use, and where is the output applied?
Task 1
- Let’s make the blue LED behave similarly.
- Use Timer0 (
OC0A
onPB3
) in Fast PWM mode. - Be sure to check Section 12.9 in the ATmega324P Datasheet for proper register usage — Timer0's registers differ from Timer1's!
- Make the blue LED pulse faster than red, and in parallel.
- What do you notice?
- Use Timer0 (
Task 2
- Time to turn on the green LED!
- Challenge:
PD7
(OC2A) is used by Timer2, which is already used formillis()
system ticks... - Solution: We’ll generate PWM manually using interrupts:
- Timer2 counts from 0 to 188 and triggers an interrupt via
COMPA
(to incrementsysticks
). - Timer2’s second comparator
COMPB
triggers an interrupt at a value between 0–188. - In
COMPA ISR
: turn ON green LED → set GPIO LOW. - In
COMPB ISR
: turn OFF green LED → set GPIO HIGH. - Duty cycle is given by
OCR2B
.
- Timer2 counts from 0 to 188 and triggers an interrupt via
- Challenge:
Keep OCR2A = 188
to retain proper millis()
timing.
Task 3
- Cycle through all colors using HSV color space:
- Convert HSV → RGB using provided
convert_HSV_to_RGB()
function. - Let
Hue
animate over time, keepSaturation = 1
,Value = 1
(for full brightness). - Use system ticks to animate the color smoothly, just like LED pulsing.
- Make sure to disable any conflicting code that modifies LED intensity.
- Convert HSV → RGB using provided
Task 4 (Bonus)
- Use the speaker connected to
PD4
(alsoOC1B
) to play a melody from thesound.c
module.surprise_notes[]
contains frequencies.durations[]
stores note durations.- In
update_notes()
, reconfigure Timer1 each time a new note is played.- Use CTC mode (
OCR1A
sets the TOP → controls frequency). - Set
OCR1B = OCR1A / 2
for 50% duty cycle (square wave). - Call
update_notes()
every 25ms usingsysticks
. - Reset
TCNT1 = 0
after changingOCR1A
to avoid timing delays.
- Use CTC mode (
Bonus 2
Combine:
- RGB animation (HSV cycling) and
- Sound playback (melody via speaker)
— both running simultaneously and smoothly.
From now on you will be working with Wokwi because its easier to import libraries. The 📥homework skeleton will be the same for all the labs. You don't need to modify the circuit for the homework. You will only change the .c and .h files. The connections to the peripherals wil be different from the ones in the lab development board. For the homework you will work with Arduino Uno (ATmega328P).
Make sure to read the README.md
before solving the exercises.
The goal of these exercises is to control the color of an RGB LED using PWM. You can produce any color by adjusting the brightness of each diode (Red, Green, Blue) independently!
The RGB LED pins are connected as follows:
- Red: pin
PD6
, functionOC0A
(linked to Timer1) - Green: pin
PD5
, functionOC0B
(linked to Timer0) - Blue: pin
PB3
, functionOC2B
(linked to Timer2)
The RGB LED is wired with common anode / "active-low" configuration — the LED turns ON when the pin is set to LOW, and OFF when set to HIGH.
Task 0
- Clone the homework skeleton and readme before starting the exercises. (When running the simulation, make sure to use the switches to use the RGB LED and not the LCD)
Task 1
- Let’s make the RED LED behave similarly.
- Use Timer0 (
OC0A
on PD6) in Fast PWM mode. - Be sure to check Section 12.9 in the ATmega324P Datasheet for proper register usage — Timer0's registers differ from Timer1's!
- Make the blue LED pulse faster than red, and in parallel.
- What do you notice?
- Use Timer0 (
Task 2
- Time to turn on the BLUE LED!
- Challenge:
PD3
(OC2B
) is used by Timer2, which is already used formillis()
system ticks... - Solution: We’ll generate PWM manually using interrupts:
- Timer2 counts from 0 to 188 and triggers an interrupt via
COMPA
(to incrementsysticks
). - Timer2’s second comparator
COMPB
triggers an interrupt at a value between 0–188. - In
COMPA ISR
: turn ON blue LED → set GPIO LOW. - In
COMPB ISR
: turn OFF blue LED → set GPIO HIGH. - Duty cycle is given by
OCR2B
.
- Timer2 counts from 0 to 188 and triggers an interrupt via
- Challenge:
Keep OCR2A = 188
to retain proper millis()
timing.
Task 3
- Cycle through all colors using HSV color space:
- Convert HSV → RGB using provided
convert_HSV_to_RGB()
function. - Let
Hue
animate over time, keepSaturation = 1
,Value = 1
(for full brightness). - Use system ticks to animate the color smoothly, just like LED pulsing.
- Make sure to disable any conflicting code that modifies LED intensity.
- Convert HSV → RGB using provided
Task 4 (Bonus)
- Use the speaker connected to
PB1
(alsoOC1A
) to play a melody from thesound.c
module.surprise_notes[]
contains frequencies.durations[]
stores note durations.- In
update_notes()
, reconfigure Timer1 each time a new note is played.- Use CTC mode (
OCR1A
sets the TOP → controls frequency). - Set
OCR1B = OCR1A / 2
for 50% duty cycle (square wave). - Call
update_notes()
every 25ms usingsysticks
. - Reset
TCNT1 = 0
after changingOCR1A
to avoid timing delays.
- Use CTC mode (
Bonus 2
Combine:
- RGB animation (HSV cycling) and
- Sound playback (melody via speaker)
— both running simultaneously and smoothly.