Accurately measuring duty cycle
Specification¶
- To measure the average duty cycle in a PWM/pulse train
- To accept a minimum high of 3uS
- To measure to the highest accuracy easily attainable with the mbed
- To measure signals with frequency 1Hz - 100kHz
Implementation¶
The LPC1768 (and indeed many others) include Counter/Timer peripheral blocks which make this task rather easy. They have the facility to capture the timer value on certain pin toggle events (the CAPn.0/1 inputs). The timer can be configured to count up on every clock; record when the signal goes low and record when it goes high again, and have interrupts on either of these events.
Unfortunately the two capture functions (cap 1 and 0) can only work on separate pins (with a register for storing the value for each) so to measure both high and low going times one is required to wire two pins together. In this example we are using Timer 2 and therefore pins 30 and 29. To test pin 23 was used for generating a PWM signal of various frequencies.
Here is a test program:
Import program
00001 // Measure and print the average duty cycle of a signal connected to p29 and p30 (together) while the button (p16) is pulled high 00002 00003 #include "mbed.h" 00004 #include "PWMAverage.h" 00005 00006 DigitalOut myled(LED1); 00007 00008 PWMAverage pa(p29,p30); 00009 00010 DigitalIn button (p16); 00011 00012 Timer tmr; 00013 00014 int main() 00015 { 00016 button.mode(PullDown); 00017 while(1) 00018 { 00019 pa.reset(); 00020 00021 while (!button) {} 00022 pa.start(); 00023 tmr.start(); 00024 myled=1; 00025 00026 while (button) {} 00027 pa.stop(); 00028 tmr.stop(); 00029 myled=0; 00030 00031 printf("Average dudy cycle over %d us was %.4f\n\r",tmr.read_us(),pa.read()); 00032 } 00033 }
A class was written to do the heavy lifting: PWMAverage
Import library
Public Member Functions |
|
PWMAverage (PinName cap0, PinName cap1) | |
Create a
PWMAverage
object.
|
|
void | reset () |
Reset the counters and values.
|
|
void | start () |
Start the timer.
|
|
void | stop () |
Stop the timer.
|
|
float | read () |
Read the duty cycle measured.
|
|
double | avg_up () |
Read the average length of time the signal was high per cycle in seconds.
|
|
float | avg_UP () |
Read the average length of time the signal was high per cycle in seconds.
|
|
double | avg_down () |
Read the average length of time the signal was low per cycle in seconds.
|
|
double | period () |
Read the average period in seconds.
|
|
int | count () |
Read the number of cycles counted over.
|
Of course it was necessary to test the performance of the library: To do this a small test program (using a slightly modified library admittedly: the count_, total and totalup member variables were moved to public access) was written. This used mbeds on-board PWM generation to characterize the stability and accuracy of the system:
Import programpwm_duty_measurement
A program which test the PWMAverage library internally: just connect pins 23, 29 and 30 short and check it on the terminal
The results show that accuracy varies, however shows pretty good accuracy in the default configuration of the test program: PWM at 100kHz. Study shows that re-coding the ISR in assembler may be a good idea, as this seems to vary in length slightly in use. This is the only cause of inaccuracy (other than rounding errors) in the program. If 10ppm or less accuracy was needed, re-writing the ISR in assembler would be essential.