Reciprocal Counter Demo - Page 4 — Parallax Forums

# Reciprocal Counter Demo

• Posts: 14,640
Rayman wrote: »
I had no idea what "reciprocal counter" meant, but I got curious just now and googled this up:
https://community.keysight.com/community/keysight-blogs/general-electronics-measurement/blog/2018/02/26/how-does-a-frequency-counter-work

It's a simpler thing than I imagined...

Yes the block diagram and maths are simple.
Chip added support in the smart pins to make the hardware details simple too..
(it can get tricky to ensure the two captures of time and cycles are made on the same edge, and ideally, you want lossless/gapless capture too, so 10 or 100 captures of time and cycles can be summed to give higher precisions )
• Posts: 11,932
I think it should actually be called "period counter".
I guess they mean "reciprocal of frequency" counter.
But "reciprocal of frequency" == period.
I think that would make usage more clear.

But, when you google "period counter" you get other stuff, which is maybe why the chose this name...
• Posts: 662
@evanh

I used it to do the other waves that you helped me with in the past.
• Posts: 14,640
Rayman wrote: »
I think it should actually be called "period counter".
I guess they mean "reciprocal of frequency" counter.
But "reciprocal of frequency" == period.
I think that would make usage more clear.

But, when you google "period counter" you get other stuff, which is maybe why the chose this name...

I think it started as a simple one-word prefix so 'Frequency Counter' became 'Reciprocal Frequency Counter'.

If you wanted to create a new name, 'period counter' infers it measures a single periods, which is inaccurate.
'whole periods timer' is clearer, in that now includes key elements of measuring the time over N Whole Periods, and then calculates N/Time to give Cycles/Second units.
- however, because the final units are Hz, including Frequency in the name seems important. - Instrument users care less about the how, than what they ultimately measure.

On that note, the thread tile would be better as Reciprocal Frequency Counter Demo
• Posts: 662
@evanh
@cgracey

Hey guys been a while. I have been working at home teaching aircraft mechanics teaching our academic side without lab. Each 24 days is a new class. Took three classes to get all dialed in.
I am now back learning this P2 asm and asm stuff.
Much thanks to @cgracy for the reciprocal counter demo and much thanks to @evanh for his help.
I stripped out the counter demo code to get to the asm code to print to the serial terminal.
Think because of now starting from the basics I am getting a much better understanding. Thanks to evanh I think I am getting it. I looked up the pop command on youtube and get it.
I have a couple of questions that I commented in the attached code. If either of you or anybody else have a minute would you please answer them.
Much thanks.
Martin
• Posts: 458
edited 2021-04-05 03:20

Many Thanks for the test code.

I have been testing it on recent FlexProp and PNUT and (Thanks to Ozpropdev!) have found a few thinks that I think it is worth mentioning.

For flexprop will need to add a LineFeed (10) : (otherwise, output will rewrite the current line)

```Line 80 (old):      byte    13," clocks:",0
Line 80 (new):      byte    13,10," clocks:",0
```

For PNUT we can add this line (to avoid error message "DEBUG requires at least 10MHz of Crystal" when we use DEBUG, CTRL+F10):

```Line 8  (add):      _clkfreq    = 20_000_000        'system frequency
```

I was able to check the frequency of my DP83848 Ethernet board 50MHz oscillator output pin (OSCIN)

``` clocks:    2,500,003    states:    1,264,366    periods:      499,968    duty:  505/k    frequency:   49,996,740.003911
clocks:    2,500,003    states:    1,264,705    periods:      499,968    duty:  505/k    frequency:   49,996,740.003911
clocks:    2,500,003    states:    1,267,611    periods:      499,968    duty:  507/k    frequency:   49,996,740.003911
clocks:    2,500,003    states:    1,266,037    periods:      499,968    duty:  506/k    frequency:   49,996,740.003911
clocks:    2,500,003    states:    1,266,152    periods:      499,968    duty:  506/k    frequency:   49,996,740.003911
clocks:    2,500,004    states:    1,266,724    periods:      499,968    duty:  506/k    frequency:   49,996,720.005247
clocks:    2,500,003    states:    1,265,262    periods:      499,968    duty:  506/k    frequency:   49,996,740.003911
clocks:    2,500,003    states:    1,266,263    periods:      499,968    duty:  506/k    frequency:   49,996,740.003911
```

IMPORTANT: If there is no signal present, then there will be not output at all on the serial terminal or debug terminal !

• Posts: 10,785
edited 2021-04-05 06:44
Not sure if Pilot is still around but I'll have a shot at answering his questions now anyway:

```tx_string	pop	x				'pop return address and make byte ptr
'DOES THIS TAKE WHAT EVER X IS AT THE TOP OF THE STACK (THE LAST VALUE OF X)
```
Not the last value of x, but takes the last value off the stack and puts it into the x variable (register). Overwriting the old value of x.

```'QUESTION: is x the last address of the caller????????????
```
Almost. This is architecture dependant but the common (propeller2) behaviour is when a routine is CALLed the program counter of the calling routine is pushed to the stack for later retrieval by a RET instruction. At the CALL time, the program counter holds the address of the subsequent instruction after the CALL ... so that is the address that gets pushed on to the stack.

EDIT: Formatting
• Posts: 10,785
edited 2021-04-05 06:55
```        shl x,#2
'I looked up altgb what is it doing to x and getbyte?

.loop       altgb   x               'get character
getbyte y
```

The SHL is multiplying x by 4. This is typical when rescaling an index or pointer. In this case x contains a cogRAM address so is longword scaled. But to use GETBYTE requires byte scaling, hence the times 4.

ALTGB is a prefixing instruction. A prefixing instruction means it modifies the behaviour of the subsequent instruction. The ALTxx instructions in general are a little hard to get and keep your head wrapped around. They provide what's called indirect addressing, or referencing in loose C speak. The trick is remembering that the variable holds an address, as opposed to holding the data.

ALTGB is tailored for prefixing GETBYTE, it won't correctly prefix any other instruction. There is other prefixes that are generic and function with many or most other instructions. ALTGB has two forms, the above form is simply copy a byte from location x and place it in y. x can address anywhere in cogRAM.

• Posts: 458

How can I convert this PASM code to SPIN2 with the minimal lines of code?

I tried in this way, but failed:

```'****************************************
'*  Reciprocal Counter Demonstration    *
'*  - inputs frequency on P0            *
'*  - transmits serial text on P62      *
'****************************************
'
con     sysfreq     = 250_000_000.0     'system frequency
_clkfreq    = 20_000_000.0      'system frequency
msr_us      = 10_000.0      'minimum measurement time in microseconds (float)
msr_pin     = 0         'pin to measure frequency on, uses next two pins
baud        = 921_600       'serial baud rate on P62 (float)

msr_min     = sysfreq/1e6*msr_us    'minimum measurement time in system clocks
msr_pins    = 2<<6 + msr_pin    'group of three pins starting at msr_pin

DEBUG_DELAY     = 100
DEBUG_BAUD      = 921_600

rx_pin   = 63
tx_pin   = 62

tx_mode     = (round(sysfreq / baud * 65536.0) & \$FFFFFC00) + 7 '8N1
msr_time    = %0000_0000_000_0000_000000000_00_10101_0  'msr_pin+0 config
msr_states  = %0111_0111_000_0000_000000000_00_10110_0  'msr_pin+1 config
msr_periods = %0110_0110_000_0000_000000000_00_10111_0  'msr_pin+2 config

VAR

LONG clocks
LONG states
LONG periods

OBJ
ser: "spin/SmartSerial"
fmt: "spin/ers_fmt"

PUB main

ser.start(rx_pin, tx_pin, 0, baud)
ser.printf("Frequency counter demo\n")

wrpin(msr_time,msr_pin+0)
wrpin(msr_states,msr_pin+1)
wrpin(msr_periods,msr_pin+2)
wxpin(msr_min,msr_pins)
wypin(%00,msr_pins)

org
dirh    #msr_pins           'concurrently enable smart pins
.loop       akpin   #msr_pins           'clear any old measurement
waitx   #3
.wait       testp   #msr_pin    wc      'wait for new measurement
if_nc   jmp #.wait
end
clocks  := rqpin(msr_pin+0)
states  := rqpin(msr_pin+1)
periods := rqpin(msr_pin+2)
'duty = states * 1_000 / clocks
'frequency = periods * sysfreq / clocks
'frequency_sub = remainder / clocks * 1_000_000

ser.printf("clocks: %x  states: %x periods: %x \n", clocks, states, periods )

org
jmp #.loop

end
```
• Posts: 232

@Ramon said:
How can I convert this PASM code to SPIN2 with the minimal lines of code?

While not an answer to your Spin2 code conversion question, take a look at the TSL235R Quick Byte that I wrote. I converted Chip's standalone Reciprocal Counter PASM2 code to inline PASM2 and placed it into two driver routines:

• fb_measfreq2P.spin2 <-- Read frequency, periods using 2 P2 smart pins.
• fb_measfreq3P.spin2 <-- Read frequency, periods, duty cycle using 3 P2 smart pins.

Inline PASM2 code lets you have the best of both worlds, easier programming/integration in Spin, while still having speedy PASM2 routines.

• Posts: 10,785

Ramon,
You won't be able to JMP between assembly blocks like that. Each Pasm block is handled one at a time with just what is defined for it, not unlike Spin methods. Any branching will have to be within a block.

• Posts: 458

Francis, many thank for your help. I will take a look to that code, I am yet a little scared by PASM and try to use the most simple SPIN keywords. Nice that you can reduce the number of smart pins used. I was about to ask if it could be possible to just use one smart pin to detect frequency.

Evanh, yes I thought that the loop would not execute but in fact was repeating the code. Those ASM instructions I am not sure how to correctly convert into SPIN.

• Posts: 2,455

@Ramon said:
How can I convert this PASM code to SPIN2 with the minimal lines of code?

Here is a simple version in Spin2. I use P40 as input and output a test-frequency at P39, which is connected to pin 40 with a wire. It only measures the frequency, not the duty.

```CON
_clkfreq  =  200_000_000

MSR_PIN   =  40    {connected to pin 39}

PUB main() | ticks, periods, freq, mintime
pinstart(39, P_NCO_FREQ+P_OE, 1, 1_000_150 frac clkfreq)   'test freq

mintime := clkfreq / 1_000             '1ms min measure time
pinstart(MSR_PIN+0, P_COUNTER_TICKS, mintime, %00)
pinstart(MSR_PIN+1, P_MINUS1_A + P_MINUS1_B + P_COUNTER_PERIODS, mintime, %00)

repeat
akpin(MSR_PIN addpins 1)              'start next measurement
repeat until pinr(MSR_PIN)            'wait until done
ticks   := rqpin(MSR_PIN+0)           'read measured values
periods := rqpin(MSR_PIN+1)
freq := muldiv64(clkfreq, periods, ticks-1)     'calc frequency

debug(udec(ticks), udec(periods), udec(freq))
waitms(300)
```

Andy

• Posts: 458

Thank you Ariba!

I got it working, but I think you have some typo on lines 4 and 7.

```Line 4 :  should be -> MSR_PIN = 39
Line 7 :  should be -> pinstart(MSR_PIN, P_NCO_FREQ+P_OE, 1, 1_000_150 frac clkfreq)   'test freq
```

Also the last parameter (1_000_150 frac clkfreq) seems to result zero always in (%00).
According to SPIN docs, the frac equivalent should be:

```1_000_150 frac clkfreq   =  (1_000_150<<32) / clkfreq      '  Result is Zero
```

Is that some mistake, or copy&paste from another code?
I am able to run the code with good results when I change the '1_000_150 frac clkfreq' with '%00'.

I have made a Flexprop version (for Retroblade2) here (Flexprop doesn't allow addpins, frac, or debug instructions)

```CON
_clkfreq  =  200_000_000
rx_pin   = 63
tx_pin   = 62
baud     = 115_200
DEBUG_DELAY = 100
DEBUG_BAUD = 115_200

MSR_PIN   =  0    {connected to pin 0}

OBJ
ser: "spin/SmartSerial"

PUB main() | ticks, periods, freq, mintime

ser.start(rx_pin, tx_pin, 0, baud)
'pinstart(39, P_NCO_FREQ+P_OE, 1, 1_000_150 frac clkfreq)   'test freq
pinstart(MSR_PIN, P_NCO_FREQ+P_OE, 1, (1_000_150<<32) / clkfreq)   'test freq

mintime := clkfreq / 1_000             '1ms min measure time
pinstart(MSR_PIN+0, P_COUNTER_TICKS, mintime, %00)
pinstart(MSR_PIN+1, P_MINUS1_A + P_MINUS1_B + P_COUNTER_PERIODS, mintime, %00)

repeat
'akpin(MSR_PIN addpins 1)              'start next measurement
akpin( (MSR_PIN & \$3F) | (1 & \$1F) << 6)
repeat until pinr(MSR_PIN)            'wait until done
ticks   := rqpin(MSR_PIN+0)           'read measured values
periods := rqpin(MSR_PIN+1)
freq := muldiv64(clkfreq, periods, ticks-1)     'calc frequency

'debug(udec(ticks), udec(periods), udec(freq))
ser.printf("ticks: %u periods: %u  freq: %u \n", ticks, periods, freq)

waitms(300)
```

It is working. But still have no idea how it works. why do we need two pinstarts (first NCO mode, and later ticks counter) for MSR_PIN?

pinstart(MSR_PIN, P_NCO_FREQ+P_OE, 1, (1_000_150<<32) / clkfreq) 'test freq
pinstart(MSR_PIN+0, P_COUNTER_TICKS, mintime, %00)
pinstart(MSR_PIN+1, P_MINUS1_A + P_MINUS1_B + P_COUNTER_PERIODS, mintime, %00)

By the way, what is a TICK? rise or fall?
And what are periods? Is a 'period' according to our system clock, or a full cycle (period) detected while reading the pin?

These are the kind of examples that I will never understand until some nice diagrams or drawings are made.

• Posts: 2,455

I output a test frequency on Pin 39 which I can vary to test the Frequency counter. For this is the pinstart on the first line in main(), it just starts an NCO. If you don't need that you can delete this line.

The counter input is on the MSR_PIN and the MSR_PIN + 1, which is pin 40 and pin 41 in my code (you seem to use pin 0 and 1).

My code works also with Flexprop without any modification. Flexprop knows FRAC, ADDPINS and DEBUG (in this simple form). Maybe you have a very old version.

By the way, what is a TICK? rise or fall?
And what are periods? Is a 'period' according to our system clock, or a full cycle (period) detected while reading the pin?

A tick is one single sysclock clock. Raise or fall defines if the positive or the negative edges of the input frequency are use to measure the periods. A period is the time from one positive edge to the next positive edge of the input signal.
One smartpin (41) measures the number of periodes that fit into the 1ms window, and the other smartpin (40) measures the time that all these periods take. They don't stop instantly after the 1ms time window, like a simple frequency counter. No they both finish the last periode that goes over the 1ms window, that's the trick of this reciprocal counter.

If you know the time that a certain number of periods take, you can calculate the frequency from that.

Andy

• Posts: 4,853

@Ramon said:
I have made a Flexprop version (for Retroblade2) here (Flexprop doesn't allow addpins, frac, or debug instructions)

I think you need to update your flexprop. addpins, frac, and debug have been supported for some time now.

• Posts: 10,785

How is debug supported? I presume not via loadp2 -t.

• Posts: 4,853

@evanh said:
How is debug supported? I presume not via loadp2 -t.

It is exactly via loadp2 -t . debug statements just get translated into the equivalent of BASIC "print" statements. The backtick kinds of debug (for graphics) just print the backtick strings, no graphics. debug() statements in PASM code are ignored.

• Posts: 10,785
edited 2021-04-08 01:56

Ah, no graphics makes sense. But still not getting anything reported with the simple demo from Pnut's .zip

```CON _clkfreq = 10_000_000

PUB go() | i
repeat i from 0 to 9
debug(udec(i))
```

Propeller Spin/PASM Compiler 'FlexSpin' (c) 2011-2021 Total Spectrum Software Inc.
Version 5.3.3-beta-v5.3.2-9-gf905169e Compiled on: Apr 8 2021

PS: I'm not using Flexprop. Flexspin and Loadp2 are hand entered in the shell.

• Posts: 4,853

@evanh : DEBUG() statements are optional (as they are in PNut). In order to enable debug() flexspin needs the -g flag on the command line. We should probably continue this on the flexspin thread if you have more questions.

• Posts: 10,785

Thanks, that got it. The needed .c sources was a surprise.

• Posts: 458

@ersmith said:

@Ramon said:
I have made a Flexprop version (for Retroblade2) here (Flexprop doesn't allow addpins, frac, or debug instructions)

I think you need to update your flexprop. addpins, frac, and debug have been supported for some time now.

I was using 5.2.0. I just downloaded 5.3.2, and looks that it is the same error I had before.

```error: syntax error, unexpected identifier `frac', expecting ')' or ','

error: syntax error, unexpected identifier `addpins', expecting ')' or ','

error: unknown identifier debug used in function call
error: unknown identifier udec used in function call
error: unknown identifier udec used in function call
error: unknown identifier udec used in function call
```

• Posts: 458

Ariba, Thank you for explaining all the details. Now I understand.

Never occurred to me that something like that was possible !!

Being able to configure the (smart)pin for output to create a test frequency and at the same time configure the same pin with counter function for input is like having two smartpins on each pin.

That could be great to make a simple test program that can check all pin input/ouputs. (with a wire between each pin pair:

• Frequency test output (pin 0, 2, 4, 6, 8, 10 ... 62) with NCO.
• 'Loopback' cable between 0+1, 2+1, 4+1 ... 62+1.
• And configure frequency counters on 0+1, 2+1 .. 62+1.

Much cheaper than 64 resistors + LEDs.

• Posts: 4,853

I think you need to update your flexprop. addpins, frac, and debug have been supported for some time now.

I was using 5.2.0. I just downloaded 5.3.2, and looks that it is the same error I had before.

Ah, I see the problem, your file is named .spin (for Spin 1) instead of .spin2 (for Spin 2). The two languages are slightly different, unfortunately. To use Spin2 operators like addpin or frac you need to name the file with a .spin2 extension.

• Posts: 458

Haha, Sorry !! At least, that time was not wasted. I have learned what those instructions were doing.

• Posts: 10,785
edited 2021-04-08 23:48

@Ramon said:
Ariba, Thank you for explaining all the details. Now I understand.

Never occurred to me that something like that was possible !!

Being able to configure the (smart)pin for output to create a test frequency and at the same time configure the same pin with counter function for input is like having two smartpins on each pin.

That could be great to make a simple test program that can check all pin input/ouputs. (with a wire between each pin pair:

You can do all that and more without any wires at all. However, Ariba used smartpin's 39, 40 and 41. Three of them. Each pin has one smartpin. Each smartpin does only one job at a time.

Each pin input can be rerouted, by the mode bits, to up to six other nearby pin INs. This saves using any linking wires. Which is what Chip has done for two of the three smartpins in the original Reciprocal Counter Demo.

• Posts: 10,785

Here's Ariba's setup without any wires needed:

```  pinstart(MSR_PIN, P_NCO_FREQ+P_OE, 1, 1_000_150 frac clkfreq)   'test freq
pinstart(MSR_PIN+1, P_MINUS1_A + P_MINUS1_B + P_COUNTER_TICKS, mintime, %00)
pinstart(MSR_PIN+2, P_MINUS2_A + P_MINUS2_B + P_COUNTER_PERIODS, mintime, %00)
```
• Posts: 458

Is it possible to generate a test frequency and calculate its frequency, with just two pins?

I don't want to save any wire. The wire is a requirement, to be able to test the pins output and input.

• Posts: 10,785

You only need the one active pin. The mode configurable re-routing works on physical pin inputs only. It doesn't shortcut anything. So, you can fully test each pin input and output without any loopback wires.

In my config above the pin under test is MSR_PIN. The fact that there is two extra smartpins involved doesn't detract from it being a real physical action on that pin - With both the output driver slewing times and the input threshold propagation times.

• Posts: 10,785
edited 2021-04-09 01:17

There is block diagram we put together that might help - https://forums.parallax.com/discussion/171420/smartpin-diagram/p1
The "Logic Input" block is where to select the routed inputs. Each is from another nearby pin input, just the same way as the immediate input is routed. In fact, for the odd pin, -1 is even shown coming from the even pin. And, for the even pin, +1 is coming from the odd pin.