I'm trying to measure the period of a pulse that varies from 5mS through
30mS with at better than 10 uS resolution. The target processor is running
at 4 MHz (1uS instruction cycles). My problem is that I need a timeout
function, so that a missing signal doesn't become a fatal condition.
The easiest way seemed to call for a 16 bit counter. I immediately thought
of Andrew Warren's fast 5 cycle counter, but I can't think of an easy way to
detect overflow without increasing the loop time drastically.
so... I came up with the following:
;measure HI period
clrf HiPerLO
clrf HiPerHI
movlw 1
MeasHiPerLoop ;8 cycles per count
addwf HiPerLO,F ;add puts carry in known state
skpnc ;lower byte roll over?
addwf HiPerHI,F
skpnc ;upper byte roll over?
goto TimeOut
btfsc PIN ;pin still HI?
goto MeasHiPerLoop
I get 8 cycles for the loop. I can also limit my timeout period by using
bit tests on the high byte if necessary (replace the 2nd 'skpnc' with a
btfsc HiPerHI, n). But I REALLY like Andy's 5 cycle version and am hoping
that someone can come up with an idea on how to do better than what I've
come up with.
Just a reminder: here is Andy's 5 cycle timing loop:
Andrew Warren wrote:
; 5 cycles per count
CHECK_PULSE:
INCFSZ pulse_width_lo
DECF pulse_width_hi
BTFSC PULSE_PORT,PULSE_BIT
GOTO CHECK_PULSE
DONE:
MOVF pulse_width_lo,W
ADDWF pulse_width_hi
My other thought was to use Andy's version, then use the watchdog timer to
limit how long I would wait for the pulse to end. But I need the prescaler
for my main timekeeping routines - and I couldn't reliably measure pulses
that would approach the full time that the counters would be capable of.
Well, it looks as if I forgot something - the watchdog timer! So my loop is
now going to be 9 cycles. I may just stick a 'nop' in there and make it 10
cycles for 10 uS resolution <grin>.
;measure HI period
clrf HiPerLO
clrf HiPerHI
movlw 1
MeasHiPerLoop ;9 cycles per count
clrwdt
addwf HiPerLO,F ;add puts carry in known state
skpnc ;lower byte roll over?
addwf HiPerHI,F
skpnc ;upper byte roll over?
goto TimeOut
btfsc PIN ;pin still HI?
goto MeasHiPerLoop
> Well, it looks as if I forgot something - the watchdog timer! So my loop is
> now going to be 9 cycles. I may just stick a 'nop' in there and make it 10
> cycles for 10 uS resolution <grin>.
This loop takes 7 Cycles (8 with clrwdt)
clrf CounterLo
clrf CounterHi
Loop:
btfss PULSE_PORT,PULSE_BIT ; If low then end
goto MeasureEnd
incfsz CounterLo
decf CounterHi
incfsz CounterHi
goto Loop
goto TimeOut
I'm new to PIC, but have worked with other mc's. Am sending my comment,
might be of use.
If I had to measure the pulse width using 80C51 or 80C552 I would AND the
input pulse and a 100KHz (10uS resolution) clock and give it to the on-chip
counter. If the onchip counter is 8 bit, I would program it to generate an
interrupt on overflow, and use the interrupt to inc. the high byte/word.
Something equivalent should be possible with PIC.
At 20:43 4/25/98 -0600, Dwayne Reid wrote: {Quote hidden}
>Hiya, gang.
>
>I'm trying to measure the period of a pulse that varies from 5mS through
>30mS with at better than 10 uS resolution. The target processor is running
>at 4 MHz (1uS instruction cycles). My problem is that I need a timeout
>function, so that a missing signal doesn't become a fatal condition.
>
>The easiest way seemed to call for a 16 bit counter. I immediately thought
>of Andrew Warren's fast 5 cycle counter, but I can't think of an easy way to
>detect overflow without increasing the loop time drastically.
>
>so... I came up with the following:
>
>;measure HI period
>
> clrf HiPerLO
> clrf HiPerHI
> movlw 1
>
>MeasHiPerLoop ;8 cycles per count
> addwf HiPerLO,F ;add puts carry in known state
> skpnc ;lower byte roll over?
> addwf HiPerHI,F
> skpnc ;upper byte roll over?
> goto TimeOut
> btfsc PIN ;pin still HI?
> goto MeasHiPerLoop
>
>I get 8 cycles for the loop. I can also limit my timeout period by using
>bit tests on the high byte if necessary (replace the 2nd 'skpnc' with a
>btfsc HiPerHI, n). But I REALLY like Andy's 5 cycle version and am hoping
>that someone can come up with an idea on how to do better than what I've
>come up with.
>
>Just a reminder: here is Andy's 5 cycle timing loop:
>
>Andrew Warren wrote:
> ; 5 cycles per count
>
> CHECK_PULSE:
>
> INCFSZ pulse_width_lo
> DECF pulse_width_hi
>
> BTFSC PULSE_PORT,PULSE_BIT
> GOTO CHECK_PULSE
>
> DONE:
>
> MOVF pulse_width_lo,W
> ADDWF pulse_width_hi
>
>
>My other thought was to use Andy's version, then use the watchdog timer to
>limit how long I would wait for the pulse to end. But I need the prescaler
>for my main timekeeping routines - and I couldn't reliably measure pulses
>that would approach the full time that the counters would be capable of.
>
>Ideas, suggestions?
>
>Thanks!
>
>dwayne
>
>
What PIC cpu are you using?
On eg 16F84 you could perhaps use interrupt on change caused
by the end of the pulse to drag you out of the fast counter
loop.
If you haven't got interrupts available this may not be easy
to implement :-)
>
> Loop CntHi CntLo
> btfss PULSE_PORT,PULSE_BIT ; If low then end 00 00
> goto MeasureEnd
> incf CounterLo 00 01
> skpnz skip next
> incfsz CounterHi
> goto Loop
> goto TimeOut
>
> It looks as if this might work. Thanks for the tip!
>
> Any other ideas on how I can speed this up even more?
>
Sure! This will get you back down to 5 cycles per sample and
it can be scaled to how ever many bytes you wish. I show 3
just as an example.
MOVLW C_LOW
MOVWF count_low
MOVLW C_MID
MOVWF count_mid
MOVLW C_HIGH
MOVWF count_high
s1:
BTFSC IOPORT,IOBIT
goto went_high
DECFSZ count_low,F
goto s1
NOP
BTFSC IOPORT,IOBIT
goto went_high
DECFSZ count_mid,F
goto s1
NOP
BTFSC IOPORT,IOBIT
goto went_high
DECFSZ count_high,F
goto s1
time_out:
The three-byte count is pre-loaded with the desired time-out. If
the code reaches the label 'time_out' then the IOPORT didn't
change states.
One caveat: The three byte count is actually:
COUNT = C_LOW + 257*C_MID + 257^2 * C_HIGH
(note 257 and not 256). So to preset the count you'll need to do
something like:
You don't need a 4 cycle loop to check something every 4 cycles.
Here is a 16 cycle loop with a test every 4 cycles.
NOTE: This hasn't been assembled or tested. Before using, make sure
I test the correct status bits, and include ,f and ,w where appropriate.
Also, make that the btfss's shouldn't be btfsc and vice versa.
I've been busy recently and have switched to the digest format. So,
someone else may have answered, and I missed it. Also, this uses many
words of program space. I didn't try to make it small. There are
many oportunities for optimization.
;
; Sample pin PulseBit of port PulsePort every 4 cycles until it
; goes high, or 2^18 passes through the loop, whichever comes first.
; The low 2 bits of the count will be in HiPerLo, the next 8 bits
; will be in HiPerMid, the high 8 bits in HiPerHi.
;
; preload HiPerMid and HiPerHi with some other value to get the loop
; to timeout sooner.
;
; Check my cleanup logic for when the middle byte goes from 255 -> 0.
;
clrf HiPerMid
clrf HiPerHi
sampleloop;
btfsc pulsePort,PulseBit ; 1 Test the bit
goto gotpulse0 ; 2
nop ; 3
incf HiPerMid,f ; 4 increment the middle 8 bits
btfsc pulsePort,PulseBit ; 1 Test the bit again
goto gotpulse1 ; 2
btfsc status,z ; 3 Does it carry into the high 8 bits
incf HiPerHi,f ; 4
btfsc pulsePort,PulseBit ; 1 and again
goto gotpulse2 ; 2
btfsc status,z ; 3
goto timeout ; 4
btfsc pulsePort,PulseBit ; 1 and again
goto gotpulse3 ; 2
goto sampleloop ; 4
gotpulse0
clrf HiPerLo ; low 2 bits zero
; add delay here for constant path length.
; otherwise the loop is fastest when the low
; 2 bits are zero.
return
gotpulse1
decf HiPerMid,f ; undo last increment
movlw 1
movwf HiPerLo
goto fixupa
gotpulse2
movlw 2
movwf HiPerLo
goto fixupb
gotpulse3
movlw 3
movwf HiPerLo
fixupb
movf HiPerMid,f ; Test for zero
btfsc status,z ; optionally undo inc of Hi byte
decf HiPerHi,f
fixupa
decf HiPerMid,f
return
timeout ;
; Do something usefull here.
return
==================================
I do something similar in my latest sonar code. There I'm checking for
and echo on 4 transducers every 4 cycles. The transducers are hooked
pins 0-3 on porta, pin 4 is pulled low. I swapf porta,w, then 4 cycles
later iorwf porta,w. Testing the Z flag tells if any transducer saw
an echo. Looking at the bits in W tells which transducer(s) and whether
it saw the echo on the 1st or second test in the loop.
>Any other ideas on how I can speed this up even more?
Use a timer interrupt for the timeout. Rather than return from the ISR,
jump out to the timeout handler. The counter routine needs to be at the
top-level (i.e. not a subroutine) so corrupting the stack by not
returning from the interrupt doesn't cause problems. A similar approach
uses a hardware interrupt caused by the pulse being measured to stop the
counting.
_____________________________________________________________________
You don't need to buy Internet access to use free Internet e-mail.
Get completely free e-mail from Juno at http://www.juno.com
Or call Juno at (800) 654-JUNO [654-5866]
>If I had to measure the pulse width using 80C51 or 80C552 I would AND the
>input pulse and a 100KHz (10uS resolution) clock and give it to the on-chip
>counter. If the onchip counter is 8 bit, I would program it to generate an
>interrupt on overflow, and use the interrupt to inc. the high byte/word.
I don't have any of those available. I'm using the 12c508 with the internal
clock - no external fast clock is available and there are no interrupts on
this chip. I'm measuring duty cycle of an incoming signal - absolute times
are not important but the ratio of HI period to LO period is important.
>
>Sure! This will get you back down to 5 cycles per sample and
>it can be scaled to how ever many bytes you wish. I show 3
>just as an example.
>
> MOVLW C_LOW
> MOVWF count_low
>
> MOVLW C_MID
> MOVWF count_mid
>
> MOVLW C_HIGH
> MOVWF count_high
>
>s1:
> BTFSC IOPORT,IOBIT
> goto went_high
>
> DECFSZ count_low,F
> goto s1
>
> NOP
>
> BTFSC IOPORT,IOBIT
> goto went_high
>
> DECFSZ count_mid,F
> goto s1
>
> NOP
>
> BTFSC IOPORT,IOBIT
> goto went_high
>
> DECFSZ count_high,F
> goto s1
>
>time_out:
>
>The three-byte count is pre-loaded with the desired time-out. If
>the code reaches the label 'time_out' then the IOPORT didn't
>change states.
>
>One caveat: The three byte count is actually:
>
>COUNT = C_LOW + 257*C_MID + 257^2 * C_HIGH
>
>(note 257 and not 256). So to preset the count you'll need to do
>something like:
>
>
>C_LOW EQU COUNT % 257
>C_MID EQU (COUNT / 257) % 257
>C_HIGH EQU (COUNT / 257 / 257) % 257
>
>Scott
>
>
Scott - since I don't need a particular timeout for this application, why
wouldn't INCFSZ work. That is:
clrf c_low
clrf c_mid
clrf c_high
s1:
BTFSC IOPORT,IOBIT
goto went_high
INCFSZ c_low,F
goto s1
clrwdt ;
BTFSC IOPORT,IOBIT
goto went_high
INCFSZ c_mid,F
goto s1
nop
BTFSC IOPORT,IOBIT
goto went_high
INCFSZ c_high,F
goto s1
time_out:
Note also that your routine lets me take care of the watchdog with no added
penalty!
Now, since incrementing each added byte of counter causes the previous bytes
to miss a count (the mod 257 that you alluded to in your message),
correcting to mod 256 should be easy. The modulus changes as you extend the
counter: in the 3 byte counter above, the lowest byte misses the counts
accumulated by both of the upper bytes; the mid byte misses the counts
accumulated by the high byte. A first pass of the correction routine
follows - but its pretty stinky and I'm sure that I am missing something
obvious.
After looking at the last message that Scott sent me, I think that I have a
solution that fits my purposes. It will accumulate up to 65,535 counts
without error and times out after 65,791 counts. The count in the region
between those two numbers is held at 65,535 (ffff).
;measure puse width on a 12c508 with 5 uSec resolution.
;Times out after about 327 mSec.
clrf c_low
clrf c_high
s1:
BTFSC IOPORT,IOBIT ;5 cycles
goto went_high
INCFSZ c_low,F
goto s1
clrwdt ;
BTFSC IOPORT,IOBIT ;5 cycles
goto went_high
INCFSZ c_high,F
goto s1
;note: each time HI byte increments, LO byte to misses a count
;final count == LoByte + HiByte
time_out:
;do time out stuff here
went_high ;fix up the mod 257 counter
movfw c_high ;
addwf c_low,F
skpnc ;no carry - leave Hi byte alone
incfsz c_high,F ;carry - does Hi byte contain ff?
goto $+2 ;either no carry or did not wrap
movwf c_low ;wrapped past ffff - hold at ff
movwf c_high ;ditto
The logic is simple - if the high byte contained ff and a carry was
generated when the 2 bytes were added together (which would wrap the high
byte to 00), the value in W (which could only be ff) is written to both
bytes. The choice of $+2 for the junp is deliberate - both paths take the
same # of cycles.
My thanks to all who contributed ideas and suggestions. Scott - Thank you
again for taking time out of your very busy day.
> After looking at the last message that Scott sent me, I think that I have a
> solution that fits my purposes. It will accumulate up to 65,535 counts
> without error and times out after 65,791 counts. The count in the region
> between those two numbers is held at 65,535 (ffff).
Scrap it.
Here's a routine that gives you 3-cycle resolution:
Scott Dattalo wrote:
>
>Scrap it.
>
>Here's a routine that gives you 3-cycle resolution:
most of routine snipped
BRAVO! BRAVO!
Fantastic!
Talk about lateral thinking!
I have 2 observations: I don't see any way for the routine to time out. It
looks as if I can either look after the watchdog or have timeout capability
but not both. I'll keep looking, tho.
The other observation is slightly more serious, I think. If the routine
exits without incrementing any counters, (enters the routine but the pin
went or was non-active - zero time) an erroneous result will occur. I think
the solution is simple: relpace the 'SKPZ' below with 'SKPC'. Of course - I
might be out to lunch . . .
This is at the end of the normalisation routine: {Quote hidden}
> I have 2 observations: I don't see any way for the routine to time out. It
> looks as if I can either look after the watchdog or have timeout capability
> but not both. I'll keep looking, tho.
I think you can extend the concept to 16 samples (or whatever) per loop
instead of 8 and do something like this
BTFSC IOPORT,IOBIT
goto went_high_x
BTFSS count_high,7 ;Overflow bit
BTFSC IOPORT,IOBIT
goto went_high_y
....
BTFSS IOPORT,IOBIT
goto loop
went_high_z:
.....
went_high_y:
BTFSC count_high,7
goto over_flowed
...
>
> The other observation is slightly more serious, I think. If the routine
> exits without incrementing any counters, (enters the routine but the pin
> went or was non-active - zero time) an erroneous result will occur. I think
> the solution is simple: relpace the 'SKPZ' below with 'SKPC'. Of course - I
> might be out to lunch . . .
I thought about that too. The skpc to skpz change won't fix it though.
It may be easier to add a flag that's initialized before the sampling
begins, and is cleared in the loop. Then during the normalization you
can check the flag and process the counters accordingly. It might also
be necessary to sample the IOPORT once before the loop begins and abort
if it's already high. Tell us how you decide to solve it :).
>> The other observation is slightly more serious, I think. If the routine
>> exits without incrementing any counters, (enters the routine but the pin
>> went or was non-active - zero time) an erroneous result will occur. I think
>> the solution is simple: relpace the 'SKPZ' below with 'SKPC'. Of course - I
>> might be out to lunch . . .
>
>I thought about that too. The skpc to skpz change won't fix it though.
>It may be easier to add a flag that's initialized before the sampling
>begins, and is cleared in the loop. Then during the normalization you
>can check the flag and process the counters accordingly. It might also
>be necessary to sample the IOPORT once before the loop begins and abort
>if it's already high. Tell us how you decide to solve it :).
Scott - how about a little bit different slant on things. Instead of
shifting the counter bytes over to make room for the LSBs accumulated by
the main loop, why not just add those LSBs to the count, propogating
carries as required. The main loop doesn't have to be a power of 2 states
and allows easy addition of the overflow logic.
overflow ;wrapped past ff ff ff - hold at ff
movlw ff
movwf count_low
movwf count_mid
movwf count_high
done
Now, having done all that, I think that I see a fundamental problem with
the middle byte in your routine. Assume that the loop terminated at the
6th, or later exits. There is no way of knowing whether the middle byte
had incremented or not: the carry information is lost. In other words,
just because the high byte didn't increment, that doesn't say that the
middle byte didn't also. The middle byte could have incremented without
wrapping. I fixed it by seeing if the low byte was zero at that point.
I think that it will work (I'll try tommorrow, after I've heard your
thoughts).
>The code looks good (your latest post - 19 bits of dynamic range).
>
>Probably the easiest way to test the code is with the stopwatch.
>Make IOPORT a ram register, single step to the first sample,
>clear the stopwatch, and press F9 (run). After it runs for a while,
>force a break. Then go modify the ram register to where IOPORT
>points. Single step and note the value of the stop watch when
>you break out of the loop. This value should be 3 times the value
>get in the counters.
>
>Repeat this process a few times - perhaps even attempt the timeout.
>
>Scott
Hi again, Scott.
Looking not too bad right now. I found a couple errors: testing the wrong
register for overflow in 1 spot and I wasn't fixing up 'count_high' at the
end of the normalisation routine. I've been simulating for the past couple
hours - no probelms so far, with no more than 1 count error between
calculated and actual count. I'm about to stick the code into a 12c508
driving some LED displays (my standard serial stuff that I have on the
shelf) and I'm going to see if it actually works when I feed the output of a
pulse generator to it.
This is neat stuff, Scott. I can't even begin to thank you for all your help.
dwayne
Here is the code for those interested.
;up to 19 bit counter with 3 cycle resolution
;original concept by Scott Dattalo; this version by Dwayne Reid
CLRF count_low
CLRF count_mid
CLRF count_high ;used as known zero for main loop
loop
BTFSC PULSE
goto went_high_1st
MOVLW 1
BTFSC PULSE
goto went_high_2nd
ADDWF count_low,F
BTFSC PULSE
goto went_high_3rd
RLF count_high,W ;get C into W lsb (adding 0 or 1)
BTFSC PULSE
goto went_high_4th