How To Change the PWM Frequency Of Arduino

How to Change the PWM Frequency Of Arduino: Epic Guide

The microcontroller has several timers that can perform different functions, such as generating a PWM signal. In order for the timer to generate a PWM signal, it has to be pre-configured by editing the timer register. When we work in the Arduino IDE, the timers are configured without our knowledge in the Arduino.h library, and actually get the settings the developers wanted. And these settings are not very good: the default PWM frequency is low, and the timers are not used to their full potential. Let’s look at the standard PWM of the ATmega328 (Arduino UNO/Nano/Pro Mini):

Timer Pins Frequency Resolution
Timer 0 D5 and D6 976 Hz 8 bits (0- 255)
Timer 1 D9 and D10 488 Hz 8 bits (0-255)
Timer 2 D3 and D11 488 Hz 8 bits (0-255)

In fact, all timers can easily give out 64 kHz PWM signal, and timer 1 – it is even 16 bits, and at the frequency that was given to him Arduino, could work with a resolution of 15 bits instead of 8, and that, by the way, 32768 gradations of filling instead of 256! So why this injustice? Timer 0 is in charge of timing and is set so that the milliseconds are ticking precisely. The other timers are combed to zero to prevent the Arduino-enthusiast from having unnecessary problems. This approach is generally understandable but would have made at least a couple of standard functions for a higher frequency, well, seriously! Okay, if they didn’t, we will.

PWM Frequency Setting Via Registers

The PWM generation is tuned through the timer registers. Then you will find some ready-made “pieces” of code, which you need to insert into setup(), and the PWM frequency will be reconfigured (the pre-delimiter and the timer mode will change). You can still work with the PWM signal with the analogWrite() function, controlling the filling of the PWM on the standard pins.

Changing the PWM Frequency on the ATmega328 (Arduino UNO/Nano/Pro Mini)

Pins D5 and D6 (Timer 0) – 8 bits

// Pins D5 and D6 are 62.5kHz
TCCR0B = 0b00000001; // x1
TCCR0A = 0b00000011; // fast pwm
// Pins D5 and D6 - 31.4 kHz
TCCR0B = 0b00000001; // x1
TCCR0A = 0b00000001; // phase correct
// Pins D5 and D6 - 7.8 kHz
TCCR0B = 0b00000010; // x8
TCCR0A = 0b00000011; // fast pwm
// Pins D5 and D6 - 4 kHz
TCCR0B = 0b00000010; // x8
TCCR0A = 0b00000001; // phase correct
// Pins D5 and D6 - 976 Hz - default
TCCR0B = 0b00000011; // x64
TCCR0A = 0b00000011; // fast pwm
// Pins D5 and D6 - 490 Hz
TCCR0B = 0b00000011; // x64
TCCR0A = 0b00000001; // phase correct
// Pins D5 and D6 - 244 Hz
TCCR0B = 0b00000100; // x256
TCCR0A = 0b00000011; // fast pwm
// Pins D5 and D6 - 122 Hz
TCCR0B = 0b00000100; // x256
TCCR0A = 0b00000001; // phase correct
// Pins D5 and D6 - 61 Hz
TCCR0B = 0b00000101; // x1024
TCCR0A = 0b00000011; // fast pwm
// Pins D5 and D6 - 30 Hz
TCCR0B = 0b00000101; // x1024
TCCR0A = 0b00000001; // phase correct

Pins D9 and D10 (Timer 1) – 8 bits

// Pins D9 and D10 - 62.5 kHz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00001001; // x1 fast pwm
// Pins D9 and D10 - 31.4 kHz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000001; // x1 phase correct
// Pins D9 and D10 - 7.8 kHz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00001010; // x8 fast pwm
// Pins D9 and D10 - 4 kHz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000010; // x8 phase correct
// Pins D9 and D10 - 976 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00001011; // x64 fast pwm
// Pins D9 and D10 - 490 Hz - default
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000011; // x64 phase correct
// Pins D9 and D10 - 244 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00001100; // x256 fast pwm
// Pins D9 and D10 - 122 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000100; // x256 phase correct
// Pins D9 and D10 - 61 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00001101; // x1024 fast pwm
// Pins D9 and D10 - 30 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000101; // x1024 phase correct

Pins D9 and D10 (Timer 1) – 10 bit

// Pins D9 and D10 - 15.6 kHz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001001; // x1 fast pwm
// Pins D9 and D10 - 7.8 kHz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00000001; // x1 phase correct
// Pins D9 and D10 - 2 kHz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001010; // x8 fast pwm
// Pins D9 and D10 - 977 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00000010; // x8 phase correct
// Pins D9 and D10 - 244 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001011; // x64 fast pwm
// Pins D9 and D10 - 122 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00000011; // x64 phase correct
// Pins D9 and D10 - 61 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001100; // x256 fast pwm
// Pins D9 and D10 - 30 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00000100; // x256 phase correct
// Pins D9 and D10 - 15 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001101; // x1024 fast pwm
// Pins D9 and D10 - 7.5 Hz 10bit
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00000101; // x1024 phase correct

Pins D3 and D11 (Timer 2) – 8 bits

// Pins D3 and D11 - 62.5 kHz
TCCR2B = 0b00000001; // x1
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 31.4 kHz
TCCR2B = 0b00000001; // x1
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 8 kHz
TCCR2B = 0b00000010; // x8
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 4 kHz
TCCR2B = 0b00000010; // x8
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 2 kHz
TCCR2B = 0b00000011; // x32
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 980 Hz
TCCR2B = 0b00000011; // x32
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 980 Hz
TCCR2B = 0b00000100; // x64
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 490 Hz - default
TCCR2B = 0b00000100; // x64
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 490 Hz
TCCR2B = 0b00000101; // x128
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 245 Hz
TCCR2B = 0b00000101; // x128
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 245 Hz
TCCR2B = 0b00000110; // x256
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 122 Hz
TCCR2B = 0b00000110; // x256
TCCR2A = 0b00000001; // phase correct
// Pins D3 and D11 - 60 Hz
TCCR2B = 0b00000111; // x1024
TCCR2A = 0b00000011; // fast pwm
// Pins D3 and D11 - 30 Hz
TCCR2B = 0b00000111; // x1024
TCCR2A = 0b00000001; // phase correct

Example of Usage

void setup() {
  // Pins D5 and D6 - 7.8 kHz
  TCCR0B = 0b00000010; // x8
  TCCR0A = 0b00000011; // fast pwm
  // Pins D3 and D11 - 62.5 kHz
  TCCR2B = 0b00000001; // x1
  TCCR2A = 0b00000011; // fast pwm
  // Pins D9 and D10 - 7.8 kHz 10bit
  TCCR1A = 0b00000011; // 10bit
  TCCR1B = 0b00000001; // x1 phase correct
  analogWrite(3, 15);
  analogWrite(5, 167);
  analogWrite(6, 241);
  analogWrite(9, 745); // yes, range 0-1023
  analogWrite(10, 345); // yes, range 0-1023
  analogWrite(11, 78);
}
void loop() {
}
Important!
If you change the frequency on pins D5 and D6, you will lose the time functions (millis(), delay(), pulseIn(), setTimeout(), etc.), they will not work correctly. Also, the libraries that use them will stop working!

If you really want or need an overclocked PWM on the system (zero) timer without loss of time functions, you can correct them as follows:

#define micros() (micros() >> CORRECT_CLOCK)
#define millis() (millis() >> CORRECT_CLOCK)

void fixDelay(uint32_t ms) {
  delay(ms << CORRECT_CLOCK);
}

Defines should be placed before plugging in the libraries so that they get into the code and substitute functions. The only thing is that you can not correct the delay in another library this way. You can use fixDelay() for yourself as written above.

The most important thing is CORRECT_CLOCK. This is an integer equal to the ratio of the default timer divider and the new one set (for PWM acceleration). For example, we set the PWM to 8 kHz. From the list above, we see that the default divider is 64, and 7.8 kHz will be 8, which is eight times smaller. CORRECT_CLOCK is set accordingly.

#define CORRECT_CLOCK 8
void fixDelay(uint32_t ms) {
  delay(ms << CORRECT_CLOCK);
}
void setup() {
  pinMode(13, 1);
  // Pins D5 and D6 - 4 kHz
  TCCR0B = 0b00000010; // x8
  TCCR0A = 0b00000001; // phase correct
}
void loop() {
  digitalWrite(13, !digitalRead(13));
  fixDelay(1000);
}

Libraries for Working with PWM

In addition to fiddling with the registers manually, there are ready-made libraries that allow you to change the PWM frequency of the Arduino. Let’s take a look at some of them:

PWM library (GitHub) – a powerful library that allows you to change the PWM frequency on ATmega48 / 88 / 168 / 328 / 640 / 1280 / 1281 / 2560 / 2561 microcontrollers, of which 328 is on UNO/Nano/Mini and 2560 is an Arduino Mega.

  • Allows you to set any PWM frequency, pre-delay, TOP
  • Only one channel is available when working with 8-bit timers (for example, on the ATmega328, only D3, D5, D9, and D10)
  • Allows to work with 16-bit timers at a higher resolution (16 bits instead of the standard 8)
  • The library is very complicated, so it can’t be shredded into pieces.
  • See examples in the folder with the library!

GyverPWM library (GitHub) – the library, which we wrote together with my friend. The library allows very flexible work with PWM on microcontroller ATmega328 (we will add Mega later):

  • Allows you to set any PWM frequency in the range of 250 Hz – 200 kHz
  • Bit selection: 4-8 bits for 8-bit timers, 4-16 bits for 16-bit timers (at 4 bits, the PWM frequency is 1 MHz)
  • PWM mode selection: Fast PWM or Phase-correct PWM (favorable for motors)
  • Generation of meander frequencies from 2 Hz to 8 MHz on pin D9 with maximum accuracy
  • Only one channel is available when working with 8-bit timers (for example, on an ATmega328, only D3, D5, D9, and D10)
  • There are functions to reconfigure the standard PWM outputs without losing the PWM
  • The library is written in a very simple way, and you can take pieces of code from it
  • See examples in the folder with the library!

Final Words

By following the steps above, you can change the PWM frequency on your Arduino. This can be useful for controlling motors or other devices that require a specific frequency to function correctly. Thanks for reading!

6 thoughts on “How to Change the PWM Frequency Of Arduino: Epic Guide”

  1. Thanks for this. I’m driving some motors with a Mega board and they whine worse than my ex-wife. There is a big jump from the 4 Khz to the 32 Khz values. Is there the possibility to set the frequency somewhere between 16 and 24 Khz.
    Thanks,
    John

  2. Great article. Did you add support or the Mega? I went to GitHub but it is written in Russian (?). Thanks in advance.

  3. I appreciated that the article provided step-by-step instructions and even included code snippets to simplify the process. The author also took the time to explain the importance of changing the PWM frequency and the benefits it can provide.

    Overall, I found this article to be a great resource for anyone looking to change the PWM frequency of their Arduino. It was well-written, easy to understand, and provided all the necessary information needed to complete the task successfully. I highly recommend giving it a read!

  4. Hello Robert and thank you for providing a neat and simple to implement way to screw another two bits’ worth of resolution out of the timer 1 based PWM on pin D10 which I use to control a cooling fan attached via a heat spreader to the base plate of an LPRO 101 rubidium oscillator based frequency standard which now (generally most of the time) maintains its temperature to within +/- 1 mK of the 36.050 deg C set point.

    However, despite this level of stability being maintained over the 10 to 30 deg ambient range, I had reason to believe it could still provide temperature control down to 4 deg or lower so I used a fridge to run such low temperature testing and witnessed a rather inexplicable problem when it hit the 8 deg mark whereupon it suddenly dropped off the 50mK line in the serial plotter window, triggering an endless under/overshoot cycling until the temperature came back up to the ten degree mark, allowing it to settle back on the set point as if nothing bad had happened.

    In short, I spent the next three weeks trying to fettle my code to overcome this issue but to no avail despite making hardware adjustments to the servo controlled ventilation flap diverter valves which for ambient temperatures of less than 22 deg put it into total internal re-circulation mode to save having to cope with fan intake temperatures below 20 deg which avoids the need to bring the fan to a complete standstill as my initial attempts had obliged me to resort to when the ambient dropped to 18 degrees before I’d hit upon the idea of controlling ventilation with a servo controlled diverter valve setup to save subjecting the fan intake to temperatures below the 20 degree mark.

    Anyhow, the fact that this undershoot/overshoot cycling could peak to several hundred mK over the set point, despite the fact that the fan was never brought to a standstill, gave me cause to see just how fast I could reset this minimum tick-over speed. I modified the program to send static values to the PWM function gradually altering the rate of temperature drift. When I set the value to 255, the temperature suddenly took a massive nose dive out of all expectations. Testing with adjacent values (254 and 256) resulted in expected behavior whilst setting it back to 255 gave the same dramatic result as before.

    Over recent months, I had always put my coding effort failures down to gremlins rather than the more manageable presence of ‘mere bugs’ . In this case, I had at last discovered where my suspected gremlin had been hiding ever since I’d upgraded to a 10 bit PWM fan control setup several months ago. Luckily, the solution was a simple one liner test for this 255 gremlin to substitute it with 254 on its every occurrence.

    Although the value of 255 suggested the possibility of another two repeats of this gremlin at 511 and 767, I chose the time honoured method of “Shoot first, ask questions later ” to effect an instant quick fix.

    I tested the question with my bread boarded “flight spare” and on an UNO which confirmed I had no need to add another two such one liner solutions into my code. It’s only the value of 255 that causes this issue.

    I think it’s either a hardware issue in the Atmel 328p chips used in these clones (and quite likely the original Arduino boards) or else a cock up in the Arduino 1.8.19 compiler I use in preference to the later version 2 compiler which offers an even more useless serial plotting feature than version 1.8.19 offers. Or it might simply be a bug in the built in Arduino library support (I’m only guessing because this is at a level ‘beyond my pay grade’).

    If there’s no simple fix to this elegantly simple 10 bit enhancement, it’s still worth offering it with a note about the need to exclude 255 from the data input to the analogWrite function.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top