Shop OBEX P1 Docs P2 Docs Learn Events
Reciprocal Counter Demo — Parallax Forums

Reciprocal Counter Demo

cgraceycgracey Posts: 14,133
edited 2019-12-04 14:16 in Propeller 2
There have been some questions about the reciprocal counter modes. A few of you have also been asking about how to do simple I/O, like serial with decimal number printing, so that you can see what's going on inside. It's all in here. This reciprocal counter is Jmg's baby, by the way. He really wanted this in the chip and it's pretty neat how it works.

This demo shows the reciprocal counter modes working together to form a pretty competent frequency counter. It outputs serial text at 1Mbaud and works really well with the Parallax Serial Terminal:
'****************************************
'*  Reciprocal Counter Demonstration    *
'*  - inputs frequency on P0            *
'*  - transmits serial text on P62      *
'****************************************
'
con		sysfreq		= 250_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		= 1_000_000.0		'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

dat		org
'
'
' Setup
'
		hubset	##%1_000001_0000011000_1111_10_00	'enable 20MHz crystal and PLL
		waitx	##20_000_000/100			'wait 10ms for crystal and PLL to stabilize
		hubset	##%1_000001_0000011000_1111_10_11	'switch to PLL

		wrpin	msr_time,#msr_pin+0		'configure smart pin for clocks count
		wrpin	msr_states,#msr_pin+1		'configure smart pin for states count
		wrpin	msr_periods,#msr_pin+2		'configure smart pin for periods count
		wxpin	##round(msr_min),#msr_pins	'set smart pins' x-value to clocks in msr_us
		wypin	#%00,#msr_pins			'set smart pins' y-value to rise-to-rise
		dirh	#msr_pins			'concurrently enable smart pins

		wrpin	#%01_11110_0,#62		'configure async serial output
		wxpin	tx_mode,#62
		dirh	#62
'
'
' Take measurements
'
.loop		akpin	#msr_pins			'clear any old measurement
		waitx	#3

.wait		testp	#msr_pin	wc		'wait for new measurement
	if_nc	jmp	#.wait

		rqpin	clocks,#msr_pin+0		'get clocks
		rqpin	states,#msr_pin+1		'get states
		rqpin	periods,#msr_pin+2		'get periods
'
'
' Compute duty and frequency
'
		qmul	states,##1_000			'duty = states * 1_000 / clocks
		getqx	x
		getqy	y
		setq	y
		qdiv	x,clocks
		getqx	duty

		qmul	periods,##round(sysfreq)	'frequency = periods * sysfreq / clocks
		getqx	x
		getqy	y
		setq	y
		qdiv	x,clocks
		getqx	frequency

		getqy	y				'frequency_sub = remainder / clocks * 1_000_000
		qfrac	y,clocks
		getqx	x
		qmul	x,##1_000_000
		getqy	frequency_sub
'
'
' Send results serially on P62
'
		call	#tx_string			'clocks
		byte	13," clocks:",0
		callpa	clocks,#tx_decimal

		call	#tx_string			'states
		byte	"    states:",0
		callpa	states,#tx_decimal

		call	#tx_string			'periods
		byte	"    periods:",0
		callpa	periods,#tx_decimal

		call	#tx_string			'duty
		byte	"    duty:",0
		mov	decx,dec4
		callpa	duty,#tx_decimal
		mov	decx,dec10

		call	#tx_string			'frequency
		byte	"/k    frequency:",0
		callpa	frequency,#tx_decimal
		callpa	#".",#tx_chr
		callpa	frequency_sub,#tx_decimal_sub
		jmp	#.loop
'
'
' Transmit zero-terminated string following call
'
tx_string	pop	x				'pop return address and make byte ptr
		shl	x,#2

.loop		altgb	x				'get character
		getbyte	y
		tjz	y,#.done			'if 0, done
		callpa	y,#tx_chr			'else, output character
		ijnz	x,#.loop			'inc byte ptr and loop

.done		shr	x,#2				'make long ptr
		add	x,#1				'inc long ptr to instruction following string
		jmp	x				'return to caller
'
'
' Transmit decimal value via callpa
'
tx_decimal	mov	x,pa				'decimal value

		mov	v,#2				'init comma tracker
		mov	w,#0				'init leading-zero flag
		mov	z,decx				'init tens place

.digit		qdiv	x,z				'divide x by tens place
		getqx	y

		cmpr	z,#1		wc		'output digit if last or not leading-zero
		or	w,y		wz
  if_nc_or_nz	callpa	y,#tx_digit
  if_c_and_z	callpa	#" ",#tx_chr			'else, output leading space

  if_c		incmod	v,#2		wc		'output commas or leading spaces as needed
  if_c_and_nz	callpa	#",",#tx_chr
  if_c_and_z	callpa	#" ",#tx_chr

		qmul	y,z				'subtract tens place digit
		getqx	y
		sub	x,y

		qdiv	z,#10				'divide tens place by 10
		getqx	z

	_ret_	tjnz	z,#.digit			'if not zero, another digit


decx		long	1_000_000_000
dec10		long	1_000_000_000
dec4		long	1_000
'
'
' Transmit decimal sub value via callpa
'
tx_decimal_sub	mov	x,pa				'decimal value

		mov	z,dec6				'init tens place

.digit		qdiv	x,z				'divide x by tens place
		getqx	y

		callpa	y,#tx_digit			'output digit

		qmul	y,z				'subtract tens place digit
		getqx	y
		sub	x,y

		qdiv	z,#10				'divide tens place by 10
		getqx	z

	_ret_	tjnz	z,#.digit			'if not zero, another digit


dec6		long	100_000
'
'
' Transmit digit/character via callpa
'
tx_digit	or	pa,#"0"				'turn 0..9 into "0".."9"

tx_chr		rdpin	pb,#62		wc		'wait for pin not busy
	if_c	jmp	#tx_chr

		wypin	pa,#62				'transmit character

		ret			wcz		'return and preserve caller's flags
'
'
' Data
'
tx_mode		long	(round(sysfreq / baud * 65536.0) & $FFFFFC00) + 7	'8N1

msr_time	long	%0000_0000_000_0000_000000000_00_10101_0	'msr_pin+0 config
msr_states	long	%0111_0111_000_0000_000000000_00_10110_0	'msr_pin+1 config
msr_periods	long	%0110_0110_000_0000_000000000_00_10111_0	'msr_pin+2 config

clocks		res	1
states		res	1
periods		res	1

duty		res	1
frequency	res	1
frequency_sub	res	1

v		res	1
w		res	1
x		res	1
y		res	1
z		res	1

It's set up for 250MHz and the minimum sample time (msr_us) can be set from 1us to 5 seconds. It also demonstrates some practical math and number printing.

Here is a screenshot of some serial output, while the minimum sample time is 10ms and it's receiving a 1MHz square wave into P0:


Reciprocal_Counter.png

«1345

Comments

  • BeanBean Posts: 8,129
    Chip,
    Thank so much for this demo. This is exactly what I want to use the P2 for.
    I got everything working except how to compute the frequency.

    Bean
  • cgraceycgracey Posts: 14,133
    Bean wrote: »
    Chip,
    Thank so much for this demo. This is exactly what I want to use the P2 for.
    I got everything working except how to compute the frequency.

    Bean

    You bet. Note that the duty and frequency computations first multiply to produce 64-bit products, then divide those 64-bit products by 32-bit values. This allows full 32-bit inputs to be handled without any interim overflows.
  • msrobotsmsrobots Posts: 3,701
    edited 2019-12-04 13:22
    Oh @cgracey, Chip, whatever.

    Those smartpins are amazing. 64 independent sub systems.

    Sometimes I cringe reading your code, today I had to smile.

    Your string output routine is absolutely stupid funny, one could say even silly.

    But it keeps the string right where one would like to have it when reading the code. I like it.

    How do you feel about eating your own bread now?

    I think P2 PASM is still a bit overwhelming for me but a logical extension to PASM. I do have a lot of fun with the P2, not much to share, I am fighting a lot with small stuff, but you obviously have a big grin in your face playing with the newborn.

    But you need to get your Spin2 Interpreter finished to keep up with Eric's pace, FlexSpin SPIN 1.5 is cool, but without you finishing, Eric can just guess where to go.

    I think it is very important that your interpreter will be able to integrate the same binaries/objects Flexgui/Fastspin uses.

    This time we can build a system where different programs can co-exist, @RossH and @ersmith seem to align the C part, @"Peter Jakacki" is adapting also where needed, and PropGcc - hmm - I am not sure where this stands.

    But we need your byte code interpreter to build a self hosted Spin IDE on the P2.

    Enjoy

    Mike
  • evanhevanh Posts: 15,126
    edited 2019-12-04 13:46
    Chip,
    "Periods" grr, "States" is terrible naming. Density is it, as in pulse density modulation.
  • Awesome work CHip. That's a great demo. I love the P2 but haven't had much time to play with the hardware yet but hopefully I will get more free time in the New Year. I love the new features such as the smart pin's however, when I read through all the options in the Doc's it makes my head hurt LOL

  • cgraceycgracey Posts: 14,133
    Thanks, Mike.

    The bread is complicated. I get lost in it. Because there are lots of different instructions, many of which can achieve two things at once, the potential to optimize never seems to end. I can think I have something as tight as possible, but I'll come back a month later and find a way to squeeze it a lot more, especially in merging related code sequences. So, it can be a recipe for madness, along with being a fun challenge.

    Yes, Spin2 needs to get done. I'm really feeling positive about it. The problem is that there's a lot to chew, at once. I think it's been more tedious to modify the old code base than it might have been to do it from scratch, because starting from scratch would have allowed incremental development. Now, it's been more parallel and it will go from doing nothing to working, all at once. Maybe it's just that I think about it in parallel because I know what all it needs to do, and I don't get the motivation that comes with seeing it grow from nothing. It's going to be really nice, anyway. I am looking forward to having it working so that I can use it build lots of objects.
  • cgraceycgracey Posts: 14,133
    evanh wrote: »
    Chip,
    "Periods" is terrible naming. Density is it, as in pulse density modulation.

    It's not even "periods". I believe you are referring to "states" from which duty (or density) is computed. "States" could be called "highs", since it tracks how many 1's were reading during the measurement.
  • evanhevanh Posts: 15,126
    Yeah, I corrected it as you were writing that I guess. The point is still valid. Calling it states is worse than periods, IMHO.
  • cgraceycgracey Posts: 14,133
    edited 2019-12-04 14:08
    evanh wrote: »
    Yeah, I corrected it as you were writing that I guess. The point is still valid. Calling it states is worse than periods, IMHO.

    Density is an average. We need a word that is a plural, to express what is being counted.

    clocks
    highs
    periods

    -or-

    time
    density
    quantity
  • evanhevanh Posts: 15,126
    edited 2019-12-04 14:34
    Duty is the average :) Density is more undefined I feel. I've used "ticks" and "count" for the other two. Clocks is fine.

    Count could also be called pulses. Periods does work although it feels more like the name to a reference/preset rather than the measurement.

    EDIT: I've also called density "accumulate" or "accum", or just "acc".

  • BeanBean Posts: 8,129
    I use counts for "count of system clocks" and cycles for "count of measurement cycles".

    Bean
  • evanhevanh Posts: 15,126
    hehe, could definitely interpret those the wrong way around.

  • Nice, Chip! I have to test this one, but before, I would like to know if it is possible to measure the frequency of a 10MHz signal. I have the perfect "guinea pig" for that: it is a 10MHz low jitter reference.

    Kind regards, Samuel Lourenço
  • evanhevanh Posts: 15,126
    Yep a 25 MHz prop2 would handle that. 2x is enough but always want a little headroom. So at 250 MHz sys-clock, you'd be able to throw 100 MHz signal at it.

  • Bean wrote: »
    I use counts for "count of system clocks" and cycles for "count of measurement cycles".

    Bean

    I will do it exactly the opposite way .... but perhaps because to much times I think in italian and translate to english.
  • jmgjmg Posts: 15,140
    evanh wrote: »
    Yeah, I corrected it as you were writing that I guess. The point is still valid. Calling it states is worse than periods, IMHO.

    Yes, Chip's docs do need some work... they can be opaque :)

    Bean wrote: »
    I use counts for "count of system clocks" and cycles for "count of measurement cycles".
    Everything uses counters, so I tend to use ticks or SysCLKs (or even just Time) for the faster counter, usually capturing time, and I use 'whole cycles' for the user measurement value.
    The 'whole' is always there, because that extra Pin Cell hardware is an important key element of the Reciprocal Counter.

  • jmgjmg Posts: 15,140
    cgracey wrote: »
    There have been some questions about the reciprocal counter modes. A few of you have also been asking about how to do simple I/O, like serial with decimal number printing, so that you can see what's going on inside. It's all in here. This reciprocal counter is Jmg's baby, by the way. He really wanted this in the chip and it's pretty neat how it works.
    This demo shows the reciprocal counter modes working together to form a pretty competent frequency counter. It outputs serial text at 1Mbaud and works really well with the Parallax Serial Terminal:
    Nice to see some more examples... especially one with all the maths included..

    some comments on the code...
    cgracey wrote: »
    		msr_pin		= 0			'pin to measure frequency on, uses next two pins
    		msr_pins	= 2<<6 + msr_pin	'group of three pins starting at msr_pin
    		wrpin	msr_time,#msr_pin+0		'configure smart pin for clocks count
    		wrpin	msr_states,#msr_pin+1		'configure smart pin for states count
    		wrpin	msr_periods,#msr_pin+2		'configure smart pin for periods count
    
    clarifies needed :
    The comments are imprecise and do not match the code. One says 2 pins, then another says 3 pins.
    It uses 3 Smart Pin cells, not pins. ie The P1 and P2 pins here do not need to be connected to anything, but their Smart Pin Cells are used,

    General comment: I also like to paste example output capture of code, into the source as comments, so users instantly know what they should see.

    Other items:
    Why does DUTY change so much here ? What is the test source ?

    Suggestions:
    Your example runs at a fast 100Hz sample, and resolves to 0.4ppm, even at that fast pace, but the HW does not miss any information, so you can update an every 1 second total, and that 1 second average will resolve to 4ppb.

    If you connect a 1pps signal from a GPS to this, it should auto-adjust to 1s samples ?
  • jmgjmg Posts: 15,140
    samuell wrote: »
    Nice, Chip! I have to test this one, but before, I would like to know if it is possible to measure the frequency of a 10MHz signal. I have the perfect "guinea pig" for that: it is a 10MHz low jitter reference.

    Of course, reciprocal counters auto-scale, so you can simply feed in any MHz, within the scope of P2 counting.
    You get out a result of so many 'digits per second' precision - here, that's ~6.4 digits/10ms

    With a Sysclk of 250MHz, and no pin-filter enabled, that max Fin will be just under 125MHz (depends on duty cycle)
    I think the line
    .wait		testp	#msr_pin	wc		'wait for new measurement
    	if_nc	jmp	#.wait
    
    is the pivotal HW-SW interface, and that will simply wait for at least a whole cycle on Fin, so frequencies below 100Hz (10ms nominal measure time), will merely update the display slower, and Chips 'periods' value will always be 1, but the capture time ('clocks') will vary, and be 250M at 1Hz

  • BeanBean Posts: 8,129
    edited 2019-12-06 00:21
    I added my own comments to the code so I could understand it better.
    I had some trouble getting my head around how the fractional part of the frequency was computed. But now I understand it.

    Bean
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"
  • cgraceycgracey Posts: 14,133
    edited 2019-12-06 00:28
    I really like the word "ticks" to represent number of system clocks. It is pretty unit-less, unlike "time".

    ticks
    highs
    cycles (need a better, but short word here)
    		msr_pin		= 0			'pin to measure frequency on, uses next two pins
    		msr_pins	= 2<<6 + msr_pin	'group of three pins starting at msr_pin
    		wrpin	msr_time,#msr_pin+0		'configure smart pin for clocks count
    		wrpin	msr_states,#msr_pin+1		'configure smart pin for states count
    		wrpin	msr_periods,#msr_pin+2		'configure smart pin for periods count
    

    Jmg, msr_pin is the actual input pin, while the other two pins also watch it from nearby. The 2<<6 adds two extra pins for a total of three.
  • It works quite well! Please, see the screenshot attached.

    Kind regards, Samuel Lourenço
    1024 x 768 - 187K
  • ersmithersmith Posts: 5,900
    edited 2019-12-06 01:02
    Bean wrote: »
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"

    It's to indicate a temporary label that only lasts until the next regular label. In P1 PASM that was indicated with a colon (":wait", ":loop", ":done" etc.) but in P2 PASM it's a dot.
  • cgraceycgracey Posts: 14,133
    edited 2019-12-06 01:50
    ersmith wrote: »
    Bean wrote: »
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"

    It's to indicate a temporary label that only lasts until the next regular label. In P1 PASM that was indicated with a colon (":wait", ":loop", ":done" etc.) but in P2 PASM it's a dot.

    I kind of wish that I had left it at ":", instead of ".", because something starting with "." looks incomplete. The impetus was to make it so that local labels could later be addressed outside of their context by "regularlabel.locallabel". I'll get there, eventually.
  • ersmith wrote: »
    Bean wrote: »
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"

    It's to indicate a temporary label that only lasts until the next regular label. In P1 PASM that was indicated with a colon (":wait", ":loop", ":done" etc.) but in P2 PASM it's a dot.

    @ersmith , Does fastspin allow the @.endloop type of addressing with REP? I vaguely recall I had trouble with this when porting over to use fastspin instead from P2ASM and I think I needed to change my labels to get rid of the period for my rep loop addressing to work.

    eg. try this...
        REP @.endloop, #4
        ADD A, #1  ' some loop
    .endloop
    





  • jmgjmg Posts: 15,140
    cgracey wrote: »
    ersmith wrote: »
    Bean wrote: »
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"

    It's to indicate a temporary label that only lasts until the next regular label. In P1 PASM that was indicated with a colon (":wait", ":loop", ":done" etc.) but in P2 PASM it's a dot.

    I kind of wish that I had left it at ":", instead of ".", because something starting with "." looks incomplete. The impetus was to make it so that local labels could later be addressed outside of their context by "regularlabel.locallabel". I'll get there, eventually.

    . as local prefix is used in other assemblers, and also common is this example, which could allow you to support anything.
    I've also seem ? used as a local prefix.
    $LOCALPREFIX (_)            ;local label prefix is _
    $LOCALPREFIX ()             ;disable local label prefix
    
  • jmgjmg Posts: 15,140
    cgracey wrote: »
    Jmg, msr_pin is the actual input pin, while the other two pins also watch it from nearby. The 2<<6 adds two extra pins for a total of three.
    Yes, but my understanding is those pins are not consumed, only their smart pin cells are used ?
    ie you can still use P1,P2 as general IO, and I guess could even attach them to P3's Pin cell if you needed to.


  • cgraceycgracey Posts: 14,133
    jmg wrote: »
    cgracey wrote: »
    Jmg, msr_pin is the actual input pin, while the other two pins also watch it from nearby. The 2<<6 adds two extra pins for a total of three.
    Yes, but my understanding is those pins are not consumed, only their smart pin cells are used ?
    ie you can still use P1,P2 as general IO, and I guess could even attach them to P3's Pin cell if you needed to.


    That's true, though you would have to use their smart pin modes to control their output enable states.
  • jmgjmg Posts: 15,140
    samuell wrote: »
    It works quite well! Please, see the screenshot attached.

    What is your test clock source there ?
    That also seems to have unexpected larger changes in DUTY or %Hi measurements.
    it varies about 904 ppm

    Maybe that's due to the high MHz in, and the low number of cycles per HI gate.

    1254196/100002 = 12.54170 cycles, not many, so the edge uncertainty/noise is going to be a contributor here.
    and 900ppm variation band on 100ns, maps to ~90ps.
    This may be showing the jitter on the P2 sysclock ?


  • rogloh wrote: »
    ersmith wrote: »
    Bean wrote: »
    P.S. Could someone explain the use of a dot before some of the labels ? ".wait", ".loop", ".done"

    It's to indicate a temporary label that only lasts until the next regular label. In P1 PASM that was indicated with a colon (":wait", ":loop", ":done" etc.) but in P2 PASM it's a dot.

    @ersmith , Does fastspin allow the @.endloop type of addressing with REP? I vaguely recall I had trouble with this when porting over to use fastspin instead from P2ASM and I think I needed to change my labels to get rid of the period for my rep loop addressing to work.

    eg. try this...
        REP @.endloop, #4
        ADD A, #1  ' some loop
    .endloop
    

    Seems to work for me.
  • evanhevanh Posts: 15,126
    My favourite feature in this counter demo is the demonstration of using a 64-bit intermediate in the calculations. That really makes the cordic shine.

Sign In or Register to comment.