Shop OBEX P1 Docs P2 Docs Learn Events
Counter Question — Parallax Forums

Counter Question

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

Comments

  • jmgjmg Posts: 15,173
    edited 2018-08-18 23:17
    ... 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.
    There is no period capture mode, but there are level gated modes.
    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 purpose of this is to take a LM331 set up to provide 1khz/volt to monitor and ultimately regulate the current through a part
    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.

  • That was what I thought about the counter. Found an object by Michael Lord that collects the pulses, shows high count, low count, ratio and frequency. Trying to modify it was the reason for the question of counting. Yes, the LM331 is not cheap, but it can also be configured as a F/V converter. Currently I have it powered from 5V and measuring a 0-5V through a multi turn pot. At 1khz/V, it seems pretty stable. probably more so if I had it on a PCB with all the usual suspects well handled. Looks at this point to be less than .5% between count values from 1V - 5V.

    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!!
  • jmgjmg Posts: 15,173
    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.

    ....
    Yes, the LM331 is not cheap, but it can also be configured as a F/V converter. Currently I have it powered from 5V and measuring a 0-5V through a multi turn pot. At 1khz/V, it seems pretty stable. probably more so if I had it on a PCB with all the usual suspects well handled. Looks at this point to be less than .5% between count values from 1V - 5V.

    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.

    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.


  • @frank freedman quote:
    Gotta say parallax peeps rock!!
    I absolutely agree. I need to thank @jmg for his help on my IR project. He mentioned that the IR detector operates in "bursts". I rewrote my code with a repeat 30 loop followed by waitcnt(clkfreq / 15 + cnt). Perfect! :smile:

    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)
    
  • frank freedmanfrank freedman Posts: 1,983
    edited 2018-08-25 02:34
    Here is the final solution I derived from the original object. Made some changes to improve accuracy and correct a couple of errors.
    { 
    
            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.                         │
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
    }}
    
    
    
    
    
    
    
    
    
    
    
    
    
        
    
  • Mark_TMark_T Posts: 1,981
    edited 2018-08-20 14:07
    If you're going to waitpeq/waitpne you don't need any counters, just capture CNT.

    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.
  • frank freedmanfrank freedman Posts: 1,983
    edited 2018-08-20 15:36
    Had not thought of using CNT, but will try it. May need to be able to ajdust/account for rollover of CNT if it happens near the point the counter passes through 0. In this case, the frequency is just about 100Hz through 5kHz unless I run Vin up to 15V. Which would raise max frequency to ~15kHz. As well, the purpose actually is to read a single period at a time. Implementing a single shot per request is my next step now that the period capture works. Currently the LM331 generating the pulse deviates ~ +/-100 counts, so I am getting around 0.2% error between measurements.

    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.
  • JonnyMacJonnyMac Posts: 9,104
    edited 2018-08-20 17:28
    As long as your period doesn't exceed ~26 seconds you won't have any problems with cnt roll-over.

    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     1
    
  • frank freedmanfrank freedman Posts: 1,983
    edited 2018-08-22 00:46
    Hey Jon,

    Ran 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
  • After additional hacking on my version of counter based pulse count routine, I have an odd quirk to ask about, that being I can wrlong ctra , hub_dest same for frqa, and get the expected results. What does not seem to work is wrlong phsa , hub_dest. Did I miss a note somewhere saying that phsa can not be directly written to a hub location?
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2018-08-22 03:37
    It's not that, per se. It's that phsa cannot be read as a destination register. When you try to do that, you get the contents of its shadow register instead.

    -Phil
  • frank freedmanfrank freedman Posts: 1,983
    edited 2018-08-22 13:48
    Thx, Phil. I get it now. Found the relevant paragraph on pg 10 of the prop data sheet about counter phsa/b read/write.
  • frank freedman;1444185
    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
  • Thank you. If you don't need to use counters, JonnyMac's solution is the better choice as it has a better accuracy at the high end ( up to ~1Mhz ) than my counter based solution which starts to loose accuracy over 100kHz. His count rate is also tighter than mine +/- a couple counts whereas mine can be +/- 10 counts at 100khz. Also reposting the final code of mine as I have changed a couple things. Seems the movi may requires an instruction between the time of mvi and the instruction it modifies for proper execution. I will repost my final edited code soon.
  • jmgjmg Posts: 15,173
    ... JonnyMac's solution is the better choice as it has a better accuracy at the high end ( up to ~1Mhz ) than my counter based solution which starts to loose accuracy over 100kHz. His count rate is also tighter than mine +/- a couple counts whereas mine can be +/- 10 counts at 100khz..

    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.

  • Another possible strategy is to use both counters -- one to measure the high side of the cycle, the other to measure the low side.
    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     496
    

  • Hey, JMG and Jon,

    First, 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.                         │
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
    }}
    
    
    
    
    
    
    
    
    
    
    
    
    
        
    
Sign In or Register to comment.