PDA

View Full Version : Quadrature encoder and ASM Interrupts. questions..



godfodder
- 2nd October 2008, 08:49
Hi all.
I've been lurking around here for about a month now learning my way to getting up to speed on pic programming, I have done numerous basicX and stamp projects in the past, but since discovering the speed and ability of direct programming, the time of stamps is in the way past now. Want to say it's amazing how helpful these forums are, armed with the power of search
there is no reason why anyone can't be up and running in a flash...

So that aside here's my first roadblock, I would like to get up to speed in using asm interrupts
decided to make a project that utilizes a rotary quadrature encoder, (panel type) it's not going to be machine driven, just for human interaction. after searching and discovering numerous threads about multiple ways to read the encoder, I wanted to try what looks like an elegant method of reading the port change interrupt on RB<7:4> to change a variable in the background letting me just check that variable in the main program loop, to display on a basic parallel lcd. so I will first describe my setup/pic/ect.. then explain what issues I am experiencing. Thanks in advance for any help or insight..

I am using a PIC16F876A

Here is my hardware wiring.

( 1) MCLR --> 4.7k --> 5v+
( 2) RA0 --> LCD Data
( 3) RA1 --> LCD Data
( 4) RA2 --> LCD Data
( 5) RA3 --> LCD Data
( 6) RA4 -->
( 7) RA5 -->
( 8) Vss --> Gnd
( 9) OSC1 --> 20mhz Osc
(10) OSC2 --> 20mhz Osc
(11) RC0 --> Status Led1
(12) RC1 -->
(13) RC2 --> Status Led2
(14) RC3 -->
(15) RC4 -->
(16) RC5 -->
(17) RC6 -->
(18) RC7 --> LCD Backlight
(19) Vss --> Gnd
(20) Vdd --> 5v+ --> .1uf Cap --> Gnd
(21) RB0 --> Encoder PushButton --> 10k --> 5v+
(22) RB1 --> LCD RS Bit
(23) RB2 --> LCD Enable Bit
(24) RB3 -->
(25) RB4 -->
(26) RB5 -->
(27) RB6 --> Encoder Phase A --> 10k --> 5v+
(28) RB7 --> Encoder Phase B --> 10k --> 5v+

I have been reading thru the following threads to learn as much as I can about using
various ways to read an encoder.

http://www.picbasic.co.uk/forum/showthread.php?t=1552
Ive based my ISR on Lucianos code from the above thread. (Thanks!!!)

also have looked at using timer interrupts for encoder reading
http://www.picbasic.co.uk/forum/showthread.php?t=1886
I have plans later for the timer, so going to try to stick with portB change for now. (baby steps)

I've also found some other code (non asm, just picbasic) that does a pretty good job
can't seem to find the post at the moment.. but it had a real wierd issue, going one direction
I would miss every 3rd step, but the other way it was perfect..
I know I don't really need the precision that an asm interrupt has for a slow turning encoder,
but would also like to use this oppertunity to learn how to do it efficiently.

so first here is my code...


'problem code

'setup PIC Config Fuses
@ Device pic16F876A, HS_OSC, BOD_OFF, PWRT_ON, WDT_OFF, PROTECT_OFF

'************************ DEFINES HERE *************************************
DEFINE OSC 20 ' set to 20mhz
DEFINE LCD_DREG PORTA ' Set Data Registers port
DEFINE LCD_DBIT 0 ' Set starting data bit
DEFINE LCD_RSREG PORTB ' Set LCD RS Port
DEFINE LCD_RSBIT 1 ' Set LCD RS Bit
DEFINE LCD_EREG PORTB ' Set LCD Enable Port
DEFINE LCD_EBIT 2 ' Set LCD Enable Bit
DEFINE LCD_BITS 4 ' Set LCD 4bit Mode
DEFINE LCD_LINES 2 ' Set number of LCD Lines
DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
DEFINE LCD_DATAUS 60 ' Set Data delay time in uS

clear 'clear out variables

'*********************** CHIP REGISTER SETUP *******************************

ADCON0 = 7
ADCON1 = 7 ' turn off analog porta, set to digital IO
CMCON = 7 ' Turn off Port A Comparator
TRISA = 0 ' output ports
TRISB = %11111001 ' set input and output ports
TRISC = 0 ' output ports

'************************ PROGRAM VARIABLES HERE ***************************

symbol led = portc.2 ' status led
symbol led2 = portc.0 ' status led2
symbol lcdbkl = portc.7 ' lcd pannel backlight
symbol sw1 = portb.0 ' encoder switch

enc_old VAR BYTE
enc_new VAR BYTE
enc_counter VAR WORD
enc_counter_old VAR WORD


'*********************** ASSEMBLY INTERUPT VARIABLES ***********************
wsave var byte $20 system
wsave1 var byte $a0 system ' Necessary for devices with RAM in bank1
wsave2 var byte $120 system ' Necessary for devices with RAM in bank2
wsave3 var byte $1a0 system ' Necessary for devices with RAM in bank3
ssave var byte bank0 system
psave var byte bank0 system

goto start 'skip over interupt handler
'*********************** ASSEMBLY INTERUPT HANDLER *************************

define INTHAND myint
Asm

myint
; BCF INTCON,GIE ;Should I have this????
; Save W, STATUS and PCLATH registers, if not done previously
movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave

movlw high 100 ; Wait 100us to debounce encoder
movwf R0 + 1
movlw low 500
call PAUSEUSL


;====== BEGINNING OF THE ROTARY ENCODER CODE ========
;The Rotary Encoder is connected to PORTB
;The A signal of the encoder connected to the PIN portB.7
;The B signal of the encoder connected to the PIN portB.6
;
;The 3 variables used are declared in the PicBasic code.
;
; enc_new VAR BYTE
; enc_old VAR BYTE
; enc_counter VAR WORD
;
;================================================

;Read latest input from PORTB & put the value in _enc_new.
movf PORTB,W
movwf _enc_new

;Strip off all but the 2 MSBs in _enc_new.
movlw 0xc0 ;Create bit mask (bits 7 & 6). b'11000000' ?
andwf _enc_new,F ;Zero bits 5 thru 0.

;Determine the direction of the Rotary encoder.
rlf _enc_old,F ;left shift it into _enc_old to align bit 6 of
;_enc_old with bit 7 of _enc_new.

movf _enc_new,W ;Move the contents of _enc_new to W in order to XOR.
xorwf _enc_old,F ;XOR previous inputs (in _enc_old) with latest
;inputs (in W) to determine CW or CCW.

btfsc _enc_old,7 ;Test bit 7 of result (in _enc_old). Skip next line
;if it is 0 (direction is CCW).
goto Up ;Bit is 1 (direction is CW). Go around Down
;and increment counter.

Down
;Decrements _enc_counter because the rotary encoder moved CCW.
;Decrements _enc_counter (16 bit value), sets Z on exit.

decf _enc_counter,F ; Decrement low byte
incfsz _enc_counter,W ; Check for underflow
incf _enc_counter+1,F ; Update
decf _enc_counter+1,F ; Fixup
movf _enc_counter,W
iorwf _enc_counter+1,W ; Set Z bit

;Add here code for the CCW LED if needed.
bsf _led ;turn on led

goto Continue ;Branch around UP.

Up
;Increments _enc_counter because the rotary encoder moved CW.
;Increments _enc_counter (16 bit value), sets Z on exit.

incfsz _enc_counter,W ; Add one to low byte
decf _enc_counter+1,F ; No carry (negates next step)
incf _enc_counter+1,F ; Add one to high byte
movwf _enc_counter ; Store updated low byte back.
iorwf _enc_counter+1,W ; Set Z flag

;Add here code for the CW LED if needed.
bsf _led2 ;turn on led

Continue
;Assign the latest encoder inputs (in _enc_new) to _enc_old.
movf _enc_new,W
movwf _enc_old

; Restore saved registers
movf psave, W
movwf PCLATH
swapf ssave, W
movwf STATUS
swapf wsave, F
swapf wsave, W
bcf INTCON, RBIF ; Clear the Interupt Flag
RETFIE ; Return from interrupt
endasm

'************************************************* **************************
'************************* PROGRAM STARTS HERE *****************************
'************************************************* **************************

START: ' Main Program starts here

LCDOUT $FE,1 'Init The LCD
pause 500 'wait for LCD to start

lcdout $FE,1,"ENCODER" 'display splash screen
LCDOUT $FE,$C0," TEST "
high lcdbkl ' turn on backlight


'************************** SET DEFAULT SETTINGS HERE **********************
pause 2000 ' just wait a bit to read splash screen
enc_counter = 1024 ' set default encoder value
enc_counter_old = 1024
lcdout $FE,1,"ENCODER" ' change display
INTCON = %10001000 ' Enable PortB Change Interupts


'************************** TESTING ROUTINES HERE **************************
test:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
lcdout $fe,$C0, DEC5 enc_counter 'display enc_counter value
low led 'turn off CCW led
low led2 'turn off CW led
endif
goto test


ok, questions I have.. [oops post is getting too long... see next post]

godfodder
- 2nd October 2008, 08:52
1) have I missed anything as far as configuration goes?, I've just got a handle on reading and configuring things from the microchip datasheets, still have a ways to go.. so probably missed something stupid.

2) When entering/exiting the asm interrupt I am saving and restoring the processor context, but is there any other things I should be saving/restoring? also should I disable the interrupt once first entered? can an interrupt be triggered while within the interrupt itself?


3) The routine executes everytime a condition changes on the interrupt pins, yet there is no provision in the code to exit if the values didn't change... um ok leme explain that one better. every "indent click" of the encoder the routine gets triggered 4 times (twice for each phase), each time the routine needs to exit with an Up/Down result.. shouldn't there be a exit if nothing changed? this is where i'm a bit fuzzy, I am including a few screenshots bellow of a logic analyzer capture to illustrate this better I hope. basically I get increments of 4 on the counter every click of the encoder.

4) I've added some debounce asm to the routine which seems to work. just want to double
check it is ok way to do it.


5) now my biggest issue which is really confusing. every few turns of the encoder, about a second after, the pic freaks out and triggers the interrupt routine what looks like 100-1000 times. scrambling the counter, and sometimes causing some corruption on the lcd. I've included the logic analyzer capture bellow. What could be causing this??

oh, forgot to mention I am using "Panasonic EVQ-WTEF2515B" rotary encoder.
here is what I managed to find as far as a datasheet. (http://dl.getdropbox.com/u/127158/panasonic_rotaryEnc_datasheet.jpg)

I have used a led on/off to be able to capture when the interrupt is tiggered by my
logic analyzer.

Screen Shot of Logic Analyzer for Multiple Interrupts per indent
http://dl.getdropbox.com/u/127158/encoder_interupts_cu.jpg

Screen Shot of Logic Analyzer for Pic freakout..
http://dl.getdropbox.com/u/127158/bad_interupt1.jpg

Screen Shot of Logic Analyzer Closeup of Pic freakout..
https://dl.getdropbox.com/u/127158/bad_data_closeup.jpg

Thanks again for any help, and looking forward to be able to contribute to the community.

Darrel Taylor
- 2nd October 2008, 11:24
Here's a few things I see off the bat.


myint
; BCF INTCON,GIE ;Should I have this????No, you won't need that. With ASM interrupts, another interrupt can not happen until the current interrupt is finished.


This section is for context saving. But since the PIC you're using has more than 2k of code space, PBP has already inserted the same code. So by the time it gets to your handler, those registers have been changed, and it's saving and restoring the wrong values. These should be commented out.

movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave


The debounce pause is using PBP's system registers which by itself can cause the program to go wacko. Saving and restoring the system registers would help, although I think once you fix these things, you won't need that delay anyhow.
movlw high 100 ; Wait 100us to debounce encoder
movwf R0 + 1
movlw low 500
call PAUSEUSL


PORTB.4 and PORTB.5 will also trigger the PORTB change interrupts.
They seem to be left floating and in input mode. Reason #3 for wacko-ness.
Either pull them up or down with resistors, or set those 2 pins to output, and do not use them for anything else.
<br>

skimask
- 2nd October 2008, 12:43
Now those first two posts should be the PRIME EXAMPLE, a template if you will, for first time posters on the 'what/when/how/why' of how to describe a problem/issue or whatever! It's got almost everything a person would need to help troubleshoot.
Wow!

godfodder
- 2nd October 2008, 21:37
Darrel: Thank you, I will implement your suggestions this evening and post my results.
also brings up another question about interrupts, just to clarify since the pic I am using
has >2k codespace, PBP does the context saving? so only on pic's with <2k I need to
worry about saving the context? do I need to restore it still as well?

skimask: Thanks. after numerous searches it's frustrating to run into a question like
"how does this work.. I need code... blaa bla bla.." searching combined with
some experimentation can get you thru most hurdles. some people just don't try
me thinks.

Darrel Taylor
- 2nd October 2008, 23:11
> since the pic I am using has >2k codespace, PBP does the context saving? so only on pic's with <2k I need to worry about saving the context? do I need to restore it still as well?

Correct! You only need to Save the Context on chips with 2K or less.

The reason is because on chips with more than 2K, the Code Space is divided into several "Pages", and the PCLATH register may have to be set in order to GOTO the interrupt handler from the vector at address 0x0004. But if it changes PCLATH before the Context has been saved, then it will also change the W register that hasn't been saved yet. So PBP has to add the context saving BEFORE it jumps to the handler. (Clear as mud)

Those registers should ALWAYS be restored at the end of the handler, no matter what size the chip is.
<br>

godfodder
- 3rd October 2008, 07:16
I just implemented the changes Darrel suggested and IT WORKS!!! works really well! Thank you again!

I also solved my problem of the encoder routine counting by 4's, added a little asm in the
beginning that checks to see if the encoder has moved, my first bit of asm I've made.. slowly
getting a handle on things.. I also made sure to set portb<5:4> bits to outputs so they don't float.
here is the working code if anyone needs it. the connection diagram is the same as
my first post.



'Read Quadrature Encoder and display value on LCD. for pic16f876a

'setup PIC Config Fuses
@ Device pic16F876A, HS_OSC, BOD_OFF, PWRT_ON, WDT_OFF, PROTECT_OFF

'************************ DEFINES HERE *************************************
DEFINE OSC 20 ' set to 20mhz
DEFINE LCD_DREG PORTA ' Set Data Registers port
DEFINE LCD_DBIT 0 ' Set starting data bit
DEFINE LCD_RSREG PORTB ' Set LCD RS Port
DEFINE LCD_RSBIT 1 ' Set LCD RS Bit
DEFINE LCD_EREG PORTB ' Set LCD Enable Port
DEFINE LCD_EBIT 2 ' Set LCD Enable Bit
DEFINE LCD_BITS 4 ' Set LCD 4bit Mode
DEFINE LCD_LINES 2 ' Set number of LCD Lines
DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
DEFINE LCD_DATAUS 60 ' Set Data delay time in uS

clear 'clear out variables

'*********************** CHIP REGISTER SETUP *******************************

ADCON0 = 7 ' turn off analog porta, set to digital IO
ADCON1 = 7 '
CMCON = 7 ' Turn off Port A Comparator
TRISA = 0 ' output ports
TRISB = %11000001 ' set input and output ports
TRISC = 0 ' output ports

'************************ PROGRAM VARIABLES HERE ***************************

symbol led = portc.2 ' status led
symbol led2 = portc.0 ' status led2
symbol lcdbkl = portc.7 ' lcd pannel backlight
symbol sw1 = portb.0 ' encoder switch

enc_old VAR BYTE
enc_new VAR BYTE
enc_tmp var byte
enc_counter VAR WORD
enc_counter_old VAR WORD
enc_scaler var word




'*********************** ASSEMBLY INTERUPT VARIABLES ***********************
wsave var byte $20 system
wsave1 var byte $a0 system ' Necessary for devices with RAM in bank1
wsave2 var byte $120 system ' Necessary for devices with RAM in bank2
wsave3 var byte $1a0 system ' Necessary for devices with RAM in bank3
ssave var byte bank0 system
psave var byte bank0 system

goto start 'skip over interupt handler
'*********************** ASSEMBLY INTERUPT HANDLER *************************

define INTHAND myint
Asm

myint
; Save W, STATUS and PCLATH registers
; Not Necessary for Chips with >2k of Codespace
; movwf wsave
; swapf STATUS, W
; clrf STATUS
; movwf ssave
; movf PCLATH, W
; movwf psave




;====== BEGINNING OF THE ROTARY ENCODER CODE ========
;The Rotary Encoder is connected to PORTB
;The A signal of the encoder connected to the PIN portB.7
;The B signal of the encoder connected to the PIN portB.6
;
;The 4 variables used are declared in the PicBasic code.
;
; enc_new VAR BYTE
; enc_old VAR BYTE
; enc_tmp VAR BYTE
; enc_counter VAR WORD
;
;================================================

;Read latest input from PORTB & put the value in _enc_new.
movf PORTB,W
movwf _enc_new

;Strip off all but the 2 MSBs in _enc_new.
movlw 0xc0 ;Create bit mask (bits 7 & 6). b'11000000' ?
andwf _enc_new,F ;Zero bits 5 thru 0.

;check to see if encoder has moved
movf _enc_old,W ;move enc_old to W
movwf _enc_tmp ;put W to enc_tmp
movf _enc_new,W ;move enc_new to W for XOR
xorwf _enc_tmp,F ;XOR enc_tmp to detect encoder movement
btfsc _enc_tmp,7 ;if bit is clear, encoder moved.
goto Continue ;no movement exit isr

;Determine the direction of the Rotary encoder.
rlf _enc_old,F ;left shift it into _enc_old to align bit 6 of
;_enc_old with bit 7 of _enc_new.

movf _enc_new,W ;Move the contents of _enc_new to W in order to XOR.
xorwf _enc_old,F ;XOR previous inputs (in _enc_old) with latest
;inputs (in W) to determine CW or CCW.

btfsc _enc_old,7 ;Test bit 7 of result (in _enc_old). Skip next line
;if it is 0 (direction is CCW).
goto Up ;Bit is 1 (direction is CW). Go around Down
;and increment counter.

Down
;Decrements _enc_counter because the rotary encoder moved CCW.
;Decrements _enc_counter (16 bit value), sets Z on exit.

decf _enc_counter,F ; Decrement low byte
incfsz _enc_counter,W ; Check for underflow
incf _enc_counter+1,F ; Update
decf _enc_counter+1,F ; Fixup
movf _enc_counter,W
iorwf _enc_counter+1,W ; Set Z bit


;Add here code for the CCW LED if needed.
bsf _led ;turn on led

goto Continue ;Branch around UP.

Up
;Increments _enc_counter because the rotary encoder moved CW.
;Increments _enc_counter (16 bit value), sets Z on exit.

incfsz _enc_counter,W ; Add one to low byte
decf _enc_counter+1,F ; No carry (negates next step)
incf _enc_counter+1,F ; Add one to high byte
movwf _enc_counter ; Store updated low byte back.
iorwf _enc_counter+1,W ; Set Z flag


;Add here code for the CW LED if needed.
bsf _led2 ;turn on led

Continue
;Assign the latest encoder inputs (in _enc_new) to _enc_old.
movf _enc_new,W
movwf _enc_old

; Restore saved registers
movf psave, W
movwf PCLATH
swapf ssave, W
movwf STATUS
swapf wsave, F
swapf wsave, W
bcf INTCON, RBIF ; Clear the Interupt Flag
RETFIE ; Return from interrupt
endasm

'************************************************* **************************
'************************* PROGRAM STARTS HERE *****************************
'************************************************* **************************

START: ' Main Program starts here

LCDOUT $FE,1 'Init The LCD
pause 500 'wait for LCD to start

lcdout $FE,1,"ENCODER" 'display splash screen
LCDOUT $FE,$C0," TEST "
high lcdbkl ' turn on backlight


'************************** SET DEFAULT SETTINGS HERE **********************
pause 2000 ' just wait a bit to read splash screen
enc_counter = 1024 ' set default encoder value
enc_counter_old = 1024

lcdout $FE,1,"ENCODER" ' change display
lcdout $fe,$C0, DEC5 enc_counter
INTCON = %10001000 ' Enable PortB Change Interupts


'************************** TESTING ROUTINES HERE **************************
test:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
lcdout $fe,$C0, DEC5 enc_counter 'display enc_counter value
low led 'turn off CCW led
low led2 'turn off CW led
endif
goto test

Darrel Taylor
- 3rd October 2008, 08:16
WooHoo! We're on a roll.

People maken stuff happen ... I love it! :D

Stick around godfodder.
<br>

RFsolution
- 14th July 2009, 20:53
After trying your code I got the following compiler error ?

Warnin[207] C:\pbp..... Filename.asm 81 : Found label after column 1. (Device)
Error[122] C:\pbp........ Filename.asm 81 : Illegal opcode (Pic16F876A)

Any idea ?

PBP2.50 and MPASM

Darrel Taylor
- 14th July 2009, 21:07
Any idea ?

DEVICE statements are for the PM assembler.

You could compile it with PM, or change them to __config's for MPASM.
<br>

RossWaddell
- 17th March 2013, 15:45
I just implemented the changes Darrel suggested and IT WORKS!!! works really well! Thank you again!

I also solved my problem of the encoder routine counting by 4's, added a little asm in the
beginning that checks to see if the encoder has moved, my first bit of asm I've made.. slowly
getting a handle on things.. I also made sure to set portb<5:4> bits to outputs so they don't float.
here is the working code if anyone needs it. the connection diagram is the same as
my first post.



'Read Quadrature Encoder and display value on LCD. for pic16f876a

'setup PIC Config Fuses
@ Device pic16F876A, HS_OSC, BOD_OFF, PWRT_ON, WDT_OFF, PROTECT_OFF

'************************ DEFINES HERE *************************************
DEFINE OSC 20 ' set to 20mhz
DEFINE LCD_DREG PORTA ' Set Data Registers port
DEFINE LCD_DBIT 0 ' Set starting data bit
DEFINE LCD_RSREG PORTB ' Set LCD RS Port
DEFINE LCD_RSBIT 1 ' Set LCD RS Bit
DEFINE LCD_EREG PORTB ' Set LCD Enable Port
DEFINE LCD_EBIT 2 ' Set LCD Enable Bit
DEFINE LCD_BITS 4 ' Set LCD 4bit Mode
DEFINE LCD_LINES 2 ' Set number of LCD Lines
DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
DEFINE LCD_DATAUS 60 ' Set Data delay time in uS

clear 'clear out variables

'*********************** CHIP REGISTER SETUP *******************************

ADCON0 = 7 ' turn off analog porta, set to digital IO
ADCON1 = 7 '
CMCON = 7 ' Turn off Port A Comparator
TRISA = 0 ' output ports
TRISB = %11000001 ' set input and output ports
TRISC = 0 ' output ports

'************************ PROGRAM VARIABLES HERE ***************************

symbol led = portc.2 ' status led
symbol led2 = portc.0 ' status led2
symbol lcdbkl = portc.7 ' lcd pannel backlight
symbol sw1 = portb.0 ' encoder switch

enc_old VAR BYTE
enc_new VAR BYTE
enc_tmp var byte
enc_counter VAR WORD
enc_counter_old VAR WORD
enc_scaler var word




'*********************** ASSEMBLY INTERUPT VARIABLES ***********************
wsave var byte $20 system
wsave1 var byte $a0 system ' Necessary for devices with RAM in bank1
wsave2 var byte $120 system ' Necessary for devices with RAM in bank2
wsave3 var byte $1a0 system ' Necessary for devices with RAM in bank3
ssave var byte bank0 system
psave var byte bank0 system

goto start 'skip over interupt handler
'*********************** ASSEMBLY INTERUPT HANDLER *************************

define INTHAND myint
Asm

myint
; Save W, STATUS and PCLATH registers
; Not Necessary for Chips with >2k of Codespace
; movwf wsave
; swapf STATUS, W
; clrf STATUS
; movwf ssave
; movf PCLATH, W
; movwf psave




;====== BEGINNING OF THE ROTARY ENCODER CODE ========
;The Rotary Encoder is connected to PORTB
;The A signal of the encoder connected to the PIN portB.7
;The B signal of the encoder connected to the PIN portB.6
;
;The 4 variables used are declared in the PicBasic code.
;
; enc_new VAR BYTE
; enc_old VAR BYTE
; enc_tmp VAR BYTE
; enc_counter VAR WORD
;
;================================================

;Read latest input from PORTB & put the value in _enc_new.
movf PORTB,W
movwf _enc_new

;Strip off all but the 2 MSBs in _enc_new.
movlw 0xc0 ;Create bit mask (bits 7 & 6). b'11000000' ?
andwf _enc_new,F ;Zero bits 5 thru 0.

;check to see if encoder has moved
movf _enc_old,W ;move enc_old to W
movwf _enc_tmp ;put W to enc_tmp
movf _enc_new,W ;move enc_new to W for XOR
xorwf _enc_tmp,F ;XOR enc_tmp to detect encoder movement
btfsc _enc_tmp,7 ;if bit is clear, encoder moved.
goto Continue ;no movement exit isr

;Determine the direction of the Rotary encoder.
rlf _enc_old,F ;left shift it into _enc_old to align bit 6 of
;_enc_old with bit 7 of _enc_new.

movf _enc_new,W ;Move the contents of _enc_new to W in order to XOR.
xorwf _enc_old,F ;XOR previous inputs (in _enc_old) with latest
;inputs (in W) to determine CW or CCW.

btfsc _enc_old,7 ;Test bit 7 of result (in _enc_old). Skip next line
;if it is 0 (direction is CCW).
goto Up ;Bit is 1 (direction is CW). Go around Down
;and increment counter.

Down
;Decrements _enc_counter because the rotary encoder moved CCW.
;Decrements _enc_counter (16 bit value), sets Z on exit.

decf _enc_counter,F ; Decrement low byte
incfsz _enc_counter,W ; Check for underflow
incf _enc_counter+1,F ; Update
decf _enc_counter+1,F ; Fixup
movf _enc_counter,W
iorwf _enc_counter+1,W ; Set Z bit


;Add here code for the CCW LED if needed.
bsf _led ;turn on led

goto Continue ;Branch around UP.

Up
;Increments _enc_counter because the rotary encoder moved CW.
;Increments _enc_counter (16 bit value), sets Z on exit.

incfsz _enc_counter,W ; Add one to low byte
decf _enc_counter+1,F ; No carry (negates next step)
incf _enc_counter+1,F ; Add one to high byte
movwf _enc_counter ; Store updated low byte back.
iorwf _enc_counter+1,W ; Set Z flag


;Add here code for the CW LED if needed.
bsf _led2 ;turn on led

Continue
;Assign the latest encoder inputs (in _enc_new) to _enc_old.
movf _enc_new,W
movwf _enc_old

; Restore saved registers
movf psave, W
movwf PCLATH
swapf ssave, W
movwf STATUS
swapf wsave, F
swapf wsave, W
bcf INTCON, RBIF ; Clear the Interupt Flag
RETFIE ; Return from interrupt
endasm

'************************************************* **************************
'************************* PROGRAM STARTS HERE *****************************
'************************************************* **************************

START: ' Main Program starts here

LCDOUT $FE,1 'Init The LCD
pause 500 'wait for LCD to start

lcdout $FE,1,"ENCODER" 'display splash screen
LCDOUT $FE,$C0," TEST "
high lcdbkl ' turn on backlight


'************************** SET DEFAULT SETTINGS HERE **********************
pause 2000 ' just wait a bit to read splash screen
enc_counter = 1024 ' set default encoder value
enc_counter_old = 1024

lcdout $FE,1,"ENCODER" ' change display
lcdout $fe,$C0, DEC5 enc_counter
INTCON = %10001000 ' Enable PortB Change Interupts


'************************** TESTING ROUTINES HERE **************************
test:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
lcdout $fe,$C0, DEC5 enc_counter 'display enc_counter value
low led 'turn off CCW led
low led2 'turn off CW led
endif
goto test


Re-opening this thread because I'm having trouble with my rotary encoder ISR routine also seemingly happening 4 times per detent. I thought I had a similar check on whether anything changed in my PBP ISR but for some reason MotorRPM is always incrementing by 4. Any ideas?



' ************************************************** *************
' [IOC - interrupt handler]
' ************************************************** *************
RotEncAdjust:
New_Bits = PORTA & (%00000011)

IF New_Bits = Old_Bits Then DoneRotEnc

RotEncDir = New_Bits.1 ^ Old_Bits.0
IF RotEncDir = 1 Then
; CW rotation - increase speed but only to a max of 'MaxDuty'
IF MotorRPM < MaxDuty then MotorRPM = MotorRPM + 1
Else
' CCW rotation - decrease speed to a min of 0
IF MotorRPM > 0 Then MotorRPM = MotorRPM - 1
EndIF

DoneRotEnc:
Old_Bits = New_Bits

IOCAF.0 = 0 ' Clear interrupt flags
IOCAF.1 = 0

@ INT_RETURN

(A/B outputs of rotary encoder connected to PORTA.1/0)