Library that allows for higher resolution and speed than standard mbed PWM library using same syntax (drop-in replacement).

Dependents:   PwmOscillator FastStepDriver TLC5940 CameraTest ... more

FastPWM is a library that unlocks alot more of the potential of the mbed's PWM units than the normal PWM library. It is currently available for the LPC1768, LPC11u24, KLxxZ, K20D50M and most STM32 targets (those lacking are fairly easy to add). (Since I generally forget to update this list, if you want to know if your target is supported see if it compiles).

The two main points it allows for compared to the regular PwmOut library is clock cycle precision PWM and (automated) changing prescalers. It isn't perfect yet, but for now it will have to do ;). For those familiar with version 1, version 2 is almost completely rewritten to add more functions.

Usage

FastPWM is largely a drop-in replacement for the normal PwmOut library. All the same functions are available, with some extras.

Prescaler

Warning

All prescaler options are disabled for 32-bit PWM units currently, the prescaler is fixed at 1

fastpwm.prescaler(value);

With this function you can set the value of the prescaler. Aditionally the second argument of the constructor is used for the same to directly set it from the constructor. It returns the actual prescaler which is set. If the requested one isn't available it is always rounded up (unless it is larger than the maximum prescaler).

There are three options for this function. Any value larger than zero will simply be set. (Yes it is signed, so yes you cannot use the full 32-bit prescaler if your device supports it, I cannot imagine why you possibly would want that). If the value is zero dynamic prescaling is disabled and the current prescaler is returned. If the value is -1 dynamic prescaling is enabled and the current prescaler is returned.

So what is dynamic prescaling? This is the default option for FastPWM, don't use any prescaler option and it is enabled. To start with the negative, it adds quite some processing cycles, so changing the period takes longer. Luckily generally the PWM period is constant. The good part is that it automatically adapts the prescaler unit to give as much accuracy as possible: It gives highest accuracy for the duty-cycle, and also allows you to generate a wide range of periods. For example you can now create a LED blinking at 1Hz with FastPWM on the LPC11u24/Nucleo 16-bit PWM units. (On the KL25Z this isn't possible due to limitted value of the prescaler).

As the nice warning message above says, this is currently only implemented for 16-bit PWM units, simply because normally you won't need it for 32-bit PWM units. For those it is automatically disabled, and you cannot enable it. However for example the majority of the PWM units of the LPC11u24 can't be used to make servo signals with PwmOut, with FastPWM they can.

TL;DR, by default it uses dynamic prescaling. Unless period is changed very often just keep it on default and enjoy your larger range of possible periods and higher accuracy.

Ticks

fastpwm.period_ticks(ticks);
fastpwm.pulsewidth_ticks(ticks);

These two functions allow you to directly write the pwm period and pulsewidth in clock ticks. This is useful if you need to have very little overhead. It is dependent on which device you use, since they have different clock rates. You can get the current clock speed of your device with SystemCoreClock.

Double

PwmOut uses floats for setting the time in seconds, and ints for milliseconds and microseconds. All three of those don't give enough accuracy to fully use the PWM units. Which is why FastPWM uses besides int for milliseconds and microseconds, it uses doubles for seconds and also for microseconds. Generally it is adviced to use these doubles, sometimes you might need to explicitly cast your variables to doubles.

Currently setting pulsewidth in microseconds with an int is a risk with some prescaler values (not on the 32-bit timers). See known-issues.

Adding other microcontrollers

Look at the other device files on how to add other microcontrollers. Functions that need to be implemented:

  • initFastPWM(): Any setups required can be done here. Must set the number of bits of the PWM unit.
  • pulsewidth_ticks( uint32_t ticks ): Set the pulsewidth in ticks
  • period_ticks( uint32_t ticks ): Set the period in ticks
  • getPeriod(): Return the period in ticks
  • setPrescaler(uint32_t reqScale): Set the prescaler. If reqScale is zero, return the current prescaler value only. Otherwise set the requested prescaler, if needed round up to first available prescaler, return the actually set prescaler. If the PWM unit is 32-bit (or for another reason), you can set dynamicPrescaler as false, disabling the dynamic prescaler.

Known Issues

  • Changing the prescaler manually does not adapt periods/pulsewidth
    • Manually re-set the period of each FastPWM object on that PWM unit, this should also set the duty cycle.
  • Changing the period of one FastPWM object does not keep the duty cycle of other PWM objects on that PWM unit constant, but the pulsewidth.
    • Manually re-set the duty cycle of other PWM objects.
  • PwmOut objects run at wrong speed when you use FastPWM
    • Don't use PwmOut objects.
  • On certain prescaler values setting period/pulsewidth in especially microsecond integers, also to lesser extend also millisecond integers, can result in wrong values.
    • The problem is that the number of clock ticks per microsecond/millisecond as integers are pre-calculated for improved speed. However if it isn't an integer number that gives an error.
    • Solution is to preferably use doubles (or ticks). On the 32-bit pwm units this is not an issue, so for them it doesn't matter.
    • I am planning to have a further look into it, but I expect it to stay an issue.

Here the TL;DR is: Preferably set the period/prescaler once at the beginning before setting the duty-cycle/pulsewidth. If that isn't possible, take into account duty cyles need to be set again. And preferably use doubles.

Credits

Some of the ideas are 'loaned' from Jochen Krapf's fork of the original FastPWM: http://mbed.org/users/jocis/code/HighPWM/

Committer:
Sissors
Date:
Tue Aug 13 16:54:06 2013 +0000
Revision:
4:a7b9f778c4b4
Child:
6:0f57969697b6
v2.0
; Added KL25Z/LPC11u24 support
; Added prescalers
; Direct tick changes

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Sissors 4:a7b9f778c4b4 1 #include "FastPWM.h"
Sissors 4:a7b9f778c4b4 2
Sissors 4:a7b9f778c4b4 3 FastPWM::FastPWM(PinName pin, int prescaler) : PwmOut(pin) {
Sissors 4:a7b9f778c4b4 4 initFastPWM();
Sissors 4:a7b9f778c4b4 5 this->prescaler(prescaler);
Sissors 4:a7b9f778c4b4 6
Sissors 4:a7b9f778c4b4 7 //Set duty cycle on 0%, period on 20ms
Sissors 4:a7b9f778c4b4 8 _duty=0;
Sissors 4:a7b9f778c4b4 9 period(0.02);
Sissors 4:a7b9f778c4b4 10
Sissors 4:a7b9f778c4b4 11
Sissors 4:a7b9f778c4b4 12 }
Sissors 4:a7b9f778c4b4 13
Sissors 4:a7b9f778c4b4 14 void FastPWM::period(double seconds) {
Sissors 4:a7b9f778c4b4 15 if (dynamicPrescaler)
Sissors 4:a7b9f778c4b4 16 calcPrescaler((uint64_t)(seconds * (double) SystemCoreClock));
Sissors 4:a7b9f778c4b4 17
Sissors 4:a7b9f778c4b4 18 period_ticks(seconds * dticks + 0.5);
Sissors 4:a7b9f778c4b4 19 pulsewidth_ticks(getPeriod() * _duty);
Sissors 4:a7b9f778c4b4 20 }
Sissors 4:a7b9f778c4b4 21
Sissors 4:a7b9f778c4b4 22 void FastPWM::period_ms(int ms) {
Sissors 4:a7b9f778c4b4 23 if (dynamicPrescaler)
Sissors 4:a7b9f778c4b4 24 calcPrescaler(ms * (SystemCoreClock / 1000));
Sissors 4:a7b9f778c4b4 25
Sissors 4:a7b9f778c4b4 26 period_ticks(ms * iticks_ms);
Sissors 4:a7b9f778c4b4 27 pulsewidth_ticks(getPeriod() * _duty);
Sissors 4:a7b9f778c4b4 28 }
Sissors 4:a7b9f778c4b4 29
Sissors 4:a7b9f778c4b4 30 void FastPWM::period_us(int us) {
Sissors 4:a7b9f778c4b4 31 if (dynamicPrescaler)
Sissors 4:a7b9f778c4b4 32 calcPrescaler(us * (SystemCoreClock / 1000000));
Sissors 4:a7b9f778c4b4 33
Sissors 4:a7b9f778c4b4 34 period_ticks(us * iticks_us);
Sissors 4:a7b9f778c4b4 35 pulsewidth_ticks(getPeriod() * _duty);
Sissors 4:a7b9f778c4b4 36 }
Sissors 4:a7b9f778c4b4 37
Sissors 4:a7b9f778c4b4 38 void FastPWM::period_us(double us) {
Sissors 4:a7b9f778c4b4 39 if (dynamicPrescaler)
Sissors 4:a7b9f778c4b4 40 calcPrescaler((uint64_t)(us * (double)(SystemCoreClock / 1000000)));
Sissors 4:a7b9f778c4b4 41
Sissors 4:a7b9f778c4b4 42 period_ticks(us * dticks_us + 0.5);
Sissors 4:a7b9f778c4b4 43 pulsewidth_ticks(getPeriod() * _duty);
Sissors 4:a7b9f778c4b4 44 }
Sissors 4:a7b9f778c4b4 45
Sissors 4:a7b9f778c4b4 46 void FastPWM::pulsewidth(double seconds) {
Sissors 4:a7b9f778c4b4 47 pulsewidth_ticks(seconds * dticks + 0.5);
Sissors 4:a7b9f778c4b4 48 }
Sissors 4:a7b9f778c4b4 49
Sissors 4:a7b9f778c4b4 50 void FastPWM::pulsewidth_ms(int ms) {
Sissors 4:a7b9f778c4b4 51 pulsewidth_ticks(ms * iticks_ms);
Sissors 4:a7b9f778c4b4 52 }
Sissors 4:a7b9f778c4b4 53
Sissors 4:a7b9f778c4b4 54 void FastPWM::pulsewidth_us(int us) {
Sissors 4:a7b9f778c4b4 55 pulsewidth_ticks(us * iticks_us);
Sissors 4:a7b9f778c4b4 56 }
Sissors 4:a7b9f778c4b4 57
Sissors 4:a7b9f778c4b4 58 void FastPWM::pulsewidth_us(double us) {
Sissors 4:a7b9f778c4b4 59 pulsewidth_ticks(us * dticks_us + 0.5);
Sissors 4:a7b9f778c4b4 60 }
Sissors 4:a7b9f778c4b4 61
Sissors 4:a7b9f778c4b4 62 void FastPWM::write(double duty) {
Sissors 4:a7b9f778c4b4 63 _duty=duty;
Sissors 4:a7b9f778c4b4 64 pulsewidth_ticks(duty*getPeriod());
Sissors 4:a7b9f778c4b4 65 }
Sissors 4:a7b9f778c4b4 66
Sissors 4:a7b9f778c4b4 67 double FastPWM::read( void ) {
Sissors 4:a7b9f778c4b4 68 return _duty;
Sissors 4:a7b9f778c4b4 69 }
Sissors 4:a7b9f778c4b4 70
Sissors 4:a7b9f778c4b4 71 FastPWM & FastPWM::operator= (double value) {
Sissors 4:a7b9f778c4b4 72 write(value);
Sissors 4:a7b9f778c4b4 73 return(*this);
Sissors 4:a7b9f778c4b4 74 }
Sissors 4:a7b9f778c4b4 75
Sissors 4:a7b9f778c4b4 76 FastPWM::operator double() {
Sissors 4:a7b9f778c4b4 77 return _duty;
Sissors 4:a7b9f778c4b4 78 }
Sissors 4:a7b9f778c4b4 79
Sissors 4:a7b9f778c4b4 80 int FastPWM::prescaler(int value) {
Sissors 4:a7b9f778c4b4 81 int retval;
Sissors 4:a7b9f778c4b4 82 if (value == -1) {
Sissors 4:a7b9f778c4b4 83 dynamicPrescaler = true;
Sissors 4:a7b9f778c4b4 84 value = 0;
Sissors 4:a7b9f778c4b4 85 }
Sissors 4:a7b9f778c4b4 86 else
Sissors 4:a7b9f778c4b4 87 dynamicPrescaler = false;
Sissors 4:a7b9f778c4b4 88
Sissors 4:a7b9f778c4b4 89 retval = setPrescaler(value);
Sissors 4:a7b9f778c4b4 90 updateTicks(retval);
Sissors 4:a7b9f778c4b4 91 return retval;
Sissors 4:a7b9f778c4b4 92 }
Sissors 4:a7b9f778c4b4 93
Sissors 4:a7b9f778c4b4 94 void FastPWM::updateTicks( uint32_t prescaler ) {
Sissors 4:a7b9f778c4b4 95 dticks = SystemCoreClock / (double)prescaler;
Sissors 4:a7b9f778c4b4 96 dticks_us = SystemCoreClock / (double)prescaler / 1000000.0f;
Sissors 4:a7b9f778c4b4 97 iticks_ms = SystemCoreClock / prescaler / 1000;
Sissors 4:a7b9f778c4b4 98 iticks_us = SystemCoreClock / prescaler / 1000000;
Sissors 4:a7b9f778c4b4 99 }
Sissors 4:a7b9f778c4b4 100
Sissors 4:a7b9f778c4b4 101 int FastPWM::calcPrescaler(uint64_t clocks) {
Sissors 4:a7b9f778c4b4 102 uint32_t scale = (clocks >> bits) + 1;
Sissors 4:a7b9f778c4b4 103 uint32_t retval = setPrescaler(scale);
Sissors 4:a7b9f778c4b4 104 updateTicks(retval);
Sissors 4:a7b9f778c4b4 105 return retval;
Sissors 4:a7b9f778c4b4 106 }