Shop OBEX P1 Docs P2 Docs Learn Events
Share Your [Working] Spin2 Snippets, Please :) — Parallax Forums

Share Your [Working] Spin2 Snippets, Please :)

JonnyMacJonnyMac Posts: 9,104
edited 2020-02-26 18:17 in Propeller 2
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.
«134

Comments

  • JonnyMacJonnyMac Posts: 9,104
    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.
  • JonnyMacJonnyMac Posts: 9,104
    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
    
      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 := @nbuf
    

    Changes 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
  • Cluso99Cluso99 Posts: 18,069
    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”.
  • Yes, if you think that's a good idea.
  • JonnyMacJonnyMac Posts: 9,104
    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]
    
  • JonnyMacJonnyMac Posts: 9,104
    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
    
      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]
    
  • PublisonPublison Posts: 12,366
    edited 2020-03-01 20:27
    Thanks Jon. These are great posts!
  • cgraceycgracey Posts: 14,155
    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.
  • 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.
  • JonnyMacJonnyMac Posts: 9,104
    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.
  • 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.
  • 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!

  • 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.
  • 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!
  • JonnyMacJonnyMac Posts: 9,104
    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).
  • JonnyMacJonnyMac Posts: 9,104
    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
    
  • cgraceycgracey Posts: 14,155
    That is nice to see, Jon. Makes PASM approachable.

    Can you show the Spin2 code for SPI that was running at only 600KHz?
  • JonnyMacJonnyMac Posts: 9,104
    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)
    
      pinh(cs)                                                      ' load into MAX7219/21
    
    800 x 480 - 20K
  • cgraceycgracey Posts: 14,155
    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.
  • 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.
  • RaymanRayman Posts: 14,646
    I would sure love to see a smartpin SPI example in Spin2.
    QPI too...
  • cgraceycgracey Posts: 14,155
    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.
  • JonnyMacJonnyMac Posts: 9,104
    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.
  • cgraceycgracey Posts: 14,155
    Here is the current interpreter:

  • JonnyMacJonnyMac Posts: 9,104
    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.
  • 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.
  • 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.

  • JonnyMacJonnyMac Posts: 9,104
    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.
  • JonnyMacJonnyMac Posts: 9,104
    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.
    800 x 480 - 17K
  • cgraceycgracey Posts: 14,155
    It is not possible to read back the smart pin configuration.
Sign In or Register to comment.