Customized PWM Frequency

Hello Arduino community,

I wonder if I can customize the PWM frequency of Arduino. By searching the Internet, I can only find some posts teaching you to divide the clock speed by some power of 2. However, let's say I want the PWM frequency to be some arbitrary number like 130kHz, 250kHz, 380kHz, etc. Is that possible for Arduino? Or, I can only achieve that by using a more advanced microcontroller (for example, from Texas Instruments)?

Thanks for any reply!

You certainly can!

Here is an example of code I wrote for 20 hz pwm.
While it looks complicated it was rather easy following the article above,

/* ================= HEADER ==================
   PROJECT: Treadmill speed Control
            Using a MC-2100 controller that requires a 20 hz phase correct PWM signal with a 0-85% dutycycle

   VERSION: 0.0.1

   IDE VERSION: 1.8.9

   HARDWARE: Nano (328p)
             Potentiometer
             MC-2100 REV.B Motor Controller
             Treadmill Motor
*/

// ================= CONSTANTS ================
constexpr uint8_t PWM_PIN = 10;
constexpr uint16_t SPEED_POT = A0;

// ================= VARIABLES ================
uint16_t dutyCycle;


// ================================================================
// *                              SETUP                           *
// ================================================================

void setup()
{
  pinMode(PWM_PIN, OUTPUT);

  init20hzPWM();

}// End setup()


// ================================================================
// *                       MAIN PROGRAM LOOP                      *
// ================================================================

void loop()
{
  getDutyCycle();
  applyDutyCycle();

}// End loop()


// ================================================================
// *                       getDutyCycle()                         *
// ================================================================

void getDutyCycle()
{
  constexpr uint16_t MIN_DUTY = 0;
  constexpr uint16_t MAX_DUTY = 5300; // 85% of TOP_COUNT
  uint16_t rawPot = analogRead(SPEED_POT);
  dutyCycle = map2(rawPot, 0, 1023, MIN_DUTY, MAX_DUTY);
}// End getDutyCycle()


// ================================================================
// *                       applyDutyCycle()                       *
// ================================================================

void applyDutyCycle()
{
  static uint16_t oldDutyCycle;
  if (oldDutyCycle != dutyCycle)
  {
    OCR1A = dutyCycle; // setting OCR1x applies dutyCycle
    oldDutyCycle = dutyCycle;
  }
}// End applyDutyCycle()


// ================================================================
// *                       init20hzPWM()                          *
// *  Set up Timer1 for 20hz phase correct PWM output on pin 9    *
// ================================================================

void init20hzPWM()
{
  constexpr uint16_t PRESCALER = 64; // clock divisor
  constexpr uint16_t FREQUENCY = 20; // Herz
  constexpr uint16_t TOP_COUNT = F_CPU / PRESCALER / FREQUENCY / 2; // Formula derived from Secrets of Arduino PWM (http://www.righto.com/2009/07/secrets-of-arduino-pwm.html)

  // Set prescaler to /64
  TCCR1B |= (1 << CS10) | (1 << CS11);

  //TCCR1A |= (1 << WGM10) | (1 << WGM11); // Set WGM mode 11 - phase correct pwm using OCR1A as TOP
  TCCR1A |= (1 << WGM11);                  // Set WGM mode 10 - phase correct pwm using ICR1 as TOP
  TCCR1B |= (1 << WGM13);
  //OCR1A = TOP_COUNT;
  ICR1 = TOP_COUNT;
  
  //TCCR1A |= (1 << COM1A1) | (1 << COM1B1); // Set Timer1 to output on pins 9(OC1A) and 10(OC1B), WGM mode 11
  TCCR1A |= (1 << COM1A1);                   // Set Timer1 to output on pin 9(OC1A), WGM mode 10

  // setting OCR1x applies dutyCycle
  //OCR1B = dutyCycle; // output on pin 10, pin 9 tied up, WGM mode 11
  OCR1A = dutyCycle;   // output on pin 9, WGM mode 10

}// End init20hzPWM()

// ================================================================
// *                           map2()                             *
// * bperrybap https://forum.arduino.cc/index.php?topic=417690.30 *
// ================================================================

int32_t map2(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max)
{
  if( x == in_max)
    return out_max;
  else if(out_min < out_max)
    return (x - in_min) * (out_max - out_min+1) / (in_max - in_min) + out_min;
  else
    return (x - in_min) * (out_max - out_min-1) / (in_max - in_min) + out_min;
} // End map2()

It's also possible to use tone() from the Arduino library if the person who asked this question does not care about controlling the duty cycle of the PWM waveform.

Hi,

Thank you for your reply. Is your code also able to generate much higher frequency, between 100k Hz to 500k Hz? Or, is there a frequency limit (upper bound or lower bound)?

Thank you again for your time.

This is awesome. The tone function should work with any kind of Arduino board, right?

Yes, My code is for 20hz but if you study that blog you can modify it for many frequencies.

The highest I've heard of is 8Mhz and the low is around 30hz

The function is from the Arduino Reference page itself so I presume that it would work for any Arduino compatible board. I can confirm that the function works for the Arduino Uno and Arduino Mega 2560.

If you do not mind being restricted to a PWM duty cycle of 50%, the tone function will be sufficient for your purpose.

This nothing to do with PWM. The max freq for 8bit PWM is 62 kHz on arduino Uno

I think you are assuming that he is using fast PWM with TOP = 255. It is possible to use fast PWM with TOP = OCRXA where OCRXA can take any value from 0 to 255 for timer 0 and timer 2.

To get 8bit pwm duty resolution you need set TOP = 255. Therefore , the max PWM freq will be 16MHz / 255 = 62 KHz

You are correct that you need TOP = 255 for 8 bit duty cycle resolution. Looking at the table below, if PWM modes 10, 11, 14 or 15 are used, the TOP value can be set to less than 255 for a frequency up to 8 MHz (if duty cycle resolution is not important).

It is also mentioned in the datasheet for the ATmega 328P