Shop OBEX P1 Docs P2 Docs Learn Events
Need speed but ASM is too much to tackle. — Parallax Forums

Need speed but ASM is too much to tackle.

T ChapT Chap Posts: 4,223
edited 2008-10-06 12:15 in Propeller 1
In cases where Spin is not fast enough, is there any alternative to assembly? I see people discussing C and similar languages but have no idea what is going on with those languages either. Is there a compromise to get more speed with a simpler learning curve than asm?

I have looked at asm enough to consider it too much a time investment to solve what I need done. A consultant for a fee seems the only option at the moment to handle one section of code for a project that requires up to 50k instructions per second. Right now the Spin equivalent is not even at 10% the speed needed. What I am considering now is scaling the speed down by 10% just to get the working idea done, then seeing if someone can convert it for a reasonable fee.

Comments

  • Graham StablerGraham Stabler Posts: 2,510
    edited 2008-10-04 20:06
    Can you explain some more about what you need to do?

    Sometimes you can play tricks with counters etc to do something quicker but in general if you want more speed asm is the way to go. Using multiple cogs together is another option too but it doesn't sound like that will be an option.

    I learnt pasm while programming up a project and that was over two weeks, if you know what you need to do (which you do because you have the spin) it can be fairly straight forward to get something working. Especially with a little help [noparse];)[/noparse]

    Cheers,

    Graham
  • hippyhippy Posts: 1,981
    edited 2008-10-04 20:09
    With 20MIPS per cog at 80MHz Spin delivers some 200K bytecode instructions per second, so it depends what you mean by instructions.

    There's no middle ground between Spin and PASM, the best you have is an alternative language such as ImageCraft C which runs at near PASM speed but hides the complexity of PASM from you. There's also JDforth which can improve upon Spin performance but again you'll have a learning curve there. I don't know of any other languages which target the Propeller.

    You maybe able to find someone to translate what you have into PASM. If it's a simple piece of code someone may do that for free or cheaply. More complicated and it may well cost serious money. It would all depend on what it is.
  • T ChapT Chap Posts: 4,223
    edited 2008-10-04 20:30
    The Imagecraft sounds interesting, if it is less intense a curve that could be useful.

    To explain a little more detail. The code is related to driving a motor with pulses, reading an encoder and comparing the two. The max speed of the encoder and pulses is around 48k per sec each. The rotary obj already is making the position available in a variable, but instructions are required to create new pulses (a counter is not an option, no pins to sacrifice for ctra). The pulses are virtual, and do nothing but count and create a speed profile by which an asm PWM object models the value of. Then the compare and correct instructions are required, with a number of if pos > new position type instructions that keep track of things. The fastest I can run the Spin variation is around 6k pulses per sec.

    For now, just to keep making progress, I will scale the whole thing down to 10% resolution (divide the encoder by 10), and translate it as it goes. Actually the parts are quite simple, there are just quite a few phases to the motion, including reverse, ramp up, ramp down, etc, that make it look big. I will post the spin version soon and get opinions on how complex or easy it might be to translate.
  • RinksCustomsRinksCustoms Posts: 531
    edited 2008-10-04 20:33
    hippy said...
    With 20MIPS per cog at 80MHz Spin delivers some 200K bytecode instructions per second, so it depends what you mean by instructions.

    There's no middle ground between Spin and PASM, the best you have is an alternative language such as ImageCraft C which runs at near PASM speed but hides the complexity of PASM from you. There's also JDforth which can improve upon Spin performance but again you'll have a learning curve there. I don't know of any other languages which target the Propeller.

    You maybe able to find someone to translate what you have into PASM. If it's a simple piece of code someone may do that for free or cheaply. More complicated and it may well cost serious money. It would all depend on what it is.
    Just for reference spin is only 0.5MIPS,
    LCC runs at 5.0 MIPS
    & PASM runs at 20MIPS.

    @ Originator, PASM doesn't look that hard, in fact it seems a bit easier than spin (in concept) to me. DeSilva's tutorial on PASM takes you from blinking the LED to more complex tasks, setting Registers (CTR,VCFG,CLK,DIR,IN,OUT, ect..)·in PASM isn't much more difficult than doing it in spin. You should be familiar with the "IF" structure, most ASM tasks can be performed with this type of· conditional structure. Shifting, REVersing bit patterns, add, sub, shift, rotate, multiply, divide, can all be done in PASM, maybe easier than SPIN. Study and give yourself alittle time to learn it. If your doing something with shifting out opperations, look at this thread. The VSU hardware can do more than video.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    E3 = Thought

    http://folding.stanford.edu/·- Donating some CPU/GPU downtime just might lead to a cure for cancer! My team stats.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2008-10-05 02:04
    So this is some sort of servo/speed control I take it.

    your description is a little confusing but it seems simple enough for an early asm attempt.

    Graham
  • pemspems Posts: 70
    edited 2008-10-05 02:30
    Before propeller, i also was reluctant to write code in anything lower level than C. I bit the bullet however with PASM and i am so glad i did.
    I found PASM much easier and friendlier than any other ASM i've seen

    I say you muster up the courage and go through those examples. I started with FullDuplexSerial object and it was a very good base.
    Funny thing is, i have not touched spin at all! all PASM baby yeah.gif
  • evanhevanh Posts: 15,658
    edited 2008-10-05 02:41
    Since hippy says 200k inst/sec and Rinks says 500k inst/sec, it sounds like Spin can keep up with the 50k inst/sec that you are wanting already.

    What is the desired servo loop time? 1000 loops per second is usually heaps. That should be easy to achieve, even in Spin. It sounds like you need to decouple the parts some more. Specifically, the pulse output routine.

    Actually, do the output pulses represent position stepping or is it more like PWM or the "servo" pulses of hobby controllers? If they are real position steps then a servo loop is not needed. The drive is performing that function for you already. All you need to do is output the pulses at a pre-calculated rate and length. Using the precise feedback only for end of move corrections or error checking, if needed.
  • evanhevanh Posts: 15,658
    edited 2008-10-05 02:45
    Agreed with pems, Cog Asm looks to be (I've not written a line in either) an easy transition from Spin. The two are very compatible and the assembler and tokeniser are very well integrated.
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-10-05 02:57
    PASM is not that hard because it is a RISC machine. This means fewer instructions and no registers, so your code is much much simpler than other ASM machines. You should at least give it a quick look. Don't look at some of the complex objects as they will only confuse you at this time. Just keep it simple with the tutorials - there are plenty. smile.gif
  • T ChapT Chap Posts: 4,223
    edited 2008-10-05 03:46
    OK, this is still a work in progress. It does not do any comparing yet, I inserted the a PID
    object just to experiment with it. This is just a program that generates pulses that reflect
    actual distance on the motor/encoder. It is going to be a closed loop servo of sorts. The
    PWM will chase the pulses, trying at all times to reflect the error between current position and new position.

    So far this is working great but needs a lot of work still, I have not gotten the boards back
    yet from assembly to test the actual motion.

    It is currently sending out a PWM duty (0-1000) based on the pause between pulses (accel).
    That may change if I can get PID to chase the position error in a better way.

    Unless scale the encoder down, the default count is 16000 per revoltion, or 4000 per inch. I a
    testing with it reduces by /10, which may actually suffice at 400 per inch, in which case the
    spin can handle it. However, since the max speed will be 12 inches a second, that would mean
    4800 instructions a second just to compare the encoder against the current position, not to mention
    correction instructions, and all the other code below.

    I definitely intend to get started on learning PASM. Thanks for the tips guys. The code is already at
    around 1800 lines for the entire project, and no where near done.

    Where does this stuff below rate in terms of complexity if I study the tutorials for a bit?


    PUB MotionEngine | mdir, accel, decel, arate, drate, run, MaxSpeed, MinSpeed, RampDown, Correction, 
    newpos, pos, run80, run10, dutymin, dutymax                 
      'This code creates a speed profile using (virtual) Pulses that vary in speed, the time between the 
    pulses(accel value) determines PWM duty range of - 1000. This accel value will be scaled
      'or tuned to the motor to find a matching Speed/PWM value.  The motor uses a 10:1 gear box.  
    The motor has an encoder that reads 1600 counts per rev, 16000 per rev at the gearbox.
      'Currently, I have scaled the encoder counter /10 due to Spin not being fast enough to generate 
    pulses fast enough due to the instruction time demands.
      'At the scaled down amount, 400 encoder counts now = 1 inch at the belt/pulley for real world work.  
    Therefore, IPS = 400 pulses per sec in the code below.   
      'An encoder will be read and compared to the profile pulse counter to check for accuracy.  
    The difference is Error.
      'Error is factored back into the Duty value sent to the PWM object via it's correction value, ie  
    If error > 0,  error = 0 - error.  This offets the PWM by subtracting the error.
      'The error will likely need to be scaled to reflect Inches Per Sec/Pulses Per Sec  to PWM actual 
    adjustment of error. This is unknown as of now.
      'The Correction value has not been established due to the motor having not been calibrated for 
    speed>duty, nor has a PPS (Pulses Per Sec)/Speed
      'ratio been established for corresponsing PWM/Speed values.  It is also not certain the linearity 
    of PPS > Duty > Speed ( IPS ).
      'The motion code is constantly seeking a new destination, even if in motion, as input can and does 
    change sporadically.   If new pos is recieved by any means,
      '(serial input, user push button etc),the motion immediately ramps down by some ramp rate, then 
    accels towards the newpos.
      'The distance is devided into the RUN (ramp up 10% distance), the RUN80(80% of distance at fixed
     speed set by MaxSpeed), and ramp down by 10% at end.   
      'DutyMin is a value that is the minimum duty required to start the motor moving.
      'DutyMax is the maximum speed to allow the motor to turn to not overshoot the profile speed.
    
      'pid.init(-2.1,   -12.02,   -0.01664,   25.0,    0.0,      5.0, -5.0)      '(_Kp, _Ki, _Kd, setPoint, offset, maxArea, minArea)
      pid.init(-2.1,   -12.02,   -0.01664,   25,    81.0,      5.0, -5.0)      '(_Kp, _Ki, _Kd, setPoint, offset, maxArea, minArea)
      pos := 1                                          'TEST POSITION (start)    
      newpos  := 1                                 'TEST POSITION (destination)  simulates a real input value
      dira[noparse][[/noparse]17] := 1                                     'step pin dir
      outa[noparse][[/noparse]17] := 0                                     'step pin output state = 0
      reset := runmode                                  'i2c 8575 pin
      MaxSpeed := 400   ' convert to IPS/PPS          'waitcnt(400 + cnt) is max iteration, plus spin 
    code unknown time,SPEED
      MinSpeed := 500_000                               'accelval cannot exceed minspeed val
      'aRate := 10000                                    'rate to reduce Accel val on ramp up 
      aRate := (MinSPeed - MaxSpeed)/100
      dRate := 100
      repeat  ' <<<<<<<<<<  MAIN REPEAT LOOP 
        accel := MinSpeed                               'reset Staring Accel value 
        if newpos > pos                                 'Check for new position forward direction       
          run10 :=  ((newpos - pos)/10)  '<# 4000       'set 10% of distance to ramp up limit sets how long it can ramp for     
          'Ramp up to MaxSpeed
          repeat  run10                                 '10% range of motion, Accel ramp up MaxSpeed
            Posvar := pos                               'for LCD display and testing only, LCD on it's own cog, looping 
    and showing the Profile counter
            outa[noparse][[/noparse]17] := 1                               'set direction
            accel   := (accel - aRate)  #> MaxSpeed     'accel starts out slow, then gets faster due to aRate reducing Accel
            'accel   :=  (MinSpeed - aRATE)     #> MaxSpeed 
            pwmasm.SetDuty(accel, minSpeed, maxSpeed)   'output parameters to pwmasmDuty1000 object 
    ( 0 -1000 duty range )
            pos := pos  + 1                             'update profile position counter
            error := encodercount - pos
            pidval := pid.calculate(error)
            waitcnt(accel + cnt)
            if newpos < pos
              Quit                                      'jumps to interrupt below if pos has changed to a more reverse 
    position than current
          run80 := (newpos - (run10*2))                   'set run80   set 80% travel distance at MaxSpeed   
    new pos is now 10% into the distance
          'travel 80% of newpos @ MAX SPEED
          repeat run80                                    'run80  'RUN 80% of travel 
            Posvar := pos
            outa[noparse][[/noparse]17] := 1
            pwmasm.SetDuty(accel, minSpeed, maxSpeed)
            pos := pos  + 1                           'update Pos
            error := encodercount - pos 
            pidval := pid.calculate(error)          
            waitcnt(accel + cnt)                     
           'INTERRUPT and Decel if new pos in reverse received
            If newpos < pos  
                decel := accel         
                repeat  run10   'Decel 10% of newpos
                Posvar := pos                                     
                outa[noparse][[/noparse]17] := 1
                Decel   := (Decel + dRate)  #> MaxSpeed    
                pwmasm.SetDuty(Decel, minSpeed, maxSpeed)
                pos := pos  + 1
                error := encodercount - pos 
                pidval := pid.calculate(error)                      
                waitcnt(decel + cnt)            
            'Ramp down from MaxSpeed to MinSpeed
          repeat  until pos == newpos                'Decel 10% of X 
             If newpos < pos
               Quit
             Posvar := pos
             outa[noparse][[/noparse]17] := 1
             Decel   := (Decel + dRate)  #> MaxSpeed      
             pwmasm.SetDuty(Decel, minSpeed, maxSpeed)
             error := encodercount - pos 
             pidval := pid.calculate(error)                  
             pos := pos  + 1
             waitcnt(decel + cnt)
    
                                'update profile position counter
        error := encodercount - pos
        pidval := pid.calculate(error)        
    '-----------------|||||Reverse|||||---------------------
        'newpos := 1
        if newpos < pos                                 'Check for new position forward direction    
          run10 :=  ((pos - newpos)/10)  '<# 4000       'set 10% of distance to ramp up limit sets how long it can ramp for     
          'Ramp up to MaxSpeed
          repeat  run10                                 '10% range of motion, Accel ramp up MaxSpeed
            Posvar := pos                               'for LCD display and testing only, LCD on it's own cog, looping 
    and showing the Profile counter
            outa[noparse][[/noparse]17] := 0                               'set direction
            accel   := (accel - aRate)  #> MaxSpeed     'accel starts out slow, then gets faster due to aRate 
    reducing Accel
            'accel   :=  (MinSpeed - aRATE)     #> MaxSpeed 
            pwmasm.SetDuty(accel, minSpeed, maxSpeed)   'output parameters to pwmasmDuty1000 object 
    ( 0 -1000 duty range )
            pos := pos - 1                             'update profile position counter
            error := encodercount - pos 
            pidval := pid.calculate(error)        
            waitcnt(accel + cnt)
            if newpos > pos
              Quit                                      'jumps to interrupt below if pos has changed to a more reverse 
    position than current
          run80 := (pos - (run10 * 2))                   'set run80   set 80% travel distance at MaxSpeed   new 
    pos is now 10% into the distance
          'travel 80% of newpos @ MAX SPEED
          repeat run80                                    'run80  'RUN 80% of travel
            Posvar := pos
            outa[noparse][[/noparse]17] := 1
            pwmasm.SetDuty(accel, minSpeed, maxSpeed)
            pos := pos - 1                           'update Pos
            error := encodercount - pos 
            pidval := pid.calculate(error)        
            waitcnt(accel + cnt)                     
           'INTERRUPT and Decel if new pos in reverse received
            If newpos > pos
                'ledon
                decel := accel         
                repeat  run10   'Decel 10% of newpos
                Posvar := pos                                     
                outa[noparse][[/noparse]17] := 0
                Decel   := (Decel + dRate)  #> MaxSpeed    
                pwmasm.SetDuty(Decel, minSpeed, maxSpeed)
                pos := pos - 1
                error := encodercount - pos 
                pidval := pid.calculate(error)                       
                waitcnt(decel + cnt)            
            'Ramp down from MaxSpeed to MinSpeed
          Decel := accel  
          repeat  until  pos == newpos             'Decel 10% of X
             ledon
             If newpos > pos
               Quit      
             Posvar := pos
             outa[noparse][[/noparse]17] := 0
             Decel := (Decel + dRate)  <# MinSpeed      
             pwmasm.SetDuty(Decel, minSpeed, maxSpeed)         
             pos := pos - 1
             error := encodercount - pos 
             pidval := pid.calculate(error)         
             waitcnt(decel + cnt)
             ledoff
    
    
    

    Post Edited (Originator) : 10/6/2008 12:53:43 AM GMT
  • T ChapT Chap Posts: 4,223
    edited 2008-10-05 04:27
    BTW, just messing around with the PID object for the first time as shown above. It seems like a better idea to let PID manage the PWM than simply driving the PWM from the pulses and subtracting the encoder from the current position for correction.
  • evanhevanh Posts: 15,658
    edited 2008-10-05 05:17
    Yep. Profile generation should be compared with the feedback position.

    What I would do is shift the PID to it's own Cog for the short term. And late incorporate it with the Duty program. The PID servo loop can run, tracking a target value, on it's own at a higher loop rate than what the profile generator needs to.

    Next would be to modularise the profile building routines into tidy, well documented functions. This can be a pain keeping a tidy structure but you will be rewarded later on.
  • CarlosFandangoCarlosFandango Posts: 67
    edited 2008-10-05 06:22
    Well, I've only been working with the prop a few weeks and I have to get a product using it market-ready in about another week or two. I didn't know spin or asm when I started but got my head around both (well, I NEARLY have - can't claim to be an expert obviously!) with a bit of effort. The increase in speed and the flexibility of multiple cogs in an application makes learning these things very worthwhile, and there's plenty of help here on the forum when you get stuck. The users here are very responsive, not to mention very understanding, and keen to share. Looking at the spin code example provided above, it's obvious that this is not completely trivial - if you have the skill to write that, you can write asm. I learnt very quickly using DeSilva's tutorial, which explains things clearly on a very human level.
  • T ChapT Chap Posts: 4,223
    edited 2008-10-06 00:10
    From Desilva's tutorial:

    PUB ex01 
        cognew(@ex01A,  0) 
     
    DAT 
            ORG  0 
    ex01A 
            MOV  DIRA,    #$FF   ' (Cel l  0) Output to I/O 0 to 7 
            MOV  pattern,  #0    ' (Cel l  1) Clear a &#8220;registers&#8221; 
    loop 
            MOV  OUTA,  pattern  ' (Cel l  2) Output the pattern to P0. .P7 
            ADD  pattern,  #1    ' (Cel l  3) Increment the &#8222;register&#8220; 
            JMP #loop           ' (Cel l  4) repeat loop      
     
    pattern LONG $AAAAAAAA      ' (Cel l  5)       
            FIT 496 
    
    




    Hey guys, can someone please explain to me why it appears that that there is no resister or cell that stores the name 'loop', yet the jmp seems to know where to go?

    According to the Celo-Cel5, loop and ex01Aa do not get written into any 'cells'.

    Thanks.
  • evanhevanh Posts: 15,658
    edited 2008-10-06 00:31
    The memory addresses where each instruction is to be located is known at assembly time. The Assembler pokes it's calculated address for where "loop" is into each jump instruction that jumps to it.
  • evanhevanh Posts: 15,658
    edited 2008-10-06 00:34
    Man, the wide format code box sucks!
  • AribaAriba Posts: 2,687
    edited 2008-10-06 12:15
    'loop' is the address of the cell or register which holds the instruction: MOV OUTA, pattern (you can write
    the label loop also on the same line as the instruction)
    There is a symbol table inside the compiler (assembler), which stores the address 'loop', and the compiler
    places this stored value every time 'loop' is used as source or destination in an instruction.

    Hope that helps

    Andy
Sign In or Register to comment.