Getting Higher than microsecond resolution with PWM

11 Jul 2012

Hi All,

I've been struggling with getting pwm signals generated with resolutions less than the microsecond level. Is this even possible?

For example, if I want a period of 25 microseconds at a 50% duty cycle, it's easy:

myPWM.period(.000025); myPWM.pulsewidth(.0000125);

Now, if I want a period of 25.x (e.g., 25.5 us), everything just gets truncated. So it basically steps between 40 Khz, to 41.67 Khz (24us), and up in increments of +- 1us. Anyway to get any higher resolution (e.g., at the nano second level)?

Thanks, mrtm3050

11 Jul 2012

I believe you can directly write to the LPC PWM hardware to get higher resolution than the default 1us (someone should really write a library for that).

The NXP user guide http://www.nxp.com/documents/user_manual/UM10360.pdf will show which registers are required. If I am bored tomorrow (which is not unlikely), I will try if you can use the standard mbed library while hijacking some register settings, that would be the easiest solution.

11 Jul 2012

Hey Erik,

Thanks for the reply. Let me know if you have any luck with the highjacking. I'm very new to this stuff, but I'll try to wrap my head around some of the lower level calls.

Thanks, mrtm3050

12 Jul 2012

Well I expected the prescaler was set to achieve 1us resolution, just like the mbed timer has (afaik), so PWM abilities would effectively be equal to the mbed timer abilities. Don't ask me why you would want a PWM pulse width of half an hour, but at least it would make some sense.

However the prescaler is not set, the compare match register (which determines the period of the PWM), is only set in multiples of 24 by the mbed library. Add that the PWM is driven by the main clock divided by four currently, and you got effectively multiples of 96. With the 96MHz clock that means you can't reach better resolution than 1us. But I cannot see any reason why you can set it only in multiples of 24, besides that they internally set everything with the period_us(int time) function, but that would be a quite bad reason for not allowing better resolution.

Anyway should be doable later today to fix a library with better resolution.

12 Jul 2012

Study this code and the NXP LPC1768 user manual if you want to achieve your requirement

#include "mbed.h"

int main()
{
    //PWM code on channel 2 and 4 (LED2/4 on MBED)
    
    LPC_SC->PCONP|=(1<<6);                                                        //enable power on PWM1
    LPC_SC->PCLKSEL0|=(3<<12);                                                    //run PWM1 clock at prescaler 8 (96MHz/8 = 12MHz)
    LPC_PINCON->PINSEL3|=(2<<14) | (2<<8);                                        //PWM on channel 2 and 4
    LPC_PINCON->PINMODE3|=(2<<14) | (2<<8);                                       //no pull ups/pull downs on PIN1.20 and 1.23
    LPC_PWM1->MCR|=(0<<14) | (0<<13) | (0<<12) | (0<<8) | (0<<7) | (0<<6) | (1<<1);        //no interrupt, no reset, no stop of TC compare match
    LPC_PWM1->CTCR|=(0<<0);                                                       //timer mode
    LPC_PWM1->PCR|=(1<<12) | (1<<10) | (1<<4) | (1<<2);                           //double edge control on channel 2 and 4
    LPC_PWM1->PR=1199999;                                                         //pre scale PWM to 10 Hz
    LPC_PWM1->MR0=9;                                                              //top value of timer/counter, reset counter on match
    LPC_PWM1->MR1=2;                                                              //ON LED 2 after 2 counts
    LPC_PWM1->MR2=9;                                                              //OFF LED 2 after 9 counts
    LPC_PWM1->MR3=6;                                                              //ON LED 4 after 6 counts
    LPC_PWM1->MR4=9;                                                              //OFF LED 4 after 9 counts
    LPC_PWM1->LER|=(1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0);                    //update values
    LPC_PWM1->TCR|=(1<<3) | (1<<0);                                               //enable PWM and timer NOW
    
    
    while(1);
}
12 Jul 2012

While doing it that way is an option, I was always teached it is better to be lazy than tired. So I simply decided to make a standard PWM object and let that set all the stuff like pin settings, and only change the compare match registers manually. Maybe not the nicest method, but definately easier (not so hard to do it with registers, but finding out which register you need is always irritating).

Anyway I *think* the library should work:

Import libraryFastPWM

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

Few remarks, most important you can just directly replace mbed functions with this, you should not need to change anything (besides including the library and making the PwmOut object a FastPWM object). The library changes the clock speed of the PWM hardware, the divide by four is removed. This gives four times more resolution, but that happens also for PwmOut objects in the same program, so they also run four times faster. Of course in the FastPWM library this is compensated, if you tell the period should be 1 second, it will be one second. If you have a PwmOut object and you tell it its period should be 1 second, it will be 0.25 seconds.

Finally I found at least one reason why the standard resolution is not better: floating point precision. A 32-bit float simply lacks precision. So FastPWM uses doubles for everything. You can still send a float to it, the compiler will cast that automatically, but then you will lack precision again. (If you use for example PWM.period(0.0001321312); it will be a double automatically).

So let me know if it works. I did check it with a LED and checking register values, but I did not connect it to a scope. Resolution is 1/clock frequency, by default 96MHz, but if you use a higher clock you can define it in your code and the library will do the calculations with your value.

Btw this only works for LPC1768, I assume you got that one. Since I don't have the other one I cannot even compile for it, so kinda hard to add that functionality. Also it has one aditional function that normal PWM library does not have, you can write microsecond pulsewidth/period also as double (/float).

12 Jul 2012

Awesome guys, thanks for the assistance. Will be trying it in the next couple of hours. :)

12 Jul 2012

FYI I accidently mulitplied by 1000 to go from milli seconds to seconds, instead of dividing, and same error for microseconds. So if you tried them, they didnt do much. New revision should have fixed that.

24 Jul 2012

Sorry for the late response, but I just got a chance to test it using the following code:

/media/uploads/mrtm3050/main.txt

Seems to set it on the first try just fine, but continuously updating based on user input seems to be a problem. Am I doing something wrong?

I tested on an OScope and the output is 1/4th of the value I'm initially setting, then never gets updated again on user input for subsequent period/pulsewidth calls.

25 Jul 2012

Hey,

Thanks for your test. Thats why it is useful to have someone else also look at it, then you find all the stupid mistakes I made. TL;DR, updated version is out, update the library and it should work hopefully.

Changes:

1. I actually didnt add the clock selection changes (from F_CLK/4 to just F_CLK) into the program. It was probably in my test program still added, so I never noticed I forgot it in the library. Thats why you got 1/4th of the value.

2. I really should read manuals better. There is a latch enable register, when written to one it will update the value of the registers next time a new PWM cycle starts. I never noticed, and only tested with setting once at initialization. Result: registers were never updated after first time.

3. And finally if you change the period, the duty cycle is supposed to stay constant, but I saw in your code you set the pulsewidth again everytime. Now since it didnt work that was probably a bit random, but still I looked at it, and if you use pulsewidth to set the pulsewidth instead of duty cycle, the duty cycle is never updated. So now if you use pulsewidth it will also update its internal duty cycle variable. Then if you change the period, the dutycycle will stay the same.

Now that might not always be the behavior you want (you might rather want to keep same pulsewidth), but thats the way it is specified in mbed library, and this way there is some consistency.

Anyway I hope this fixed the issues for you, and hopefully not too many more bugs in such a short/simple program. Let me know if it works now :)

Erik