Shop OBEX P1 Docs P2 Docs Learn Events
How to learn Pasm equivalent of Spin? — Parallax Forums

How to learn Pasm equivalent of Spin?

I hit a wall with my current project. I wrote Spin code that does one of three things individually:
Wirelessly control two DC motors differentially, four servos for an arm or two servos for a pan/tilt camera.
I can't do all three in the same object because of the overhead.
This means I'll have to learn to write equivalent code in assembly.
Can anyone recommend a good place to start?

Comments

  • You do not need to resort to PASM, you can start multiple COGS in spin running in parallel.

    But moving to PASM is not a bad idea at all. It is way faster, and PASM is a quite nice language.

    Look in the sticky's for deSilvia or so.

    Enjoy!

    Mike
  • First question is whether you really need to use PASM, whether there are some optimizations you could do to accomplish your tasks in Spin. For a start, how are you using the available cogs? Why are you combining the three tasks in one object? Objects are normally used to create abstractions for functionality ... like a servo driver that can handle up to 32 servos using a single cog ... takes servo positions and issues the control pulses and can do ramping automatically.
  • lardomlardom Posts: 1,659
    edited 2016-04-18 05:01
    Mike Green wrote: »
    First question is whether you really need to use PASM, whether there are some optimizations you could do to accomplish your tasks in Spin. For a start, how are you using the available cogs? Why are you combining the three tasks in one object? Objects are normally used to create abstractions for functionality ... like a servo driver that can handle up to 32 servos using a single cog ... takes servo positions and issues the control pulses and can do ramping automatically.

    In this case I need PASM. I developed the code on a single Propeller which worked fine. The wireless device is a bottleneck. One limitation is 8-bits. The other problem is I have to insert delays or the device tends to hang.
  • lardom wrote: »
    I hit a wall with my current project. I wrote Spin code that does one of three things individually:
    Wirelessly control two DC motors differentially, four servos for an arm or two servos for a pan/tilt camera.
    I can't do all three in the same object because of the overhead.
    This means I'll have to learn to write equivalent code in assembly.
    Can anyone recommend a good place to start?

    spincvt is a GUI that will let you convert Spin code to PASM. The underlying program (spin2cpp) was originally designed to convert Spin to C++, but it's since grown and can now convert to PASM as well. In fact you can use it to compile your program to a binary that will run much faster than one produced by openspin (but is also much larger, because it contains PASM instructions instead of bytecode).
  • You could try posting your code and see if anyone here can offer suggestions as to how it might be reworked to make it faster or perform within your restrictions.

    You said you need to insert delays - if that's true, PASM can't wait any faster than Spin. :) Would it be possible to change the code so it's doing something else during that wait time?
  • ErNaErNa Posts: 1,752
    The key to a good solution is a good understanding of the problem
  • lardom wrote: »
    I hit a wall with my current project. I wrote Spin code that does one of three things individually:
    Wirelessly control two DC motors differentially, four servos for an arm or two servos for a pan/tilt camera.
    I can't do all three in the same object because of the overhead.
    This means I'll have to learn to write equivalent code in assembly.
    Can anyone recommend a good place to start?

    Easy solution: Rewrite in PropBASIC.

  • JasonDorie wrote: »
    ...You said you need to insert delays - if that's true, PASM can't wait any faster than Spin. :)Would it be possible to change the code so it's doing something else during that wait time?
    Ha! The light just went on in my head. I have to insert a delay between tx packets. The problem didn't show up when I tested my code on a solitary Propeller because it runs in parallel. The nRF24L01 is a SPI tranceiver.
    I have a servo method that gets refreshed every 25ms. In my code it takes 6.7ms for the tx radio to transmit the data packet to the rx radio and I could reduce that to 5.4ms if necessary. I have the time.
    I was causing the irq flag to get set before the repeat loop had time to finish. Life is good again. Thanks.
    ...I still need to understand Pasm so I will follow up on the recommendations.
  • In this case I need PASM. I developed the code on a single Propeller which worked fine. The wireless device is a bottleneck. One limitation is 8-bits. The other problem is I have to insert delays or the device tends to hang.

    Inserting delays = wasting time. You might re-code your cog as a state-machine and use a timer variable (referenced to cnt) to run when it's time to run.

    Of course, if you posted your code, you'd get more and very specific help. Just sayin'. :)
  • lardomlardom Posts: 1,659
    edited 2016-04-18 18:28
    @Mike Green, I apologize. I don't need Pasm in this case. I had an apparent case of 'foot in mouth'.
    @JasonDorie, I tested sending data packets at a rate that matches the refresh rate of the rx servo method. The servos rotate smoothly and I've gained close to 18ms per wireless tx. Thanks for your input.
    @JonnyMac, I have special folders on my pc for your work. What's a "state machine" and how can I make use of it?
  • lardom wrote: »
    What's a "state machine" and how can I make use of it?

    Good question. I have what I understand to be a state machine in most of my coding but I don't call it that just in case I'm wrong :lol:
  • ErNaErNa Posts: 1,752
    edited 2016-04-18 19:00
    You cut your problem into chunks that way, that when such a chunk is executed, nothing happens. That is what normally is called "wait". So your program knows different states where nothing happens.
    First, enumerate all those chunks and call them state_X.
    Now being in a state, don't just wait, but wait for something to happen. Create a list of all possible events end enumerate the events.
    Being in a state and experiencing an event, you leave that state and switch over to another state.
    So write down: being in State X, experiencing event Y, switch over to State Z.
    Thats boring, just wait and switch, this calls for action. So when leaving a state as an event happens, do something. Start to enumerate all actions that make sense when leaving state X following an Event Y before entering State Z. Let this action be A, C, D, ..

    So you just have to program a loop, have a switch state statement and if nothing happens, do nothing. If something happens, execute action, switch to the dedicated state and continue looping.

    This is a quite stupid, mechanical process, just the right stuff for a machine, so this is called state machine.
  • If you really want to get into it, here's the Wikipedia entry on Finite State Machine:
    https://en.wikipedia.org/wiki/Finite-state_machine

    And here's a simple example:
    https://en.wikipedia.org/wiki/Event-driven_finite-state_machine

    And finally, look up the CASE statement in the Propeller Manual, which would substitute for the C equivalent, switch(state), in the second link.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2016-04-19 13:26
    Here's a strategy that I use when I want one cog to do a lot of disparate things. In this example, there are three processes you need to handle -- I'm only adding details to the servo process.
    var
    
      long  servostate
      long  servopos[4]
      long  servotimer
    
    
    dat
    
      ServoPins     long    0, 1, 2, 3   
    
    
    pri process_cog                                                 ' launch with cognew
    
      servotimer := cnt - MS5                                       ' MS5 is ticks in 5ms
      longfill(@servopos, 1500, 4)                                  ' start in center
    
      repeat
        process_servo
        do_something_else1
        do_something_else2
    
    
    pri process_servo
    
      if ((cnt - servotimer ) < MS5)                                ' time for a servo pulse?
        return
    
      servotimer += MS5                                             ' advance timer to next slot
    
      ctra := (%00100 << 26) | ServoPins[servostate]
      frqa := 1
      phsa := -servopos[servostate] * US_001                        ' convert us to ticks
      dira[ServoPins[servostate]] := 1
    
      if (++servostate == 4)
        servostate := 0
    

    In your case with servos it works out nicely to divide the 20ms servo refresh rate into four, 5ms chunks; a constant called MS5 is created which is the number of system ticks in 5ms. A timer variable is referenced to the cnt register which uses the MS5 constant. I find it simplest to call the process and let it decide if it's time to run. When that's the case it does what it has to do. In the example the current servo is updated, the servo state (index) is advanced, and the next process is called. No waitcnt to slow anything down.

    Here's an example from my PAB template that runs a 1ms background loop. The ADC on that board requires a lot of clocks -- too many to do a full reading in 1ms. Since I'm not looking for high-speed analog input do a pulse every time through the loop. Not appropriate for everything, but this works for my simple apps.
    var
    
      long  analog[4]   
    
      long  astate
      long  ach
      long  actrl
      long  awork
    
    
    pri adc_process                                                 ' call from background()  
    
      case astate
        0 :
          outa[ADC_CS] := 0                                         ' activate adc  
          actrl := (ach << 27) | (ach << 11)                        ' setup control value (dual reads)
    
        1..32 :
          outa[ADC_CLK] := 0                                        ' lower clock pin
          outa[ADC_DI] := (actrl <-= 1)                             ' output control bit
          awork := (awork << 1) | ina[ADC_DO]                       ' input work bit (reading) 
          outa[ADC_CLK] := 1                                        ' raise the clock line
    
        33 :
          outa[ADC_CS] := 1
          analog[ach] := awork & $0FFF
          ach := ++ach & %11   
    
      if (++astate == 34)
        astate := 0
    


    Give this a try, by using differential timing versus delays, you may be able to pack everything into the time you have.
Sign In or Register to comment.