PNut/Spin2 Latest Version (v40 - New 'REPEAT count WITH variable' added to simplify 0..n-1 loops) - Page 11 — Parallax Forums

• Posts: 13,290
@JonnyMac I posted this in Mixed Signal Scope thread... Maybe helps you:
```The docs for NCO duty mode are not very clear...
It doesn't actually tell you how to set the %duty.

I just played with it and see now that Y controls duty.
Y=\$FFFF_FFFF is 100% duty, Y=\$8000_0000 is 50% duty and Y=0 is 0% duty.

Makes some sense now...

X controls the minimum pulse width apparently. Not exactly sure how...
```
• Posts: 8,732
whicker wrote: »
Does what Chip said on April 8 (scroll up) also apply here? Does the first y in pinstart have to be 0?
Should pinstart( ) be modified to have two y values? (Before and after?)
I tried that -- it didn't make a difference.
• Posts: 8,732
X controls the minimum pulse width apparently. Not exactly sure how...
Thanks for the nudge, @Rayman. I changed the value of X and am now getting better output. Going to build a table to determine how X affects the output frequency. It could be that at 1, the capacitance of my 'scope lead was destroying the signal.
• Posts: 2,646
edited 2020-04-11 17:57
This gives a 100 kHz Signal with a 100ns high-pulse on my scope:
```  _clkfreq = 100_000_000

PUB Duty() | x,y
x := 10
y := 1 frac 100
pinstart(34, %01_00111_0, x, y)
repeat
```

Andy
• Posts: 8,732
edited 2020-04-11 18:29
I got that, too, Andy. I did a quick test to determine how X affects output frequency.

For duty cycle in range 0.01 to 0.50...
```fr = clkfreq / X * duty_cycle
```
Note that 50% is the highest frequency because that value causes a carry every other cycle.

With X at 1 I'm guessing my 'scope probe could not handle the frequency.

• Posts: 1,115
A minor documentation (ver. 34m) change. In PUB MinimalSpin2Program() 'first PUB method executes
PINWRITE(63..56, GETRND) 'write a random pattern to P63..P56 needs () after GETRND

John Abshier

• Posts: 13,939
Here is an NCO-duty smart pin program that updates at 1MHz and makes a 50% duty output:
```CON
_clkfreq	= 180_000_000
UpdateRate	= 1_000_000
BaseTicks	= _clkfreq / UpdateRate
DutyPercent	= 50
DutyValue	= DutyPercent FRAC 100

PUB go()
PINSTART(56, P_OE | P_NCO_DUTY, BaseTicks, DutyValue)
REPEAT
```
• Posts: 13,290
What does update rate mean?
I think I’d want it defined in terms of period in clocks and duty fraction (this one is just Y, easy)
• Posts: 749
Rayman wrote: »
What does update rate mean?
I think I’d want it defined in terms of period in clocks and duty fraction (this one is just Y, easy)
Code written for a specific CPU clock speed falls apart in the real world when that speed changes.
• Posts: 13,290
Which is why I proposed period in terms of clocks instead of frequency... But, it's just some math to convert...
• Posts: 13,939
I was trying to show the timing relationships. You could just define BaseTicks directly.
• Posts: 13,290
Ok, I get it now. I think I'd change "update rate" to "NCO Frequency".
• Posts: 8,732
edited 2020-04-12 18:39
Ok, I get it now. I think I'd change "update rate" to "NCO Frequency".
In NCO_DUTY mode, for any given X, the output frequency will change with duty cycle, hence that may not be an accurate descriptor.
• Posts: 13,290
I thought remembered there was something strange going on there...

It'd be nice to be able to specify this in terms of NCO Frequency and Duty.
• Posts: 8,732
edited 2020-04-12 19:39
Not with the NCO modes. In NCO_FREQ, you can specify the frequency but always get a duty cycle of 50%. In NCO_DUTY, you can specify the duty cycle, but the frequency varies (50% dc produces the highest frequency for any give X, dc of 1 produces the lowest [51..91 mirrors 1..49]). The only way to have control of both is with PWM_ modes, triangle being the most straightforward.
• Posts: 13,290
Thanks, I can see that.
But I still don’t understand exactly what baseticks setting does then...
• Posts: 8,732
X really acts like a clock divisor for the NCO modes. Internally the pin counts from X to 1, and then adds Y into Z. In NCO_FREQ, the pin output is z.[31]. In NCO_DUTY, the output is the carry bit after the addition.
• Posts: 13,290
That sounds right. I hope there turns out to be an easier way to understand it in the future.
• Posts: 8,732
Thankfully, PWM allows one to control frequency and duty-cycle -- though the low-end frequency is limited by the 16-bit counts used.
• Posts: 2,646
edited 2020-04-12 22:40
Not sure why the duty mode gets so much attention here. This mode is rather obsolet on the P2. Now we have real PWM and 16bit dithered DAC modes.
Is there some application that needs duty mode?

Andy
• Posts: 13,290
edited 2020-04-12 22:25
I have no idea what it's good for... Have to figure out what is does first...

It's a good question though. Is there some intended use for this?
• Posts: 8,732
edited 2020-04-12 23:56
I wonder if it's a carry-over from the P1 counter NCO and DUTY modes. While trying to figure out what was going on, I hooked up a P1 and could see the output on my 'scope. I couldn't with the P2 because I had X too small hence the output frequency was too fast for my 'scope probes. In the P1 I used NCO mode for creating a known frequency, and Chip used DUTY mode for the output of his stereo_dacs object that I use in my WAV player.
• Posts: 14,619
JonnyMac wrote: »
... and Chip used DUTY mode for the output of his stereo_dacs object that I use in my WAV player.
That makes a lot of sense. Intended as a cheap DAC substitute. As I noted in the other topic, it does have very similar properties to sigma-delta.

• Posts: 13,290
Andy's got me thinking about use case... Doesn't make any sense for audio when you have a DAC.

But, if your driving some kind of external circuit that has digital and not analog control, might be useful...
• Posts: 14,619
edited 2020-04-13 00:17
Right, and as Jon says, Chip probably threw it in because was in the prop1 and isn't a burden to include. Those two NCO modes will be the smallest circuit size of the smartpin modes. Well, except for the "repository" modes.
• Posts: 8,732
Those modes may have been included to facilitate quick porting from P1 to P2.
• Posts: 18,066
edited 2020-04-13 04:18
SPIN2 Questions

The docs imply that spin uses Cog \$140-\$1D7 (and of course \$1F0-\$1FF) and all LUT \$200-\$3FF. A memory diagram would be nice at some time.

The docs imply that Cog registers \$1D8-\$1DF are for use between pasm and spin, and can be referenced as PR0-PR7 in spin.

So that leaves Cog \$000-\$13F and registers \$1E0-\$1EF for p2asm routines, and \$1D8-\$1DF for common registers between spin2 and p1pasm?

However, looking at the spin2 interpreter code, I see that cog_code starts at \$132. So I presume the docs are wrong.

P2 ROM Code

Now the ROM Monitor/Debugger uses Cog registers \$1E0-\$1EF so these routines should be callable from spin2

But the ROM SD Driver uses Cog registers \$1C0-\$1DF so these routines cannot be called from Spin2

How do you call pasm from spin2

These methods are listed
```CALL(Addr)      CALL PASM code at Addr, PASM code should avoid registers \$140..\$1D7 and LUT
```

I am guessing that the parameter "Addr" points to an address containing a structure...
```  WORD @StartReg
WORD NumRegs
instructions ???
```
but how do we tell the method where the code is located in hub ???

Alternately, if "Addr" is the hub address, then how do we pass the word StartReg and the word NumRegs to the method?

Looking at the interpreter code it looks like these routines are passed the hub address in the method call, and the hub address starts (ie prefixed) with two words (which are not loaded), the first being the cog address (to load into) followed by the length-1 (length of longs to load), and then followed by the code to be loaded.

An example of their use would be nice please
• Posts: 13,939
edited 2020-04-13 05:18
Cluso99 wrote: »
SPIN2 Questions

The docs imply that spin uses Cog \$140-\$1D7 (and of course \$1F0-\$1FF) and all LUT \$200-\$3FF. A memory diagram would be nice at some time.

The docs imply that Cog registers \$1D8-\$1DF are for use between pasm and spin, and can be referenced as PR0-PR7 in spin.

So that leaves Cog \$000-\$13F and registers \$1E0-\$1EF for p2asm routines, and \$1D8-\$1DF for common registers between spin2 and p1pasm?

However, looking at the spin2 interpreter code, I see that cog_code starts at \$132. So I presume the docs are wrong.

P2 ROM Code

Now the ROM Monitor/Debugger uses Cog registers \$1E0-\$1EF so these routines should be callable from spin2

But the ROM SD Driver uses Cog registers \$1C0-\$1DF so these routines cannot be called from Spin2

How do you call pasm from spin2

These methods are listed
```CALL(Addr)      CALL PASM code at Addr, PASM code should avoid registers \$140..\$1D7 and LUT
```

I am guessing that the parameter "Addr" points to an address containing a structure...
```  WORD @StartReg
WORD NumRegs
instructions ???
```
but how do we tell the method where the code is located in hub ???

Alternately, if "Addr" is the hub address, then how do we pass the word StartReg and the word NumRegs to the method?

Looking at the interpreter code it looks like these routines are passed the hub address in the method call, and the hub address starts (ie prefixed) with two words (which are not loaded), the first being the cog address (to load into) followed by the length-1 (length of longs to load), and then followed by the code to be loaded.

An example of their use would be nice please

The calibrated 8-channel ADC program I posted uses REGEXEC, which loads a program that starts with two words:

And, yes, the Spin2 docs need to be updated to reflect a PASM space of \$000..\$131.
• Posts: 18,066
Hooray

I've called the ROM Monitor/Debugger from spin

• Posts: 18,066
edited 2020-04-13 07:48
Chip,
Is this legitimate?
```CON
lmm_x         = \$1e0          ' parameter passed to/from LMM routine (typically a value)
lmm_f         = \$1e1          ' parameter passed to      LMM routine (function options; returns unchanged)
lmm_p         = \$1e2          ' parameter passed to/from LMM routine (typically a hub/cog ptr/addr)
lmm_p2        = \$1e3          ' parameter passed to/from LMM routine (typically a 2nd hub/cog address)
lmm_c         = \$1e4          ' parameter passed to/from LMM routine (typically a count)

PUB go()
REG[lmm_x]  := \$1000                                ' start address
CALL(_HubHex8)                                      ' print in hex, 8 digits
CALL(_hubTxCR)                                      ' print cr+lf

REG[lmm_p]  := \$1000                                ' start address
REG[lmm_x]  := REG[lmm_p]                           ' copy start address
CALL(_HubHex8)                                      ' print in hex, 8 digits
CALL(_hubTxCR)                                      ' print cr+lf

REG[lmm_x]  := \$1000                                ' start address
REG[lmm_x]  += 256                                  ' end addrs = start + length(bytes)
CALL(_HubHex8)                                      ' print in hex, 8 digits
CALL(_hubTxCR)                                      ' print cr+lf
```
because...
1) lmm_x = \$00001000 correct
2) lmm_x = \$0000001F incorrect
3) lmm_x = \$F60BACE0 incorrect

Next, after spin code, this DAT ORGH \$4000 is set to hub \$1000 and not \$4000 (ie code is placed into hub at \$1000)
```''============[ HUB VARIABLES ]=================================================
DAT
orgh    \$4000
str_fun         byte    "Fun with P2 ROM v010",13,10,0
str_msg         byte    "More P2 fun...",13,10,0
alignl
''---------------------------------------------------------------------------------------------------
```

I have also noticed that some CALL statements appear to sometimes return missing the next CALL. I haven't tracked this down yet.