Focused on Your Needs
4. Pulse-width Modulation (PWM) Generation
This tutorial is to introduce PWM and show how to generate PWM signal from the AVR microcontroller.
PWM is a way to represent percentage from digital signals by adjusting HIGH & LOW timings. For example, if a sequence of PWM signal is 2ms in HIGH and 8ms in LOW, then the PWM has a period of 10ms and it could represent 20% of something, such as speed of a device or brightness of a LED.
There are two main types of waveform generation modes:
Phase correct PWM: the pulse starts at the midpoint of the period because the timer counts up and then counts down
Fast PWM: the pulse starts at the end of each period because the timer counts up and then resets to 0
Example 1: Set to the fast PWM mode
TCCR0A |= 1 << WGM00 | 1 <<WGM01; //Put in Fast mode
In Fast PWM mode, the PWM signal is generated on the OC0x pins.
Assume the clock frequency is 1MHz and an 8-bit counter would count up to 256. If the prescaler is set to 8, the PWM frequency would be 1,000,000/256/8 = 488Hz.
Example 2: Set the prescaler to 8
TCCR0B |= 1<< CS01; //Set the prescaler to 8
In inverting mode, the PWM signal starts LOW at BOTTOM, and HIGH when the counter passes the value set in OCR0x; on the other hand, in non-inverting mode, the PWM signal starts HIGH at BOTTOM. The OC0x pin has to be set to output in order to perform the PWM function.
Example 3: Fast PWM modes on OC0A/B
COM0A/B[1] COM0A/B[0] Description
0 0 Normal port operation, OC0A/B disconnected
0 1 WGM0[2:0]: Normal port operation, OC0A disconnected; WGM0[2:1]: Toggle OC0A on compare match (only for OC0A)
1 0 Clear OC0A/B on compare match, set OC0A/B at BOTTOM (Non-inverting mode)
1 1 Set OC0A/B on compare match, clear OC0A/B at BOTTOM (Inverting mode)
.
Example 4: Set to the inverting mode
TCCR0A |= 1<< COM0A0 | 1<< COM0A1; //Set to inverting mode
Example 5: Set the PWM signal to 25% HIGH
OCR0A = 191;//Pulse starts at 191 when the total count is 255 (counts from 0)
Example 6: Full code to generate PWM signal with 25% HIGH from OC0A pin
#include <avr/io.h>
int main(void)
{
DDRD |= 1 << 6; //Set OC0A pin (PD6 in ATmega328P) as an output
TCCR0A |= 1 << WGM00 | 1 <<WGM01; //Put in Fast mode
TCCR0A |= 1<< COM0A0 | 1<< COM0A1; //Set to inverting mode
TCCR0B |= 1<< CS01; //Set the prescaler to 8
OCR0A = 191; //Pulse starts at 191 when the total count is 255 (counts from 0)
while (1) {}
}
Example 7: Full code to generate 8-bit PWM signal with 25% HIGH from OC0A pin and 75% HIGH from OC0B pin
#include <avr/io.h>
#include
int main(void)
{
DDRD |= 1 << 5 | 1 << 6; //Set OC0A and OC0B pin (ATmega328P) as an output
TCCR0A |= 1 << WGM00 | 1 <<WGM01; //Put in Fast mode
TCCR0A |= 1<< COM0A0 | 1<< COM0A1; //Set to inverting mode on OC0A
TCCR0A |= 1<< COM0B0 | 1<< COM0B1; //Set to inverting mode on OC0B
TCCR0B |= 1<< CS01; //Set the prescaler to 8
OCR0A = 191; //Pulse starts at 191 when the total count is 255 (counts from 0)
OCR0B = 63; //Pulse starts at 63
while (1) {}
}
Example 8 (extended): Full code to generate 16-bit PWM signal with 25% HIGH from OC1A pin and 75% HIGH from OC1B pin
#include <avr/io.h>
int main(void)
{
DDRB |= 1 << 1 | 1 << 2; //Set OC1A and OC1B pins (PB1 and PB2 in ATmega328P) as an output
TCCR1A |= 1 << WGM11; //set fast PWM Mode with TOP at ICR1
TCCR1B |= 1 << WGM12 | 1 << WGM13; //set fast PWM Mode with TOP at ICR1
TCCR1A |= 1 << COM1A1 | 1 << COM1A0; //Set to inverting mode on OC1A
TCCR1A |= 1 << COM1B1 | 1 << COM1B0; //Set to inverting mode on OC1B
TCCR1B |= 1 << CS10; //Set the prescaler to 1
ICR1 = 1999; // Set TOP value, the PWM frequency is 1,000,000/2000 = 500 Hz
OCR1A = ICR1 - 1500; //Pulse goes up after 500 counts
OCR1B = ICR1 - 500; //Pulse goes up after 1500 counts
while (1) {}
}
Example 9 (extended): Full code to generate 16-bit PWM signal from mutiple I/O digital outputs
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void)
{
DDRD |= 0b00001111; //Set PD0-PD3 outputs
TCCR1A |= 1 << WGM11; //set fast PWM Mode with TOP at ICR1
TCCR1B |= 1 << WGM12 | 1 << WGM13; //set fast PWM Mode with TOP at ICR1
TCCR1B |= 1 << CS10; //Set the prescaler to 1
TIMSK1 |= 1 << OCIE1A; //Enable the interrupt when the counter gets to the TOP value
ICR1 = 1999; // Set TOP value, the PWM frequency is 1,000,000/2000 = 500 Hz
sei(); //Set the global interrupt
while (1)
{
if (bit_is_clear (PORTD,0))
if (TCNT1 >= 199)
PORTD |= 1<<0; //PD0 is 10% LOW and 90% HIGH
if (bit_is_clear (PORTD,1))
if (TCNT1 >= 399)
PORTD |= 1<<1; //PD1 is 20% LOW and 80% HIGH
if (bit_is_clear (PORTD,2))
if (TCNT1 >= 599)
PORTD |= 1<<2; //PD2 is 30% LOW and 70% HIGH
if (bit_is_clear (PORTD,3))
if (TCNT1 >= 799)
PORTD |= 1<<3; //PD3 is 40% LOW and 60% HIGH
}
}
ISR (TIMER1_COMPA_vect)
{
PORTD &= 0b11110000; //Set PD0-PD3 to LOW
}