Pages: [1] 2 3 ... 5   Go Down
Author Topic: PWM frequency library  (Read 52366 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I discovered in a recent project involving an Arduino microcontroller that there was no method to change PWM frequency without directly manipulating low-level memory. As far as I can Google, there is no general purpose library that can change PWM frequencies on Arduino Microcontrollers. The internet is full of partial examples and code snippets for changing PWM frequency, but in the end I still had to consult the 400+ page sec sheet (http://www.atmel.com/Images/doc2549.pdf) to get the code for my Mega functional.
It is my speculation that the programmers at Arduino have not released any methods for changing PWM frequency because it is difficult to write a simple and intuitive wrapper for hardware timers that wont run the risk of confusing a beginner (the whole draw to Arduino in the first place). The hardware is has very specific limitations that present themselves in odd ways. Allow me to share a few:

  • PWM behavior is determined by integrated components called timers. Every timer has two to four channels. Each channel is connected to a pin. Changing one pin's frequency requires changes to the timer it connects to. Which in turn changes the frequency of other pins connected to that same timer.
  • Timer 0 is usually used for Arduino's time keeping functions, (i.e. the millis() function). Changing the frequency on timer 0 will break the time keeping functions you may or may not be using in other parts of your project
  • There are two types of timer, 8bit and 16bit. Long story short, they have nuances that make common code difficult to implement without limiting one or the other.
  • Creating custom frequencies (beyond messing with the prescaler) with an 8bit timer requires the sacrifice of one channel. In other words, each 8bit timer that creates a custom frequency loses the ability to perform PWM on one pin (the one connected to the A channel to be more precise). All Arduinos except the Leonardo have two 8bit timers, meaning that setting all timers to a particular frequency will sacrifice a total of two pins on said Ardiuno.

Regardless of this, I still felt it would still be worth while to make a library/wrapper for hardware timers so that I, and anyone else who chooses to use this, will not have to spend quite as many hours needlessly digging through blocks of bug prone bit wise and preprocessor slurry.

The library has five global functions:
InitTimers()Initializes all timers. Needs to be called before changing the timers frequency or setting the duty on a pin
InitTimersSafe()Same as InitTimers() except timer 0 is not initialized in order to preserve time keeping functions
pwmWrite(uint8_t pin, uint8_t val)Same as 'analogWrite()', but it only works with initialized timers. Continue to use analogWrite() on uninitialized timers
SetPinFrequency(int8_t pin, int32_t frequency)Sets the pin's frequency (in Hz) and returns a bool for success
SetPinFrequencySafe(int8_t pin, int32_t frequency) Same as SetPinFrequency except it does not affect timer 0

The library also has five functions for each Timer 'object'. I could not get the code size down to what I felt was reasonable so I ditched C++ classes and did some fancy macro work instead. Each of these functions are technically preprocessor macros with nice self explanatory names that swap out for more cryptic functions inside the library header just before compile time. For timer 1 the functions are:

Timer1_GetFrequency()Gets the timer's frequency in Hz
Timer1_SetFrequency(int frequency)Sets the timer's frequency in Hz
Timer1_GetPrescaler()Gets the value (not bits) of the prescaler. Don't know what this means? Don't worry about it, just use SetFrequency(int frequency)
Timer1_SetPrescaler(enum value)Sets the prescaler*
Timer1_GetTop()Gets the timer register's maximum value
Timer1_SetTop(int top)Sets the timer register's maximum value
Timer1_Initialize()Initializes the timer
*The prescaler is inconsistent among different timers. I figured using enumerators was the best solution because most types of invalid input will be caught at compile time. For a normal timer, use one of these as a parameter: ps_1, ps_8, ps_64, ps_256, ps_1024. If those give a type error, then the timer you are using is one of the inconsistent ones, and you should use psalt_1, psalt_8, psalt_32, psalt_64, psalt_128, psalt_256, or psalt_1024 instead.

If you want to mess with a different timer, just change the number (i.e Timer2_GetFrequency() to get timer 2's frequency). It is up to your discretion whether or not you want to use the timer specific functions. The global ones should be good enough for most situations.

With this Library, 16bit timers have a frequency range from 1Hz to 2MHz. 8bit timers have a range from 31Hz to 2MHz. As the frequency becomes larger, the smaller the range power duties for a pin becomes. It is technically possible to push the frequency to 8MHz, but the range of possible power duties get stupidly small by that point. To be sure the frequency was correctly set, be sure to check the return value. If you don't want to sacrifice any 8bit PWM pins, don't call the initialize function for that timer, try changing the prescaler to manipulate frequency instead. There are many tutorials on how the prescaler affects timers and this library contains methods that make easier and less bug prone to manipulate. So far, I have tested this library on an Uno and a Mega. This library should be compatible with all Arduinos except the Leonardo and Due. If you have an Arduino that is not a Mega or Uno, please test it and tell me how it went. If somebody has an oscilloscope on hand to verify the frequencies being generated are correct, that would also be helpful.

For now, consider this library to be in beta. Developments on this library are described in later posts.
Here are some of the current features of this library:
  • Wraps timer specific properties (such as timer top and prescaler) with functions
  • Has pin based (timer agnostic) functions
  • Has functions for getting and setting frequency at the timer level and pin level
  • Has tools for measuring timer resolution at the timer level and pin level

The latest is version .05
link: http://code.google.com/p/arduino-pwm-frequency-library/downloads/list
« Last Edit: January 27, 2013, 04:12:30 pm by runnerup » Logged

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I added a fix that removed a bug limiting 8 bit timers to 65535 Hz (I will let you guess what the problem was...) and I added an example sketch to the project. Both versions can be found at the same link provided above.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 3
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

What's the deal with this not working on the Leonardo?
Logged


Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The Leonardo has a different CPU architecture. That means different timers with different properties that I would have to wrap.
So far, Arduino microcontrollers use three main architectures 'groups'.  Arduino Mega and ADK are of the AVR mega 1280/1281/2560/2561 group, which are all identical except for the amount of memory they hold. The Leonardo uses ATmega32u4. And pretty much everything else uses 88/168/328 variants.
I have an Uno and a Mega, which means I was able to build and test for every architecture except the Leonardo. If there is enough interest in this project, I would be willing to try to get it working on the Leonardo. But I have no reliable way too test it in real life to make sure it works.
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 61
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

awesome library !

I have a question here.

my mega 2560 is connect to tlc5940 pwm chip. and it use timer 1 and timer 2.

according to the datasheet of mega 2560 pin 7 is using timer 4.

so i using a led and try to connect pin 7  and it is  working (can see the different between 20hz to 20000hz while it is 20 hz the led dim and blinking. with 20000Hz led dim without blinking). but i want to know is the pin 7 still using timer 4? after i change the pin from default pin to pin 7.

 
Code:
#include <PWM.h>

//use pin 11 on the Mega instead, otherwise there is a frequency cap at 31 Hz
int led = 7;                // the pin that the LED is attached to
int brightness = 0;         // how bright the LED is
int fadeAmount = 5;         // how many points to fade the LED by
int32_t frequency = 20000; //frequency (in Hz)

void setup()
{
  //initialize all timers except for 0, to save time keeping functions
  InitTimersSafe();

  //sets the frequency for the specified pin
  bool success = SetPinFrequencySafe(led, frequency);
 
  //if the pin frequency was set successfully, pin 13 turn on
  if(success) {
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);   
  }
}

void loop()
{
  //use this functions instead of analogWrite on 'initialized' pins
  pwmWrite(led, brightness);

  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
  }     
 
  delay(30);     
}
Logged

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I am sorry, it is not clear to me what you mean by 'default pin'. If you mean the timer the pin is connected to before and after the initialize function was called then yes, the timer does not change. On the mega, pin 11 is on timer 1, pin 9 is wired to timer 2, and pin 7 is wired to timer 4. That cannot be changed by software.
Logged

France
Offline Offline
Newbie
*
Karma: 0
Posts: 29
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi,

very nice library it was really needed.
One question or suggestion, as far as I understand, it is not possible with your library to have more than 8 bit resolution for pwmWrite ?
For my application I need 10bits or so, do you see any way to implement that in you library ?

thanks for sharing, good job.
Logged

Mgth : ArduixPL http://www.mgth.fr

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

8 bit timers are 'capable' of 8 bit resolution, 16 bit timers are 'capable' of 16 bit resolution. For the sake of consistency, I stuck with 8 in this library. It didn't occur to me that there was any need to bump up the resolution. I will happy to add some methods when I get some time to do so within a few days. It should be noted, however, that this would only affect 16 bit timers.

Note: there is also one caveat you must associate with AVR timers and resolution. The resolution effectively decreases inversely with the frequency. At 1KHz you get ~13 bits of resolution. At 100 KHz, you get ~6 bits of resolution. At 1 MHz you get ~3 bits of resolution. This is a property of the timers on AVR CPUs and cannot be changed by software.

To check your resolution at a given frequency, set the frequency on the timer of your choice. The SetFrequency functions are aware of all of the variables, and will mathematically determine the method of creating the highest possible resolution. Then call TimerX_GetTop() and add 1. That will be your resolution in base 10 (decimal). Take Log base 2 of that and you can get your resolution in binary. In other words:
Resolution = LOG2(TimerX_GetTop() + 1).

Just out of curiosity; what do you need the resolution for, and what board are you using.

Edit: forgot the +1 in the original post
« Last Edit: August 16, 2012, 12:48:33 pm by runnerup » Logged

France
Offline Offline
Newbie
*
Karma: 0
Posts: 29
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi,

thanks for your explanation. My application is about dimming power leds. Dimming is not linear at all, and for some reason, I cannot go near zero . I think it is because of integrated led driver, that must perform pwm internally. Anyway, I count get it working by using 16bits timer on an arduino Uno using LUT table to obtain linear fading. now I have to port to an arduino Mega, and your library is very "time saver" for me.

thank you.


Logged

Mgth : ArduixPL http://www.mgth.fr

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I have working "high resolution" functions and an example, I will post it later today when I get the chance.
Logged

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The code is now up. There are three new functions and an example sketch to explain how they work.
To be consistent with analogWrite(), pwmWrite() will always perform 8 bit pwm. If a higher resolution is necessary, use pwmWriteHR() instead.

void pwmWrite(uint8_t pin, uint8_t duty) 8-bit, 0 - 255
void pwmWriteHR(uint8_t pin, uint16_t duty) 16-bit, 0 - 65535

Unfortunately, resolution control is not that simple once custom frequencies come into play. If you modify the frequency on a timer, the resolution will change. The general rule of thumb is that the higher the frequency the lower the resolution. There are several variables that the SetFrequency functions wrap. They are aware of them and will mathematically determine the highest possible resolution at that given frequency. Although pwmWriteHR() accepts a 16 bit integer, it will automatically map to any timer resolution. To know whether the resolution is too low for your tolerances at a particular frequency, I have added two functions.

float GetPinResolution(uint8_t pin)
float TimerX_GetResolution() (replace X with a timer number)

These functions find the resolution in base 2/the number bits required to represent the resolution. Please note that both return floats, not ints; that is intentional. If you prefer to find the number of possible values instead of messing with binary, use TimerX_GetTop() and add 1 (which is the same thing but in base 10).

I added a sketch called PWM_lib_resolution_example to the project to demonstrate these functions and the relationship between timer frequency and resolution.
I have not been able to find enough time to exhaustively check this code for bugs, so if you find any please speak up.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 46
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Awesome library runnerup!
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 46
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Wait, I can't verify my sketch as it says 'prescaler' has not been declared in the following line:

Code:
extern void SetPrescaler_8(const int16_t timerOffset, prescaler psc);

What should I do?

Many thanks
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 46
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I mean, I tried using the psalt and then it says that prescaler_alt hasn't been declared either? Am I modifying it incorrectly by just simply deleting ps_... values and the line of code in the previous reply?
« Last Edit: August 20, 2012, 10:42:05 am by ksshhs » Logged

Offline Offline
Newbie
*
Karma: 2
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Good work Ksshhs, you found a bug for me XD... but no, it was not the cause of your problem.
The library currently has a bug in the preprocessor directives. It calls the wrong internal SetPrescaller function for timer 2. I will publish version .04 to fix that right away.

I have no way of knowing what your problem is. For future reference, you should post the source you are working with, along with the type of board you are compiling for.
My guess is that you are trying to call either Timer0_SetPrescaler() or Timer2_SetPrescaler() explicitly and using the wrong syntax for the parameter.

Here is an example of setting the prescaller to 1024 on all of the mega's timers:

Code:
void setup()
{
        Timer0_SetPrescaler(ps_1024);
Timer1_SetPrescaler(ps_1024);
Timer2_SetPrescaler(psalt_1024);
Timer3_SetPrescaler(ps_1024);
Timer4_SetPrescaler(ps_1024);
Timer5_SetPrescaler(ps_1024);
}

Same deal on the Uno and most other non megas except there are only 3 timers (0, 1, and 2) available. Attempting to use the others will give you a compiler error

For all possible prescaler parameters, look at how it it is defined in the header (you can also look at the first post in this thread):
Code:
enum prescaler
{
ps_1 = 1,
ps_8 = 2,
ps_64 = 3,
ps_256 = 4,
ps_1024 = 5
};

//certain 8 bit timers read the CSn register differently
enum prescaler_alt
{
psalt_1 = 1,
psalt_8 = 2,
psalt_32 = 3,
psalt_64 = 4,
psalt_128 = 5,
psalt_256 = 6,
psalt_1024 = 7
};

Don't worry about the values the enum elements are set to, they are used as bit masks and can only confuse humans.
Oh, and be sure to download the newest version of the library before you mess with timer 2.
Logged

Pages: [1] 2 3 ... 5   Go Up
Jump to: