Thursday, 7 April 2011

Creating a variable frequency PWM output on Arduino UNO

By Sami Mughal
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:
http://www.arduino.cc/playground/Main/TimerPWMCheatsheet
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.
http://www.arcfn.com/2009/07/secrets-of-arduino-pwm.html
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.
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM
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:
clip_image002
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.
clip_image004
clip_image006
First thing to adjust is choosing Phase and Frequency correct mode in invert or non-invert mode.
clip_image008
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.
clip_image010
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.
clip_image012
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 xxx;
// Float numbers to calculate duty for PWM 1 and PWM 2
float yyy;
unsigned long pwm1;
// Value read from A0 and A2 to give PWM duty cycle output in terms // of 0-5V
unsigned long pwm2;
void setup(){
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
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.
}
void loop(){
// 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) <200 && analogRead(A3) > 100) ICR1 = 5000; // 200Hz
if (analogRead(A3) <300 && analogRead(A3) > 200) ICR1 = 2500; // 400Hz
if (analogRead(A3) <400 && analogRead(A3) > 300) ICR1 = 1000; // 1000Hz
if (analogRead(A3) <500 && analogRead(A3) > 400) ICR1 = 500; // 2000Hz
if (analogRead(A3) <600 && analogRead(A3) > 500) ICR1 = 333; // 3000Hz
if (analogRead(A3) <700 && analogRead(A3) > 600) ICR1 = 250; // 4000Hz
if (analogRead(A3) <800 && 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
// adjustment.
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);
}

23 comments :

  1. hey! long time... :)

    and after so many ages I drop by to see a typically "geeky" code..so cool! :)

    ReplyDelete
  2. Thanks! Yeah has been a while. Have not really done the blogging I have been planning to do lately but at least got some stuff in :)

    ReplyDelete
  3. Shall be updating the blog with numbers for Aruino MEGA in some while as well if anybody is interested!

    ReplyDelete
  4. :) I came back to see updates. Ye freq and all doesn't go well with me - I understand only computer languages :)

    ReplyDelete
  5. to me frequency and pwm are very much of computer languages as well... but then again i did specialize in electronics so I cannot judge :)

    ReplyDelete
  6. Well i know about arduino that it can understand alphabetical as well as numerical the great advantage of arduino uno.
    Jaycon

    ReplyDelete
  7. Most helpful posting I have seen so far on AVR timers. Still working on it. Where should I read to find out more about which registers define the compare level, which define top (I'm confused why it switches between ICR1 and OCR1A).

    I'm also wanting to see if I can define an ISR for when the Phase (and freq?) correct count goes down to 0. i.e. I want an interrupt at the start of each pulse period.

    I'll also want to adjust the timing a bit on the fly, depending on sensors.
    AFAIK, I should be able to set the current count register for micro-timing adjustments.
    I may want to adjust period/compare level on the fly too according to sensor info.

    ReplyDelete
    Replies
    1. Thanks Aaron.
      I think your best bet is to read the ATMEL datasheet for whatever Arduino chip you are using on your Arduino. It is a lot to read but from what I gather, you are not scared to touch registers. The ICR1/OCR1A etc are defined in those datasheets.

      I do not have much experience with interrupts, so I would refer you to either the datasheet, or the ATMEL/ARDUINO forums.

      As far as adjusting timing on the fly is concerned, that is not very easy. Really depends on what you mean by on the fly. For example if a certain condition is fulfilled, yeah, just update the values in the register and your frequency would change. However, if you want to adjust the values with say an attached pot constantly, that does not quite work.

      Hopefully this answers some/most of your queries.

      Thanks for your kind comments, and spread the word :)

      Delete
  8. can smo please make a fuction out of it, for god's sake.
    I donot understand a thing.
    like:
    void startPwmService(float MHz, int pinid, int pwmToSacrifice.... bla bla)
    and
    void stopPwmService(float MHz, int pinid, int pwmToSacrifice.... bla bla)

    Regrads,

    ReplyDelete
    Replies
    1. Dear Unknown,
      You're not the only one :-)

      Delete
  9. Yes. Dear Sami Mughal, can you pls show us a simple way to make a pwm pin on arduino to pulse at any frequency we would want to.

    ReplyDelete
    Replies
    1. Hi Shankar,

      I think the way above was the best way I found to do that.

      Thanks for your comment.

      Delete
  10. curious if this code can be loaded on to a ATTiny85 as a standalone w/o the arduino.

    ReplyDelete
    Replies
    1. Hi jrb,
      Thanks for your comment. Theoretically, this could work with minimum change on the ATTiny85, since it is the same architecture. However, I have not gone through the datasheet, so not entirely sure if all the timers and associated parameters are the same. Feel free to try the code, and if it works, do tell us about it :)
      Regards,
      Sami

      Delete
  11. why u use "2" in ur frequency equation?

    ReplyDelete
  12. which 2 are you talking about?

    ReplyDelete
  13. F/2.N.TOP
    WHAT DOES THE 2 MEAN THERE?

    ReplyDelete
  14. That is part of the formula, as given by the datasheet. Refer to the datasheet for more information.

    ReplyDelete
  15. any links to the datasheet reference please?I have been trying to look but cant find.

    ReplyDelete
  16. Look at your hardware. See the microcontroller on it. If you are using an Arduino, it is probably an Atmel. Look it up. Google that number. You will get the datasheet through that.

    ReplyDelete
  17. thanks boss. got it.

    ReplyDelete
  18. This is without a doubt the best Timer explanation I have found for the Arduino. I have being able to push my Atmega328 so much further! Thank you!

    ReplyDelete
  19. Thank you ! Glad this is helping people!

    ReplyDelete