By

Fast PWM on ATmega328, up to 8MHz

sacrificing duty cycle resolution to get higher frequency

A couple of days earlier, a friend asked me how he could get fast PWM from an Atmel ATmega328 microcontroller —fast as in over 62.5KHz. Surpisingly I couldn't find a working code example, despite the fact there are many articles and forum posts about this. Of course one may always refer to the uC's datasheet, but the code part isn't always straightforward.

So, without delay here is a sample code you can load to your Arduino Uno or directly to an AVR, which will give you a 250KHz, 6 bit resolution PWM on pin 3 (ATmega pin 5) and a 8MHz, 1 bit resolution —thus only 50% duty cycle— on pin 5 (ATmega pin 11). The duty cycle of the 250KHz PWM is rolling.

// A sketch that creates an 8MHz, 50% duty cycle PWM and a 250KHz,
// 6bit resolution PWM with varying duty cycle (changes every 5μs
// or about every period.

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
  pinMode(3, OUTPUT); // output pin for OCR2B
  pinMode(5, OUTPUT); // output pin for OCR0B

  // Set up the 250KHz output
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(WGM22) | _BV(CS20);
  OCR2A = 63;
  OCR2B = 0;

  // Set up the 8MHz output
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
  TCCR0B = _BV(WGM02) | _BV(CS00);
  OCR0A = 1;
  OCR0B = 0;

  // Make the 250KHz rolling
  while (1) {
  _delay_us(5);
  if ( OCR2B < 63 )
    OCR2B += 5;
  else
    OCR2B = 0;
  }
}

So how does this code work? Let's talk about the uC for a bit first.

Fast PWM

The ATmega328 has 3 counters and usually runs at 16MHz. We will focus on counters #0 and #2 —counter #1 is a bit different.

A PWM waveform is generated from a counter by counting clock ticks, a register and a comparator. The counter's purpose is to create the duty cycle resolution. One complete cycle of the counter is one period of your PWM. In most tutorials this is referred as the second clock division during PWM generation. The counter's size (and thus the duty cycle resolution) is 8 bits, which equals to 256 values. So from the get-go, your 16MHz clock gets divided by 256 —because a cycle of the counter takes 256 ticks— and may give a maximum frequency of 62.5KHz. In order to get lower frequencies, you can divide your clock by another factor, known as the first clock division. This is also called pre-scaling, because it precedes the counter.

The register and the comparator are used to set and create the duty cycle output. The register sets for how long during each period the output is high. For example for a 25% duty cycle, you would set the register to 256 * 0.25 - 1 = 63. The comparator compares the register's value with the current counter value and gives high in the output if the value of the former is equal or lower from the value of the latter and low otherwise.

For each counter you actually get 2 registers and 2 comparators, so you can get 2 PWM waveforms with different duty cycles but with the same frequency.

If you managed to read until here, you learnt how the normal fast PWM mode of the ATmega328 works. In the datasheet it is referred to as waveform generation mode 3.

Faster than Fast

It should be clear by now that in order to get above 62.5KHz you have to sacrifice something; duty cycle resolution. You also have to give up one PWM output per counter. Enter waveform generation mode 7.

What happens in this mode, is that you use one register and one comparator to set the top value of the counter. By controlling when your 8bit counter resets, you effectively set the frequency —remember, one cycle of the counter is one period of your PWM. Also you lower your duty cycle resolution since it depends on how may discrete values your counter has in a period. The second register and comparator pair works as always, generating the PWM output. An obvious requisite, is that the second register should be lower or equal to the first register.

Code Explained

Here is how we got the 250KHz waveform from timer/counter #2. The counter has control registers TCCR2A and TCCR2B which hold flags that set its mode of operation. We show only the settings for fast PWM. Also it has registers OCR2A and OCR2B, the function of which we described in the previous section (remember registers and comparators).

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
  pinMode(3, OUTPUT); // output pin for OCR2B, this is Arduino pin number

  // In the next line of code, we:
  // 1. Set the compare output mode to clear OC2A and OC2B on compare match.
  //    To achieve this, we set bits COM2A1 and COM2B1 to high.
  // 2. Set the waveform generation mode to fast PWM (mode 3 in datasheet).
  //    To achieve this, we set bits WGM21 and WGM20 to high.
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);

  // In the next line of code, we:
  // 1. Set the waveform generation mode to fast PWM mode 7 —reset counter on
  //    OCR2A value instead of the default 255. To achieve this, we set bit
  //    WGM22 to high.
  // 2. Set the prescaler divisor to 1, so that our counter will be fed with
  //    the clock's full frequency (16MHz). To achieve this, we set CS20 to
  //    high (and keep CS21 and CS22 to low by not setting them).
  TCCR2B = _BV(WGM22) | _BV(CS20);

  // OCR2A holds the top value of our counter, so it acts as a divisor to the
  // clock. When our counter reaches this, it resets. Counting starts from 0.
  // Thus 63 equals to 64 divs.
  OCR2A = 63;
  // This is the duty cycle. Think of it as the last value of the counter our
  // output will remain high for. Can't be greater than OCR2A of course. A
  // value of 0 means a duty cycle of 1/64 in this case.
  OCR2B = 0;

  // Just some code to change the duty cycle every 5 microseconds.
  while (1)
  {
    _delay_us(5);
    if ( OCR2B < 63 )
      OCR2B += 5;
    else
      OCR2B = 0;
  }
}

Everyone loves images

This is how our PWMs look on a logic analyzer.

250KHz with rolling duty cycle:

8MHz, logic analyzer samples at 24MHz, so it can't detect accurately the duty cycle:

Attribution

As stated there are many articles and forum posts about the subject. The one I found really useful and have to give credit is this. Of course do not forget to read your microcontroller's datasheet.

That's all folks

The reason my friend needed that fast PWM is interesting on its own and I hope he will find some time to write about it in the future.

comments powered by Disqus