Shop OBEX P1 Docs P2 Docs Learn Events
Question on Rotary obj — Parallax Forums

Question on Rotary obj

T ChapT Chap Posts: 4,223
edited 2013-12-18 05:45 in Propeller 1
In special cases I need to reverse the input pins on the rotary encoder object to get it to count in reverse. Currently I just flip A and B at the encoder, but this is sometimes not easy to access. I want to create an option to do this so that the object can be launched with the pins in either configuration ie:

PUB Start(StartPin, PinB, NumEnc, NumDelta, PosAddr)

Looking at the object trying to see where to make changes, something doesn't make sense. What is inb doing here?

:PinSrc         mov     St2, inb                        '  Sample encoders         (St2 = B2:A2 left shifted by first encoder offset)

VAR
  byte          Cog             'Cog (ID+1) that is running Update
  byte          TotDelta        'Number of encoders needing deta value support.
  long          Pos             'Address of position buffer


PUB Start(StartPin, NumEnc, NumDelta, PosAddr): Pass
''Record configuration, clear all encoder positions and launch a continuous encoder-reading cog.
''PARAMETERS: StartPin = (0..63) 1st pin of encoder 1.  2nd pin of encoder 1 is StartPin+1.
''                       Additional pins for other encoders are contiguous starting with StartPin+2 but MUST NOT cross port boundry (31).
''            NumEnc   = Number of encoders (1..16) to monitor.
''            NumDelta = Number of encoders (0..16) needing delta value support (can be less than NumEnc).
''            PosAddr  = Address of a buffer of longs where each encoder's position (and deta position, if any) is to be stored.
''RETURNS:    True if successful, False otherwise.

  Pin := StartPin                                                                                     
  TotEnc := NumEnc                                                                                    
  TotDelta := NumDelta
  Pos := PosAddr
  Stop
  longfill(Pos, 0, TotEnc+TotDelta)
  Pass := (Cog := cognew(@Update, Pos) + 1) > 0


PUB Stop
''Stop the encoder-reading cog, if there is one.

  if Cog > 0
    cogstop(Cog-1)


PUB ReadDelta(EncID): DeltaPos
''Read delta position (relative position value since last time read) of EncID.

  DeltaPos := 0 + -(EncID < TotDelta) * -long[Pos][TotEnc+EncID] + (long[Pos][TotEnc+EncID] := long[Pos][EncID])
  
    
 
'************************************
'* Encoder Reading Assembly Routine *
'************************************

DAT
'Read all encoders and update encoder positions in main memory.
'See "Theory of Operation," below, for operational explanation.
'Cycle Calculation Equation:
'  Terms:     SU = :Sample to :Update.  UTI = :UpdatePos through :IPos.  MMW = Main Memory Write.
'             AMMN = After MMW to :Next.  NU = :Next to :UpdatePos.  SH = Resync to Hub.  NS = :Next to :Sample.
'  Equation:  SU + UTI + MMW + (AMMN + NU + UTI + SH + MMW) * (TotEnc-1) + AMMN + NS
'             = 92 + 16  +  8  + ( 16  + 4  + 16  + 6  +  8 ) * (TotEnc-1) +  16  + 12
'             = 144 + 50*(TotEnc-1)

                        org     0
                                                                                                
Update                  test    Pin, #$20               wc      'Test for upper or lower port
                        muxc    :PinSrc, #%1                    'Adjust :PinSrc instruction for proper port
                        mov     IPosAddr, #IntPos               'Clear all internal encoder position values
                        movd    :IClear, IPosAddr               '  set starting internal pointer
                        mov     Idx, TotEnc                     '  for all encoders...  
        :IClear         mov     0, #0                           '  clear internal memory
                        add     IPosAddr, #1                    '  increment pointer
                        movd    :IClear, IPosAddr               
                        djnz    Idx, #:IClear                   '  loop for each encoder
                                                                
                        mov     St2, ina                        'Take first sample of encoder pins
                        shr     St2, Pin                
:Sample                 mov     IPosAddr, #IntPos               'Reset encoder position buffer addresses
                        movd    :IPos+0, IPosAddr                               
                        movd    :IPos+1, IPosAddr
                        mov     MPosAddr, PAR                           
                        mov     St1, St2                        'Calc 2-bit signed offsets (St1 = B1:A1)
                        mov     T1,  St2                        '                           T1  = B1:A1 
                        shl     T1, #1                          '                           T1  = A1:x 
        :PinSrc         mov     St2, inb                        '  Sample encoders         (St2 = B2:A2 left shifted by first encoder offset)
                        shr     St2, Pin                        '  Adj for first encoder   (St2 = B2:A2)
                        xor     St1, St2                        '          St1  =              B1^B2:A1^A2
                        xor     T1, St2                         '          T1   =              A1^B2:x
                        and     T1, BMask                       '          T1   =              A1^B2:0
                        or      T1, AMask                       '          T1   =              A1^B2:1
                        mov     T2, St1                         '          T2   =              B1^B2:A1^A2
                        and     T2, AMask                       '          T2   =                  0:A1^A2
                        and     St1, BMask                      '          St1  =              B1^B2:0
                        shr     St1, #1                         '          St1  =                  0:B1^B2
                        xor     T2, St1                         '          T2   =                  0:A1^A2^B1^B2
                        mov     St1, T2                         '          St1  =                  0:A1^B2^B1^A2
                        shl     St1, #1                         '          St1  =        A1^B2^B1^A2:0
                        or      St1, T2                         '          St1  =        A1^B2^B1^A2:A1^B2^B1^A2
                        and     St1, T1                         '          St1  =  A1^B2^B1^A2&A1^B2:A1^B2^B1^A2
                        mov     Idx, TotEnc                     'For all encoders...
:UpdatePos              ror     St1, #2                         'Rotate current bit pair into 31:30
                        mov     Diff, St1                       'Convert 2-bit signed to 32-bit signed Diff
                        sar     Diff, #30
        :IPos           add     0, Diff                         'Add to encoder position value
                        wrlong  0, MPosAddr                     'Write new position to main memory
                        add     IPosAddr, #1                    'Increment encoder position addresses
                        movd    :IPos+0, IPosAddr
                        movd    :IPos+1, IPosAddr
                        add     MPosAddr, #4                            
:Next                   djnz    Idx, #:UpdatePos                'Loop for each encoder
                        jmp     #:Sample                        'Loop forever

'Define Encoder Reading Cog's constants/variables

AMask                   long    $55555555                       'A bit mask
BMask                   long    $AAAAAAAA                       'B bit mask
MSB                     long    $80000000                       'MSB mask for current bit pair

Pin                     long    0                               'First pin connected to first encoder
TotEnc                  long    0                               'Total number of encoders

Idx                     res     1                               'Encoder index
St1                     res     1                               'Previous state
St2                     res     1                               'Current state
T1                      res     1                               'Temp 1
T2                      res     1                               'Temp 2
Diff                    res     1                               'Difference, ie: -1, 0 or +1
IPosAddr                res     1                               'Address of current encoder position counter (Internal Memory)
MPosAddr                res     1                               'Address of current encoder position counter (Main Memory)
IntPos                  res     16                              'Internal encoder position counter buffer

Comments

  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-12-17 06:01
    Would inverting only ONE of the pins have the same result? That might be easier / trivial in software.
  • T ChapT Chap Posts: 4,223
    edited 2013-12-17 06:17
    Thanks. What about this change:

    orig
            :IPos          add     0, Diff                         'Add to encoder position value
                            wrlong  0, MPosAddr                     'Write new position to main memory
                            add     IPosAddr, #1                    'Increment encoder position addresses 
    
    reverse
            :IPos          sub     0, Diff                         'Add to encoder position value
                            wrlong  0, MPosAddr                     'Write new position to main memory
                            sub     IPosAddr, #1                    'Increment encoder position addresses 
    
    
    
    
  • T ChapT Chap Posts: 4,223
    edited 2013-12-17 06:30
    Assuming I get a fix for the PASM, the next thing is whether to A. ( currently using) implement the option as a separate object name under OBJ, which means the system has to be reprogrammed to comment/uncomment the correct object. B. Find a trick to allow for conditional object selection via stored eeprom byte value under OBJ ie
    (illustration only, this wont work)
    
    PUB GetPrefs
       'load presets
    OBJ
    
       if direction == 1
         enc        : "rotary"
       if direction == 0
         enc        : "rotaryreverse"
    

    Or C. Store the direction mode in eeprom, then have the PASM actual contain two sets of the same code in DAT with alternate names for each section, one set is using the sub versus add function:
    PUB Start(StartPin, Mode, NumEnc, NumDelta, PosAddr): Pass
    IF Mode == 0
      Pass := (Cog := cognew(@Update, Pos) + 1) > 0
    IF Mode == 1
      Pass := (Cog := cognew(@UpdateReverse, Pos) + 1) > 0
    
  • kuronekokuroneko Posts: 3,623
    edited 2013-12-18 00:50
    T Chap wrote: »
    orig
            :IPos          add     0, Diff                         'Add to encoder position value
                            wrlong  0, MPosAddr                     'Write new position to main memory
                            add     IPosAddr, #1                    'Increment encoder position addresses 
    
    reverse
            :IPos          sub     0, Diff                         'Add to encoder position value
                            wrlong  0, MPosAddr                     'Write new position to main memory
                            sub     IPosAddr, #1                    'Increment encoder position addresses 
    
    Don't touch the IPosAddr update. The only change should be to :IPos (add vs sub). And this can be accomplished by e.g. passing the position array address with bit 15 set for the opposite behaviour or placing a flag/mode indicator in the first array element (they are zero'd right now anyway). IOW there is really no need for two copies of what is basically the same code (with one bit flipped).
  • T ChapT Chap Posts: 4,223
    edited 2013-12-18 04:09
    Thanks for the suggestion. I will have a system this afternoon to start testing the ideas. I think what you are saying is that there is no need to add any extra parameters to the method for the caller to use, just have the caller set a bit 15 of the address to 1 as a flag that the rotary start method can check for the flag(in spin), then affect the PASM code to behave in reverse by doing add or sub. What I don't understand is how a flag can be used to change the add to sub, except for adding a condition before the add/sub part that will choose which one to perform. The SPIN/PASM hybrid example as I am understanding would be something like this:
    If directionFlag == 1     'find a PASM version of the SPIN example shown
       :IPos          sub     0, Diff  
    else  
         :IPos          add     0, Diff  
    

    If this is what you are suggesting then I can figure out how to write this in PASM. I am not sure how else you would cause the add or sub lines to act except for a condition right before the summing to choose which one to perform. Except for the option I mentioned earlier which is to copy and paste all the DAT section and rename the pasted parts with an indicator they are the reverse section, only changing the add to sub. Obviously this is wasteful.
  • kuronekokuroneko Posts: 3,623
    edited 2013-12-18 05:09
    As this driver works with injection (SPIN modifies PASM) we might as well stick to that style. The Start method will need something to the effect of:
    if PosAddr < 0
        AMask[-8] |=  |< 26       ' sub
      else
        AMask[-8] &= !|< 26       ' add
    
    The long in cog location AMask-8 is :IPos (local labels are not visible in SPIN). An add differs from a sub insn only in bit 26. So for a negative address (bit 31 set) bit 26 will be set (sub) otherwise reset (add). The Start method will then be called like this:
    enc.Start(pin, cenc, cdelta, [COLOR="#FF0000"]NEGX|[/COLOR]addr)
    
    Also, the Stop method needs fixing (cogstop(Cog~ -1)).
  • T ChapT Chap Posts: 4,223
    edited 2013-12-18 05:45
    Ok I think I understand now. You are modifying the PASM add instruction from SPIN.
            :IPos           add     0, Diff                         'Add to encoder position value
                            wrlong  0, MPosAddr                     'Write new position to main memory
                            add     IPosAddr, #1                    'Increment encoder position addresses
                            movd    :IPos+0, IPosAddr
                            movd    :IPos+1, IPosAddr
                            add     MPosAddr, #4                            
    :Next                   djnz    Idx, #:UpdatePos                'Loop for each encoder
                            jmp     #:Sample                        'Loop forever
    AMask                   long    $55555555                       'A bit mask
    

    You find the "add" by counting backwards in DAT from AMask 8 longs to find the address for the ADD instruction. Then, since the instruction for add is only different from sub by 1 bit, you are using AMask as simply an address to work from to reach the long holding the add instruction. The add instruction is 100000 and the sub instruction is 100001, and these instructions obviously are stored at a bitshift left location of 26, since you are working from that offset.

    This is a big help, much appreciated!

    On a side note, this exercise really helps visualize how PASM works. I can see more clearly the PASM as linear column or memory addresses of instructions and variables parked in ram at a starting point, where instructions run top to bottom acting on either a real number it is presented with on that line, or an address of a number. When the bottom is reaching, an instruction tells the code where to go to start the repeat over.
Sign In or Register to comment.