Posts: 7,699
edited 2020-02-26 18:17
I like looking through small bits of successful code for ideas and solutions -- with Spin2 being new and having differences from Spin1, I thought I'd start this thread so that we can share what we've learned with each other.

If you would, post your snippet and detail syntax changes from Spin1.

• Posts: 7,699
edited 2020-02-26 20:55
I'll start with this. It's a little parsing routine that can pull one or two numbers out of a string. It returns a pointer to the first character after the number(s), and two values. If there is only one number, the second value will be returned as 0. This demonstrates a big change from Spin1: multiple return values.
```pri get_nargs(p_str) : p_str1, val1, val2 | c, sign

'' Parse one or two numbers from string in n, -n, n.n, or -n.n format
'' -- dpoint separates values
'' -- only first # may be negative
'' -- returns pointer to 1st char after value(s)

c := byte[p_str]                                              ' check for negative on first value
if (c == "-")
sign := -1
++p_str
else
sign := 0

repeat                                                        ' get first value
c := byte[p_str++]
if ((c >= "0") and (c <= "9"))
val1 := (val1 * 10) + (c - "0")
else
if (sign)
val1 := -val1
quit

if (c == ".")                                                 ' if dpoint
repeat                                                      '  get second value
c := byte[p_str++]
if ((c >= "0") and (c <= "9"))
val2 := (val2 * 10) + (c - "0")
else
quit

p_str1 := p_str - 1                                           ' back up to non-digit
```

Changes from Spin1
-- return value(s) must be declared on method slug-line
-- the =< and => operators become <= and >=

In this case, the changes were fairly minor from the Spin1 version which passed pointers to the value variables.
• Posts: 7,699
edited 2020-02-26 18:35
Here's another conversion routine from that same project; this will convert a value to a string if the desired radix is 2 (binary), 4 (quarternary), 8 (octal), or 16 (hexadecimal).
```pub po2toa(value, radix, digits) : p_str | shift, mask, len, d

'' Convert to value power-of-2 string
'' -- radix --> 2: binary, 4: quarternary, 8: octal, 16: hexadecimal
'' -- digits is 0 (auto size) to limit for long using radix

bytefill(@nbuf, 0, NBUF_SIZE)                                 ' clear buffer
p_str := @nbuf + (NBUF_SIZE-2)                                ' move to end

02    : digits := 0 #> digits <# 32
04    : digits := 0 #> digits <# 16
08    : digits := 0 #> digits <# 11
16    : digits := 0 #> digits <#  8
other : return p_str

shift := encod radix                                          ' bits per digit
len := 0                                                      ' length of output string

repeat
d := value & mask                                           ' get right-most digit
if (d < 10)                                                 ' convert to ASCII
byte[--p_str] := "0" + d
else
byte[--p_str] := "A" + (d - 10)
value >>= shift                                             ' remove digit
if (digits)                                                 ' length limited?
if (++len == digits)                                      '  check size
quit
else
if (value == 0)                                           ' done?
quit

bytemove(@nbuf, p_str, strsize(p_str)+1)                      ' align with nbuf[0]

p_str := @nbuf
```

Changes from Spin1
-- return value(s) must be declared on method slug-line
-- >| operator becomes encod (P2 version behaves differently than P1)
• Posts: 17,937
Jon,
Great idea! Would you like to place a link in the P2 Tricks and traps reference thread, and in the P2 Tools and sample code “sticky”.
• Posts: 7,699
Yes, if you think that's a good idea.
• Posts: 7,699
edited 2020-03-02 01:13
Here's a little quickie that shows the convenience of multiple assignments with the := operator in Spin2. This method will reverse the order of characters in a string.
```pub revstr(p_str) : result | first, len, last

result := first := p_str                                      ' start
len := strsize(p_str)                                         ' length
last := first + len - 1                                       ' end

repeat (len >> 1)                                             ' reverse them
byte[first++], byte[last--] := byte[last], byte[first]
```
• Posts: 7,699
edited 2020-03-02 01:13
Here's a take on po2toa() (above) that will convert a value to string using 10, 2, 4, 8, or 16 as the radix. This demonstrates the use of unsigned modulus (+//) and unsigned division (+/) operators that are new to Spin2.
```pub itoa(value, radix, digits, p_str) : result | sign, len, d

'' Convert to value string
'' -- supports radix 10, 2, 4, 8, and 16
'' -- digits is 0 (auto size) to limit for long using radix
'' -- p_str is pointer to output buffer (should be 33+ bytes)

result := p_str                                               ' save string location

02    : digits := 0 #> digits <# 32
04    : digits := 0 #> digits <# 16
08    : digits := 0 #> digits <# 11
10    : digits := 0 #> digits <# 10
16    : digits := 0 #> digits <#  8
other :
byte[p_str] := 0
return

if ((radix == 10) && (value < 0))                             ' deal with negative decimals
if (value == negx)
sign := 2
value := posx
else
sign := 1
value := -value
else
sign := 0

len := 0

repeat
d := value +// radix                                        ' get digit (1s column)
byte[p_str++] := (d < 10) ? d + "0" : d - 10 + "A"          ' convert to ASCII
value +/= radix                                             ' remove digit
if (digits)                                                 ' length limited?
if (++len == digits)                                      '  check size
quit
else
if (value == 0)                                           ' done?
quit

if (sign)
byte[p_str++] := "-"                                        ' add sign if needed
if (sign == 2)
byte[result] := "8"                                       ' fix negx if needed

byte[p_str++]:= 0                                             ' terminate string

revstr(result)                                                ' fix order (reverse)

pub revstr(p_str) : result | first, len, last

result := first := p_str                                      ' start
len := strsize(p_str)                                         ' length
last := first + len - 1                                       ' end

repeat (len >> 1)                                             ' reverse them
byte[first++], byte[last--] := byte[last], byte[first]
```
• Posts: 12,044
edited 2020-03-01 20:27
Thanks Jon. These are great posts!
• Posts: 13,610
So, if the revstr method has a string of an odd length, it doesn't need to move the middle character. I had to look at that to figure out how it would handle that case.
• Posts: 7,699
Yep. I had to think about that, too. This is a translation of a C function, but I was able to streamline with the multi-assignment := operator.
• Posts: 7,699
edited 2020-03-04 05:09
For fun I thought I'd translate a very simple little MAX7219 driver from Spin1 to Spin2. This demonstrates the new and useful IO control in Spin2. As I have not figured out Smart Pins yet, the SPI output is bit-banged (direct translation from P1).
New commands like PINL(), PINH(), PINF, PINW(), and PINR() make IO access very easy, and maintain the same flexibility as accessing the IO registers in the P1.
• Posts: 7,699
While poking around the forums I came across a post from @cheezus demonstrating inline assembly for bit-banged SPI. I gave it a try and my MAX7219 code saw an increase from about 600kHz to the rated speed of 10MHz (I'm running the P2 at 200MHz).
Here's the updated out() method that uses inline assembly for speed.
```pub out(r, value) | bits, clk, do, sel, tix

'' Update register in MAX7219/21 with value

bits.byte[3] := r                                             ' set register
bits.byte[2] := value                                         ' set value

if (r == DECODE)                                              ' save decode register bits
decodebits := value.byte[0]

if ((r >= DIG_0) && (r <= DIG_7))                             ' save new segment bits
segments[r-1] := value

longmove(@clk, @sclk, 4)                                      ' copy pins & timing

org                                                           ' start inline pasm
drvl    sel                                     ' select device
nop

rep     @.end, #16                              ' do 16 bits
rol     bits, #1                wc              ' get bit
drvc    do                                      ' output to miso
waitx   tix                                     ' let miso settle
drvh    clk                                     ' clock high
waitx   tix                                     ' hold
waitx   tix
drvl    clk                                     ' clock low
waitx   tix
.end
drvh    sel                                     ' deselect
drvl    do                                      ' leave miso low
end                                                           ' end inline pasm
```
Note that inline assembly only works with local variables, so I have to copy the pins and timing from the global vars of the object -- this is a very small price to pay for the increase in speed. I also have to pass the clock speed (in Hertz) to the updated object.
• Posts: 12,044
JonnyMac wrote: »
While poking around the forums I came across a post from @cheezus demonstrating inline assembly for bit-banged SPI. I gave it a try and my MAX7219 code saw an increase from about 600kHz to the rated speed of 10MHz (I'm running the P2 at 200MHz).
Here's the updated out() method that uses inline assembly for speed.
```pub out(r, value) | bits, clk, do, sel, tix

'' Update register in MAX7219/21 with value

bits.byte[3] := r                                             ' set register
bits.byte[2] := value                                         ' set value

if (r == DECODE)                                              ' save decode register bits
decodebits := value.byte[0]

if ((r >= DIG_0) && (r <= DIG_7))                             ' save new segment bits
segments[r-1] := value

longmove(@clk, @sclk, 4)                                      ' copy pins & timing

org                                                           ' start inline pasm
drvl    sel                                     ' select device
nop

rep     @.end, #16                              ' do 16 bits
rol     bits, #1                wc              ' get bit
drvc    do                                      ' output to miso
waitx   tix                                     ' let miso settle
drvh    clk                                     ' clock high
waitx   tix                                     ' hold
waitx   tix
drvl    clk                                     ' clock low
waitx   tix
.end
drvh    sel                                     ' deselect
drvl    do                                      ' leave miso low
end                                                           ' end inline pasm
```
Note that inline assembly only works with local variables, so I have to copy the pins and timing from the global vars of the object -- this is a very small price to pay for the increase in speed. I also have to pass the clock speed (in Hertz) to the updated object.

That's a very significant upgrade. Very cool!

• Posts: 7,699
Inline assembly is going to make a lot of things very interesting. For example, you no longer need to spawn a cog to control just a few WS2812-type LEDs.
• Posts: 10,193
JonnyMac wrote: »
Inline assembly is going to make a lot of things very interesting. For example, you no longer need to spawn a cog to control just a few WS2812-type LEDs.

While Spin on the P1 was way too slow for this, I think that there should be no problem doing this directly in Spin2, rather than assembly since the speed will be limited by the LED timing anyway. I demonstrated this at the Tachyon code level on the P1 (even though in practice I used a cog code module), so Spin2 should work fine. I basically broke up each data bit into 3 periods, the "from low" to high "clock" period, the data period, and then the clock idle low period. Let's see it done in pure Spin2!
• Posts: 7,699
edited 2020-03-05 06:17
I'm going to do it with inline PASM2, because my experiments with simple SPI code in Spin2 don't comport with your expectations (I got about 600kHz max running the P2 at 200MHz).
• Posts: 7,699
edited 2020-03-05 06:08
Here's a working method that will write to WS2812b LEDs. The calculations for t0, t1, and tc are verbose; I made no attempt to refine other than the small delta at the end based on 'scope measurements.

That said, this is first draft code -- it's meant to inspire others to try inline PASM2.
```pub ws2812b(pin, count, p_colors) | outval, t0, t1, tc, cycle

'' Assumes 24-bit colors are MSB aligned (red in byte3)

t0 := (clkfreq / 1_000_000) *  400 / 1000 - 6                 ' 0 bit timing
t1 := (clkfreq / 1_000_000) *  800 / 1000 - 6                 ' 1 bit timing
tc := (clkfreq / 1_000_000) * 1250 / 1000                     ' cycle ticks @ 800kHz

org
drvl    pin                             ' make output
waitx   t0                              ' allow reset

led_loop                rdlong  outval, p_colors                ' get color
add     p_colors, #4                    ' point to next

mov     cycle, outval                   ' swap R & G bytes
shr     cycle, #16
setbyte outval, cycle, #3
shr     cycle, #8
setbyte outval, cycle, #2

getct   cycle                           ' start timing frame

rep     @.bitz, #24                     ' 8 bits x 3 colors
rol     outval, #1              wc      ' get MSB
drvh    pin                             ' pin on
if_nc           waitx   t0                              ' hold for bit timing
if_c            waitx   t1
drvl    pin                             ' pin off
addct1  cycle, tc                       ' update cycle timer
waitct1                                 ' let cycle finish
.bitz
djnz    count, #led_loop                ' do next LED
end
```
• Posts: 13,610
That is nice to see, Jon. Makes PASM approachable.

Can you show the Spin2 code for SPI that was running at only 600KHz?
• Posts: 7,699
edited 2020-03-05 15:56
Sure. This is the "before" code that I modified with inline PASM in the post above. Again, I still don't have my head wrapped around Smart Pins, so there may be a faster pure-Spin2 mechanism for doing this.
```pub out(r, value) | bits

'' Update register in MAX7219/21 with value

bits.byte[3] := r                                             ' set register
bits.byte[2] := value                                         ' set value

if (r == DECODE)                                              ' save decode register bits
decodebits := value.byte[0]

if ((r >= DIG_0) && (r <= DIG_7))                             ' save new segment bits
segments[r-1] := value

pinl(cs)                                                      ' enable

repeat 16                                                     ' shift out reg & value (MSBFIRST)
pinw(mosi, (bits rol= 1))
pinh(sclk)
pinl(sclk)

```
• Posts: 13,610
Jon, you could use the synchronous serial output for the MOSI pin and use transition mode for the SCLK pin. I need to make a bunch of symbolic constants to facilitate smart pin operations. Now that the compiler solves constant equations, there's no penalty for OR'ing a bunch of values together.
• Posts: 7,699
I still haven't wrapped my head around setting up smart pins. I'm going to give that a little more effort today. In the end, I'm hoping these posts will help regular programmers like me be able to port their P1 code to the P2. Happily, I'm getting used to Spin2 and am encountering few syntax errors.

When will you add all the new Spin2 commands to your document? For the time being, I have been scanning the compiler listing for them.
• Posts: 12,172
I would sure love to see a smartpin SPI example in Spin2.
QPI too...
• Posts: 13,610
JonnyMac wrote: »
I still haven't wrapped my head around setting up smart pins. I'm going to give that a little more effort today. In the end, I'm hoping these posts will help regular programmers like me be able to port their P1 code to the P2. Happily, I'm getting used to Spin2 and am encountering few syntax errors.

When will you add all the new Spin2 commands to your document? For the time being, I have been scanning the compiler listing for them.

Look in the Spin2_interpreter.spin2 file and you'll see all the commands listed in the comments, spread throughout. I hope to get to method syntax soon.
• Posts: 7,699
edited 2020-03-05 19:13
Sorry, that's what I meant: the interpreter listing. Have you released an update? -- not that I understand all of it, but it is interesting.
• Posts: 13,610
Here is the current interpreter:

• Posts: 7,699
edited 2020-03-10 17:51
Here's a practical example of smart pins that will get a lot of use: standard 0..255 PWM on any pin. With smart pins, it becomes set and forget. Note, though, that low frequencies are affected by clock speed. On my 200MHz setup I was able to get down to about 12Hz, which really isn't useful for PWM, anyway, so it's not a problem. I have friends in the movie business that are going to be very happy with high frequency PWM (better for LED control when using high frame rates).

I don't know if the current docs are wrong or I'm completely misunderstanding something, but after failing for a couple days I found that I could do what I wanted with TAQOZ, so I downloaded and tore through the source code. Once I found the proper mode bits for PWM, everything came together.
```pub pwm(pin, duty, hz) | x

'' DMX-compatible PWM
'' --  pin - output pin (0..55)
'' -- duty - duty cycle of output 0 to 255 (100%)
'' --   hz - pwm output frequncy

if (hz == 0)
pinstart(pin, 0, 0, 0)
else
x := (255 << 16) | (1 #> ((CLK_FREQ / 255) / hz) <# \$FFFF)
pinstart(pin, %01_01001_0, x, duty)
```
For the Arduino lurkers (grin), this is the equivalent of analogWrite() with frequency control and the ability to use any pin.
• Posts: 5,014
JonnyMac wrote: »
I'm going to do it with inline PASM2, because my experiments with simple SPI code in Spin2 don't comport with your expectations (I got about 600kHz max running the P2 at 200MHz).

I believe compiling the Spin2 code with fastspin should let you get full speed without requiring inline assembly, and perhaps Chip will be able to optimize his compiler/interpreter combo to get closer to full speed as well -- in fact I'm quite confident he could.

Inline assembly is nice, but really we want users to be able to program in high level languages as much as possible.

I think all of your examples could work with fastspin except that I need to add some new Spin2 operators (fastspin uses Spin1 syntax for operators for now) and the inline assembly syntax is slightly different. Fastspin uses asm / endasm instead of org / end. I hope to fix this once I'm active again, unless someone beats me to it (it really would be a minor change to the Spin yacc file).

For maximum speed you'd probably want to use -O2 optimization, which among other things tells fastspin to use LUT as cache for loops. But the default -O1 may suffice.
• Posts: 5,014
Oh, two other thoughts:

(1) thank you @JonnyMac for starting this thread, it's a great idea!

(2) Spin2 code that doesn't use inline assembly or P2 specific hardware features may be compiled for P1 by fastspin. Multiple return values, for example, make a lot of things easier and I'm glad Chip came up with this feature, and I use it a lot for my P1 code.

• Posts: 7,699
edited 2020-03-10 19:59
Purely as a programming exercise, I converted the pwm() method to inline PASM2.
```pub pwm2(pin, duty, hz) | x

'' DMX-compatible PWM
'' --  pin - output pin (0..55)
'' -- duty - duty cycle of output 0 to 255 (100%)
'' --   hz - pwm output frequncy

org
fltl    pin                             ' disable smart pin
tjnz    hz, #pwm_setup
wrpin   #0, pin                         ' clear sp regs
wxpin   #0, pin
wypin   #0, pin
jmp     #done

pwm_setup               rdlong	x, #\$44                         ' read clkfreq
qdiv    x, #255                         ' divide by 255 (units in 100%)
getqx   x                               ' result in x
qdiv    x, hz                           ' divide by hz
getqx   x                               ' result in x (ticks in 1 unit for hz)
fge     x, #1                           ' 1 #> x
fle     x, ##\$FFFF                      ' x <# \$FFFF
or      x, ##(255 << 16)                ' set frame (units in 100%)
wrpin   #%01_01001_0, pin               ' configure smart pin
wxpin   x, pin
wypin   duty, pin
drvl    pin                             ' enable smart pin
done
end
```
This is meant to be more educational than "better to do it this way." That said, the call to this version is about 25% faster.
• Posts: 7,699
edited 2020-03-11 22:48
Just had lunch with my friend Rick and the P2 consumed a large part of the conversation. Rick asked if the PWM method I wrote could be modified for servos. The answer, of course, is yes.

This version uses a timing constant for ticks per microsecond.
```pub servo_pos(pin, us) | x

'' Create servo-compatible pulse on pin every 20ms
'' -- pin - servo output
'' --  us - position in microseconds (600 to 2400)

us := 600 #> us <# 2400                                       ' keep legal
x := (20_000 << 16) | US_001                                  ' setup timing
pinstart(pin, %01_01001_0, x, us)                             ' servo output
```
I also did a version using inline PASM2 that reads clkfreq from the system.
```pub servo_pos(pin, us) | x

'' Create servo-compatible pulse on pin every 20ms
'' -- pin - servo output
'' --  us - position in microseconds (600 to 2400)

org
fge     us, ##600                       ' keep us legal
fle     us, ##2400
rdlong  x, #\$44                         ' get clkfreq
qdiv    x, ##1_000_000                  ' tix/us
getqx   x                               ' result in x
or      x, ##(20_000 << 16)             ' setup frame (20ms)
fltl    pin                             ' disable smart pin
wrpin   #%01_01001_0, pin               ' configure smart pin
wxpin   x, pin                          ' setup timing
wypin   us, pin                         ' set position
drvl    pin                             ' enable smart pin
end
```
Question: Is is possible to read back the smart pin configuration? I would like the PASM version to check the pin for previous configuration and skip ahead to the wypin instruction if that is the case.
• Posts: 13,610
It is not possible to read back the smart pin configuration.