Shop OBEX P1 Docs P2 Docs Learn Events
Porting Spin Program from Propeller 1 to Propeller 2 — Parallax Forums

Porting Spin Program from Propeller 1 to Propeller 2

I have a 1000 line Spin/PASM program running on a Propeller 1. Will the same program load and run unaltered on a Propeller 2?

«1

Comments

  • AribaAriba Posts: 2,682

    No

  • I've been porting a bunch of code from Propeller 1 to Propeller 2. It's been harder than I thought it would be but it keeps getting better as I learn more and as useful objects are created for the Propeller 2.

    I was a bit surprised last week when I worked on a Propeller 1 project and I found myself missing some of the new Propeller 2 features.

    Make sure to ask questions if you get stuck. People on the forum have been very helpful with my efforts.

  • Duane DegnDuane Degn Posts: 10,588
    edited 2021-02-21 04:56

    There's probably a better P2 list somewhere but here are a few links I keep handy.
    Make sure to set any Google Doc to "View" so you don't accidently make unwanted edits. (It only took me two unwanted edits to learn this lesson.)
    Main P2 Document
    Evaluation Board B
    Spin2
    Evaluation Board C Product Page
    Github Example Programs
    Parallax Propeller 2 (PASM) Instructions

    I also find JonnyMac's cheat sheet of P1 and P2 operator differences very useful.
    I don't understand why all the changes from Spin1 to Spin2 were made but I trust Chip had good reasons to make the changes he did.

    One thing which surprised me recently was to learn longs now longer need start on a multiple of 4 in RAM. I initially thought I had an error in my code when I printed out RAM addresses for various variables and some long addresses were odd numbers.

  • If your code is mostly Spin1 (with little or no PASM) then FlexSpin may be able to compile it for the P2 -- FlexSpin can handle both Spin1 files (with a .spin extension) and Spin2 files (with a .spin2 extension). However, it doesn't do any translation of the PASM parts of the Spin files, so that you'll have to do by hand. There's a limited subset of PASM that works on both P1 and P2, but for the most part the assembly languages are quite different.

  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-02-22 23:23

    I was porting a P1 object to the P2 yesterday. Things to remember:

    • Methods without parameters need empty parenthesis
    • Any method that returns a value must declare it (name after colon following method parameters)
    • Some operators have changes -- check my cheat sheet (referenced above)
    • As Eric pointed out, PASM1 will require conversion to PASM2
    • IMPORTANT: Save your file with the .spin2 extension
  • Thank you for all the input. The main program is all SPIN, but I run three objects in different cogs that are from OBEX and written in PASM1. I know nothing about assembly programming, and every time I think I will try to learn, I give up in a couple days. Do you know of any good starter manuals for PASM? Now that the old OBEX site has moved to GitHub, I can't find anything. Where can I find objects written for the P2? Are there any conversion programs for PASM1 to PASM2? I have a P2 arriving tomorrow, but unless I can find more resources, I doubt I will be able to move up to it.

  • What objects are you running that use PASM1? Maybe there are suitable P2 replacements.

  • jcfjrjcfjr Posts: 74
    edited 2021-03-01 22:49

    I use the following objects that all have PASM in them.

    fMath : "F32_1_6" 'Fast floating point math cog
    PST : "Parallax Serial Terminal" 'used for comments to debug cog
    LcdSerial : "FullDuplexSerialPlus" 'Communicate with display cog
    MCP3202 : "MCP3202" 'ADC for measuring fwd/ref voltage cog
    Encoder : "MultiQuadEncoder" 'Reading encoders input cog

    I cannot even find these on line now.

  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-03-01 23:03

    Can you post a test program and those objects so that we can assist?
    Notes:
    -- I think OzPropDev ported a P1 FP library to the P2
    -- My version of FDS has features that you could use for debugging, or you could use the P2 DEBUG statement (if running Windows)
    -- the LCD may be able to use my FDS as well; it has richer formatting features
    -- The P2 has analog input that will work for 3.3 and lower
    -- the P2 has a smart pin mode for quadrature encoders -- I have an object.

  • jcfjrjcfjr Posts: 74
    edited 2021-03-02 19:53

    I just spent probably six months porting my program/design from a Basic stamp to the P1. I essentially ended up rewriting all my code by the time it was running. The time was worth it, I learned a lot about Spin, and my antenna tuner runs 6 times faster. It appears that I may be getting myself into another total rewrite. My main program is all Spin1, so I am hoping that converting it is not that big of a challenge. But PASM has always scared me and I have never really learned it. So if I need to rewrite these PASM objects, I need to learn PASM1, as well as PASM2. Or find Objects that already have been ported to P2. I am just evaluating if I want to undertake this project, and if I am even capable.

    -- Where can I find OzPropDev FP library?
    --Yes, I could use either to replace PST
    --Communications with display(4D Systems Touchscreen) is simply sending/receiving commands through serial port
    WriteObjValue($0F,$03,Frequency2) 'Write frequency to display

    PUB WriteObjValue(ObjectId,ObjectIndex,Value) 'Sets the value of a displayed GUI object
    SendByte(CmdWriteValue) 'Byte 1 is command byte 'CmdReadValue'
    SendByte(ObjectID) 'Byte 2 is ObjectID
    SendByte(ObjectIndex) 'Byte 3 is Object Index
    Sendbyte(Value>>8) 'Byte 4 is value upper byte
    SendByte(Value & $FF) 'Byte 5 is value lower byte
    SendChksum 'Byte 6 is calculated checksum, of previous bytes

    waitcnt(50000 + cnt)

    return RtnBlnAck 'Look for ACK NAK response, and return as boolean..
    'False=NAK, True = ACK

    --Unfortunately, all my hardware is designed around 5v, so will still need to use the 3202

    I don't have a test program for the MCP3202 ADC, but it is probably pretty generic. I use the P1 to repeat a loop that reads two channels, and then averages 50 readings per channel. Quicker than using 3202 to calculate an average!

    --I have attached two of the objects that have PASM in them, as well as the demo objects .

    I am using the multquadencoder Object to monitor three quad encoders. This Object basically monitors encoder which simply presents a number. I use the number to determine if it has changed +/-, and if so, turn a stepper motor in correct direction.

    If you think I can get around converting the PASM by using some new features offered in the P2, I would appreciate your input.

    Do you know of any good tutorials on learning PASM programming?

    Will my old Prop Plug I used on the P1 work with the P2. Mine is not latest Rev E.

  • Have a look at this simple demo for the encoder and motor. In this case I am only doing one, just so you can clearly see how things work. The P2 encoder object uses a smart pin mode, hence no cog is required. You can have as many of these as you like (objects can be arrayed, though I don't do that often). You'll also see that the pulsout() method is much cleaner in the P2 than the P1.

    Suggestion: Be mindful of your formatting; messy formatting can lead to confusion and hide bugs. I'll have look at your 3202 code, though I have my own P1 object that I can translate for you.

    Where can I find OzPropDev FP library?

    I am working with Chip Gracey on his version which uses inline assembly (big speed, no cog). You'll have to search for Brian's port -- I don't know where it is, just that it exists.

  • Cluso99Cluso99 Posts: 18,066

    P1 v P2 pasm differences really depends on what hardware you’re directly referencing.

    • If you’re using the counters then there’s a big difference as the counters are in the smart pins in P2.
    • If you’re directly reading and writing pins using DIR, IN and OUT then provided you stick to P0-P31 the code is the same, but beware the timing is way different. There are clocking delays for the signal to go out on the pin and a delay when reading because when you read, you’re actually reading the pin as it was a few clocks earlier. Whether this matters depends on what you’re doing.
    • There’s serial objects that replace fullduplexserial but not sure about pst as I’ve not used it. @JonnyMac has done an easy to use replacement and I have done a multiport version.
    • Most instructions in the P1 will just compile with these big exceptions
    • CALL and JMPRET and to a lesser extent RET and JMP are very different. Every one needs to be examined!!! There is no exact equivalent in P2. You will need to understand this difference when converting pasm.
    • Self modifying code needs to be examined. Mostly you can get away with just inserting a NOP after the MOVS/MOVD (now called SETS/SETD IIRC).
    • Self modifying tricks that change the instruction opcodes or disable it using condition bits in the instructions are a problem. There are not many pasm programs that do this tho.
    • There are a few other tricks like accessing the shadow registers that aren’t used much.

    As always, plenty of help on these forums.

  • @jcfjr said:
    -- Where can I find OzPropDev FP library?

    I found it thanks to the clues left by JonnyMac.
    Here's the link.

    I need the object for my project so I thought I'd save you from performing the same search I just completed.

  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-03-16 23:48

    Unfortunately, all my hardware is designed around 5v, so will still need to use the 3202

    Okay, I translated my MCP3202 code to the P2 using inline PASM2 instead of a cog. After you have it setup you can use the startx() method and built-in mux constants to read the channels. Since you're running it at 5v, do put a 4.7K series resistor in the MISO line.

    Warning: I couldn't find an MCP3202 to test with, so this code is not tested.

    Updated 16 MAR 2021 -- small clean-up.

  • jcfjrjcfjr Posts: 74
    edited 2021-03-03 20:19

    @JonnyMac said:
    Okay, I translated my MCP3202 code to the P2 using inline PASM2 instead of a cog.

    Thank you JonnyMac. I just got my P2 today, but discovered I needed the new Prop Rev E to work with the P2. I have ordered, and when I get it, I will try your code with a 3202.

    Is Spin2 as fast as PASM1? Is there an advantage to using PASM2? Is there a speed advantage to inline PASM2 vs running the object on a separate cog?

    I also looked at OzPropDev FP library, and noticed that it did not have the code to calculate powers, something I need in my program.
    I understand that the P2 can run Basic mixed in with the Spin. I remember reading a thread that stated it would be easier to use Basic than a floating point object, but I need to find that thread again. Maybe I can use Basic for my floating point.

  • @Cluso99 said:

    • CALL and JMPRET and to a lesser extent RET and JMP are very different. Every one needs to be examined!!! There is no exact equivalent in P2. You will need to understand this difference when converting pasm.

    Thanks for the info, a lot to absorb!

    Where can I find documentation that I can understand the differences? I have the P1 documentation for PASM1, but only can find the list of PASM2 that doesn't really go into enough info for me to understand.

  • @"Duane Degn" said:

    I found it thanks to the clues left by JonnyMac.

    Thank you Duane!

  • Duane DegnDuane Degn Posts: 10,588
    edited 2021-03-03 20:58

    @jcfjr said:
    I also looked at OzPropDev FP library, and noticed that it did not have the code to calculate powers, something I need in my program.

    I just noticed this myself. I just need integer powers of ten to convert scientific notation to a float so I whipped this up.

    PUB Pow(floatBase, integerExponent) : floatResult
    {Returns floating point number.}
    
      floatResult := 1
    
      repeat
        floatResult := FMath.FMul(floatResult, floatBase)
    
    

    Of course this doesn't help if you need fractional exponents. If this isn't enough make sure you let us know. Someone (maybe even myself) might take a crack at writing a proper power method.

    By the way, I haven't tested the above code yet. It seems too simple to fail but I thought I'd warn you.

  • @"Duane Degn" said:
    Of course this doesn't help if you need fractional exponents. If this isn't enough make sure you let us know. Someone (maybe even myself) might take a crack at writing a proper power method.

    Duane thanks, but unfortunately the power is usually a fraction(x) between .3 and .5, so something like fmath.Pow(10.0,x).

  • @jcfjr said:
    Duane thanks, but unfortunately the power is usually a fraction(x) between .3 and .5, so something like fmath.Pow(10.0,x).

    I spent a few hours yesterday trying to convert F32 to PASM2. I can't get it to fit in the new smaller cog. I'm not sure I'm ready to dive into the execution from hub but I might try converting the PASM sections to inline PASM using Spin2.

    If anyone else if working on a floating point object, I hope they let us know. I'd hate to spend time making an inferior object to one already in the works.

  • AribaAriba Posts: 2,682

    Here is the float code from Catalina:
    https://forums.parallax.com/discussion/comment/1497474/#Comment_1497474

    But I think a float object should not use its own cog on P2, we have inline assembly and cogexec for such blocking PASM code. Unfortunatly many just port objects from P1 one to one without thinking about the new possibilities. On P1 it was necessary to start a new cog for PASM routines, that's not the case for P2.

    Andy

  • @Ariba said:
    But I think a float object should not use its own cog on P2, we have inline assembly and cogexec for such blocking PASM code. Unfortunatly many just port objects from P1 one to one without thinking about the new possibilities.

    I'm giving this a try. I hope you and others make suggestions of how it could be done better. Thanks for the link to the Catalina code.

  • Duane DegnDuane Degn Posts: 10,588
    edited 2021-03-04 21:49

    @Ariba said:
    Here is the float code from Catalina:

    I tried writing a method to convert an integer to a floating point number using my attempt to translate code from F32. I failed.

    I tried again using the Catalina code as a starting point and IT WORKED!

    PUB FFloat(integerInput) : floatResult | manA, flagA, t9, expA, fnumA, Minus23, Mask23
    'From Catalina_Float32_C_Plugin
    
    longfill(@floatResult, 0, 6) 'Shouldn't hurt to initialize to zero.
    Minus23 := -23
    Mask23 := $007F_FFFF
    fnumA := integerInput
    
    '------------------------------------------------------------------------------
    ' _FFloat  fnumA = float(fnumA)
    ' changes: fnumA, flagA, expA, manA
    '------------------------------------------------------------------------------
                            org
    ._FFloat                mov     flagA, fnumA          ' get integer value
                            mov     fnumA, #0              ' set initial result to Zero
                            abs     manA, flagA wz        ' get absolute value of integer
              if_z          ret                             ' if Zero, exit
                            shr     flagA, #31             ' set sign flag
                            mov     expA, #31              ' set initial value for exponent
    .normalize2             shl     manA, #1 wc            ' normalize the mantissa
              if_nc         sub     expA, #1               ' adjust exponent
              if_nc         jmp     #.normalize2
                            rcr     manA, #1               ' justify mantissa
                            shr     manA, #2
                            call    #._Pack                 ' pack and exit
                            jmp      #floatDone
    
    
    ._Pack                  cmp     manA, #0 wz            ' check for Zero
              if_z          mov     expA, #0
              if_z          jmp     #.exit6
    
    .normalize1             shl     manA, #1 wc            ' normalize the mantissa
              if_nc         sub     expA, #1               ' adjust exponent
              if_nc         jmp     #.normalize1
    
                            add     expA, #2               ' adjust exponent
                            add     manA, #$100 wc         ' round up by 1/2 lsb
              if_c          add     expA, #1
    
                            add     expA, #127             ' add bias to exponent
                            fges    expA, Minus23
                            fles    expA, #255
    
                            cmps    expA, #1 wc            ' check for subnormals
              if_nc         jmp     #.exit6
    
    .subnormal              or      manA, #1               ' adjust mantissa
                            ror     manA, #1
    
                            neg     expA, expA
                            shr     manA, expA
                            mov     expA, #0               ' biased exponent = 0
    
    .exit6                  mov     fnumA, manA           ' bits 22:0 mantissa
                            shr     fnumA, #9
                            mov     t9, expA
                            shl     t9, #23
                            and     fnumA, Mask23
                            or      fnumA, t9             ' bits 23:30 exponent
                            shl     flagA, #31
              _ret_         or      fnumA, flagA          ' bit 31 sign
    floatDone
                            end
    
      floatResult := fnumA
    
    

    Thanks again for the link Andy.

    @jcfjr I think there's a good chance we'll have a full featured floating point object in the near future.

    Edit: Right after posting this reply, I saw this post from @ersmith. I haven't looked at the code yet but it's good to know there's someone else working on this. I've very excited about the 64-bit feature.

  • Duane,

    I got my P2, but it was damaged, so still waiting. I noticed that @ersmith code does have Powers function. The code is only the PASM, w/o a Spin2 shell. At first glance, not sure how to call these functions from main program.

    How could I keep track of progress of any floating point objects for the P2? It seems that everything mentioned in this thread are not on GitHub, or at least I cant find them.

  • @jcfjr said:
    I got my P2, but it was damaged, so still waiting. I noticed that @ersmith code does have Powers function. The code is only the PASM, w/o a Spin2 shell. At first glance, not sure how to call these functions from main program.

    That's not my code, my code doesn't have Pow() yet but does have Spin2 wrappers for everything.

    How could I keep track of progress of any floating point objects for the P2? It seems that everything mentioned in this thread are not on GitHub, or at least I cant find them.

    You could follow the thread Floating point routines 64 and 32 bit for p2. The first post has 3 different floating point objects. They're still works in progress, but I'll update the thread from time to time, and when they're complete I'll post them on GitHub.

    If you need floating point in the meantime, the C compilers for the P2 (FlexProp, Catalina, riscvp2 at least) have pretty complete floating point support. So does the BASIC compiler built in to FlexProp. In fact BASIC has a built in power operator, so the program:

    print 2^1.5
    

    prints 2.8284, i.e. the square root of 8.

  • jcfjrjcfjr Posts: 74
    edited 2021-03-16 21:32

    @JonnyMac said:

    Okay, I translated my MCP3202 code to the P2 using inline PASM2 instead of a cog. After you have it setup you can use the startx() method and built-in mux constants to read the channels. Since you're running it at 5v, do put a 4.7K series resistor in the MISO line.

    Jon,
    Finally got my P2 Edge card and am ready to test it with a 3202 and the code you supplied earlier in this thread.
    I am struggling with understanding where the pin assignments are coming from

    How have you associated

    SF_CS = 61 { O } '3202 pin numbers
    SF_SCK = 60 { O }
    SF_SDO = 59 { O }
    SF_SDI = 58 { I }

    into the start object

    pinh(cs) ' initialize pins
    pinl(sck)
    pinl(mosi)
    pinclear(miso)

    I see the longmove that moves cspin (start input parameter) into cs, I think.
    But how does pinl(sck) know that sck is pin 60?

  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-03-16 23:49

    These pins:

    con { fixed io pins }
    
      PGM_RX   = 63  { I }                                          ' programming / debug
      PGM_TX   = 62  { O }
    
      SF_CS    = 61  { O }                                          ' serial flash
      SF_SCK   = 60  { O }
      SF_SDO   = 59  { O }
      SF_SDI   = 58  { I }
    

    ... are used by the Propeller for programming/debugging and the serial flash. Your MCP3202 can share three of the four pins used by the flash; you'll need a separate chip select. In your app, then, you might do this:

    con { app io pins }
    
      ADC_CS   = 57      { O }                                      ' to MCP3202./CS
      ADC_SCK  = SF_SCK  { O }                                      ' to MCP3202.CLK
      ADC_SDO  = SD_SDO  { O }                                      ' to MCP3202.DIN
      ADC_SDI  = SD_SDI  { I }                                      ' from MCP3202.DOUT (via 4.7K resistor)
    

    Note that my pin naming convention is subsystem __ p2function; that is to say the the pin names in my code reflect what's happening on the Propeller side.

    To instantiate the object you would do:

      adc.start(ADC_CS, ADC_SCK, ADC_SDO, ADC_SDI)
    

    I see the longmove that moves cspin (start input parameter) into cs, I think.
    But how does pinl(sck) know that sck is pin 60?

    The longmove (which should be 4 longs, not 5) copies the pin numbers passed through the start() method to the object global variables. From there, we can use them at any place in the object.

    I did a small clean-up of the file; see original post with code for update of the object.

  • jcfjrjcfjr Posts: 74
    edited 2021-03-17 01:06

    Jon,

    @JonnyMac said:

    To instantiate the object you would do:

      adc.start(ADC_CS, ADC_SCK, ADC_SDO, ADC_SDI)
    

    I am familiar with starting an object in another cog in the P1

    OBJ
    MCP3202 : "MCP3202"

    MCP3202.start(dataio, clk, cs, %0011)

    But I am not familiar with inline PASM, so do I still have to define your code as the Object i.e. ADC, and write shell object that starts readings

    OBJ
    ADC : " jm_at24c32"

    Pub Main
    adc.start(ADC_CS, ADC_SCK, ADC_SDO, ADC_SDI)
    a = adc.read(0,0)
    b = adc.read(1,0)

    I also don't understand how the input parameters in start( cspin, sckpin, misopin, etc. get stripped of the pin and become pinh(cs) and pinl(sck) and pinclear(miso)

    Regards,
    Jim

  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-03-17 01:14

    But I am not familiar with inline PASM, so do I still have to define your code as the Object i.e. ADC, and write shell object that starts readings

    Gotcha -- I misunderstood your question.

    The start() method gets the pins for the device from the parent and stores them as object global variables. It also sets up the pins. The problem is that we cannot use object global variables directly in the inline pasm code. Have a look at the shiftout() method.

    pri shiftout(value, bits) | clk, do, di, tix
    
      longmove(@clk, @sck, 4)                                       ' copy io to locals
    
      org
                    ror       value, bits                           ' adjust msb to value.31
    
                    rep       #8, bits
                     shl      value, #1                     wc      ' get bit31
                     drvc     do                                    ' output to mosi
                     waitx    tix                                   ' let it settle
                     drvh     clk                                   ' clock the bit
                     waitx    tix
                     waitx    tix
                     drvl     clk
                     waitx    tix
      end
    

    Notice the local variables clk, do, di, tix. The longmove() in this method copies four of the object globals to these locals. The parameters (value, bits) and locals (clk, do, di, tix) and code are copied into the low addresses of the interpreter cog ram and executed. When finished, the parameters and local variables are updated -- though we don't need that here.

    We do with shiftin(), however, as it has a return value. With this method, the result variable is passed to the cog and the modified vision comes back which gets returned to the caller.

  • Jon,

    Thanks for the explanation, I think I understand how the pins get assigned. But I still don't understand how to run your code on a P2 within the one cog. Do I make your code an Object like for P1 i.e.

    OBJ
    ADC : " jm_at24c32"

    Pub Main
    adc.start(ADC_CS, ADC_SCK, ADC_SDO, ADC_SDI)
    a = adc.read(0,0)
    b = adc.read(1,0)

    But this would start your code on another cog I think.

Sign In or Register to comment.