Shop OBEX P1 Docs P2 Docs Learn Events
what's your best pasm code for a gray code quadrature encoder? — Parallax Forums

what's your best pasm code for a gray code quadrature encoder?

Alex.StanfieldAlex.Stanfield Posts: 198
edited 2012-10-16 20:33 in Propeller 1
Has anyone a compact pasm code to sense direction and step on a 2 bit gray code encoder?

Is there anything better than converting to binary and substracting fron the last value?

Thanks in advance

Alex

Comments

  • jmgjmg Posts: 15,173
    edited 2012-10-13 15:03
    How many channels ? Does size matter more than speed ?
    For those speed demons, I've thought that a small PLD can clock two counters, and the difference is the location.

    I think that needs 3 channels, two as counters, and one to generate the PLD state engine clock.

    Edit : The generic design would need 3 Counter, but I think the Prop allows this to shrink to 2 Counters (1 COG) if you use
    CTRMODE %01011 POSEDGE detector w/ feedback INC@[A1 & !A2] BPIN: !A1

    - those BPIN sigs then drive the PLD as ack-style handshakes, which can create a ack-clk, to advance the pld quad phase lock.
    That uses 4 pins per Quad Counter : CLK_U, CLK_D, QFBn_U, QFBn_D

    It may be possible to drop that to 3, if we flip to
    CTRMODE %01111 NEGEDGE detector w/ feedback INC @[!A1 & A2] BPIN: !A1

    - and map both BPIN to the same IO, and use the OR feature ?
    Pins are now : CLK_U, CLK_D, PINB == (QFBn_U OR QFBn_D)


    I'm guessing here that two CTRs can drive (OR) a single pin in FB mode ?
    if CLKin is mostly HI, FB out will be mostly low, allowing OR to work.

    Here, 2 Quad 'support' channels can fit into a SPLD, and you need 1 COG Counter pair, per HW Quad supported.
    The PLD does the edge conditioning, and Phase-follow, and the Prop does the 32 bit dual (difference) counting. Maximum Quad Speed should be >20MHz-40MHz region.
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-10-13 15:13
    Try this site:

    Spinning Up Fun With Encoders

    Duane J
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-14 20:13
    jmg wrote: »
    How many channels ? Does size matter more than speed ?
    For those speed demons, I've thought that a small PLD can clock two counters, and the difference is the location.

    Thanks for the idea, anyway the counters are used in the cog I'll use.

    Alex
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-14 20:28
    Try this site:

    Spinning Up Fun With Encoders

    Duane J

    Thanks Duane, I bowsed the doc however I found it too verbose and inefficient.

    I found on another site the clue I was looking for:

    - After the usual step detection your direction is given by (pcode):

    If New(A) == Old(B) then going down
    else going up


    The following example (untested) should do. Whenever I find a little time to hook up the test I'll try it out.
    :ReadInput              mov     newscan, ina            'Get input
                            shr     newscan, basepin        'move encoder inputs to bits 1&0
                            and     newscan, # $ 3           'Mask input
                            xor     newscan, oldscan        'Detect changes
                            tjz     newscan, #:ReadInput    'No change? -> keep reading
    
                            xor     newscan, oldscan        'Restore newscan                   
    
                            test    newscan, # $ 2 WC        'C=     New A bit
                            test    oldscan, #  $ 1 WZ        'Z= not old B bit
                            mov     oldscan, newscan        'Save input for next change
                            
                            mov     posadj, resolution      'Init value to increment position
    
            'If C<>Z that means New(A) == Old(B)
            'If New(A) == Old(B) THEN we are going DOWN
            '                    Else we are going UP    
    
            if_C_ne_Z       neg     posadj, resolution      'If New A == Old B -> going down
    
                            adds    Position, posadj
                            jmp     #:ReadInput
    
    It's so simple I just can't imagine something better. :cool:
    If you'd like to say that you are going UP instead of DOWN for New(A) == Old(B) then just swap the test pattern on the test instructions.

    Alex
  • jmgjmg Posts: 15,173
    edited 2012-10-14 22:20
    It depends how many edges you want active.
    The classic Quad system uses 2 old values, and 2 new values to give 16 choices, 4 are hold, 4 are illegal, and 4 each for Up and Dn.

    In Change Pseudo code that is usually along the lines of
    IF  A_Changed THEN 
      IF  NewA == NewB THEN
        DEC(QuadValue)
      ELSE
        INC(QuadValue)
    ELSIF B_Changed THEN 
      IF  NewA <> NewB THEN
        DEC(QuadValue)
      ELSE
        INC(QuadValue)
        
    added: and if you cannot guarantee poll rates, and/or want to trap illegal conditions you can add
    
    IF (A_Changed AND B_Changed) THEN
       StickyFlagError      // Both signals should not change in one poll pass
    ELSIF  A_Changed THEN 
      IF  NewA == NewB THEN
        DEC(QuadValue)
      ELSE
        INC(QuadValue)
    ELSIF B_Changed THEN 
      IF  NewA <> NewB THEN
        DEC(QuadValue)
      ELSE
        INC(QuadValue)   
    // Implicit 4th option is Nothing changed == do nothing
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-10-14 22:38
    jmg,

    Your code is asymmetrical. The first conditional is also true if both change; the second, only if B changes and not A. You have to make a decision whether two changes entail a double jump due to an inadequate sampling rate or constitute an illegal condition.

    -Phil
  • jmgjmg Posts: 15,173
    edited 2012-10-14 23:09
    jmg,

    Your code is asymmetrical. The first conditional is also true if both change; the second, only if B changes and not A. You have to make a decision whether two changes entail a double jump due to an inadequate sampling rate or constitute an illegal condition.

    Correct, it is merely the counting code, not the error handling needed if you cannot guarantee you polling is faster than the edge rate.
    In a fully working system, it is good practice to include code that catches and sticky-flags an illegal condition.
    That way you know if you are not having illegal events. I've added that as an expanded example.
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-15 06:05
    There's no need to know which bit changed, just the relationship between one bit on the current sample and the other bit of the previous sample. So your code just has to catch that independently of which bit changed. There is no need for a dual branch on this.

    You should always catch +/-1step, but If the mechanical system's speed is near the speed limit of you SW loop, you could catch a double jump with:
             if (newsample XOR oldsample) == $3 
                   Flag sticky error
    
    Anyway you shouldn't treat that as a double jump since you can't know for sure in which direction that was (if 2 bits changed then you are exactly at the opposite end of the table not knowing through which path you got there)

    Alex
  • jmgjmg Posts: 15,173
    edited 2012-10-15 12:38
    Here is my std Quad reminder sketch...
            a  b  c  d  Up               A  B  C  D  Down
    |       +  +  +  +  +  +  +  +  +    -  -  -  -  -  -  -  -  -  
    |A  ____/=====\_____/=====\_____/====\_____/=====\_____/=====\__
    |B  _______/=====\____/======\__________/=====\_____/======\_____
    |      -----UP -------------------| -------------Down --------------
    
    

    If you want all 4 edges to count properly then I think you you do need more tests, but if you only want one count per 4 edges, then one ==, <> is ok.
    Basically you can feed a Quad pair into a CLK.DIRN counter for one count per 4 edges.
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-15 13:34
    jmg wrote: »
    Here is my std Quad reminder sketch...
            a  b  c  d  Up               A  B  C  D  Down
    |       +  +  +  +  +  +  +  +  +    -  -  -  -  -  -  -  -  -  
    |A  ____/=====\_____/=====\_____/====\_____/=====\_____/=====\__
    |B  _______/=====\____/======\__________/=====\_____/======\_____
    |      -----UP -------------------| -------------Down --------------
    
    

    If you want all 4 edges to count properly then I think you you do need more tests, but if you only want one count per 4 edges, then one ==, <> is ok.
    Basically you can feed a Quad pair into a CLK.DIRN counter for one count per 4 edges.

    There's no need for that once you know the inputs changed (with XOR between new_sample and old_sample)

    With a single " IF new(A) == old(B) " you will count correctly on every edge. See the attached spreadsheet.

    encoder.xls

    Alex
  • jmgjmg Posts: 15,173
    edited 2012-10-15 14:37

    With a single " IF new(A) == old(B) " you will count correctly on every edge. See the attached spreadsheet.

    Interesting.
    If this was really 100% coverage, my PLD optimizer should give the same reduction.

    Here is what the PLD optimizer says, spaced for clarity, note Qfb is old(B), Qia is new(A)

    CLK_Dn.d  =>
        CLK_Dn    & Qfb & Qia       & Qib & !Qfa
      # CLK_Dn    & !Qfb & !Qia     & Qib & !Qfa 
      # CLK_Dn    & Qfb & Qia       & !Qib & Qfa 
      # CLK_Dn    & !Qfb & !Qia     & !Qib & Qfa  
    
    
    Illegal =>
        !Qfb & !Qia     & Qib & Qfa    << passes IF new(A) == old(B) test
      # Qfb & Qia       & !Qib & !Qfa  << passes IF new(A) == old(B) test
      # !Qfb & Qia      & Qib & !Qfa  
      # Qfb & !Qia      & !Qib & Qfa 
    

    It does not reduce to match your example, and the clue as to why more qualifiers are needed, is in the Illegal case.
    Notice two of those illegal branches, pass your single test of " IF new(A) == old(B) "

    So the correct coverage is given by first excluding the Illegal case, in a second test
    If (Inputs_Changed AND Inputs_Legal) THEN
    IF new(A) == old(B) THEN INC() ELSE DEC()


    In the case of Prop+PASM, you may be able to use the unique feature that CY is Parity result, as No Change and Illegal are both even parity, and one-only-changed are odd-parity ?
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2012-10-15 15:10
    entry                   mov   combined, #0         
                                                    
    :loop                   mov   look, lookyup        ' load look up table
                            mov   state, ina           ' Read inputs
                            and   state, pinmask       ' Mask
                            shl   combined,#2          ' Shift old bits left
                            or    combined,state       ' combine to provide 4 bit word
                            and   combined,#15         ' mask off really old bits
                            mov   shift,combined       ' Make ready to use as index 
                            shl   shift,#1             ' Shift to multiply by two
                            shl   look,shift           ' Use to shift look up table bits to bits 30,31                                       
                            sar   look,#30             ' Shift to bits 0 and 1 keeping the sign                        
                            
                            adds  count,look           ' Add the result to the count                             
    
                            jmp   #:loop 
    
    pinmask                 long    %0011
    count                   long    0
    combined                long    0
    
    '                                 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
    lookyup                 long    %00_01_11_00_11_00_00_01_01_00_00_11_00_11_01_00  
    
    
    state                   res     1
    look                    res     1
    shift                   res     1
    

    http://forums.parallax.com/showthread.php?89954-quadrature-encoders/page2
  • jmgjmg Posts: 15,173
    edited 2012-10-15 15:15
    I like the compressed look up table.. !
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-10-15 15:38
    Graham,

    That's a brilliant piece of code! -- and from 2007, too, when we were all still learning the ins and outs of the Prop!

    -Phil
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2012-10-15 15:43
    I wrote it but really if you read the thread it was not me that came up with the idea, the look up table was something "Skogsgurra" suggested and Chip provided the novel implementation idea.

    Graham
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-10-15 15:51
    This would make a good golf challenge -- both for the smallest program and for the fastest program.

    -Phil
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-15 18:48
    jmg wrote: »

    So the correct coverage is given by first excluding the Illegal case, in a second test
    If (Inputs_Changed AND Inputs_Legal) THEN
    IF new(A) == old(B) THEN INC() ELSE DEC()


    In the case of Prop+PASM, you may be able to use the unique feature that CY is Parity result, as No Change and Illegal are both even parity, and one-only-changed are odd-parity ?

    Exactly! That's what I meant with " once you know the inputs changed (with XOR between new_sample and old_sample)"

    Result of current_input XOR previous_input


    00
    Nothing changed => loop and keep reading


    01
    1 step detected => PROCESS


    10
    1 step detected => PROCESS


    11
    2 steps detected => ILLEGAL




    Results of 00 and 11 don't get processed
    Results 01 and 10 signal that we received a single step, now we need only to determine it's direction. You go through the single "IF new(A)==old(B) THEN..." for both cases and you get the right direction. (only this last step is modeled in the excel file above)

    Alex
  • jmgjmg Posts: 15,173
    edited 2012-10-15 19:42
    Exactly! That's what I meant with " once you know the inputs changed (with XOR between new_sample and old_sample)"

    It may be what you meant, but it not what you actually said, or what your code does.

    You need to re-word that test to be
    once you know the inputs legally changed
    Results of 00 and 11 don't get processed

    but the 11 (both changed) really does need to be caught, before you apply the "IF new(A)==old(B) THEN" test.
    I'm not sure I'd call that "does not get processed" ?

    If you fail to test/catch that case, your INC/DEC coverage is different from a classic Quadrature design, in that you will (wrongly) change on an illegal case.

    I think in PASM, a IF_Z_OR_NC test is able to exit on (NoChange OR Illegal )
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-15 19:51
    Nice code Graham, I saved it already for a future need!

    Alex
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-15 20:09
    Trimmed and modified code to catch illegal double step.
    entry                   mov     old, #0                                                         
    :loop                   mov     state, ina              ' Read inputs
                            and     state, #%11             ' Mask
                            
                            xor     state, old WC           'detect pin changes C=0 if 00/11
                            xor     state, old              'restore state
                  if_nc     jmp     #:skip                  'no changes or ILLEGAL -> ignore
    
    
                            test    state, #%01 WC          'C =     new(B)
                            test    old,   #%10 WZ          'Z = not old(A)
                  
            if_C_eq_Z       add     count, #1               'Going UP
            if_C_ne_Z       sub     count, #1               'Going DOWN
    
    
    :skip                   mov     old, state              'save for next round
      
                            jmp     #:loop 
    
    
    count                   long    0
    old                     long    0
    
    
    state                   res     1
    
    If I did correctly the math It's even 5 longs shorter and 1 instruction faster than Graham's code.

    Alex
  • kuronekokuroneko Posts: 3,623
    edited 2012-10-15 20:45
    You could use xor state, old wc,nr instead and the second half looks like a candidate for sum[?] usage.
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-16 05:48
    kuroneko wrote: »
    You could use xor state, old wc,nr instead and the second half looks like a candidate for sum[?] usage.

    YES! Thanks for reminding me of NR, I use it so little I always forget it's there. So now we shaved another long. :cool:

    On the sum[?] I'm not so sure. The key is to add (or substract) when one bit on the new sample is the same as the other bit of the previous sample. It doesn't matter if it's 0 or 1, just that it be the same. How can we do that with sum[?] ?

    Alex
  • kuronekokuroneko Posts: 3,623
    edited 2012-10-16 07:19
    (Untested) from an old sample I dug out:
    test    status, #%10 wc
                    muxc    old, #%10 wc
                    sumc    count, #1
    
  • MicksterMickster Posts: 2,694
    edited 2012-10-16 10:04
    Is this an over-simplified solution to the problem?

    Bit shifting and masking, for me at least, can be hard to follow. I know that this is not native PASM but the PropBASIC compiler that I'm currently evaluating produces PASM and at any time, there should be no
    more than approximately 10 instructions executing.

    At this point, I didn't bother with the incrementing/decrementing of the counter variable but would like opinions as to whether this looks feasible.

    Edit: Just added the counter Inc/Dec stuff.

    DEVICE P8X32A, XTAL1, PLL16X
    FREQ 80_000_000
    
    Chan_A PIN 3 INPUT
    Chan_B PIN 4 INPUT
    Counter VAR LONG
    
    
    PROGRAM Start
    
    Start:
    
    'Decide where to go first
    If Chan_A = 1 And
       Chan_B = 1 Then
       Goto A1B1
    Endif
    
    If Chan_A = 0 And
       Chan_B = 0 Then
       Goto A0B0
    Endif
    
    If Chan_A = 1 And
       Chan_B = 0 Then
       Goto A1B0
    Endif
    
    If Chan_A = 0 And
       Chan_B = 1 Then
       Goto A0B1
    Endif
    
    'Should keep hopping between these labels below unless an illegal 
    'condition crops up.
    
    A1B1:
    If Chan_A = 0 Then
       Dec Counter
       Goto A0B1
    Elseif Chan_B = 0 Then
       Inc Counter
       Goto A1B0
    Endif
    Goto A1B1
    
    A0B0:
    If Chan_A = 1 Then
       Dec Counter
       Goto A1B0
    Elseif Chan_B = 1 Then
       Inc Counter
       Goto A0B1
    Endif
    Goto A0B0
    
    A1B0:
    If Chan_A = 0 Then
       Inc Counter
       Goto A0B0
    Elseif Chan_B = 1 Then
       Dec Counter
       Goto A1B1
    Endif
    Goto A1B0
    
    A0B1:
    If Chan_A = 1 Then
       Inc Counter
       Goto A1B1
    Elseif Chan_B = 0 Then
       Dec Counter
       Goto A0B0
    Endif
    Goto A0B1
    End
    
    

    Cheers!

    Mickster
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-16 10:58
    kuroneko wrote: »
    (Untested) from an old sample I dug out:
    test    status, # wc
                    muxc    old, # wc
                    sumc    count, #1
    

    Absolutely COOL :cool:. hand tested modifying the excel above shows it should work. I'll try it tonight.

    We keep shaving longs...

    I'll post the final tests tonight (I hope)

    Alex
  • jmgjmg Posts: 15,173
    edited 2012-10-16 12:32
    We keep shaving longs...

    If you want to push down in size, a JMP to another JMP never 'looks good', and I like to speed the fastest path, and then use that time for optional 'other stuff'.
    eg It is nice to have an (optional?) line that can somehow flag illegal cases, (either toggle a pin, or a sticky pin ) as that can indicate the SW is not keeping up.
    This should be possible without increasing the longest path time ?

    Roughly sketched :
    :ChkValid
            if_nc_and_nz    'Pin opcode. Optional Signal illegal with Toggle or Set
    :Reload                 mov     old, state              ' save for next round
    :loop                   mov     state, ina              ' Read inputs
                            and     state, #MaskQaQb        ' Mask just two pins.
                            xor     state, old WC, nr       ' detect pin changes C=0 if 00/11, Z=0 if 00 
                  if_nc     jmp     #:ChkValid              ' no changes or ILLEGAL -> ignore
    ' action block
    
    

    SW pace issues can sneak up as users add more code to the loop (Chips time sliced COG is going to be great...)
    - or they might splice on multiple Quad channels.
  • Alex.StanfieldAlex.Stanfield Posts: 198
    edited 2012-10-16 20:33
    It worked ok as we expected.

    Here's an encoder simulator with the PASM code under test and the results seen



    Encoder 1 - Alex.spin


    |0|
    Going 20 steps up
    |1||2||3||4||5||6||7||8||9||10||11||12||13||14||15||16||17||18||19||20|
    Going 10 steps down
    |19||18||17||16||15||14||13||12||11||10|
    Faking 5 errors
    
    
    Going 15 steps down into negative positions
    |9||8||7||6||5||4||3||2||1||0||-1||-2||-3||-4||-5|
    Faking again 3 errors
    
    
    Going 5 steps up returning to 0
    |-4||-3||-2||-1||0|
    

    Have Fun!

    Alex
Sign In or Register to comment.