Decoding PWM duty - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: October 29, 2016, 07:02:49 am
content: I need to read a 250 Hz PWM signal and determine what its duty is. I was thinking of using interrupts to trigger on the rising and falling edges of the signal. However it occurs to me that I must make sure I have enough time to carry out what I need to do in the interrupt. Using an 8 MHz clock I would have 320 clock cycles to carry out anything I want to do when the PWM is at 1%. I suppose at a worst-case scenario this is 80 instruction cycles. As all I would need to do is store the timestamp that the rising edge occurs at I would expect 80 instructions would be sufficient would it not?

author: JPortici
author_rank: Super Contributor
timestamp: October 29, 2016, 07:22:26 am
content: what accuracy you need?i am doing this same thing* with a PIC18F, 32 MHz (8 MIPS):Free running timerRising edge measure the period by copying the value of the timer, resets the timer.Falling edge measure the duty by copying the value of the timer.Calculation done in the main loop. i have at least one decimal digit precisionif i wanted i could take care of the fixed error by adding/subtracting constants and increase the resolution a bit before i get to the limit imposed by the mcu frequency but i didn't even need to have that decimal digit precision*well in my application i have to take account for random, variable frequency... or even worse "random pulse trains". if you are sure that once the system is running the signal is always present and the frequency will be fixed (or varying below a certain amount) you may want to spend a few cycles acquiring the period lenght and then optimize the hell out of it before the main cycle starts, transforming all divisions in multiplications and such...Of course there are better methods, depending on your MCU of choice you should have hardware peripherals that take care of most of the work without upsetting the program flow with interrupts and you only have to make the calculations. in my case i had to work with existing hardware. i did upgrade the mcu with a better pin to pin compatible one but i still didn't have access to input capture or other peripherals.

author: Simon
author_rank: Unknown
timestamp: October 29, 2016, 07:44:53 am
content: I'm using an AVR attiny24 which I can run at 8 MHz with internal clock. The 16 bit counter will be running anyway for the PWM output so I could use that to read off the current time when the interrupt fires or I could use one of the other eight bit counters. The other way of doing it would be to take the signal buffet and lowpass filter it and read it in analogue.

author: bktemp
author_rank: Super Contributor
timestamp: October 29, 2016, 07:52:34 am
content: It should be doable. If you want to measure really low duty cycles, you should do it in assembler, because a compiler will add unnecessary push/pops of status register.In the ISR you need to read the timer and reconfigure the interrupt for the next edge. This takes probably 20-60 cyles. In assembler it is probably twice as fast.If you also need to measure 0% and 100% you need to add a timeout and check if the signal is permanently low or high.

author: Simon
author_rank: Unknown
timestamp: October 29, 2016, 07:59:47 am
content: I would expect the minimum duty to be 1%. I will declare to the customer the anything less than 1% will produce errors. Unfortunately I don't know assembler and have no intention of starting by now. Why do I need to reconfigure the interrupts? Don't the individual settings have their own vector so the low to high go to 1 set of instructions and the heightened low would go to a separate set of instructions or do I need to change the interrupts setting every time it fires to get ready for the reverse edge change?

author: bktemp
author_rank: Super Contributor
timestamp: October 29, 2016, 08:13:35 am
content: There is only one interrupt vector for each external interrupt pin.One possible solution is configuring the interrupt to trigger on both edges (you could also use the pin change interrupt on any pin for this) and then read the pin value in software when reading the timer. It takes some additional time and may produce a wrong result if the pulse is too short or there was a glitch and has changed again before the software could read the state.You could also reconfigure the edge in each interrupt, this avoids reading the pin state, because it only triggered at the selected edge. But you need to reconfigure the edge, so probably not much better than the first solution.The other solution is connecting the signal to both interrupt pins: One configured for rising and the other one for falling edge. Then you have two seperate interrupt vectors and save some time, but you need an additional pin.

author: Simon
author_rank: Unknown
timestamp: October 29, 2016, 08:19:24 am
content: Yes i thought of using both pins, sounds like that is the best solution. I certainly have pins to spare as it is PWM in and PWM out, nothing more.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 29, 2016, 08:41:17 am
content: If you have a free-running 16 bit timer, running at 8Mhz it rolls over every 1/122th of a second, which is enough for 250Hz PWM rate:- enable interrupt-on-change on the pin being monitoredIn the interrupt:- on rising edge you store the timer count in "rising_edge_time", and set a flag "rising_edge_valid"- on the falling edge, if rising_edge_valid is set, you store "timer count - rising_edge_time" to the current pulse_length, and clear the "rising_edge_valid" flag.Code:void irq_pin_change(void) {/* get the counter straight away */this_count = get_counter();/* work out what to do with it */if(get_input_pin_state() = '1') {rising_edge_time = this_count;rising_edge_valid = 1;} else if(rising_edge_valid != 0) {pulse_length = this_count - rising_edge_time;rising_edge_valid = 0;}}In the main program- if pulse_length is not 0xFFFF, then you have a new length value you can use, and can set pulse_length to an invalid value (e.g. -1) if you want to detect when a new value arrives.Code:/* Atomic read and reset of pulse_length */disable_interrupts();this_pulse_length = pulse_length;pulse_length = 0xFFFF;enable_interrupts();if(this_pulse_length != 0xFFFF) {/* Do stuff with this_pulse_length - it is a new reading */}To make it bomb-proof you should also add code to detect when you don't receive a falling edge within the expected 250Hz window. This could be done by making "rising_edge_valid" a counter, and setting it to 2. A IRQ is then needed when the counter rolls over, which decrements a non-zero rising_edge_valid. After the counter roles over twice this 'forgets' about seeing the rising edge, as rising_edge_valid becomes zero (false) again.

author: Psi
author_rank: Super Contributor
timestamp: October 29, 2016, 11:34:14 am
content: Might have missed a step or two, did this from memory, but...Dedicate a timer just for this.Feed the signal into one of the input capture pinsSetup the timer clock so that it wont overflow until a bit after 250hzSet timer to input capture on rising edge.Now you can enable interrupt on input capture event.On interrupt save capture timer value (number of timer ticks of "high" waveform). Then clear timer value change the input capture to falling edge.When the interrupt next occurs you have the number of timer ticks of "low" waveform. Now you can calculate the duty. Set the timer back to capture on rising edge again and the cycle repeats.Ideally you want to do the duty cycle calculation in the main loop and just save the values in the interrupt.Ya want to keep the slow float/divide function out of the interrupt handlerAnd, as always, dont forget to use volatile

author: bap2703
author_rank: Regular Contributor
timestamp: October 30, 2016, 01:41:50 pm
content: You might find that application note useful :http://www.atmel.com/Images/Atmel-8014-Using-Timer-Capture-to-Measure-PWM-Duty-Cycle_ApplicationNote_AVR135.pdf

author: lujji
author_rank: Contributor
timestamp: October 30, 2016, 05:07:02 pm
content: Quote from: bktemp on October 29, 2016, 07:52:34 amAVR GCC allows declaring naked ISRs. This way prologue and epilogue are not generated by the compiler.Code:ISR(XXX_vect, ISR_NAKED) {...reti();}

author: Simon
author_rank: Unknown
timestamp: October 30, 2016, 05:21:44 pm
content: The simplest way is to just low pass filter it as all i need to know is the duty. so comparator with rail to rail output followed by a low pass filter.

author: lujji
author_rank: Contributor
timestamp: October 30, 2016, 06:05:44 pm
content: Using supply voltage of 3.3V as ADC reference gives you approximately 3mV resolution. That is just about good enough to measure duty cycle with 1% resolution.Since you're dealing with a low frequency signal (250Hz), you'd need more than a simple RC-network to provide <3mV ripple. Averaging ADC samples could be an option as well.

author: Simon
author_rank: Unknown
timestamp: October 30, 2016, 06:10:10 pm
content: I've not yet been told how accurate they want to be. Yes averaging ADC samples is no problem, all I'm doing is taking one PWM in and converting it to another PWM out with perhaps some custom scaling, so all the CPU time can be used to run smoothing algorithms.

author: Simon
author_rank: Unknown
timestamp: October 30, 2016, 06:11:22 pm
content: why would I not use 5V as the ref (same as comparator supply) ?

author: lujji
author_rank: Contributor
timestamp: October 30, 2016, 06:26:24 pm
content: Quote from: Simon on October 30, 2016, 06:10:10 pmPerhaps it might be reasonable to opt for a purely analog solution in that case?QuoteIt doesn't matter really, as long as you're using supply voltage for reference.

author: Simon
author_rank: Unknown
timestamp: October 30, 2016, 06:26:38 pm
content: and it would be a 0.1% accuracy reading but like you say smothing should be applied.

author: Simon
author_rank: Unknown
timestamp: October 30, 2016, 06:28:14 pm
content: Quote from: lujji on October 30, 2016, 06:26:24 pmno because i may have to do some math on the input to produce the output or use a lookup table. trying to do that stuff in analogue is asking for trouble and as it is yet undefined I'd rather be able to design the circuit and leave the logic until later if needs be and be able to modify on customer request.

author: lujji
author_rank: Contributor
timestamp: October 30, 2016, 06:38:28 pm
content: Quote from: Simon on October 30, 2016, 06:26:38 pmYeap, you're right, I was a bit out (i.e. by an order of magnitude). 10bit ADC result would give you 0.1% resolution, not 1%.

author: aromring
author_rank: Unknown
timestamp: February 20, 2019, 11:21:59 pm
content: I had the same problem: too short pulse widths were taxing my MCU. They way I solved it involves converting the <0 - period> pulse width to <period - 2*period> pulse width much more manageable by the MCU. See details here:https://www.instructables.com/id/How-to-Measure-High-Frequency-and-Duty-Cycle-Simul/

author: NorthGuy
author_rank: Super Contributor
timestamp: February 21, 2019, 12:07:26 am
content: A standard way of doing this is very simple:- a free running timer with rollower frequency 1/2 of your signal or less.- an Input Capture module (IC1) which latches the timer on the rising edge- another Input Capture module (IC2) which latches the timer on the falling edge and produces an interruptIn the ISR, you just calculate (IC2 - IC1) /(IC1 - IC1_previous). If this turns out to be negative, then the duty is almost 1 and you have captured the next IC1 already. In this case you simply add 1 to your result.Does your chip have Input Capture modules?

author: NorthGuy
author_rank: Super Contributor
timestamp: February 21, 2019, 12:38:45 am
content: Another method is this:- free running timer (T1) of high frequency producing interrupts at rollover- a gated timer (T2) of the same frequency - gated by the signal you measureThen in the ISR you calculate (T2 - T2_previous)/timer_periodDoes you chip have a gated timer?

author: Fire Doger
author_rank: Regular Contributor
timestamp: February 21, 2019, 02:32:00 am
content: Quote from: aromring on February 20, 2019, 11:21:59 pmI guess in 2 years he found a solutionIts not very nice to resurrect dead questions (and by dead I mean with latest reply before 1 year+) to post your publishing from other sites...

author: Simon
author_rank: Unknown
timestamp: February 21, 2019, 07:24:59 am
content: Quote from: Fire Doger on February 21, 2019, 02:32:00 amquite so and yes after 2 years I do have a solution and it's in the latest ATmega4809 series that have counters with specific modes measuring PWM