Shop OBEX P1 Docs P2 Docs Learn Events
Using the Encoder Sample — Parallax Forums

Using the Encoder Sample

SailerManSailerMan Posts: 337
edited 2008-07-30 07:27 in General Discussion
I used the encoder sample and attached it to Two US Digital· Optical Encoders.. Everything works as expected with the following code which is the sample code that came with the SX/B Software.. I striped the LCD Code and the non Essentials and made it work for two seperate encoders.. I some what understand the code, but was wondering if there was a way to make this code more efficient.· Take a Look.. Oh yeah I am out putting the Encoder counts to a 74HC595.

DEVICE          SX28, OSC4MHZ, TURBO, STACKX, OPTIONX
FREQ            4_000_000,4_480_000
ID  "ENCODER"

EncPort         VAR     RA                      ' encoder port
TRIS_Enc        VAR     TRIS_A

MaxVal          CON     255

 
'encCheck VAR Byte
Encoder_A_Old  VAR     Byte                  
Encoder_A_New   VAR     Byte                   
Encoder_A_Count VAR     byte  
Encoder_B_Old  VAR     Byte                  
Encoder_B_New   VAR     Byte                   
Encoder_B_Count VAR     byte  

tmpB1           VAR     Byte
tmpB2           VAR     Byte
tmpB3           VAR     Byte

 
' =========================================================================
INTERRUPT
' =========================================================================

' Runs every 64 uS @ 4 MHz (no prescaler)

ISR_Start:
 Encoder_A_New = EncPort & %00001100             
   TmpB3 = Encoder_A_Old XOR Encoder_A_New                  
 
 IF tmpB3 > 0 THEN                  
     Encoder_A_Old = Encoder_A_Old << 1                       
      Encoder_A_Old = Encoder_A_Old XOR Encoder_A_New               
     IF Encoder_A_Old.3 = 1 THEN 
   INC Encoder_A_Count                          
     ELSE
   DEC Encoder_A_Count                           
      ENDIF
      Encoder_A_Old = Encoder_A_New                         
   ENDIF
 Encoder_B_New = EncPort & %00000011            
   TmpB3 = Encoder_B_Old XOR Encoder_B_New               
' ========================================================================= 
 IF tmpB3 > 0 THEN                  
      Encoder_B_Old = Encoder_B_Old << 1                       
      Encoder_B_Old = Encoder_B_Old XOR Encoder_B_New                
      IF Encoder_B_Old.1 = 1 THEN 
     INC Encoder_B_Count                          
      ELSE
   DEC Encoder_B_Count                           
      ENDIF
      Encoder_B_Old = Encoder_B_New                         
   ENDIF
 
'ISR_Exit:
  RETURNINT

' =========================================================================
  PROGRAM Start
' =========================================================================

Start:
                              
 
  Encoder_A_New = EncPort & %00001100                
  Encoder_A_Old = Encoder_A_New                               
  Encoder_A_Count = 0 
  Encoder_B_New = EncPort & %00000011               
  Encoder_B_Old = Encoder_B_New                               ' copy
  Encoder_B_Count = 0 
                           
   
  OPTION = $88                                  ' interrupt, no prescaler

Main:
  DO
 
 ShiftOut RC.7,RC.6,1,Encoder_A_Count
 Pulsout RC.0,1 
  
 ShiftOut RC.7,RC.6,1,Encoder_B_Count
 Pulsout RC.0,1  
  
   
   
  LOOP

END


Thanks for anyhelp.

And then What I am looking for is to be able to read this information from a Stamp or Propeller.. What would be the best way. Serial Output??

Regards,
Eric

Comments

  • pjvpjv Posts: 1,903
    edited 2006-10-05 02:06
    Hi Eric;

    The best way is to forget the 74HC595, and keep it all inside the SX or Propeller. Why would you need to send it out, only to retrieve it again ??

    Cheers,

    Peter (pjv)
  • SailerManSailerMan Posts: 337
    edited 2006-10-05 12:04
    I guess I should have said... I'm using the 74595 just for feedback to make sure everything is counting properly. I supose that I could have used a port to light the LEDs.. But I'm new and learning.·smile.gif




    Post Edited (SailerMan) : 10/5/2006 12:12:38 PM GMT
  • Michael ChadwickMichael Chadwick Posts: 80
    edited 2006-10-05 14:52
    I haven't tested this (no hardware eh?) but it compiles.

    This saves a couple bytes, at the cost of·using a second temp in the interrupt. Since you have both encoders on the same port, there is no reason you can't save both channels in one oldbyte and save space.· You can also do the shifting and xoring of both channels at the same time, since you only look at one bit of the results to determine direction.
    This also lends itself to extension to 4 channels on one 8 bit port, by adding the appropriate bit tests for additional channels.· Or make the thing a loop with a bit mask to test the change and direction bits·and increment or decrement the positions in an array.
    [size=2][code]
    DEVICE          SX28, OSC4MHZ, TURBO, STACKX, OPTIONX
    FREQ            4_000_000,4_480_000
    ID  "ENCODER"
    

    EncPort         VAR     RA                      ' encoder port
    TRIS_Enc        VAR     TRIS_A
    MaxVal          CON     255
    

     
    'encCheck VAR Byte
    EncodersOld     VAR     byte                  
    EncodersNew     VAR     byte                   
    Encoder_A_Count VAR     byte  
    Encoder_B_Count VAR     byte  
    tmpB1           VAR     byte
    tmpB2           VAR     Byte
    tmpB3           VAR     Byte
     
    ' =========================================================================
    INTERRUPT
    ' =========================================================================
    ' Runs every 64 uS @ 4 MHz (no prescaler)
    ISR_Start:
     EncodersNew = EncPort
     TmpB1 = EncodersOld XOR EncodersNew ' compare old with new
                                         ' to work out which channels have changed
     TmpB2 = TmpB1 << 1   ' make single bit that shows change on A or B phase
     TmpB1 = TmpB1 OR TmpB2
     EncodersOld = EncodersOld << 1  ' make direction bits by comparing old A with new B
     EncodersOld = EncodersOld XOR EncodersNew ' xor old A with new B
     IF TmpB1.3 = 1 THEN
         IF EncodersOld.3 = 1 THEN 
       INC Encoder_A_Count                          
         ELSE
       DEC Encoder_A_Count                           
          ENDIF
      ENDIF
    ' ========================================================================= 
     IF tmpB1.1 = 1 THEN                  
          IF EncodersOld.1 = 1 THEN 
         INC Encoder_B_Count                          
          ELSE
       DEC Encoder_B_Count                           
          ENDIF
       ENDIF
     EncodersOld=EncodersNew
    

    'ISR_Exit:
      RETURNINT
    

    ' =========================================================================
      PROGRAM Start
    ' =========================================================================
     
    Start:
                                  
     
      EncodersNew = EncPort
      EncodersOld = EncodersNew
      Encoder_A_Count = 0 
      Encoder_B_Count = 0 
       
      OPTION = $88                                  ' interrupt, no prescaler
    Main:
      DO
     
     ShiftOut RC.7,RC.6,1,Encoder_A_Count
     Pulsout RC.0,1 
      
     ShiftOut RC.7,RC.6,1,Encoder_B_Count
     Pulsout RC.0,1  
      
       
       
      LOOP
    


    [/code][/size]
    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    MRC
  • SailerManSailerMan Posts: 337
    edited 2006-10-05 22:43
    That is really cool... I wish I could think in Binary... This shifting and XORing is really confusing.... If you have time...Maybe you could Tell me your logic on this because you made this work right without even having any hardware hooked up.

    Thanks for any help.
    Eric
  • Michael ChadwickMichael Chadwick Posts: 80
    edited 2006-10-06 14:16
    Encoders 101
    
    
    Most encoders generate two signals, generally square waves 90 degrees out of phase. I say most, because you can get ones that also put out an index pulse once per revolution, and there are some that output analog sine waves instead of digital signals.  The basic idea is that with the two phases 90 degrees apart, you can distinguish the direction of rotation.  Position comes from keeping track of the number of pulses and the direction.
    
    
    This is also known as a Gray or Grey code.  The key feature there is that only one bit can change at a time when moving from one state to another adjacent state.
    
    You can list them like this, I did two cycles. I labeled the phases A and B.
    If you look at it in sequence, each two bits from top to bottom, you can see each signal goes 0 to 1 then back again, but overlapped. Looking at it from top down might represent the output sequence of the encoder moving in a clockwise direction, looking at it from bottom up counterclockwise.
    
    
    AB
    00
    10
    11
    01
    00
    10
    11
    01
    
    Side ways:
    A01100110011001100110011
    B00110011001100110011001
    
    If the encoder isn't moving, it outputs the same state continuously. To detect a change in position, we sample the encoder outputs at regular intervals and compare the current sample to the previous one.  Using an XOR to do this works nicely because when you XOR two bits you get a one only if they are different.
    
    Truth table for XOR:
    
    XOR
    A B O
    0 0 0
    0 1 1
    1 0 1
    1 1 0
    
    So if we went from encoder state 00 to state 01, and XOR'd the two, we would get 01. If we went from 01 to 11 we would get 10.  So for some changes we get a result of 01, for others we get 10.  If we only want to do one test to see if either bit has changed, we can do that by ORing the two bits together.
    
    I did it by shifting a copy of the original XOR result up one bit, and ORing it with the original.  This gives me a single bit that will be a 1 if either channel of the encoder changed state.
    
    Consider your situation where you have two encoders as two pairs of bits next to each other in the same byte.  Bits 0 and 1 are one encoder, bits 2 and 3 are the next:
    
    Encoder:    AABB
    Phase:      ABAB
    Bit #   76543210
            00001100    :encoders sitting at rest, old state
            00000100    :encoder A moved one tick clockwise, new state
    XOR     00001000    :result of XOR old and new
    <<      00010000    :shifted copy of the XOR, low bit of comparison shifted
                         into position to line up with the high bit.
    OR      00011000    :result of oring the two together
    
    Now, we have to be careful to pick the correct bits to test, we want the bit
    that is the OR of the two change bits for each encoder.  In this case, since we shifted up, we want to pick the higher bit of each pair, because we OR'd the high bit of the pair with the lower bit. The low bit of each pair is now meaningless since the lower bit of the pair got OR'd with the high bit of the pair to its right.
    
    So we test bits 1 and 3 and we see we got a 1 in bit 3, which indicates encoder B moved, and a 0 in bit 1 which indicates encoder A didn't move.
    
    
    We need to work out how to detect the direction based on getting any two states in sequence. If we take the old sample B phase and XOR it with the new sample A phase, we get a one when the encoder is moving CW, and a zero when the encoder is moving CCW. You can see from the diagrams below that moving CW, the old B and new A are always different, moving CCW the old B and new A are always the same.
    
    I've used the slashes to indicate the two bits getting XORd, with the result to the right of the slash.
    
    I've labeled the phases A and B, keep in mind both sequences are the same, moving along them top to bottom represents clockwise, moving bottom to top is counter clockwise, the slashes indicate which bits get compared in each direction:
    
    XOR old B new A moving CW:
    
    Time       Time
    moves      moves
    down       up
    
    Start      End
    A B  CW    A B  CCW
    0 0        0 0
     /   1      \   0
    1 0        1 0
     /   1      \   0
    1 1        1 1
     /   1      \   0
    0 1        0 1
     /   1      \   0
    0 0        0 0
     /   1      \   0
    1 0        1 0          
     /   1      \   0
    1 1        1 1
     /   1      \   0
    0 1        0 1
    End        Start
    
    Referring to the previous example:
    Encoder:    AABB
    Phase:      ABAB
    Bit #   76543210
            00001100    :encoders sitting at rest, old state
            00000100    :encoder A moved one tick clockwise, new state
    <<      00011000    :old state shifted left, old B now lined up with new A
    XOR     00011100    :result of XOR shifted old and unshifted new
    
    Again, since we shifted to the left one bit, we want to use the higher bit of each pair, the A phase bit of the result, to indicate direction. Encoder A bit A is a 1 in the result, which indicates clockwise movement.  The direction bit is only meaningful if the encoder actually moved, so in the code we test for the change bit, and then the direction bit.
    
    Hopefully this shows how you could do 4 encoders on one 8 bit port, using one set of operations to calculate the change and direction bits.
    
     
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    MRC

    Post Edited (Michael Chadwick) : 10/6/2006 5:42:11 PM GMT
  • pjvpjv Posts: 1,903
    edited 2006-10-06 14:31
    Hi Michael;

    Nice explanation, but I believe bits 3 and 2 of the "XOR" line in the first sequence you posted are reversed.

    Cheers,

    Peter (pjv)
  • Michael ChadwickMichael Chadwick Posts: 80
    edited 2006-10-06 17:41
    Good catch Peter, thanks. I've edited the post.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    MRC
  • SailerManSailerMan Posts: 337
    edited 2006-10-06 23:24
    Thanks for that really lengthy explaination... I read it once and will have to read it a few times, because it's still unclear.. Thanks for taking the time to explain this.
  • Clock LoopClock Loop Posts: 2,069
    edited 2008-07-29 00:05
    I tried to turn this code into code that can read SIX encoders.

    I ran into variable space limits.
    Is there any way to process that many encoders with a single sx? I am I running into territory that would be eaiser if I went with a PROPELLER?

    Michael Chadwick said...
    I haven't tested this (no hardware eh?) but it compiles.

    This saves a couple bytes, at the cost of using a second temp in the interrupt. Since you have both encoders on the same port, there is no reason you can't save both channels in one oldbyte and save space. You can also do the shifting and xoring of both channels at the same time, since you only look at one bit of the results to determine direction.
    This also lends itself to extension to 4 channels on one 8 bit port, by adding the appropriate bit tests for additional channels. Or make the thing a loop with a bit mask to test the change and direction bits and increment or decrement the positions in an array.

    
    [noparse][[/noparse]code]DEVICE          SX28, OSC4MHZ, TURBO, STACKX, OPTIONX
    FREQ            4_000_000,4_480_000
    ID  "ENCODER" 
    
    [noparse][[/noparse]code]EncPort         VAR     RA                      ' encoder port
    TRIS_Enc        VAR     TRIS_A
    MaxVal          CON     255
    
    [noparse][[/noparse]code] 
    'encCheck VAR Byte
    EncodersOld     VAR     byte                  
    EncodersNew     VAR     byte                   
    Encoder_A_Count VAR     byte  
    Encoder_B_Count VAR     byte  
    tmpB1           VAR     byte
    tmpB2           VAR     Byte
    tmpB3           VAR     Byte
     
    ' =========================================================================
    INTERRUPT
    ' =========================================================================
    ' Runs every 64 uS @ 4 MHz (no prescaler)
    ISR_Start:
     EncodersNew = EncPort
     TmpB1 = EncodersOld XOR EncodersNew ' compare old with new
                                         ' to work out which channels have changed
     TmpB2 = TmpB1 << 1   ' make single bit that shows change on A or B phase
     TmpB1 = TmpB1 OR TmpB2
     EncodersOld = EncodersOld << 1  ' make direction bits by comparing old A with new B
     EncodersOld = EncodersOld XOR EncodersNew ' xor old A with new B
     IF TmpB1.3 = 1 THEN
         IF EncodersOld.3 = 1 THEN 
       INC Encoder_A_Count                          
         ELSE
       DEC Encoder_A_Count                           
          ENDIF
      ENDIF
    ' ========================================================================= 
     IF tmpB1.1 = 1 THEN                  
          IF EncodersOld.1 = 1 THEN 
         INC Encoder_B_Count                          
          ELSE
       DEC Encoder_B_Count                           
          ENDIF
       ENDIF
     EncodersOld=EncodersNew
    [noparse][[/noparse]code]'ISR_Exit:
      RETURNINT                                    
    [noparse][[/noparse]code]' =========================================================================
      PROGRAM Start
    ' =========================================================================
     
    Start:
                                  
     
      EncodersNew = EncPort
      EncodersOld = EncodersNew
      Encoder_A_Count = 0 
      Encoder_B_Count = 0 
       
      OPTION = $88                                  ' interrupt, no prescaler
    Main:
      DO
     
     ShiftOut RC.7,RC.6,1,Encoder_A_Count
     Pulsout RC.0,1 
      
     ShiftOut RC.7,RC.6,1,Encoder_B_Count
     Pulsout RC.0,1  
      
       
       
      LOOP
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Meh. Nothing here, move along.
  • PJMontyPJMonty Posts: 983
    edited 2008-07-29 19:17
    BPM,

    The SX can easily read 6 encoders. Not a problem. Not an issue. Piece of cake. The fact that you tried to modify some existing code and ran into problems in no way changes the ability of the SX to do the job.

    I have no idea how you modified the code since no code was posted, but it's quite likely an erroneous or inefficient modification that is causing you problems.

    Thanks,
    PeterM
  • Clock LoopClock Loop Posts: 2,069
    edited 2008-07-29 19:58
    I just basically added variables to cover the extra encoders.

    The problem I ran into is that when all is said and done, you have too many variables.

    I thought about some method of data storage, but don't see how that is different from declaring variables.

    I might be able to eliminate 6 variables, but I don't see any way to do more.

    I think the problem is, I need to maintain a WORD cound for every encoder.

    thats six word variables. on Top of everything else.


    Right now its set up for 3 encoders. If I add 1 more byte variable I am over my ram.
    My fear isn't that it can't be done, unless done in a way that removes the simplicity of sxb.
    I would prefer to keep it fairly simple. And am not going to bother with assembly.

    ' -------------------------------------------------------------------------
    ' Device Settings
    ' -------------------------------------------------------------------------
    DEVICE          SX28, OSC4MHZ, TURBO, STACKX, OPTIONX
    FREQ            4_000_000
    ID              "ENCODER"
    
    
    ' -------------------------------------------------------------------------
    ' IO Pins
    ' -------------------------------------------------------------------------
    EncPort3        VAR     RC                      ' encoder port
    EncPort2        VAR     RB                      ' encoder port
    TRIS_Enc3       VAR     TRIS_C
    TRIS_Enc2       VAR     TRIS_B
    
    ' -------------------------------------------------------------------------
    ' Constants
    ' -------------------------------------------------------------------------
    
    
    
    ' -------------------------------------------------------------------------
    ' Variables
    ' -------------------------------------------------------------------------
    encCheck    VAR     Byte
    'MaxVal         VAR     Byte
    
    
    enc1old     VAR     Byte                    ' previous encoder bits
    enc2old     VAR     Byte                    ' previous encoder bits
    enc3old     VAR     Byte                    ' previous encoder bits
    'enc4old     VAR     Byte                    ' previous encoder bits
    'enc5old     VAR     Byte                    ' previous encoder bits
    'enc6old     VAR     Byte                    ' previous encoder bits
    
    
    enc1        VAR     Byte                    ' new encoder bits
    enc2        VAR     Byte                    ' new encoder bits
    enc3        VAR     Byte                    ' new encoder bits
    'enc4        VAR     Byte                    ' new encoder bits
    'enc5        VAR     Byte                    ' new encoder bits
    'enc6        VAR     Byte                    ' new encoder bits
    
    
    encCount1   VAR        Byte                    ' encoder value
    encCount2   VAR        Byte                    ' encoder value
    encCount3   VAR        Byte                    ' encoder value
    'encCount4   VAR        Byte                    ' encoder value
    'encCount5   VAR        Byte                    ' encoder value
    'encCount6   VAR        Byte                    ' encoder value
    
    
    change1     VAR     Byte
    change2     VAR     Byte
    change3     VAR     Byte
    'change4     VAR     Byte
    'change5     VAR     Byte
    'change6     VAR     Byte
    
    
    Pot1        VAR     Word
    Pot2        VAR     Word
    Pot3        VAR     Word
    'Pot4        VAR     Word
    'Pot5        VAR     Word
    'Pot6        VAR     Word
    
    
    '--------------------------------------------------------------------------
    ' Interrupt Start
    ' -------------------------------------------------------------------------
    INTERRUPT
    GOTO Handle_Int
    
    ' -------------------------------------------------------------------------
    ' Subroutines / Jump Table
    ' -------------------------------------------------------------------------
    
    ProcessPot1        SUB
    
    
    ' -------------------------------------------------------------------------
    ' Interrupt Code
    ' -------------------------------------------------------------------------
    Handle_Int:
    ' Runs every 64 uS @ 4 MHz (no prescaler)
        
        enc1 = EncPort2 & %00000011                  ' get econder bits
        enc2 = EncPort2 & %00001100
        enc3 = EncPort2 & %00110000
    
        'enc4 = EncPort2 & %00001100
        'enc5 = EncPort2 & %00110000
        'enc6 = EncPort2 & %11000000
    
    
        change1 = enc1old XOR enc1                     ' test for change1, put result into change1
        change2 = enc2old XOR enc2                     ' test for change1, put result into change1
        change3 = enc3old XOR enc3                     ' test for change1, put result into change1
    
        'change4 = enc4old XOR enc4                     ' test for change1, put result into change1
        'change5 = enc5old XOR enc5                     ' test for change1, put result into change1
        'change6 = enc6old XOR enc6                     ' test for change1, put result into change1
    
    
    GoSub ProcessPot1
        
    ISR_Exit:
      RETURNINT
    
    
    ' =========================================================================
      PROGRAM Start
    ' =========================================================================
    
    ' -------------------------------------------------------------------------
    ' Program Code
    ' -------------------------------------------------------------------------
    Start:
    
        encCount1 = 8
          
        enc1 = EncPort2 & %00000011                  ' read encoder pos
          
        enc1old = enc1                               ' copy
        
        Toggle RB.7
        Pot1 = 32768
    
          OPTION = $88                                  ' interrupt, no prescaler
        
    
    Main:
     
    
         Watch Pot1, 16, UDEC
    
    GoTo Main
    
    
    ' -------------------------------------------------------------------------
    ' Subroutine Code
    ' -------------------------------------------------------------------------
    ProcessPot1:
    
        IF change1 > 0 THEN                              ' change1?
             enc1old = enc1old << 1                       ' adjust old bits
            enc1old = enc1old XOR enc1                  ' test direction
        
            IF enc1old.1 = 1 THEN
                IF encCount1 < 12 THEN                 ' if max, no change1
                    INC encCount1
                    If encCount1 = 12 Then
                        Toggle RC.0
                        encCount1 = 8
                        Inc Pot1                            ' increase value
                      Endif
                  Endif
            Else
                  IF encCount1 > 4 THEN                      ' if 0, no change1
                    DEC encCount1                             ' decrease value
                    If encCount1 = 4 Then
                        Toggle RC.0
                        encCount1 = 8
                        DEC Pot1                            ' decrease value
                      Endif      
                  Endif
            Endif
    
               enc1old = enc1                             ' save last input
        Endif    
    
    Return
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Meh. Nothing here, move along.
  • CapdiamontCapdiamont Posts: 218
    edited 2008-07-30 01:04
    I think you can use arrays which allow more/different space to be used.
  • John BondJohn Bond Posts: 369
    edited 2008-07-30 07:27
    Thanks for raising this topic Eric and thanks for your description of using bits Mr Chadwick!!! It’s amazing how often this forum gives solutions just when I need them.

    I’m using a mechanical encoder and battling with debouncing the input. This approach will solve the problem. I was using a complex If-ElseIf routine with a flag but occasionally the encoder will count 1 byte backwards when turned clockwise (and visa versa). The problem is that while the contact bounce is usually under 100ns, it’s occasionally over 30ms (30X). Set the debounce for 50ms and you can’t spin the encoder fast, set the debounce for 10ms and the miss-counting occasionally occurs.

    If I use this routine, contact bounce won’t matter and I’ll get much faster code.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Sign In or Register to comment.