top of page

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                                 

}




©2020 by Pulin Global

bottom of page