Language: EN

como-cambiar-la-frecuencia-de-un-pwm-en-arduino

How to change the PWM frequency on Arduino

Today we are going to see how to change the frequency of an analog output pin on Arduino, for which we will have to configure the registers that control the Timers associated with the PWM pins. But don’t worry, because we are going to make it easy in a single function.

The use of timers is normal in processors. However, you have often seen us recommend avoiding their manipulation in Arduino since it can have unexpected effects, especially in third-party libraries. So, if we normally recommend avoiding manipulating Timers in Arduino, why do we want to modify their frequency now? Well, basically, because this allows us to improve the behavior of certain devices when they are controlled by a PWM signal.

Remember that when controlling a device using a PWM signal, we are actually turning the device on and off completely multiple times per second. That is not the same as applying a true analog signal and, as a general rule, many actuators do not respond well to PWM control.

Increasing the PWM frequency can improve the operation of certain devices, because they have less time to “realize” that we have turned them off. If the inertia of the device is sufficient, and its natural frequency is higher than that of the PWM, the behavior will be (almost) identical to feeding it with an analog voltage.

The most common practical case is when controlling the speed of a motor, which, when controlled with PWM, turns the motor on and off multiple times per second. The motor is an inductive load, and each start-up represents a beautiful surge. This leads to the situation that when applying a speed reduction, the motor consumes more, heats up more, makes more noise… when, a priori, we might think it should be the other way around because it is running at a lower speed.

This also does not mean that we can raise the frequency indefinitely like crazy. Not all the drivers we are using will support the frequency and if they do not support it… bang!. We have to look at each particular case, but in general, increasing the PWM control frequency to 4-10 Khz is quite reasonable.

So here is a cheat sheet with the frequency, prescalers, default values, and consequences of each of the PWM pins for Atmega328p and Atmega 32u2 processors, as well as a function to modify it easily.

Atmega 328p (Arduino Uno, Nano)

Frequency

PinsTimerFrequency
5, 6Timer062500 Hz
9, 10Timer131250 Hz
3, 11Timer231250 Hz

Default values

PinsTimerPrescalersFrequency
5, 6Timer064977Hz
9, 10Timer164490Hz
3, 11Timer364490Hz

Prescalers

PinsTimerPrescalers
5, 6Timer01 8 64 256 1024
9, 10Timer11 8 64 256 1024
3, 11Timer21 8 32 64 128 256 1024

Consequences and effects

PinsTimerEffect
5, 6Timer0delay() and millis()
9, 10Timer1Servo library
3, 11Timer2

Code

//Atmega 328p (Arduino Uno, Nano)
// Frequencies
// 5, 6   Timer0  62500 Hz
// 9, 10   Timer1  31250 Hz
// 3, 11   Timer2  31250 Hz

// Prescalers
// 5, 6   Timer0  1 8 64 256 1024
// 9, 10   Timer1  1 8 64 256 1024
// 3, 11   Timer2  1 8 32 64 128 256 1024

// Default values
// 5, 6   Timer0 64  977Hz
// 9, 10   Timer1 64  490Hz
// 3, 11   Timer2 64  490Hz

// Consequences
// 5, 6   Timer0  delay() and millis()
// 9, 10   Timer1  Servo library
// 3, 11   Timer2  

void setPWMPrescaler(uint8_t pin, uint16_t prescale) {
  
  byte mode;
  
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   64: mode = 0b011; break; 
      case  256: mode = 0b100; break;
      case 1024: mode = 0b101; break;
      default: return;
    }
    
  } else if(pin == 3 || pin == 11) {
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   32: mode = 0b011; break; 
      case   64: mode = 0b100; break; 
      case  128: mode = 0b101; break;
      case  256: mode = 0b110; break;
      case 1024: mode = 0b111; break;
      default: return;
    }
  }
  
  if(pin==5 || pin==6) {
    TCCR0B = TCCR0B & 0b11111000 | mode;
  } else if (pin==9 || pin==10) {
    TCCR1B = TCCR1B & 0b11111000 | mode;
  } else if (pin==3 || pin==11) {
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

Atmega 32U (Micro and Leonardo)

Frequencies

PinsTimerFrequency
3, 11Timer064500Hz
9, 10Timer131250Hz
5Timer331250Hz
6, 13Timer431250Hz

Default values

PinsTimerPrescalersFrequency
3, 11Timer064977Hz
9, 10Timer164490Hz
5Timer364490Hz
6, 13Timer464490Hz

Prescalers

PinsTimerPrescalers
3, 11Timer01 8 64 256 1024
9, 10Timer11 8 64 256 1024
5Timer31 8 64 256 1024
6, 13Timer41 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384

Consequences

PinsTimerEffect
3, 11Timer0delay() and millis()
9, 10Timer1
5Timer3
6, 13Timer4

Code

// Atmega 32U
// Frequencies
// 3, 11  Timer0  64500Hz
// 9, 10  Timer1  31250Hz
// 5  Timer3  31250Hz
// 6, 13  Timer4  31250Hz

// Prescalers
// 3, 11  Timer0  64  1 8 64 256 1024
// 9, 10  Timer1  64  1 8 64 256 1024
// 5  Timer3  64  1 8 64 256 1024
// 6, 13  Timer4  64  1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384

// Default values
// 3, 11  Timer0  64  977Hz
// 9, 10  Timer1  64  490Hz
// 5  Timer3  64  490Hz
// 6, 13  Timer4  64  490Hz

// Consequences
// 3, 11  millis(), delay()
// 9, 10
// 5
// 6, 13

void setPWMPrescaler(uint8_t pin, uint16_t prescale) 
{ 
  byte mode;
  
  if(pin==3 || pin==5 || pin==9 || pin==10 || pin==11) { 
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   64: mode = 0b011; break; 
      case  256: mode = 0b100; break;
      case 1024: mode = 0b101; break;
      default: return;
    }
    
    
  } else if(pin==6 || pin==13) {
    switch(prescale) {
      case     1: mode = 0b0001; break;
      case     2: mode = 0b0010; break;
      case     4: mode = 0b0011; break;
      case     8: mode = 0b0100; break;
      case    16: mode = 0b0101; break;
      case    32: mode = 0b0110; break;
      case    64: mode = 0b0111; break;
      case   128: mode = 0b1000; break;
      case   256: mode = 0b1001; break;
      case   512: mode = 0b1010; break;
      case  1024: mode = 0b1011; break;
      case  2048: mode = 0b1100; break;
      case  4096: mode = 0b1101; break;
      case  8192: mode = 0b1110; break;
      case 16384: mode = 0b1111; break;
      default: return;
    }
  }
  
  if(pin==3 || pin==11) {
    TCCR0B = TCCR1B & 0b11111000 | mode;
  } else if (pin==9 || pin==10) {
    TCCR1B = TCCR1B & 0b11111000 | mode;
  } else if (pin==5) {
    TCCR3B = TCCR3B & 0b11111000 | mode;
  } else if (pin==6 || pin==13) {
    TCCR4B = TCCR4B & 0b11110000 | mode;
  }
}

Download the code

All the code in this post is available for download on Github. github-full