Encoder expander circuit and code

Discussion in 'The Completed Projects Collection' started by cmartinez, Jan 29, 2017.

  1. cmartinez

    Thread Starter AAC Fanatic!

    Jan 17, 2007
    I recently completed a small project that required the use of a two-channel linear incremental encoder.

    When I got started I ran into a very peculiar problem. The machine's position had to be monitored at all times, and sometimes the machine moved so fast that the MCU lost track of the encoder's signals, immediately reporting an error. This was because the MCU has to monitor other things too, and not only the encoder. It also monitors an emergency stop button, several special function switches and an error report signal from the servo-driver. And it has to increment or decrement position registers too, among other things. So performing monitoring sweeps while also doing arithmetic operations is very time consuming.

    There was nothing wrong with the encoder, except that its pitch was way too small for what I needed. That is, the encoder's original pitch is only 0.005 mm per pulse, while my application required a precision of no more than 0.1 mm. So it turned out that I was using an encoder with 20 times more resolution than I actually needed!

    This small project is based on the AT89LP4052 MCU, which uses the 8051 architecture. It's really fast (only one clock cycle per average instruction) and has 15 configurable I/O

    Also used is a AM26LS32AC differential line receiver. This is because the encoder's outputs are differential and must be translated to single-ended before interfacing them to the MCU

    What the program does is, it monitors the encoder's channels and expands their signals into multiples of its original pitch. The amount of expansion depends on the value assigned to a variable named ENCODER_ADDER, and it is recommended that this is always an odd number, for reasons explained in the attached Excel worksheet, and the assembly code.

    The attached zip file contains the HEX code, the .asm file, a DWG drawing of the circuit, and an Excel sheet with the expander's simulation numbers.

    I've also included the PDF file showing the contents of the DWG drawing, for those unable to open that kind of file.

    The part list is rather short:
    1 - AT89LP4062 MCU (Digikey AT89LP4052-20PU-ND, $1.58 U.S.D.)
    1 - AM26LS32AC differential line receiver (Digikey 296-1376-5-ND, $0.91 U.S.D.)
    2 - 100 nF ceramic bypass capacitors (Digikey 478-1395-1-ND, $0.10 U.S.D.)
    1 - 14.7465 Mhz crystal oscillator (Digikey CTX089-ND, $0.66 U.S.D.)
    1 - 20-pin ic socket
    1 - 16-pin ic socket
    5 - 5mm pitch PCB connectors (Digikey ED2714-ND, $0.78 U.S.D)

    Capture 04.PNG

    Capture 01.PNG


    Capture 03.PNG
    Code (Text):
    2. ;*****************************************************************************************
    3. ;                                  1/29/2017 9:32:56 PM:
    4. ;             Copyright 2017 TechnoMech, S.A. de C.V. info@technomech.info
    5. ;
    6. ;     Encoder shifter program, this program has motion error check.
    7. ;     This program takes the input of a 2-channel encoder, and produces an output that
    8. ;    has a longer waveform than the original, with the purpose of lowering the resolution,
    9. ;    thus delivering a waveform at a slower frequency than the original.
    10. ;     The technique being used to accomplish this, is simply waiting for an extra number
    11. ;    of events (determined by the ENCODER_ADDER register) before effectively
    12. ;    changing the output channel.
    13. ;
    14. ;*****************************************************************************************
    16. F1             EQU D1H ;address of PSW.1, to be used as a flag
    18. ;************************************* I/O definitions ***********************************
    19. OUT_CHAN_A     EQU 94H ;P1.4, output, stretched waveform, channel A
    20. OUT_CHAN_B     EQU 95H ;P1.5, output, stretched waveform, channel B
    21. INP_CHAN_A     EQU 96H ;P1.6, input, encoder's channel A
    22. INP_CHAN_B     EQU 97H ;P1.7, input, encoder's channel B
    24. ;******************************* Register name substitutions *****************************
    28. ;*********************** address definitions of bit addressable RAM **********************
    29. STATUS0          EQU 20H    ;status byte
    30. ERROR_FLAG         EQU 00H  ;define bit address 00H as variable ERROR_FLAG
    32. ;************************* address definitions general purpose RAM ***********************
    33. COUNT_CHAN_A     EQU 30H  ;encoder counter, channel A
    34. COUNT_CHAN_B     EQU 31H  ;encoder counter, channel B
    35. ENCODER_ADDER    EQU 32H  ;additional events needed per encoder input channel to reflect a
    36.                           ;change at their outputs
    37. ENCODER_OVERFLOW EQU 33H  ;this simplifies programming, by assigning it a constant value
    38.                           ;of ENCODER_ADDER+1
    40. ;************************** commands and instructions definitions ************************
    42. ;********************************** constants definitions ********************************
    44. ;-------------------------------------------
    45. FILTER_ENCODER   EQU 11000000B
    46. ;-------------------------------------------
    47. POSD             EQU 10000000B
    48. POSC             EQU 11000000B  ;^  increase
    49. POSB             EQU 01000000B
    50. POSA             EQU 00000000B
    53. ;*********************************** set I/O's initial conditions **************************************
    56.          ;*********************** Ports I/O definitions ***********************
    57.          ;************* ignore compiler errors for these addresses ************
    58.          ;------------- port 1 ---------------
    59.          ;(P1.0) = not used
    60.          ;(P1.1) = not used
    61.          ;(P1.2) = not used
    62.          ;(P1.3) = not used
    63.          ;(P1.4) = output, stretched waveform, channel A
    64.          ;(P1.5) = output, stretched waveform, channel B
    65.          ;(P1.6) = input, encoder's channel A
    66.          ;(P1.7) = input, encoder's channel B
    67.          MOV     C2H, #11001111B ;ignore compiler error
    68.          MOV     C3H, #00110000B ;ignore compiler error
    70.          ;------------- port 3 ---------------
    71.          ;(P3.0) = not used
    72.          ;(P3.1) = not used
    73.          ;(P3.2) = not used
    74.          ;(P3.3) = not used
    75.          ;(P3.4) = not used
    76.          ;(P3.5) = not used
    77.          ;(P3.6) = unusable in the AT89LP4052
    78.          ;(P3.7) = not used
    79.          MOV     C6H, #11111111B ;ignore compiler error
    80.          MOV     C7H, #00000000B ;ignore compiler error
    83. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    84. ;                                   **** Set initial conditions ****
    85. ; The formula to obtain the value of ENCODER_ADDER, by plugging in the desired output resolution R (which
    86. ;will always be a multiple of the original encoder's 0.005 mm/pulse resolution) is:
    87. ;                                    ENCODER_ADDER = 2*R/0.005 - 1
    88. ; For consistency, ENCODER_ADDER must always be an odd number
    89.    MOV ENCODER_ADDER, #39D ; Set the output resolution at 0.100 mm/pulse
    90.                            ; Check the "Econder Pattern 03.xls" file to make sense of why it is being
    91.                            ;done this way
    92.    MOV ENCODER_OVERFLOW, ENCODER_ADDER ;including this variable simplifies programming later on
    94. ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    96.    MOV COUNT_CHAN_A, #00D  ;The counter for channel A will always start at zero
    98.    CLR C                   ;necessary for the next instruction, to avoid carring a 1 into the MSb of A
    99.    RRC A
    100.    MOV COUNT_CHAN_B, A     ;The counter for channel B will always start (ENCODER_ADDER + 1)/2
    102.    SETB OUT_CHAN_A         ;startup conditions of output signals
    103.    CLR  OUT_CHAN_B
    105.    ;----- initialize encoder registers
    106.    MOV A, P1               ;read encoder inputs into A
    107.    ANL A, #FILTER_ENCODER  ;filter only the pertinent values of the encoder
    108.    MOV CURR_ENCODER, A     ;load the actual encoder reading into CURR_ENCODER
    109.    MOV PREV_ENCODER, A     ;load the previous encoder reading into PREV_ENCODER
    112. ;********************************************************************************************************
    113. ; Monitor the encoder's inputs and update the current position. Reporting its value if any change has
    114. ;taken place
    115. ;********************************************************************************************************
    117.                                     ;read the left and right signals
    118.          MOV A, P1                  ;of the encoder into A (new values)
    119.          ANL A, #FILTER_ENCODER     ;filter only the pertinent values of the encoder
    120.          MOV CURR_ENCODER, A        ;load the actual encoder reading into CURR_ENCODER
    122.          MOV B, PREV_ENCODER        ;necessary for the next instruction
    123.          CJNE A, B, CHANGE_HAPPENED ;detect if motion or the push of a button has been performed
    124.        JMP MONITOR_CIRCUIT          ;if not, keep looking for change in encoder inputs
    128.    ; Compare the state of CURR_ENCODER (current reading) vs PREV_ENCODER (previous reading) and increment
    129.    ; or decrement the counter accordingly, also monitoring for error detection.
    130.    ;
    131.    ; This is the following valid encoder reading, after filtering:
    132.    ;
    133.    ;------------------------------------------
    134.    ; FILTER_ENCODER EQU 11000000B
    135.    ;------------------------------------------
    136.    ;           POSD EQU 10000000B
    137.    ;           POSC EQU 11000000B  ^  increase
    138.    ;           POSB EQU 01000000B
    139.    ;           POSA EQU 00000000B
    141.       SC00:
    142.         CJNE CURR_ENCODER,  #POSA, SC01
    143.        SC00FWD:
    144.          CJNE PREV_ENCODER, #POSB, SC00REV
    145.          JMP INCREMENT
    146.        SC00REV:
    147.          CJNE PREV_ENCODER, #POSD, SC00ERR
    148.          JMP DECREMENT
    149.        SC00ERR:
    150.         ;CJNE PREV_ENCODER, #POSC, SC01 ;<- not necessary, since this is the only option left
    151.          SETB ERROR_FLAG
    152.         JMP CONT_LOOP
    154.       SC01:
    155.         CJNE CURR_ENCODER,  #POSB, SC11
    156.        SC01FWD:
    157.          CJNE PREV_ENCODER, #POSC, SC01REV
    158.          JMP INCREMENT
    159.        SC01REV:
    160.          CJNE PREV_ENCODER,  #POSA, SC01ERR
    161.          JMP DECREMENT
    162.        SC01ERR:
    163.         ;CJNE PREV_ENCODER, #POSD, SC11
    164.          SETB ERROR_FLAG
    165.         JMP CONT_LOOP
    167.       SC11:
    168.         CJNE CURR_ENCODER,  #POSC, SC10
    169.        SC11FWD:
    170.          CJNE PREV_ENCODER, #POSD, SC11REV
    171.          JMP INCREMENT
    172.        SC11REV:
    173.          CJNE PREV_ENCODER,  #POSB, SC11ERR
    174.          JMP DECREMENT
    175.        SC11ERR:
    176.         ;CJNE PREV_ENCODER, #POSA, SC10
    177.          SETB ERROR_FLAG
    178.         JMP CONT_LOOP
    180.       SC10:
    181.        ;CJNE CURR_ENCODER, #POSD, CONT_TRANS ;<- not necessary, since this is the only option left
    182.        SC10FWD:
    183.          CJNE PREV_ENCODER, #POSA, SC10REV
    184.          JMP INCREMENT
    185.        SC10REV:
    186.          CJNE PREV_ENCODER,  #POSC, SC10ERR
    187.          JMP DECREMENT
    188.        SC10ERR:
    190.          SETB ERROR_FLAG
    191.         JMP CONT_LOOP
    193.     INCREMENT:
    194.       INC COUNT_CHAN_A  ;both counters are incremented, and then checked for overflow
    195.       INC COUNT_CHAN_B
    197.      ;see if either COUNT_CHAN_A or COUNT_CHAN_B overflowed to ENCODER_OVERFLOW, and act accordingly
    198.      CHECK_OVERFLOW_CHAN_A:
    199.         MOV A, COUNT_CHAN_A
    201.         MOV COUNT_CHAN_A, #00H ;reset channel A counter to zero
    202.         CPL OUT_CHAN_A         ;change output signal state
    203.       JMP CONT_LOOP            ;it's not possible for both channels to overflow at the same time
    204.                                ;so skip checking if channel B overflowed too
    205.      CHECK_OVERFLOW_CHAN_B:
    206.         MOV A, COUNT_CHAN_B
    208.         MOV COUNT_CHAN_B, #00H ;reset channel B counter to zero
    209.         CPL OUT_CHAN_B         ;change output signal state
    210.      JMP CONT_LOOP
    212.     DECREMENT:
    213.       DEC COUNT_CHAN_A         ;both counters are decremented, and then checked for underflow
    214.       DEC COUNT_CHAN_B
    216.      ;see if either COUNT_CHAN_A or COUNT_CHAN_B underflowed to 255, and act accordingly
    218.         MOV A, COUNT_CHAN_A
    219.         CPL A
    221.         MOV COUNT_CHAN_A, ENCODER_ADDER ;set channel A counter to its maximum allowable value
    222.         CPL OUT_CHAN_A         ;change output signal state
    223.        JMP CONT_LOOP           ;it's not possible for both channels to underflow at the same time
    224.                                ;so skip checking if channel B underflowed too
    226.         MOV A, COUNT_CHAN_B
    227.         CPL A
    228.        JNZ CONT_LOOP
    229.         MOV COUNT_CHAN_B, ENCODER_ADDER ;set channel B counter to its maximum allowable value
    230.         CPL OUT_CHAN_B         ;change output signal state
    232.     CONT_LOOP:
    234.      MOV PREV_ENCODER, CURR_ENCODER     ;update historic variables
    239. ;*** This is the last line of the program, to MAKE SURE that the compiler reads until the last end-of-line of the file
    And here's the Hex file of the compiled program:

    Code (Text):
    2. :1000000075C2CF75C33075C6FF75C70075322785B9
    3. :1000100032330533753000E533C313F531D294C262
    4. :1000200095E59054C0FAFBE59054C0FA8BF0B5F01A
    5. :10003000020127BA0011BB4003020080BB8003020B
    6. :10004000009ED2000200B9BA4011BBC0030200807A
    7. :10005000BB000302009ED2000200B9BAC011BB80EF
    8. :1000600003020080BB400302009ED2000200B9BB25
    9. :100070000003020080BBC00302009ED2000200B950
    10. :1000800005300531E530B53308753000B294020013
    11. :10009000B9E531B53323753100B2950200B9153099
    12. :1000A0001531E530F47008853230B2940200B9E5BC
    13. :0D00B00031F47005853231B295AB020127A5
    14. :00000001FF
    Last edited: Feb 14, 2017
    JohnInTX likes this.
  2. dendad

    Active Member

    Feb 20, 2016
    I don't know 8051 code, but are the encoder pulses generating an interrupt? I is a lot better to use IRQs for critical timing than polling.
    Also, if the encoder is 20x the reslution needed, can you gear it down or use another lower res encoder? If you can, the processor will have so much more time to handle stuff so will be less likely to miss pulses.
  3. cmartinez

    Thread Starter AAC Fanatic!

    Jan 17, 2007
    No, this program works through polling. That is, the program works through continuous cycles that monitor changes at the inputs, and translates and reports those changes at the outputs. I could've used interrupts of course, but in this case I think the logic is easier to read and follow without interrupts.

    Yes, of course I could've used a lower res encoder. But that is what I had with me at the moment. The encoder I used was a remnant from an older project, and buying one with the resolution I wanted would've been costly. Besides, I hate waste, so putting this encoder to good use seemed like the proper thing to do.
    As for gearing it down, in this case that would've not been possible, since the encoder is of the non-contact magnetic linear type. And even if it would've been of some other type, like optical rotary for instance, it still makes more sense to use this little circuit since it has no moving parts and does not add mechanical complexity, thus making the system far more reliable and robust. And that's not considering that it's also more economical. The 89LP4052 chip itself costs less that $2.00 dlls.

    This circuit is completely separate from the main processor, and acts as a conditioning interface between the processor and the encoder. And so the whole point is precisely to lessen the work load on the processor.
  4. GopherT

    AAC Fanatic!

    Nov 23, 2012
    Polling may be easier but it doesn't work. Also, if you do poll, you can incorporate a "manual interupt" that polls your input with code that is interlaced with the instructions to do the other tasks you want to do.

    I would use interupts.
    cmartinez likes this.
  5. cmartinez

    Thread Starter AAC Fanatic!

    Jan 17, 2007
    Thanks for the suggestion.. but it does work...

    On the other hand, I have to admit I'm biased against interrupts... I just hate not knowing what the controller is doing at all times... I know how to use interrupts, I just don't like them.
    But it's really just me. I'm certain that far more knowledgeable people than me implemented interrupts into MCU's for very good reasons.

    Now that you've brought it up, I've got the itch to start doing some experimenting with interrupts with this application. See if it makes the code smaller and/or easier to follow.
  6. GopherT

    AAC Fanatic!

    Nov 23, 2012

    If you don't like the spontaneous jump to the interupt services routine, you can simply set and check interupt flags. The PIC can have the flag triggered for an interupt-on-change for an input (a persistent flag) that can be checked with code rather than the automatic service routine.
    cmartinez likes this.
  7. Sensacell

    Senior Member

    Jun 19, 2012
    Have you determined the maximum encoder slew rate and therefore count rate?
    You may find that a soft solution is not going to cut it - regardless, even with interrupts, you always have a certain max frequency you can count.
    There are hardware chips that can do the nasty for you: