Shop OBEX P1 Docs P2 Docs Learn Events
Moving From Spin1 to Spin2 — Parallax Forums

Moving From Spin1 to Spin2

JonnyMacJonnyMac Posts: 8,912
edited 2020-11-10 18:22 in Propeller 2
In another thread I saw a person with P1 experience wishing for a guide to moving code to the P2. I'm not suggesting that is is that guide, but I'd like it be a place where we can share what we've learned moving from Spin on the P1 to the Spin on the P2.

Please...
-- keep the tips focused on Spin1 to Spin2 (there are plenty of places to discuss BASIC, C, Forth, etc.)
-- when possible, show the Spin1 version and the equivalent Spin2 version
-- please restrict examples to that which will run in the official Parallax tools, PNut and Propeller Tool (some compilers have extensions that aren't back-compatible with Parallax tools)

I've been porting my P1 objects to the P2 for the past couple months, so I'll share my mental list and the things I've learned here.

Comments

  • Tip: Identify the device type in your listing.

    Now... this hasn't been implemented yet, but it will be. No doubt those of you using Propeller Tool have written P2 code only to have the compiler complain because it defaults to the P1. For the moment the fix is to save the file with a .spin2 extension. In the future, though, Parallax will implement a feature used in the BASIC Stamp editor that allows the same tool to deal with the differences between the Stamp 1 and the Stamp 2, and even extensions in PBASIC 2.

    This is my programming header; note the comment with {$P2} in it. This modelled after the way the BASIC Stamp editor works. Down the line, we should be able to compile and check code before saving.
    '' =================================================================================================
    ''
    ''   File....... untitled.spin2
    ''   Purpose....
    ''   Author..... Jon "JonnyMac" McPhalen
    ''               Copyright (c) 2020 Jon McPhalen
    ''               -- see below for terms of use
    ''   E-mail..... jon.mcphalen@gmail.com
    ''   Started....
    ''   Updated.... dd MMM yyyy
    ''
    ''   {$P2}
    ''
    '' =================================================================================================
    
  • Spin1 to Spin2: Add empty parenthesis to any methods that do not use parameters.

    Spin1
    pub main
                                                                     
      setup                                                          
                                                                     
      wait_for_terminal(true)
                    
      term.str(string("Hello, World!", 13, 13))            
                                                                     
      repeat                                                         
        waitcnt(0)
    

    Spin2
    pub main()
    
      setup()
    
      wait_for_terminal(true)
    
      term.str(string("Hello, World!", 13, 13))
    
      repeat
        waitct(0)
    

    Note that neither my main nor setup methods use parameters, but in Spin2 must have empty parenthesis.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2022-07-06 17:12
    Spin1 to Spin2: New Spin2 keywords handle essential IO.

    Spin1
      outa[LED] := 1                                                ' activate LED
      dira[LED] := 1
    
    The initial configuration of an IO pin requires setting the outa (output register) and dira (direction register) bits.

    Spin2
      pinhigh(LED)
    

    Both languages accommodate multiple contiguous pins.

    Spin1
      outa[MSB..LSB] := %1010
      dira[MSB..LSB] := %1111
    

    Spin2
      pinwrite(LSB addpins (MSB-LSB), %1010)
    

    Warning: In the P2, the group created by addpins cannot span the pin 31 / pin 32 boundary.

    P2 IO keywords:
    -- pinhigh(n), pinh(n)
    -- pinlow(n), pinl(n)
    -- pintoggle(n), pint(n)
    -- pinfloat(n), pinf(n)
    -- pinwrite(n, value), pinw(n, value)
    -- value := pinread(n), value := pinr(n)

    Consult the official P2 documentation for keyword details.
    -- https://docs.google.com/document/d/16qVkmA6Co5fUNKJHF6pBfGfDupuRwDtf-wyieh_fbqw/edit#heading=h.1h0sz9w9bl25
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-11-10 18:24
    Spin1 to Spin2: Using the system counter to control loops requires changes in keywords.

    P1 cnt becomes getct() in the P2.
    P1 waitcnt() becomes waitct() in the P2.

    Spin1
      dira[LED] := 1                                                ' make LED an output
    
      t := cnt                                                      ' sync to system counter
      repeat
        waitcnt(t += clkfreq / 10)                                  ' wait 1/10th second
        !outa[LED]                                                  ' toggle LED
    

    Spin2
      t := getct()
      repeat
        waitct(t += clkfreq / 10)
        pintoggle(LED)
    
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-11-10 18:25
    Spin1 to Spin2: Methods that return values must have those values declared in the P2

    Spin
    pub add_numbers(n1, n2)
    
      return n1+n2
    

    Spin2
    pub add_numbers(n1, n2) : result
    
      return n1+n2
    

    Note 1: The advantage of having to declare return values allows the P2 to return multiple values from the same method.

    Spin2
    pub add_sub(n1, n2) : sum2, dif2
    
      return n1+n2, n1-n2
    

    Note 2: The calling code does not have to accept multiple return values. Use the underscore character in the assignment expression to ignore a specific result.

    Spin2
      theSum, _ := add_sub(3, 5)
    

    Note 3: The P2 can use return without declaring a variable if no value is returned -- you can use an empty return as an early escape mechanism.

    Spin2
    pub blink(pin, count, ms)
    
      if (count < 0)
        return 
    
      repeat count
        pinhigh(pin)
        waitms(ms)
        pinlow(pin)
        waitms(ms)
    
  • wow, very nice

    thank you Jon

    Mike
  • Great start to the topic!
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-11-10 05:02
    Spin1 to Spin2: Some operators have changed, and some of them have changed behavior.
    Spin Operators
    
    a  New/changed operator in P2
    b  Behavioral change in P2
    
    P2              P1              Description
    --------------------------------------------------------------------------------
    ++ (pre)        ++              Pre-increment
    -- (pre)        --              Pre-decrement
    ?? (pre)        ?      ab       XORO32, iterate and return pseudo-random
    
    ++ (post)       ++              Post-increment
    -- (post)       --              Post-decrement
    !! (post)                       Post-logical NOT
    !  (post)                       Post-bitwise NOT
    \  (post)                       Post-set
    ~  (post)       ~               Post-set to 0
    ~~ (post)       ~~              Post-set to -1
    
    !               !               Bitwise NOT, 1's complement
    -               -               Negation, 2's complement
    abs             ||     a        Absolute value
    encod           >|     ab       Encode MSB, 31..0
    decod           |<     a        Decode, 1 << (x & $1F)
    bmask                           Bitmask, (2 << (x & $1F)) - 1
    ones                            Count ones
    sqrt                            Square root of unsigned x
    qlog                            Unsigned to logarithm
    qexp                            Logarithm to unsigned
    
    >>              >>              Shift right, insert 0's
    <<              <<              Shift left, insert 0's
    sar             ~>     a        Shift right, insert MSB's
    ror             ->     a        Rotate right
    rol             <-     a        Rotate left
    rev             ><     a        Reverse y LSBs of x and zero-extend
    zerox                           Zero-extend above bit y
    signx           ~, ~~  ab       Sign-extend from bit y
    
    &               &               Bitwise AND
    ^               ^               Bitwise XOR
    |               |               Bitwise OR
    
    *               *               Signed multiply
    /               /               Signed divide, return quotient
    +/                              Unsigned divide, return quotient
    //              //              Signed divide, return remainder
    +//                             Unsigned divide, return remainder
    sca                             Unsigned scale (x * y) >> 32
    scas            **     a        Signed scale (x * y) >> 30
    frac                            Unsigned fraction {x, 32'b0} / y
    
    +               +               Add
    -               -               Subtract
    
    #>              #>              Ensure x => y, signed
    <#              <#              Ensure x <= y, signed
    
    addbits                         Make bitfield, (x & $1F) | (y & $1F) << 5
    addpins                         Make pinfield, (x & $3F) | (y & $1F) << 6
    
    <               <               Signed less than                (returns 0 or -1)
    +<                              Unsigned less than              (returns 0 or -1)
    <=              =<     a        Signed less than or equal       (returns 0 or -1)
    +<=                             Unsigned less than or equal     (returns 0 or -1)
    ==              ==              Equal                           (returns 0 or -1)
    <>              <>              Not equal                       (returns 0 or -1)
    >=              =>     a        Signed greater than or equal    (returns 0 or -1)
    +>=                             Unsigned greater than or equal  (returns 0 or -1)
    >               >               Signed greater than             (returns 0 or -1)
    +>                              Unsigned greater than           (returns 0 or -1)
    <=>                             Signed comparison          (<,=,> returns -1,0,1)
    
    !!, not         not    a        Logical NOT  (x == 0,            returns 0 or -1)
    &&, and         and    a        Logical AND  (x <> 0 AND y <> 0, returns 0 or -1)
    ^^, xor         xor    a        Logical XOR  (x <> 0 XOR y <> 0, returns 0 or -1)
    ||, or          or     a        Logical OR   (x <> 0 OR  y <> 0, returns 0 or -1)
    
    ? :                             If x <> 0 then choose y, else choose z
    
    :=              :=     a        Set var(s) to x
                                    P2: v1,v2,... := x,y,... ' set v1 to x, v2 to y, etc. '_' = ignore
    
    
    Complex math functions
    ---------------------------------------------------------------------------------------------------
    x, y := rotxy(x,y,t)            Rotate cartesian (x,y) by t and assign resultant (x,y)
    x, t := xypol(x,y)              Convert cartesian (x,y) to polar and assign resultant (r,t)
    x, y := polxy(r,t)              Convert polar (r,t) to cartesian and assign resultant (x,y)
    
  • Spin1 to Spin2: The syntax of starting a new cog has changed

    Spin 1 (start PASM cog)
      cog := cognew(@fdsuart, @rxhead) + 1
    

    Spin 2 (start PASM cog)
      cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1
    
    In the P2, coginit replaces cognew for PASM cogs. The new first parameter selects the cog; using COGEXEC_NEW will select the next available cog as was the case with cognew.

    Note: The P2 is able to run PASM code within a cog or from the hub; see the Spin2 documentation for details of coginit().


    Spin 1 (start Spin cog)
      bcog := cognew(background, @bcstack) + 1
    

    Spin 2 (start Spin cog)
      cog := cogspin(NEWCOG, play_notes(pin, p_tune, bpm, mode), @stack) + 1
    
    In the P2, cogspin replaces cognew for Spin cogs.
  • JonnyMac wrote: »
    Spin1 to Spin2: Using the system counter to control loops requires changes in keywords.

    P1 cnt becomes getct() in the P2.
    P1 waitcnt() becomes waitct() in the P2.

    Spin1
      dira[LED] := 1                                                ' make LED an output
    
      t := cnt                                                      ' sync to system counter
      repeat
        waitcnt(t += clkfreq / 10)                                  ' wait 1/10th second
        !outa[LED]                                                  ' toggle LED
    

    Spin2
      t := getct()
      repeat
        waitct(t += clkfreq / 10)
        pintoggle(LED)
    

    Jon, I started to learn Spin2 and trying out examples from Spin 1 I found out that apparently there is an important difference in the way bits are accessed and referenced in the code.

    In Spin 1:
    dira[LED]
    
    ...
    
    !outa[LED]
    

    But in Spin 2 the corresponding syntax would be
    dira.[LED]   'or dirb.[LED] if LED points to I/O's 32-63
    
    ...
    
    outa.[LED]!  'or outb.[LED]! if LED points to I/O's 32-63
    
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-11-16 03:11
    Jon, I started to learn Spin2 and trying out examples from Spin 1 I found out that apparently there is an important difference in the way bits are accessed...
    You're right, but may have missed my point a bit on this topic. My goal is to be functionally equivalent, not do a line-by-line translation of Spin1 to Spin2.

    In the P1, we only have one output register (outa) and its associated direction register (dira), but in the P2 we have double (outa and outb, dira and dirb), but each is only 32 bits -- while our pint #s are from 0 to 63. In my mind, programs should be clear, so it's best to avoid direct use of these registers for simple IO manipulation.

    Interestingly, this actually works in the P2 -- even those the dirb and outb are 32-bit registers:
    con
     
        LED = 56
    
    pub main()
    
      dirb.[LED] := 1
    
      repeat
        waitms(250)
        outb.[LED]!
    
    It works because the interpreter limits the pin number to five bits, so that corrects the value for dirb/outb. Still, this code is easily broken. If you want to toggle a pin in the range of 0..31, you have to change the pin constant and update the code for dira and outa (as you pointed out in your code comments).

    This is why I suggest the built-in pin methods available in Spin2 for basic IO control. We don't have those in the P1, so we're forced to use the direction and output registers. For simplicity of updates, and for clarity for ourselves and others, this little blinker is best (in my opinion) as
    con
     
        LED = 56
    
      repeat
        waitms(250)
        pintoggle(LED)
    
    It's simple, self explanatory, and works with any IO pin with a single edit of the LED constant.

    When moving from P1 to P2, strive to work alike, even if line-by-line you don't look alike. Spin2 is blessed with more power and features, often simplifying code we wrote for the P1.

    Follow-up: Even PASM2 is simpler in this regard. There are single instructions to drive a pin high, low, or make it float, and will accept a pin number from 0 to 63 -- the microcode for each instruction handles the specifics of which register group to use. This makes programs cleaner and easier to write, read, and debug.

    Follow-up #2: I will sometimes allow less-than-clear code when it runs a lot faster. For grins, I used the Propeller to measure the duration of the two mechanisms discussed here. The results are interesting.
      outb.[LED]!     ' 104 clock ticks
      pintoggle(LED)  '  32 clock ticks
    
    I was surprised, but happy. The pintoggle() instruction is 3x faster, while being easier to read and maintain.
  • 💯 Agree with you Jon,

    I totally understand the new pin methods are way better for readability and makes cleaner code, and frankly I find it (Spin 2) better to learn from scratch than Spin 1.

    I wrote that reply because as a :confused: newbie, I am using as a reference a book I have had for about 6 years and I was a bit confused as to why I couldnt make the simple examples (in Spin1) work in Spin 2 using the dir and out instructions used by the book. Fortunately I came across this thread :smiley: and have cleared so far most of my confusion while reading the Google Docs on Spin2 vs the book I have.

    I am just trying to learn and this forum has proven to be great! Btw, thank you for your breakout board!!! I can't believe I have a signed Jon McPhalen board to learn P2!!!! :smile:

    Perhaps in the near future this thread could be the basis for an updated book on Propeller?
  • I've been coding the Propeller since before it was available to the public (I worked for Parallax back then), so I have a LOT of code to translate. The reason I started this thread was to help those going through what I've been doing for the past six months, and to exchange ideas as I still stumble from time-to-time. Yes, there are frustrating days. I've written a few hundred thousand lines of P1 code (a lot of it deployed in commercial apps) and sometimes moving it to the P2 requires new ways of thinking. One of my clients is a laser tag company. Interestingly, doing IR output on the P2 is a lot easier, but IR input on the P2 takes more work due to losing the counters in the cogs. There may be a smart pin mode I can use for that, but I haven't sussed that out yet.

    If you haven't seen it, I wrote an article for Nuts & Volts about the P2 (note that this was early on and there have been a couple changes).
    -- https://www.nutsvolts.com/magazine/article/an-introduction-to-the-parallax-propeller-2

    I also did an early-adopter seminar which is about the Propeller family, with a bit of focus on the P2:
    --

    Thank Parallax for the breakout board -- they did all the hard work. It was something I wanted, so I "sketched" the idea with a PCB program and put it up in the forums. Luckily, my practical development needs are similar to others and the idea kind of took off. My signature on the bottom is a bit of a goof because I am not a famous actor (though I am a member of SAG/AFTRA). I guess if do get famous it will be a good story to tell -- how many actors have their signature on an electronic development board?

    I'm sure the future holds lots of books about the Propeller family. The first step is to let Parallax get the P2 manual out (the P1 manual is really good, I trust the P2 manual will follow suit).
  • Cluso99Cluso99 Posts: 18,066
    edited 2020-11-16 03:59
    Jon,
    You have to become famous so that these boards become collectors items like guitars, clothes, etc. Collectors items usually surge in value when the person passes away, but I expect I'll be long gone before you!
    Have you seen the value of the Apple 1 boards? Ouch! Jobs is gone but Woz is still here.
  • AribaAriba Posts: 2,682
    edited 2020-11-16 04:01
    JonnyMac wrote: »
    ...
    Interestingly, this actually works in the P2 -- even those the dirb and outb are 32-bit registers:
    con
     
        LED = 56
    
    pub main()
    
      dirb.[LED] := 1
    
      repeat
        waitms(250)
        outb.[LED]!
    
    It works because the interpreter limits the pin number to five bits, so that corrects the value for dirb/outb. Still, this code is easily broken. If you want to toggle a pin in the range of 0..31, you have to change the pin constant and update the code for dira and outa (as you pointed out in your code comments).
    ...

    Actually this toggles the LED on pin 56, but it also toggles pin 57. The bits higher than 5 are used to define additional bits (=pins in this case), so dirb.[56] means the same as dirb.[24 addbits 1].

    Just another reason to use the built in pin functions, like pinw / pinwrite or pint / pintoggle.

    Andy
  • Excellent! Thank you for the links!!!
    I guess if do get famous it will be a good story to tell -- how many actors have their signature on an electronic development board?

    Not many I hope!!! That will make my board even more valuable!!! :wink:
  • Actually this toggles the LED on pin 56, but it also toggles pin 57. The bits higher than 5 are used to define additional bits (=pins in this case), so dirb.[56] means the same as dirb.[24 addbits 1].
    Thank you, Andy -- great catch. I didn't see that when I ran the program because pin 57 was defined as an input in this case. In another case, that could have become a problem.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-11-16 14:52
    Following up on Andy's eagle-eyed post.

    This syntax..
      outb.[pin]!
    
    ... treats the pin parameter as a bit field. A bit field is a 10-bit value (%00000_00000) with lower five bits (0..31) defining the base bit, and the upper five bits defining the number of bits used to extend the group (0..31). In the case of having the LED constant set to 56, that bit field becomes %00001_11000. What this does is create a mask that starts at bit 24 and extends by one to bit 25. Applying this mask to outb affects IO pins 56 and 57, which correspond to bits 24 and 25 of the outb register. As Andy pointed out, it's equivalent to 24 addbits 1.

    This syntax...
      pintoggle(pin)
    
    ... treats the pin parameter as a pin field. A pin field is an 11-bit value (%00000_000000) with the lower six bits (0..63) defining the base pin, and the upper five bits defining the number of pins used to extend the group. In our example, pin 56 fits into six bits which leaves the upper portion of the pin field 0; so we only affect one pin.

    See pages 6 and 7 of the Spin2 documentation for more details and examples.
    -- https://docs.google.com/document/d/16qVkmA6Co5fUNKJHF6pBfGfDupuRwDtf-wyieh_fbqw/
  • @"Ken Gracey"
    @cgracey

    This is where I asked about a tutorial similar to the PE Kit manual. There are so many differences from P1 to P2 that it is difficult to convert.
    I am not asking for a Kit. Just a manual that gives the basic explanations. The spin2 documentation as I have searched them does not address the differences.
    Thanks

    If I had more of the above the transition would be more smooth.
    Again thanks for listening.
    Semper Fi to Parallax.

  • pilot0315pilot0315 Posts: 834
    edited 2023-05-16 16:46

    @JonnyMac
    Is there an update for the Jm_serial? I have the fullduplexserial one but I am getting the fdec error on the serial version.
    Thanks

  • JonnyMacJonnyMac Posts: 8,912
    edited 2023-05-16 18:44

    jm_serial.spin2 has been retired because it is not buffered. Once I got comfortable with PASM2 I upgraded it to jm_fullduplexserial.spin2. The latest version of that is attached. Please, for the love of all that's holy, get rid of jm_serial.spin2 on your system, as well as old versions of jm_fullduplexserial.spin2, and put this code (it has a child object called jm_nstr.spin2 for numeric formatting) in your global library. In my case that is here:

    C:\Users\jmcph\Documents\Propeller Tool\Library

    If you did a custom installation of Propeller Tool, yours may be in a different location. Note that both Flex Prop and Spin Tools allow you to point at this folder as a library location, so you don't actually have to use Propeller Tool unless you want to.

    (Repeating myself) If any of your apps use the old method names: fdec(), fhex(), foct(), fqrt(), or fbin() you need to add "x" after the "f" so they become fxdec(), fxhex(), fxoct(), fxqrt(), and fxbin(). Just so you understand, Chip made an update to the debugging features of Spin2 which stomped on the method names I was using. This stuff sometimes happens, so we have to contend with it.

  • I remember the change Chip made that caused a lot of confusion.

  • Just wanted to keep this thread alive, unfortunately I have nothing to add. Thanks JonnyMac!

    Rob.

  • evanhevanh Posts: 15,126

    Bobby,
    You can make a post linking to here in the pinned links topic - https://forums.parallax.com/discussion/169542/p2-links-for-where-to-obtain-tools-sample-test-code-reference-only
    It hasn't been contributed to in a while but new additions to it are still helpful.

Sign In or Register to comment.