;****************************************************************************
; * Rotary Encoder Decoder                                                  *
; *  Version 1.1++                                                          *
; *  May 24, 2004                                                     *
; *                                                                         *
;****************************************************************************
; Description:
; This program allows a PIC to decode quadrature rotary encoder signals to
; proved direction, tachometer and magnitude outputs. Each time the encoder
; is moved, a tach output pulse is generated, and both a direction and three
; bit magnitude vale are produced.  The magnitude indicates how fast the 
; shaft is being turned, and is based on the time that passes between encoder
; movements.
;
;****************************************************************************
; Author - Bruce Stough - AA0ED
;
; Modification History
;  11/05/00 - Modified to use jump tables to determine the endoder direction.
;   4/29/01 - Modified to work with the 16f628 chip.
;   7/02/02 - Update Config definitions for 16F628 (CBJ)
;   7/29/03 - Minor tweaks (CBJ)
;   9/21/03 - Fix PIC16F628 picture. Pin 4 (!MCLR) goes to +5v, not GND (CBJ)
;   5/24/04 - Add comments re 50 ms timer (CBJ)
;
;****************************************************************************
;
; Target Controller -      PIC16F628
;                          __________
;                     RA2 |1       18| RA1---------ENCODER A
;                     RA3 |2       17| RA0---------ENCODER B
;                     RA4 |3       16| OSC1--------(Unused) 
;     +5v ----------!MCLR |4       15| OSC2--------(Unused)
;     Ground----------Vss |5       14| VDD---------+5 V
;                     RB0 |6       13| RB7---------Direction
;                     RB1 |7       12| RB6---------Encoder bit
;                     RB2 |8       11| RB5---------Encoder mid bit
;     Encoder tach----RB3 |9       10| RB4---------Encoder low bit
;                          ----------
;
;****************************************************************************
; 
;****************************************************************************
; * Device type and options.                                                *
;****************************************************************************
;       
       processor       PIC16F628
       radix           dec
;
;****************************************************************************
; * Configuration fuse information for 16F628:                              *
;****************************************************************************

_BODEN_ON                    EQU     H'3FFF'
_BODEN_OFF                   EQU     H'3FBF'
_CP_ALL                      EQU     H'03FF'
_CP_75                       EQU     H'17FF'
_CP_50                       EQU     H'2BFF'
_CP_OFF                      EQU     H'3FFF'
_PWRTE_OFF                   EQU     H'3FFF'
_PWRTE_ON                    EQU     H'3FF7'
_WDT_ON                      EQU     H'3FFF'
_WDT_OFF                     EQU     H'3FFB'
_LVP_ON                      EQU     H'3FFF'
_LVP_OFF                     EQU     H'3F7F'
_MCLRE_ON                    EQU     H'3FFF'
_MCLRE_OFF                   EQU     H'3FDF'
_ER_OSC_CLKOUT               EQU     H'3FFF'
_ER_OSC_NOCLKOUT             EQU     H'3FFE'
_INTRC_OSC_CLKOUT            EQU     H'3FFD'
_INTRC_OSC_NOCLKOUT          EQU     H'3FFC'
_EXTCLK_OSC                  EQU     H'3FEF'
_LP_OSC                      EQU     H'3FEC'
_XT_OSC                      EQU     H'3FED'
_HS_OSC                      EQU     H'3FEE'

;
   __config _CP_OFF & _LVP_OFF & _BODEN_OFF & _MCLRE_ON & _PWRTE_ON & _WDT_ON & _INTRC_OSC_NOCLKOUT
;    __config   H'3F34'
;
;****************************************************************************
; *                    Port and EEPROM Constants                            *
;****************************************************************************
;
PortA   equ     0x05
PortB   equ     0x06
TRISA   equ     0x05
TRISB   equ     0x06
WREN    equ     0x02
WR      equ     0x01
RD      equ     0x00
CMCON   equ     0x1F
PIR1    equ     0x0C
PIE1    equ     0x8C
TMR1L   equ     0x0E
TMR1H   equ     0x0F
T1CON   equ     0x10
;
;
;****************************************************************************
; * RAM page independent file registers:                                    *
;****************************************************************************
;
INDF    EQU     0x00
PCL     EQU     0x02
STATUS  EQU     0x03
FSR     EQU     0x04
PCLATH  EQU     0x0A
INTCON  EQU     0x0B
;
;*****************************************************************************
; * Bit numbers for the STATUS file register:                                *
;*****************************************************************************
;
B_RP0   EQU     5
B_NTO   EQU     4
B_NPD   EQU     3
B_Z     EQU     2
B_DC    EQU     1
B_C     EQU     0
;
;****************************************************************************
; * Assign names to IO pins.                                                *
;****************************************************************************
;
;   B register bits:
;
tach_bit equ    3
dir_bit  equ    7
speed_low equ   5
speed_high equ  6
;
;***************************************************************************
; *          General program equates                                       *
;***************************************************************************
clockwise equ   1
counter_cw equ  0
;
; The following values determine the ren_timer_1 threshole values and the
; amount to increment the timer each time around.
; 
fast_1    equ   0x10
fast_2    equ   0x40
fast_3    equ   0x70
fast_4    equ   0xFF
timer_inc equ   0x40
;
;****************************************************************************
; *           Allocate variables in general purpose register space          *
;****************************************************************************
;
        CBLOCK  0x20              ; Start Data Block
;
        timer1                    ; Used in delay routines
        timer2                    ;   "
        encoder_curr              ; New value of encoder pins A and B
        encoder_old               ; Old value of encoder pins A and B
        ren_read                  ; Encoder pins A and B
        direction                 ; Indicates last direction of encoder
        out_bits                  ; The direction and speed bits to PortB
        tick_count                ; The count for this period
        init_flag                 ; Indicates when we are starting over
        temp_w                    ; Interrupt save of W
        temp_s                    ; Interrupt save of STATUS
;
        ENDC                      ; End of Data Block
;****************************************************************************
; * The 16F84 resets to 0x00.                                               * 
; * The Interrupt vector is at 0x04. (Unused)                               *
;****************************************************************************
;
        ORG     0x0000                
        goto    start
        org     0x0004
        goto    int_server
;*****************************************************************************
; *                                                                          *
; * Purpose:  This is the start of the program.  It polls the rotary encoder *
; *           inputs.  When it detects a change, it determines the direction *
; *           the knob was turned and the speed at which it is turned and    *
; *           provides this as output.                                       *
; *                                                                          *
; *   Input:  The A and B outputs of the rotary encoder.                     *
; *                                                                          *
; *  Output:  The direction, tach, and speed outputs.                        *
; *                                                                          *
;*****************************************************************************
;
start
        clrf    INTCON            ; No interrupts for now
        movlw   0x07              ; Code to turn off the analog comparitors
        movwf   CMCON             ; Turn off comparitors
;
; New stuff for timer 1 and interrupt.
;
        movlw   0xC0              ; Get GIE and PEIE bits
        movwf   INTCON            ; Enable general interrupts
        bsf     STATUS,B_RP0      ; Switch to bank 1 to enable timer 1
        movlw   0x01              ; Get bit to enable timer 1 interrupt
        movwf   PIE1              ; Set TMR1IE
        bcf     STATUS,B_RP0      ; Switch to bank 0
;
; Initialize the timer to go for 50ms.
; Theory of operation: How to initialize the timer to interrupt after 50ms. (CBJ)
;
; TMR1IE bit is set, so an interrupt is generated when the counter rolls over.
; We will set a value into <TMR1H:TMR1L> so the counter will always start at 
; this value.  Start at  = 0x3CAF because 0xFFFF - 0x3CAF = 0xC3F0 - 50000. 
; Thus, it will generate the interrupt after 50000 ticks of the timer.
;
; Timer1 gets incremented on every instruction cycle (Internal clock / 4).
; Prescale is 1:1, since <T1CKPS1:T1CKPS0> is zero. 
; The internal clock frequency is 4 MHz, so instruction cycle is 1 MHz.   
; This means 1x10^-6 seconds per timer tick
;
; 5 x 10^4 ticks /interrupt   x 1 x 10^-6 seconds / tick
; so we have 5 x 10^-2 seconds (which is 50 ms) per interrupt.
;
;
        clrf    T1CON             ; Turn TIMER1 off
        movlw   0xAF              ; Get low byte
        movwf   TMR1L             ; Set low timer byte
        movlw   0x3C              ; Get high byte
        movwf   TMR1H             ; Set high timer byte
        movlw   0x01              ; Bit to turn on TIMER1
        movwf   T1CON             ; Turn the timer on
;
        clrf    tick_count        ; Initialize tick count to zero
        clrf    init_flag         ; Indicate we are starting over
        bsf     STATUS,B_RP0      ; Switch to bank 1
        bsf     0x01,7            ; Disable weak pullups
        movlw   0xFF              ; Tristate port A
        movwf   TRISA             ;
        clrf    TRISB             ; Set port B to all outputs
        bcf     STATUS,B_RP0      ; Switch back to bank 0
;
;       Get the power on encoder value.
;
        movf    PortA,w           ; Read port A
        movwf   ren_read          ; Save it in ren_read
        movlw   0x03              ; Get encoder mask
        andwf   ren_read,w        ; Get encoder bits
        movwf   encoder_old       ; Save in encoder_old
        clrf    PortB             ; Clear port B outputs        
        goto    main              ; Jump around tables to main routine
;
;****************************************************************************
;
; The CW and CCW jump tables return the encoder values expected if the encoder
; is moved clockwise or counter clockwise from the value passed in W.  The
; returned value (based on the old encoder value) is compared with the new
; encoder value to determine the direction the knob was turned.
;*****************************************************************************
CW
        addwf   PCL,f             ; Add offset to the program counter
        retlw   0x02              ; Value following 0x00 going clockwise   
        retlw   0x00              ; Value following 0x01 going clockwise
        retlw   0x03              ; Value following 0x02 going clockwise
        retlw   0x01              ; Value following 0x03 going clockwise
;
CCW
        addwf   PCL,f             ; Add offset to the program counter
        retlw   0x01              ; Value after 0x00 going counter clockwise
        retlw   0x03              ; Value after 0x01 going counter clockwise
        retlw   0x00              ; Value after 0x02 going counter clockwise
        retlw   0x02              ; Value after 0x03 going counter clockwise
; 
; Fall into the Main Program Loop
;
;*****************************************************************************
; *                                                                          *
; * Purpose:  This is the Main Program Loop.                                 *
; *                                                                          *
; *   Input:  None.                                                          *
; *                                                                          *
; *  Output:  None.                                                          *
; *                                                                          *
;*****************************************************************************
;
main                                
        call    poll_encoder      ; Check for knob movement
        movf    init_flag,w       ; Get the init flag
        sublw   0x01              ; Was it set?
        btfss   STATUS,B_Z        ; Was the Z flag set?
        goto    not_init          ; No:  Not init condition
        movlw   0x01              ; Get a 1 to init tick_count
        movwf   tick_count        ; Start counting again
        movlw   0x00              ; Get a zero
        movwf   init_flag         ; Clear the init flag
        goto    main              ; Keep polling
not_init
        movf    tick_count,w      ; Get the tick_count value
        sublw   0xFF              ; Has it reached the limit?
        btfss   STATUS,B_Z        ; Skip increment if equal
        incf    tick_count,f      ; Increment the tick count
;
; Need to add stuff to check the direction.
;
        goto    main              ; Keep polling
;
;*****************************************************************************
; *                                                                          *
; * Purpose:  This routine polls the encoder until the value changes.        *
; *           When the first change is found, the direction the knob was     *
; *           turned is recorded in curr_dir.                                *
; *                                                                          *
; *   Input:  The A and B encoder inputs.                                    *
; *                                                                          *
; *  Output:  direction -> an indication the direction the knob was turned.  *
; *                                                                          *
;*****************************************************************************
;
poll_encoder
        clrwdt                    ; Reset the watchdog timer
        movf    PortA,w           ; Get the current encoder value
        movwf   ren_read          ; Save it
        movlw   0x03              ; Get encoder mask
        andwf   ren_read,w        ; Isolate encoder bits
        movwf   encoder_curr      ; Save new value
        xorwf   encoder_old,w     ; Has it changed?
        btfsc   STATUS,B_Z        ; Yes:  Return directin
        goto    poll_encoder      ; No:  Keep looking until it changes
;
;       Determine which direction the encoder turned.
;
        movf    encoder_old,w     ; Get the old encoder value
        call    CW                ; Assume the knob is turning clockwise
        subwf   encoder_curr,w    ; See if the new value matches clockwise
        btfsc   STATUS,B_Z        ; Did we guess right?
        goto    check_ccw         ; No:  Try counter clockwise
        movlw   clockwise         ; Get clockwise flag
        movwf   direction         ; Record the current direction
        goto    exit_poll         ; Prepare to return to the caller
check_ccw
        movf    encoder_old,w     ; Get the old encoder value
        call    CCW               ; Get next value ccw from old encoder value
        subwf   encoder_curr,w    ; See if expected value matches curr encoder
        btfsc   STATUS,B_Z        ; Do they match?
        goto    slip              ; No:  Handle slip
        movlw   counter_cw        ; Get counter clockwise flag
        movwf   direction         ; Record the current direction
        goto    exit_poll         ; Prepare to return to the caller
slip
        movf    encoder_curr,w    ; Get the current encoder value
        movwf   encoder_old       ; Make it the new old value
        goto    poll_encoder      ; Keep looping
exit_poll 
        movf    encoder_curr,w    ; Get the current encoder bits
        movwf   encoder_old       ; Save them in encoder_old for the next time
        return                    ; Return to the caller
;*****************************************************************************
; *                                                                          *
; * Purpose:  This interrupt routine is call gets called on each timer       *
; *           interrupt.  It sends any encoder steps to the main PIC.        *
; *                                                                          *
; *   Input:                                                                 *
; *                                                                          *
; *  Output:  The three-bit encoder speed value, with the high bits cleared  *
; *                                                                          *
; ****************************************************************************
int_server
        movwf   temp_w            ; Save the contents of W
        swapf   STATUS,w          ; Save STATUS contents
        movwf   temp_s            ; 
;
        movlw   0x00              ; Prepare to clear timer 1 interrupt flag
        movwf   PIR1              ; Clear timer 1 interrupt
;
; Strobe tach bit if the tick_count is non-zero.
;
        movf    tick_count,w      ; Get the tick_count
        sublw   0x00              ; Is it zero?
        btfsc   STATUS,B_Z        ; Is the zero bit clear?
        goto    reinit            ; No:  tick_count was zero, skip strobe
;
; The encoder has been turned and the direction has been determined.  
; Display the count code in bits B4-B6 based on the tick_count and the
; following table:
;
;       tick_count              B4-B6 Value
;       1                       0       
;       2                       1
;       3-4                     2
;       5-8                     3
;       9-16                    4
;       17-32                   5
;       33-64                   6
;       64+                     7
;
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x01              ; Check for 1
        btfsc   STATUS,B_Z        ; Was tick_count 1?
        goto    set_zero          ; Yes:  Send 0
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x02              ; Check for 2
        btfsc   STATUS,B_Z        ; Was tick_count 2?
        goto    set_one           ; Yes: Send 1
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x04              ; Was tick_count > 4?
        btfsc   STATUS,B_C        ; Was tick_count > 4?
        goto    set_two           ; No:  Send 2
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x08              ; Was tick_count > 8?
        btfsc   STATUS,B_C        ; Was tick_count > 8?
        goto    set_three         ; No:  Send 3
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x10              ; Was tick_count > 16?
        btfsc   STATUS,B_C        ; Was tick_count > 16?
        goto    set_four          ; No:  Send 4
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x20              ; Was tick_count > 32?
        btfsc   STATUS,B_C        ; Was tick_count > 32?
        goto    set_five          ; No:  Send 5
        movf    tick_count,w      ; Get the tick_count value
        sublw   0x40              ; Was tick_count > 64?
        btfsc   STATUS,B_C        ; Was tick_count > 64?
        goto    set_six           ; No:  Send 6
        movlw   0x07              ; Yes:  Send 7
        goto    send_result       ; Issue value
set_zero
        movlw   0x00              ; Send 0
        goto    send_result       ; Issue value
set_one
        movlw   0x01              ; Send 1
        goto    send_result       ; Issue value
set_two
        movlw   0x02              ; Send 2
        goto    send_result       ; Issue value
set_three
        movlw   0x03              ; Send 3
        goto    send_result       ; Issue value
set_four
        movlw   0x04              ; Send 4
        goto    send_result       ; Issue value
set_five
        movlw   0x05              ; Send 5
        goto    send_result       ; Issue value
set_six 
        movlw   0x06              ; Send 6
        goto    send_result       ; Issue value
set_seven
        movlw   0x07              ; Send 7
send_result                       ; Issue value
        movwf   out_bits          ; Save value
        rlf     out_bits,f        ; Shift to B4-B6 bits
        rlf     out_bits,f              
        rlf     out_bits,f      
        rlf     out_bits,f
;
; Set the direction.
;
        bsf     out_bits,dir_bit  ; Assume clockwise
        movf    direction,w       ; Get the direction value (0=CCW, 1=CW)
        btfss   STATUS,B_Z        ; Clockwise?
        bcf     out_bits,dir_bit  ; No:  Indicate counter clockwise
        movf    out_bits,w        ; Get the bits to output
        movwf   PortB             ; Assert the direction and speed values
;
        bsf     PortB,tach_bit    ; Set the tach bit
        nop                       ; wait a bit
        bcf     PortB,tach_bit    ; Clear the tach bit
;
; Initialize the timer to go for 50ms.
;
reinit
        movlw   0x00              ; Get zero to turn off TIMER1
        movwf   T1CON             ; Turn TIMER1 off
        movlw   0xAF              ; Get low byte
        movwf   TMR1L             ; Set low timer byte
        movlw   0x3C              ; Get high byte
        movwf   TMR1H             ; Set high timer byte
        movlw   0x01              ; Bit to turn on TIMER1
        movwf   T1CON             ; Turn the timer on
;
        movlw   0x01              ; Get the INIT flag
        movwf   init_flag         ; Indicate that we are starting over
        clrf    tick_count        ; Clear the tick count
        swapf   temp_s,w          ; Reswap STATUS, result in W
        movwf   STATUS            ; Restore STATUS
        swapf   temp_w,f          ; Swap saved W, result in temp_w
        swapf   temp_w,w          ; reswap, result in W
        retfie                    ; Return to normal execution
        
;
;*****************************************************************************
; *                                                                          *
; * Purpose:  Wait for a specified number of milliseconds.                   *
; *                                                                          *
; *           Entry point wait_128ms:  Wait for 128 msec                     *
; *           Entry point wait_64ms :  Wait for 64 msec                      *
; *           Entry point wait_32ms :  Wait for 32 msec                      *
; *           Entry point wait_16ms :  Wait for 16 msec                      *
; *           Entry point wait_8ms  :  Wait for 8 msec                       *
; *                                                                          *
; *   Input:  None                                                           *
; *                                                                          *
; *  Output:  None                                                           *
; *                                                                          *
;*****************************************************************************
;
wait_128ms  ; ****** Entry point ******    
        movlw   0xFF              ; Set up outer loop 
        movwf   timer1            ;   counter to 255
        goto    outer_loop        ; Go to wait loops
wait_64ms  ; ****** Entry point ******     
        movlw   0x80              ; Set up outer loop
        movwf   timer1            ;   counter to 128
        goto    outer_loop        ; Go to wait loops
wait_32ms   ; ****** Entry point ******    
        movlw   0x40              ; Set up outer loop
        movwf   timer1            ;   counter to 64
        goto    outer_loop        ; Go to wait loops
wait_16ms   ; ****** Entry point ******    
        movlw   0x20              ; Set up outer loop
        movwf   timer1            ;   counter to 32  
        goto    outer_loop        ; Go to wait loops
wait_8ms   ; ****** Entry point ******     
        movlw   0x10              ; Set up outer loop
        movwf   timer1            ;   counter to 16
                                  ; Fall through into wait loops
;
; Wait loops used by other wait routines
;  - 1 microsecond per instruction (with a 4 MHz microprocessor crystal)
;  - 510 instructions per inner loop
;  - (Timer1 * 514) instructions (.514 msec) per outer loop
;  - Round off to .5 ms per outer loop
;
outer_loop                        
        movlw   0xFF              ; Set up inner loop counter
        movwf   timer2            ;   to 255
inner_loop
        decfsz  timer2,f          ; Decrement inner loop counter
        goto    inner_loop        ; If inner loop counter not down to zero, 
                                  ;   then go back to inner loop again
        decfsz  timer1,f          ; Yes, Decrement outer loop counter
        goto    outer_loop        ; If outer loop counter not down to zero,
                                  ;   then go back to outer loop again
        return                    ; Yes, return to caller
;       
;*****************************************************************************
;
        END


