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
Pins | Timer | Frequency |
---|---|---|
5, 6 | Timer0 | 62500 Hz |
9, 10 | Timer1 | 31250 Hz |
3, 11 | Timer2 | 31250 Hz |
Default values
Pins | Timer | Prescalers | Frequency |
---|---|---|---|
5, 6 | Timer0 | 64 | 977Hz |
9, 10 | Timer1 | 64 | 490Hz |
3, 11 | Timer3 | 64 | 490Hz |
Prescalers
Pins | Timer | 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 |
Consequences and effects
Pins | Timer | Effect |
---|---|---|
5, 6 | Timer0 | delay() and millis() |
9, 10 | Timer1 | Servo library |
3, 11 | Timer2 |
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
Pins | Timer | Frequency |
---|---|---|
3, 11 | Timer0 | 64500Hz |
9, 10 | Timer1 | 31250Hz |
5 | Timer3 | 31250Hz |
6, 13 | Timer4 | 31250Hz |
Default values
Pins | Timer | Prescalers | Frequency |
---|---|---|---|
3, 11 | Timer0 | 64 | 977Hz |
9, 10 | Timer1 | 64 | 490Hz |
5 | Timer3 | 64 | 490Hz |
6, 13 | Timer4 | 64 | 490Hz |
Prescalers
Pins | Timer | Prescalers |
---|---|---|
3, 11 | Timer0 | 1 8 64 256 1024 |
9, 10 | Timer1 | 1 8 64 256 1024 |
5 | Timer3 | 1 8 64 256 1024 |
6, 13 | Timer4 | 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 |
Consequences
Pins | Timer | Effect |
---|---|---|
3, 11 | Timer0 | delay() and millis() |
9, 10 | Timer1 | |
5 | Timer3 | |
6, 13 | Timer4 |
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.