Counter Question
frank freedman
Posts: 1,985
in Propeller 1
Playing with the counters today. The app note is a bit fuzzy on the logic levels. If I read correctly, the edge mode will only cause the counter to increment by one at each rising or falling edge. The question I have concerns causing the counter to increment from rising edge to rising edge of a square wave and I don't care about the duty cycle, only the total duration of the cycle. In this way, I need only take 1/sysclock * number of sysclock pulses the counter accumulated to give me the period of the waveform. The purpose of this is to take a LM331 set up to provide 1khz/volt to monitor and ultimately regulate the current through a part by monitoring the voltage drop across a series resistance feeding the part. The current will be provided by an external DAC or maybe the S/D described in the counter app notes/PEK docs.
Thx
Frank
Thx
Frank

Comments
The choices would be to use WAIT to sync to each edge, and capture count on same-edges, and you might want to do that over N periods.
or, you could run 2 counters, one HI gated, one LOW gated, and the sum read on an edge, is the period. the Ratio H/(L+H) is the duty cycle.
There is also this thread covering high precision reciprocal counters, most useful when your source is also high precision.
http://forums.parallax.com/discussion/123170/propbasic-reciprocal-frequency- counter-0-5hz-to-40mhz-40mhz-now
The LM331 is ancient and expensive, and not very accurate.
You might instead use a small MCU (sub 40c) with ADC and PWM mode, and send PWM coded info over the isolated link.
I could have used an ADC much the same way as when I did the ADC object a few years ago, but wanted to see if I could get a faster conversion rate than with an ADC that I have on hand. The LM331 has been around a while, but TI seems to be keeping them current. I looked at Analog Devices same functional part, and it was multiple times the price of the LM331(like almost $10.00!!) Also, the LM331 can operate from higher supply and input voltages while giving whatever logic level out I happen to pull it up to. Nice thing about hobby level, I can explore and learn from doing and even more from others comments. Gotta say parallax peeps rock!!
An alternative combination could be to combine a HC(T)4046 Oscillator and a Current Output Current Sense amplifier - the Current sense amp goes direct from millivolts to uA, including level shift, and the 4046 oscillator is a FlipFlip with S-R Ramps. You would choose a 50% duty cycle as the ideal operating point, and at that point the R*C charge time is equal to the I/C charge time. C & Threshold variations are nulled.
This is what I use to PWM at 20Khz. TC represents the cycle time. phsa and phsb represent the duty cycle.
PUB Generate_Pwm | tc, t 'dual pwm '5*800=4000 ctra[30..26] := ctrb[30..26] := %00100 ' Counters A and B → NCO single-ended ctra[5..0] := 1 ' Set pins for counters to control ctrb[5..0] := 2 frqa := frqb := 1 ' Add 1 to phs with each clock tick dira[1] := dira[2] := 1 ' Set I/O pins to output tC := 4000 ' Set up cycle time t := cnt ' Mark current time. repeat ' Repeat PWM signal phsa := -tHa_forward ' Define and start the A pulse phsb := -tHa_backward ' Define and start the B pulse t += tC ' Calculate next cycle repeat waitcnt(t){ Pulse counter by Frank Freedman on 08/19/2018 Derived from _FreqCount.spin by Michael J. Lord Electronic Design Service 2010-06-01 This program counts the pulses of the whole cycle of a period so that the frequency could be determined regardless of duty cycle. The intent is to be able to read the output of a V-F converter and determine frequency and therefore the voltage under test. The test device the output of an LM331 or similar device. } CON _xinfreq = 5_000_000 _clkmode = xtal1 + pll16x CountPin = 1 'pin for led on demo bo Obj text : "fullduplexserial" Var long Cog long PNCount ' number of counts stored here. long cycle ' cycle time '======================================================================================================================================== Pub Main '======================================================================================================================================== text.start(26,25,1,115_200) Cog := cognew(@entry, @PNCount[0] ) + 1 '=============================================================================================================== 'This is the Main Program Loop Repeat cycle := ( 80000000 / PNCount ) text.str(string("Count = ")) text.dec( PNCount[0] ) text.tx($0D) text.tx($0A) text.str(string("Freq = ")) text.dec( cycle ) text.tx($0D) text.tx($0A) PUB stop if Cog cogstop(Cog~ - 1) DAT org 0 entry muxz dira , PinMask ' Configure Pin as inputs (0) as Z is zero mov addr , par 'par has address of first variable to write back which is PosCnt mov base , addr mov frqa , #1 :MainLoop mov phsa , #0 'clear count waitpne PinMask , PinMask 'waits for negative on input ina mov ctra , ctra_set_L 'set up counter count on APIN low waitpeq PinMask , PinMask 'waits for high start mov ctra ,ctra_set_H ' set to count on APIN high waitpne PinMask , PinMask 'waits for positive to end cycle mov Phsa_Cnt , phsa ' get total time count add Phsa_Cnt , #63 ' offset correction for timing Wrlong Phsa_Cnt , base ' return final values jmp :MainLoop ' VARIABLES Phsa_Cnt long 0 PinMask long |< CountPin 'This creates a pin mask with a 1 and CountPin zeros to the right ctra_set_L long %10101_000 << 23 + 1 << 9 + 1 ' count when APIN=1 / APIN ctra_set_H long %11010_000 << 23 + 1 << 9 + 1 ' count when APIN=1 / APIN addr res 1 base res 1 ' base address for data return {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}The only advantage a gated counter gives you is not having to wait accurately - so long as you read the PHS
during the time its not changing and before the next edge, you'll get the time for that half cycle. So its good
for timing a single pulse, but hard to use for measuring a repeating period unless you synchronize, in which
case CNT does the job.
The jitter comes from the LM331(seen on my scope, a Tek 2247A), and as noted could be better if not breadboarded and a better layout with low drift parts was used rather than this quick hack from the most basic data sheet example. One side note, I also have a Digilent Analog Explorer v2, and started this project using the digital LSA mode. It sucked surprisingly bad on capture, so I have a bit of learning on that device to do.
Thanks for the suggestion on using CNT.
This should work. Call it with a pointer to the variable that will hold the cycle timing. On starting, put the pin number to monitor in this variable.
dat org 0 entry rdlong t1, par ' get input pin mov inmask, #1 ' convert pin to mask shl inmask, t1 mov t1, #0 wrlong t1, par get_cycle waitpeq inmask, inmask ' look for leading edge neg cycletix, cnt ' cycletix := -cnt :loop waitpne inmask, inmask ' wait for falling edge waitpeq inmask, inmask ' wait for start of next cycle mov endpoint, cnt ' capture end point adds cycletix, endpoint ' calc cycle duration (ticks) wrlong cycletix, par ' write to hub neg cycletix, endpoint ' reset cycle start point jmp #:loop t1 res 1 inmask res 1 cycletix res 1 endpoint res 1Ran the code you posted last night. Worked as advertised. Will definitely be keeping that one for future use. Tested both methods with a 3V 4khz square wave provided by a HP3312A function generator. Will do additional testing at much higher frequencies when I get a chance. Forget statistical hocus pocus. Jon's code works better at >1Mhz than the counter version which seems to fall off pretty fast beyond 100khz.
Thank you again Jon,
Frank
-Phil
Here is the final solution I derived from the original object.
I don't really need a high speed pulse counter at the moment.
But I sure appreciate the beautiful PASM example!
Thanks
That's a little puzzling, as both are hardware based ?
One code measures the whole period, whilst the other code measures a half-cycle, so the whole period one has ~double the time -> half the jitter (assuming 50% duty). That's just 2:1 difference.
You could also measure over some small number (8?/16?) of whole cycles, if the input frequency is high and you need best precision.
dat org 0 entry rdlong t1, par ' get input pin mov inmask, #1 ' convert pin to mask shl inmask, t1 mov t2, POS_DETECT ' use ctra to measure high side or t2, t1 mov ctra, t2 mov frqa, #1 mov t2, NEG_DETECT ' use ctrb to measure low side or t2, t1 mov ctrb, t2 mov frqb, #1 :loop waitpne inmask, inmask ' wait for low mov elapsed, phsa ' set elapsed to high side ticks mov phsa, #0 ' reset for next high waitpeq inmask, inmask ' wait for high add elapsed, phsb ' add low side for total cycle mov phsb, #0 ' reset for next low wrlong elapsed, par ' write period to hub jmp #:loop POS_DETECT long %01000 << 26 NEG_DETECT long %01100 << 26 t1 res 1 t2 res 1 inmask res 1 elapsed res 1 fit 496First, JMG, yes, I found the half cycle discrepancy yesterday when I realized that I was only getting the half cycle. Did not notice it as my function generator has a fixed duty cycle. Otherwise I would have had to chase down why the count for a given half cycle would have made little sense.
Jon, Thanks for yet another example of how to solve this one. I have last night, implemented a single counter hybrid based on your last use of the cnt and my counter based code. I will post the code here, but am getting a bit of an issue where I have to stop and restart the terminal when I change frequency from the function generator. My counts and resultant frequency are pretty close, but the serial thing is kind of annoying.
{ Pulse counter by Frank Freedman on 08/19/2018 Derived from _FreqCount.spin by Michael J. Lord Electronic Design Service 2010-06-01 This program counts the pulses of the whole cycle of a period so that the frequency could be determined regardless of duty cycle. The intent is to be able to read the output of a V-F converter and determine frequency and therefore the voltage under test. The test device the output of an LM331 or similar device. Testing the count function with an HP3312A function generator. } CON _xinfreq = 5_000_000 _clkmode = xtal1 + pll16x CountPin = %00000000_00000000_00000000_00000001 'pin for led on demo bo CountCog = 2 ' cog2 for count Obj text : "fullduplexserial" Var long PNCount[2] ' number of counts stored here. '======================================================================================================================================== Pub Main | cycle '======================================================================================================================================== PNCount[0] := CountPin Init dira[0-5] :=0 '=============================================================================================================== 'This is the Main Program Loop Repeat cycle := ( 80000000 / PNCount[1] ) text.str(string("Count = ")) text.dec( PNCount[1] ) text.tx($0D) text.tx($0A) cycle := ( 80000000 / PNCount[1] ) text.str(string("Freq = ")) text.dec( cycle ) text.tx($0D) text.tx($0A) pub init text.start(26,25,0,115_200) coginit(CountCog, @entry, @PNCount ) DAT org 0 entry mov addr , par 'par address of pin mask rdlong PinMask , addr ' get test pin add addr , #4 'set to next long mov base , addr ' move address to base mov frqa , #1 mov ctra , ctra_set_R ' counter always runs at 80mhz get_cycle waitpne PinMask, PinMask ' look for falling edge neg Phsa_St, phsa ' capture inverse phsa value MainLoop waitpeq PinMask, PinMask ' wait for rising edge waitpne PinMask, PinMask ' wait for falling edge of next cycle mov Phsa_End, phsa ' capture end point adds Phsa_St , Phsa_End ' calc cycle duration (ticks) wrlong Phsa_St , addr ' write to hub neg Phsa_St , phsa ' Update starting count jmp MainLoop ' VARIABLES Phsa_St long 0 Phsa_End long 0 PinMask long 0 ctra_set_R long %0_11111_000_00000000_000000_000_000001 ' i read this more easily than |< addr res 1 ' base res 1 ' base address for data return {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}