Share Your [Working] Spin2 Snippets, Please :)
JonnyMac
Posts: 9,502
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.
If you would, post your snippet and detail syntax changes from Spin1.

Comments
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-digitChanges 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.
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 case radix ' fix digits 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 mask := (bmask shift) >> 1 ' bit mask for 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 := @nbufChanges from Spin1
-- return value(s) must be declared on method slug-line
-- >| operator becomes encod (P2 version behaves differently than P1)
-- uses new bmask operator to create mask for digit based on radix
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”.
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]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 case radix ' fix digits 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]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.
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 pasmNote 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!
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!
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 endCan you show the Spin2 code for SPI that was running at only 600KHz?
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) pinh(cs) ' load into MAX7219/21When will you add all the new Spin2 commands to your document? For the time being, I have been scanning the compiler listing for them.
QPI too...
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.
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.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.
(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.
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 endThis is meant to be more educational than "better to do it this way." That said, the call to this version is about 25% faster.This version uses a timing constant for ticks per microsecond. 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 endQuestion: 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.