Shop OBEX P1 Docs P2 Docs Learn Events
PASM programming issues — Parallax Forums

PASM programming issues

Atal1101Atal1101 Posts: 11
edited 2015-05-12 06:08 in Propeller 1
Hey all!

Just recently started working with PASM for a recent project on the Propeller involving taking an input pulse wave at 50Hz on P0 and outputting a pulse of the same duty cycle at ~16kHz on P1. This output pulse will go into a H-Bridge circuit and essentially forms a 0-10VDC DAC with 5000 increments of resolution to drive a CNC mill spindle.

I've included my complete code for the project below. If anyone can tell a noob what simple mistakes have been made or at least point me in the right direction, it would be greatly appreciated.

'Dyna PWM Output
'Outputs a 25kHz wave based on the input frequency PWM
'Computes based on 50Hz input frequency
'Runs in 2 cores
CON
_CLKMODE=PLL16x+XTAL1
_XINFREQ=5_000_000
inpin=0
outpin=1
TestLight=16
VAR
long speed
PUB StartUp
dira[inpin]~
dira[outpin]~~
dira[TestLight]~~
coginit(2,@pulsein, @speed) 'Runs counter in POS mode to measure input duty cycle
coginit(3,@pulseout, @speed)'Runs counter in PWM modified NCO mode to output high frequency PWM wave
repeat
waitcnt(5*clkfreq+cnt)
DAT
org

pulsein movs ctra, #inpin 'Set input pin for counter
movi ctra, %01000 'Set counter mode
mov frqa, $1 'Set counter to increment each clock input is high
mov phsa, $0 'Confirm that counter output is set to zero

:inloop waitpeq %1, $1 'Wait for the pulse to come
waitpeq %0, $1 'and go
mov inwidth, phsa 'Take the number of clocks in the pulse
wrlong inwidth, par 'and write the speed (equivelant duty cycle, translated 0-500 RPM) to the main variable "speed"
mov phsa, $0000_0000 'Zero phsa for next loop
jmp :inloop 'Return to beginning of loop

pulseout movs ctra, #outpin 'Set output pin of counter
movi ctra, %00100 'Set counter mode
mov frqa, #1 'Set counter to increment once each clock
mov outtime, cnt 'Set up current time from system clock
add outtime, outperiod 'and set up the first period

:outloop rdlong outduty, par 'Get up to date pulse width
mov phsawrite, maxphsa 'Take the maximum amount that the phsa register can reach
sub phsawrite, outduty 'and subtract the duty amount from it
waitcnt cnt, outtime 'then wait until the next period is set to begin
mov phsa, phsawrite 'Alter the phsa register so that the correct amount of clocks occur before the pulse drops
jmp :outloop 'Return to beginning of loop

inwidth res 1
outduty res 1
outtime res 1
outperiod long 5000
maxphsa long $FFFF_FFFF
phsawrite res 1

Alex

Comments

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-08 09:30
    attachment.php?attachmentid=78421&d=1297987572

    -Phil
  • PublisonPublison Posts: 12,366
    edited 2015-05-08 09:31
    Welcome to the forums!

    Could you please post your code using these guides:

    attachment.php?attachmentid=78421&d=1297987572


    That will save the indentation that is crucial in SPIN programing.
  • PublisonPublison Posts: 12,366
    edited 2015-05-08 09:32
    Phil is a faster typer.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-08 09:35
    ... but Publison is less terse and more polite.
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-08 09:36
    Sorry about that, I wasn't kidding when I said I'm a noob on the forums and PASM. Here it is.
    'Dyna PWM Output
    'Outputs a 25kHz wave based on the input frequency PWM
    'Computes based on 50Hz input frequency
    'Runs in 2 cores
    CON
    _CLKMODE=PLL16x+XTAL1
    _XINFREQ=5_000_000
    inpin=0
    outpin=1
    TestLight=16
    VAR
    long speed
    PUB StartUp
    dira[inpin]~
    dira[outpin]~~
    dira[TestLight]~~
    coginit(2,@pulsein, @speed) 'Runs counter in POS mode to measure input duty cycle
    coginit(3,@pulseout, @speed)'Runs counter in PWM modified NCO mode to output high frequency PWM wave
    repeat
      waitcnt(5*clkfreq+cnt)
    DAT
                 org
    
    pulsein      movs       ctra, #inpin         'Set input pin for counter
                 movi       ctra, %01000         'Set counter mode
                 mov        frqa, $1             'Set counter to increment each clock input is high
                 mov        phsa, $0             'Confirm that counter output is set to zero
    
    :inloop      waitpeq    %1, $1               'Wait for the pulse to come
                 waitpeq    %0, $1               'and go
                 mov        inwidth, phsa        'Take the number of clocks in the pulse
                 wrlong     inwidth, par         'and write the speed (equivelant duty cycle, translated 0-500 RPM) to the main variable "speed"
                 mov        phsa, $0000_0000     'Zero phsa for next loop
                 jmp        :inloop              'Return to beginning of loop
    
    pulseout     movs       ctra, #outpin        'Set output pin of counter
                 movi       ctra, %00100         'Set counter mode
                 mov        frqa, #1             'Set counter to increment once each clock
                 mov        outtime, cnt         'Set up current time from system clock
                 add        outtime, outperiod   'and set up the first period
    
    :outloop     rdlong     outduty, par         'Get up to date pulse width
                 mov        phsawrite, maxphsa   'Take the maximum amount that the phsa register can reach
                 sub        phsawrite, outduty   'and subtract the duty amount from it
                 waitcnt    cnt, outtime         'then wait until the next period is set to begin
                 mov        phsa, phsawrite      'Alter the phsa register so that the correct amount of clocks occur before the pulse drops
                 jmp        :outloop             'Return to beginning of loop             
    
    inwidth      res        1
    outduty      res        1
    outtime      res        1
    outperiod    long       5000
    maxphsa      long       $FFFF_FFFF
    phsawrite    res        1
    
  • potatoheadpotatohead Posts: 10,261
    edited 2015-05-08 09:53
    Your comments are awesome BTW.
  • PublisonPublison Posts: 12,366
    edited 2015-05-08 10:09
    ... but Publison is less terse and more polite.

    LOL
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-08 10:58
    Alex,

    Back to the issue at hand. You seem to be using $ for immediate operands. That's the prefix for hexadecimal numbers. The correct symbol is #, e.g. mov x,#1 stores the value 1 in x.

    -Phil
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-08 11:17
    Alex,

    Back to the issue at hand. You seem to be using $ for immediate operands. That's the prefix for hexadecimal numbers. The correct symbol is #, e.g. mov x,#1 stores the value 1 in x.

    -Phil

    Phil,
    Thanks for the catch there. I've updated all of the spots I think you're talking about and have enclosed the complete code here below once again.
    'Dyna PWM Output
    'Outputs a 25kHz wave based on the input frequency PWM
    'Computes based on 50Hz input frequency
    'Runs in 2 cores
    CON
    _CLKMODE=PLL16x+XTAL1
    _XINFREQ=5_000_000
    inpin=0
    outpin=1
    TestLight=16
    VAR
    long speed
    PUB StartUp
    dira[inpin]~
    dira[outpin]~~
    dira[TestLight]~~
    coginit(2,@pulsein, @speed) 'Runs counter in POS mode to measure input duty cycle
    coginit(3,@pulseout, @speed)'Runs counter in PWM modified NCO mode to output high frequency PWM wave
    repeat
      waitcnt(5*clkfreq+cnt)
    DAT
                 org
    
    pulsein      movs       ctra, #inpin         'Set input pin for counter
                 movi       ctra, %01000         'Set counter mode
                 mov        frqa, #1             'Set counter to increment each clock input is high
                 mov        phsa, #0             'Confirm that counter output is set to zero
    
    :inloop      waitpeq    %1, %1               'Wait for the pulse to come
                 waitpeq    %0, %1               'and go
                 mov        inwidth, phsa        'Take the number of clocks in the pulse
                 wrlong     inwidth, par         'and write the speed (equivelant duty cycle, translated 0-500 RPM) to the main variable "speed"
                 mov        phsa, $0000_0000     'Zero phsa for next loop
                 jmp        :inloop              'Return to beginning of loop
    
    pulseout     movs       ctra, #outpin        'Set output pin of counter
                 movi       ctra, %00100         'Set counter mode
                 mov        frqa, #1             'Set counter to increment once each clock
                 mov        outtime, cnt         'Set up current time from system clock
                 add        outtime, outperiod   'and set up the first period
    
    :outloop     rdlong     outduty, par         'Get up to date pulse width
                 mov        phsawrite, maxphsa   'Take the maximum amount that the phsa register can reach
                 sub        phsawrite, outduty   'and subtract the duty amount from it
                 waitcnt    cnt, outtime         'then wait until the next period is set to begin
                 mov        phsa, phsawrite      'Alter the phsa register so that the correct amount of clocks occur before the pulse drops
                 jmp        :outloop             'Return to beginning of loop             
    
    inwidth      res        1
    outduty      res        1
    outtime      res        1
    outperiod    long       5000
    maxphsa      long       $FFFF_FFFF
    phsawrite    res        1
    

    However, the main PASM programs still aren't loading properly and reading/writing the pulse wave as they are supposed to be. Any thoughts on what could be preventing the PASM programs from loading?
  • kuronekokuroneko Posts: 3,623
    edited 2015-05-08 11:17
    Also, for most jumps you want direct mode, e.g. jmp #:outloop and all res should came after all data declarations.

    Binary numbers (usually) need immediate mode as well, e.g. movi ctra, #%01000.

    Then in pulseout you say movi ctra, #%00100, do you mean movi ctra, #%00100_000. Which in turn brings us to missing output setups. dira is local to each cog so has to be done in PASM.
  • AribaAriba Posts: 2,690
    edited 2015-05-08 13:26
    Also: If you start 2 cogs with PASM code then make a separate section for each cog. Begin each section with an ORG and have the register variables that are used in the code in the same section.
    Use cognew instead of coginit to start a cog, if you not have a very good reason to choose the cognumber.
               org  0
    pulsein    <code>
               ...
    inwidth    res  1
               ...
    
               org  0
    pulseout   <code>
               ...
    outperiod  long  5000
               ...
    outtime    res  1  
               ...
    

    And finally: I think you need not 2 cogs to do what you want. If the output frequency is a multiple of the 50 Hz input frequency, then just make 2 nested loops. The inner loop updates the PWM pulses and the outer loop measures the pulswith of the 50 Hz signal, every time the inner loop has looped 320 times (for example @16kHz).

    Andy
  • Cluso99Cluso99 Posts: 18,069
    edited 2015-05-08 13:56
    Welcome to the forums.

    It is normal to indent below each CON, VAR, PUB and PRI sections. It makes code easier to follow.

    The DIR settings in spin are not used as you don't output with this cog. Setting the DIR to input is the default.

    IIRC you need to also set the DIR to output in the cog that outputs from the counter. Maybe someone else can confirm this.

    Also in the startup cog, you perform a repeat with waitcnt but nothing else is being done. It would be simpler to shut this cog down with
    cogstop(cogid)

    The WAITPEQ instructions need to refer to a register setup to hold the data required and the mask. Only the mask may be an immediate value (prefix #) but this uses 9bit mask with the remaining upper bits zeroed.

    You cannot mix RES and LONG. All RES must come at the end your pasm code. So each set of RES and LONGs must be at the end of each pasm block to which it is used. What happens is that the compiler reserves these RES blocks and increments the program counter. The RES blocks do not get included in the output object saving space. see the Propeller Manual for a better explanation.

    I am no counter expert, so cannot help your here.
  • ElectrodudeElectrodude Posts: 1,658
    edited 2015-05-08 14:58
    Cluso99 wrote: »
    IIRC you need to also set the DIR to output in the cog that outputs from the counter. Maybe someone else can confirm this.

    Each cog that wants to use a pin as an output needs to set its own DIRA for that pin to be an output.

    The logic works like this: (only showing four cogs, but you get the point)
    outa_effective := (outa0 & dira0) | (outa1 & dira1) | (outa2 & dira2) | (outa3 & dira3)
    dira_effective := dira0 | dira1 | dira2 | dira3
    
  • Cluso99Cluso99 Posts: 18,069
    edited 2015-05-08 19:24
    Seems I should have written it better...
    If you output directly to a pin, that cog must set the DIRA pin as an output.
    If you are outputting from VGA or Counter, IIRC you must also set the DIRA pin as an output. (should have checked but busy compiling P1V)
    Each output pin is wire ORed together with its respective pin from the other cogs. There is no contention.
  • CRST1CRST1 Posts: 103
    edited 2015-05-09 03:44
    You are using @speed for the stack. Speed is defined as a single long. Should'nt it be Long Speed[30]. 30 is just for example. Also I'm not sure about this but is it ok to use the same stack for two cogs?
  • Heater.Heater. Posts: 21,230
    edited 2015-05-09 04:32
    There is no stack when starting PASM in COGS. The Speed he is using is a parameter passed into the COG via PAR.
  • CRST1CRST1 Posts: 103
    edited 2015-05-09 04:44
    Yup, too early in the morning!!
  • Heater.Heater. Posts: 21,230
    edited 2015-05-09 05:02
    CRST1,

    He, he, I just woke up too. It's late afternoon here :)
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-11 10:03
    Got the latest code here with nearly all of the updates suggested above. Not planning on combining into one cog in case crystals change in the future and the output frequency is no longer a nice multiple of the input frequency.
    Code is still not starting properly. Neither cog is loading, as evidenced by a quick test code I added to send out the speed it has recorded every 5 seconds via PST. It never moves from its initialized value, which tells me that it isn't reading the input pulse. In addition, the machine spindle never starts moving, even at the slow dummy speed I initialize the variable with.
    Input uses a 220 ohm resistor on a 5V input line. Output goes directly to a H-Bridge IC.
    Here is the newly updated code:
    'Dyna PWM Output
    'Outputs a 25kHz wave based on the input frequency PWM
    'Computes based on 50Hz input frequency
    'Runs in 2 cores
    CON
    
    _CLKMODE=PLL16x+XTAL1
    _XINFREQ=5_000_000
    inpin=0
    outpin=1
    OBJ
    
    PST: "Parallax Serial Terminal"
    VAR
    
    long speed
    PUB StartUp
    PST.Start(9600)
    speed:= 100
    cognew(@pulsein, @speed) 'Runs counter in POS mode to measure input duty cycle
    cognew(@pulseout, @speed)'Runs counter in PWM modified NCO mode to output high frequency PWM wave
    Repeat
      waitcnt(5*clkfreq+cnt)
      PST.Dec(speed)
      PST.Char(13)
    DAT
    
                 org
    pulsein      movs       ctra, #inpin         'Set input pin for counter
                 movi       ctra, %01000         'Set counter mode
                 mov        frqa, #1             'Set counter to increment each clock input is high
                 mov        phsa, #0             'Confirm that counter output is set to zero
    
    :inloop      waitpeq    %1, %1               'Wait for the pulse to come
                 waitpeq    %0, %1               'and go
                 mov        inwidth, phsa/320    'Take the number of clocks in the pulse
                 wrlong     inwidth, par         'and write the speed (equivalent duty cycle, translated 0-5000 RPM) to the main variable "speed"
                 mov        phsa, $0000_0000     'Zero phsa for next loop
                 jmp        #:inloop             'Return to beginning of loop
    
    
    inwidth      res        1
    DAT
    
                 org
    pulseout     movs       dira, %000000010
                 movs       ctra, #outpin        'Set output pin of counter
                 movi       ctra, %00100         'Set counter mode
                 mov        frqa, #1             'Set counter to increment once each clock
                 mov        outtime, cnt         'Set up current time from system clock
                 add        outtime, outperiod   'and set up the first period
    
    :outloop     rdlong     outduty, par         'Get up to date pulse width
                 mov        phsawrite, maxphsa   'Take the maximum amount that the phsa register can reach
                 sub        phsawrite, outduty   'and subtract the duty amount from it
                 waitcnt    cnt, outtime         'then wait until the next period is set to begin
                 mov        phsa, phsawrite      'Alter the phsa register so that the correct amount of clocks occur before the pulse drops
                 jmp        #:outloop             'Return to beginning of loop
    
    outperiod    long       5000
    maxphsa      long       $FFFF_FFFF
    outtime      res        1
    outduty      res        1
    phsawrite    res        1
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-11 10:35
    This instruction

    mov inwidth, phsa/320

    loads inwidth with the contents of the register whose address is @phsa/320.

    And this instruction

    mov phsa, $0000_0000 'Zero phsa for next loop

    loads phsa with the contents of location 0.

    -Phil
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-11 10:44
    This instruction

    mov inwidth, phsa/320

    loads inwidth with the contents of the register whose address is @phsa/320.

    And this instruction

    mov phsa, $0000_0000 'Zero phsa for next loop

    loads phsa with the contents of location 0.

    -Phil

    I can see how I must have gotten the first mov instruction wrong. I meant to take the value of phsa, divide it by 320, and then eventually output via par]/b] to the shared speed variable. Can I just do the division of the value of @speed in the wrlong instruction, or is there some other way I should do this division.
    As well, for zeroing the phsa register, should I just use the 'literal' zero (#0)?
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-11 10:53
    To divide the value of phsa by 320, you have to read it into a register, then uses a division subroutine to do the division.

    Yes to the #0.

    Also, I notice that your waitpeqs refer to locations 0 and 1, not the values 0 and 1. You can define a register to hold the value of the input mask, then refer to it, viz:
             waitpeq     inmask,inmask
             waitpne     inmask,inmask
             ...
    inmask   long        1
    

    -Phil
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-11 12:22
    To divide the value of phsa by 320, you have to read it into a register, then uses a division subroutine to do the division.

    -Phil

    Is this just a single PASM command, or can you give me a basic rundown on the subroutine and it's makeup?
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-05-11 12:37
    The Propeller does not do division in hardware. Here's a link to a division subroutine:

    -Phil
  • ChrisGaddChrisGadd Posts: 310
    edited 2015-05-11 12:38
    Getting there:
    Atal1101 wrote: »
    Is this just a single PASM command, or can you give me a basic rundown on the subroutine and it's makeup?
    Sadly, no. The mul and div instructions never made it into the Prop 1.
    ...
    
    DAT
    
                 org
    pulsein      movs       ctra, #inpin         '
                 movi       ctra, %01000         ' The PLLDIV field must be included in the counter mode, 
                 mov        frqa, #1             '  and the octothorpe '#' must be used for immediate values
                 mov        phsa, #0             '
    
    :inloop      waitpeq    %1, %1               ' again, the octothorpe must be used for immediate values,  
                 waitpeq    %0, %1               '  but cannot be used in the destination field
                 mov        inwidth, phsa/320    ' phsa/320 gets evaluated at compile time
                 wrlong     inwidth, par         '
                 mov        phsa, $0000_0000     ' missing octothorpe
                 jmp        #:inloop             '
    
    
    inwidth      res        1
    DAT
    
                 org
    pulseout     movs       dira, %000000010     ' missing octothorpe, using movs instead of mov
                 movs       ctra, #outpin        '
                 movi       ctra, %00100         ' missing PLLDIV field
                 mov        frqa, #1             '
                 mov        outtime, cnt         '
                 add        outtime, outperiod   '
    
    :outloop     rdlong     outduty, par         '
                 mov        phsawrite, maxphsa   '
                 sub        phsawrite, outduty   '
                 waitcnt    cnt, outtime         ' cnt in the destination field is a shadow register 
                 mov        phsa, phsawrite      '
                 jmp        #:outloop             
    
    outperiod    long       5000
    maxphsa      long       $FFFF_FFFF
    outtime      res        1
    outduty      res        1
    phsawrite    res        1
    
    Try this:
    CON
      _clkmode = xtal1 + pll16x                                                   
      _xinfreq = 5_000_000
      INPIN    = 0
      OUTPIN   = 1
    
    OBJ
      pst : "Parallax Serial Terminal"
    
    VAR
      long  speed
      
    PUB StartUp
      pst.start(9600)
      pst.clear
      speed := 100
      cognew(@pulseIn,@speed)
      cognew(@pulseOut,@speed)
      repeat
        waitcnt(cnt + clkfreq)
        pst.dec(speed)
        pst.newLine
    
    DAT                     org
    pulseIn
                            movs      ctra,#INPIN
                            movi      ctra,#%01000_000                          
                            mov       frqa,#1
                            mov       phsa,#0
    :inLoop
                            waitpeq   inMask,inMask                             
                            waitpne   inMask,inMask                             
                            mov       inWidth,phsa
                            mov       quotient,#0                               
    :divideLoop                                                       ' This is an extremely basic divide by subtracting routine     
                            sub       inWidth,#320         wc                
              if_nc         add       quotient,#1
              if_nc         jmp       #:divideLoop
                            wrlong    quotient,par
                            mov       phsa,#0
                            jmp       #:inLoop
                                                                           
    inMask                  long      |< INPIN
    inWidth                 res       1
    quotient                res       1
    
    DAT                     org
    pulseOut
                            mov       dira,outMask                                  
                            movs      ctra,#OUTPIN
                            movi      ctra,#%00100_000
                            mov       frqa,#1
                            mov       outTime,cnt
                            add       outTime,outPeriod
    :outLoop
                            rdlong    outDuty,par
                            mov       phsaWrite,maxPhsa
                            sub       phsaWrite,outDuty
                            waitcnt   outTime,outPeriod
                            mov       phsa,phsaWrite
                            jmp       #:outLoop
                                      
    outMask                 long      |< OUTPIN
    outPeriod               long      5000
    maxPhsa                 long      $FFFF_FFFF
    outTime                 res       1
    outDuty                 res       1
    phsaWrite               res       1
    
    I tested this with a function generator and o'scope, and it looks like it's functioning.

    Chris
  • Atal1101Atal1101 Posts: 11
    edited 2015-05-11 13:47
    ChrisGadd wrote: »
    I tested this with a function generator and o'scope, and it looks like it's functioning.

    Chris

    Chris,
    I set it up on the motor control I'm running, and while it runs great and smooth at both full speed and stopped, everything in between has a rough pulsed drive. Did you happen to check this with PWM when you tested it? Otherwise, I have to assume that it is some issue with the main motor drive I have it connected to and PWM.
    Alex
  • ChrisGaddChrisGadd Posts: 310
    edited 2015-05-12 06:08
    There is a cutoff at ~96%, likely due to the short neg pulse width during which the pulseIn routine has to perform the division and writing, the next pos pulse has already started by the time the Prop is ready. Adding a second waitpeq/waitpne immediately prior to resetting phsa cleared it up, and the output now tracks perfectly from 0.1% up to 99.9% - reading 5 to 4993 on the serial terminal. 5 / 5000 = 0.1%, 4993 / 5000 = 99.86%, so that all checks out.

    The output frequency is ~16KHz, which you mentioned in the original post, but the comments in your code mention 25KHz output.

    Chris
Sign In or Register to comment.