The ARDUINO UNO is an amazing product that incorporates an ATmega328P onto a development board with a USB Bootloader.
The product offers the user the ability to program in C language, and with a lot of libraries and open source software available, most applications are very easily achieved.
However, the challenge that faced me was producing a product that gave me a PWM signal(s) running on a variable frequency between 100Hz – 4kHz.
The Arduino offers six PWM outputs, and they are connected to three timers in the circuit in pairs:
Timer0: Pins 5 and 6
Timer1: Pins 9 and 10
Timer2: Pins 11 and 3
Being connected to three different timers means that almost always these are not in sync, despite running off of one main clock, which is 16MHz in the case of the Arduino UNO.
There is a lot of help on adjusting the frequencies.
I found the following link very useful, and as it says, it is a very good cheat sheet:
This allows the user to use the different PWM signals at the prescaled frequencies without having to go through the ATmega328p datasheet.
It also covers the issues you may encounter if you use that approach.
The second article which takes this to the next level is Ken Shirriff’s blog: Secrets of Arduino PWM.
It is also available on the Arduino website, but over there it misses a few figures, so it is better to go to the original source.
While it is not the easiest read, it is much easier than reading the datasheet, and Ken has managed to bring it down to a few numbers.
He uses Timer2 as an example, and while he explains everything in a great and easy to understand manner, the disadvantage there is that the user has to sacrifice one of the PWM outputs to gain the frequency adjustment on the other.
So in my application, I used Timer1, which is 16bit, and allows for a number to be stored in a register (ICR1), and the frequency is calculated based on that number. This gives me outputs on pins 9 and 10.
The number can be any value from 0 – 65535 (size = 16 bits).
The output frequency is given by the formula:
Where f = main clock (16MHz)
N = pre-scalar (set to 8 in my application)
TOP = the value in ICR1
Based on the values of ICR1 from 1 to 65535 we get a frequency range from 15Hz to 1MHz.
So for example:
ICR1 = 10,000 gives f = 100Hz
ICR1 = 5000 gives f = 200Hz
ICR1 = 2500 gives f = 500Hz
ICR1 = 1000 gives f = 1kHz
ICR1 = 500 gives f = 2kHz
ICR1 = 333 gives f = 3kHz
ICR1 = 250 gives f = 4kHz
ICR1 = 100 gives f = 10kHz
To do this, we configure the timer to run in phase and frequency correct mode, and we can set it to either inverting or non-inverting mode. We also select the pre-scalar.
These configurations are done by adjusting Timer1 registers TCCR1A and TCCR1B.
First thing to adjust is choosing Phase and Frequency correct mode in invert or non-invert mode.
The last two options give us non-invert and invert mode respectively.
Second thing to adjust is PWM, Phase and Frequency correct mode, with the TOP value determined by the ICR1 register, which is what determines the PWM Frequency.
In our case we will use Mode 8.
The last thing to do is to adjust the pre-scalar.
This is done by choosing the right modes from the following table. As discussed earlier, we shall be using mode which divides the main clock by 8.
All the above tables have been copied from the ATmega328P datasheet.
Now I shall go through the code which I wrote for my program. In my program, I had a POT on A3 to adjust the required frequency using ‘if’, and POT on A0 and A2 to adjust the output duty cycle.
unsigned long duty1,duty2;
// Duty Cycle in terms of a percentage.
unsigned long plus;
// Value read from A1, in case plus mode is activated
// Float numbers to calculate duty for PWM 1 and PWM 2
unsigned long pwm1;
// Value read from A0 and A2 to give PWM duty cycle output in terms // of 0-5V
unsigned long pwm2;
TCCR1A = _BV(COM1A1) | _BV(COM1B1) ; // phase and frequency correct mode. NON-inverted mode
// TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1A0) | _BV(COM1B0) ;
//phase/frequency correct mode. SELECT THIS FOR INVERTED OUTPUTS.
TCCR1B = _BV(WGM13) | _BV(CS11);
// Select mode 8 and select divide by 8 on main clock.
// Program that lets different values on A3 choose different values of frequency, e.g. 100,200,400,500,1k,2k,3k,4k,10k,
//etc in relation with a free input.
if (analogRead(A3) <100) ICR1 = 10000; // 100Hz - Default value to 100Hz for A3 = 0V
if (analogRead(A3) 100) ICR1 = 5000; // 200Hz
if (analogRead(A3) 200) ICR1 = 2500; // 400Hz
if (analogRead(A3) 300) ICR1 = 1000; // 1000Hz
if (analogRead(A3) 400) ICR1 = 500; // 2000Hz
if (analogRead(A3) 500) ICR1 = 333; // 3000Hz
if (analogRead(A3) 600) ICR1 = 250; // 4000Hz
if (analogRead(A3) 700) ICR1 = 100; // 10000Hz
if (analogRead(A3) > 800) ICR1 = 1000; // Default value to 1kHz for A3 = 5V
//ICR1 = 1000; // for ICR1 = 1000, frequency = 1kHz.
pwm1 = analogRead(A2); // read duty from A2 for PWM 2
pwm2 = analogRead(A0); // read duty from A0 for PWM 1
xxx = float(pwm2);
// Turn read values from the POTs to float for mathematical
yyy = float(pwm1);
xxx = xxx * ICR1;
// Multiply with ICR1 and divide by 1023 to give required percentage
yyy = yyy * ICR1;
xxx = xxx / 1023;
yyy = yyy / 1023;
//Assign values to OCR Registers, which output the PWM duty cycle.
OCR1B = int(xxx);
OCR1A = int(yyy);