-
hello everyone!
i'm dealing with a similiar problem this weekend.
i believe that it might be better to solve some of
the functions via hardware, using one D-type flip-flop
and one AND logical gate.
if you connect A to D input of FF and B to a
raising edge clock, the output (Q) would
indicate the direction of movement, it would
be high when A goes high before B and low
when B goes high before A. output of FF would
be connected to B.3 (i want to use 16f628 with
B.1 and B.2 as hardware UART).
then i'd lead A and B through AND gate and connect
its output to B.0/INT (interrupts enabled)
counter would then be changed (incremented
or decremented, according to B.3) whenever
INT occours.
ICE, can you please please send me complete
working code in PBP?
-
Luciano,
Thank you for the clarity and detail with which you answered Ice's questions. I was able to get my own optical encoder running using your guidance and assembly language code.
-
-
Variable Incrementation
Is it possible to increment/decrement enc_counter by 10's or 100's once some threshold has been reached? My encoder is a front panel switch with pushbutton which is used to select and change setpoints, turning the switch 32000 times can't be good for its lifetime not to mention my hand.
-
Hi,
I'm not sure I understand your problem.
* * *
Set the variable enc_counter to 30000.
If the encoder does one step you will
have either 29999 or 30001 in enc_counter. (CCW or CW).
The interrupt assembly code only decrements or increments
the 16 bit value stored in the variable enc_counter.
Is up to your PicBasic code in the main program loop to do
something with the value stored in the variable enc_counter.
Best regards,
Luciano
-
I don't know if this will help or not, but I made some changes to some earlier code I posted which was aimed at the 18F series. Besides there being a few flaws with the syntax of the rotate function in the original code, I just didn't like some aspects of it.
So here is an example of what I am now using to check an encoder's direction and movement (within Interrupt Service Routine):
Code:
chk_encoder
;Read latest input from ENCODER & put the value in NEW.
clrf _newenc ; clear "new" encoder bit pair storage,
btfsc _ENCA ; and transfer state of encoder bits.
bsf _newenc,0
btfsc _ENCB
bsf _newenc,1
;Compare previous encoder inputs (OLD) with latest ones (NEW).
movf _newenc,W ;Move the contents of NEW to TEMP and OLD to W
movwf _tmpenc ;in order to compare them with XOR.
movf _oldenc,W
xorwf _tmpenc,F ;XOR previous inputs (in W) with latest inputs
;(in TEMP) to see if they are equal.
btfsc STATUS,Z ;Test result and skip next line if zero flag clear.
goto restore ;Result = zero. Previous inputs equal latest
;inputs. Rotary encoder did not move. Return
;from interrupt.
;Result is a non-zero value. Rotary encoder moved. Determine direction.
bsf _ENCDRnew ; flag that encoder value has changed
bcf STATUS,C ;Clear the carry bit in the status register and
rlcf _oldenc,F ;left shift it into OLD to align bit 1 of OLD
;with bit 0 of NEW.
movf _newenc,W ;Move the contents of NEW to W in order to XOR.
xorwf _oldenc,F ;XOR previous inputs (in OLD) with latest
;inputs (in W) to determine CW or CCW.
btfsc _oldenc,1 ;Test bit 1 of result (in OLD). Skip next line
;if it is 0 (direction is CCW).
goto encoder_up ;Bit is 1 (direction is CW). Go around Down
;and increment counter.
encoder_down
bcf _direnc ; clear direction bit = CCW (count down)
goto countdown
encoder_up
bsf _direnc ; set direction bit = CW (count up)
;drops into countup part of routine
In this first section:
Code:
;Read latest input from ENCODER & put the value in NEW.
clrf _newenc ; clear "new" encoder bit pair storage,
btfsc _ENCA ; and transfer state of encoder bits.
bsf _newenc,0
btfsc _ENCB
bsf _newenc,1
We are using aliases for the actual encoder "A" and "B" inputs. By doing this , it makes your code more transportable to different devices and/or board configurations.
These aliases would have been earlier equated as follows:
Code:
ENCA VAR PORTC.4 ; any port, any bit
ENCB VAR PORTC.5 ; any port, any bit
Also if your counter is going backwards from the desired direction, all you have to do is swap the previous assignments:
Code:
ENCB VAR PORTC.4 ; any port, any bit
ENCA VAR PORTC.5 ; any port, any bit
And here is the code that determines if the encoder has moved or not:
Code:
;Compare previous encoder inputs (OLD) with latest ones (NEW).
movf _newenc,W ;Move the contents of NEW to TEMP and OLD to W
movwf _tmpenc ;in order to compare them with XOR.
movf _oldenc,W
xorwf _tmpenc,F ;XOR previous inputs (in W) with latest inputs
;(in TEMP) to see if they are equal.
btfsc STATUS,Z ;Test result and skip next line if zero flag clear.
goto restore ;Result = zero. Previous inputs equal latest
;inputs. Rotary encoder did not move. Return
;from interrupt.
Here is where we determine the encoder's direction of travel:
Code:
;Result is a non-zero value. Rotary encoder moved. Determine direction.
bsf _ENCDRnew ; flag that encoder value has changed
bcf STATUS,C ;Clear the carry bit in the status register and
rlcf _oldenc,F ;left shift it into OLD to align bit 1 of OLD
;with bit 0 of NEW.
movf _newenc,W ;Move the contents of NEW to W in order to XOR.
xorwf _oldenc,F ;XOR previous inputs (in OLD) with latest
;inputs (in W) to determine CW or CCW.
btfsc _oldenc,1 ;Test bit 1 of result (in OLD). Skip next line
;if it is 0 (direction is CCW).
goto encoder_up ;Bit is 1 (direction is CW). Go around Down
;and increment counter.
Besides determining which counter routine to execute (countup or countdown), a direction bit (direnc) is also assigned that can be accessed by the main PBP program and used to determine, or display the last known direction of the encoder.
Code:
encoder_down
bcf _direnc ; clear direction bit = CCW (count down)
goto countdown
encoder_up
bsf _direnc ; set direction bit = CW (count up)
;drops into countup part of routine
Lastly, be sure to include this as part of your ISR "restore" code:
Code:
restore
movf _newenc,W ; update encoder OLD to equal NEW
movwf _oldenc
In order to determine if an encoder change has occured within your PBP program just check and then clear the ENCDRnew bit.
And here are the equates for some of the variables I am using:
Code:
newenc var byte
oldenc var byte
tmpenc var byte
direnc var bit
ENCDRnew var bit
I hope this helps and doesn't just confuse the situation,
-
Quote:
Originally Posted by mytekcontrols
I hope this helps and doesn't just confuse the situation,
Michael utilizes a timer interrupt to sample the encoder.
I utilize the interrupt-on-pin change for the A/B encoder signals.
These are two different approaches.
Luciano
-
Opps! I posted to the wroung forum
Luciano is absolutely correct, and I apologize for the confusion I might have caused.
The "encoder check" code I posted is indeed part of a timer interrupt sampled encoder situation. It was actually meant as a continuation for the following thread: http://www.picbasic.co.uk/forum/showthread.php?t=1886 which is a discussion that started out to solve a problem with using indirect addressing in an ASM IRQ. And later delved into the details of a timer based 6 digit rotary encoder interrupt routine.
Sometimes in my rush to release information that's about to pop my head open, I forget where I am. Perhaps topic #46 of this forum can be moved to where it belongs - Lester?
-
Quote:
Originally Posted by
Luciano
File attachment = Incremental quadrature encoder direction drawing.
Luciano
Luciano: five stars!
-
Re: decoding quadrature encoders
Quote:
Originally Posted by
mat janssen
Hallo,
I completed the programm for testing and it works ok.
With the & ~ look in the manual it takes the not value of the bit.
here is the complete source and also the hex file
'PIC 16F628A test
@ DEVICE PIC16F628A,INTRC_OSC
@ DEVICE PIC16F628A,MCLR_OFF
@ DEVICE PIC16F628A,BOD_OFF
@ DEVICE PIC16F628A,LVP_OFF
@ DEVICE PIC16F628A,CPD_OFF
@ DEVICE PIC16F628A,PROTECT_OFF
DEFINE OSC 4
CMCON = 7
VRCON = 0
OPTION_REG.7 = 0
TRISA = %11111111
TRISB = %00000000
A_INPUT VAR PORTA.0
B_INPUT VAR PORTA.1
HULP1 VAR BIT
HULP2 VAR BIT
COUNTER VAR WORD
Clear
COUNTER = 128
START:
HULP2 = A_INPUT & ~ HULP1 'EVERY POSITIVE GOWING EDGE OF A_INPUT
HULP1 = A_INPUT 'GIVES A PULSE OF ONE PROGRAMM CYCLE
IF HULP2 = 1 AND B_INPUT = 1 Then 'MOTOR TURNS RIGHT
COUNTER = COUNTER + 1
EndIF
IF HULP2 = 1 AND B_INPUT = 0 Then 'MOTOR TURNS LEFT
COUNTER = COUNTER - 1
EndIF
PORTB = COUNTER
GoTo START
I'm trying to do the example with PIC16f877a but i have problems for example the VRCON = 0 my compiler says it's a bad instruction how can i probe with this pic
thanks
-
Re: decoding quadrature encoders
877A does not have VRCON register. Instead it has CVRCON.
Look in the P16F877A.inc file in the C:\Program Files\Microchip\MPASM Suite path.
Also pf of the chip you are using is a must.
Ioannis
-
Re: decoding quadrature encoders
Hi Ioannis exactly i'm using PIC16f877a i have the same problem that ice i cant see my count on my LCD and i cant see using pbp
only add my count never sub my count do you know what's my problem?
i tried all in my hands but i continue with same errors
My code is:
Code:
'****************************************************************
'* Name : UNTITLED.BAS *
'* Author : . *
'* Notice : Copyright (c) 2011 [Ing..] *
'* : All Rights Reserved *
'* Date : 19/04/2011 *
'* Version : 1.0 *
'* Notes : *
'* : *
'****************************************************************
'Parte de configuraciŪn de LCD
DEFINE LCD_DREG PORTB
DEFINE LCD_DBIT 4
DEFINE LCD_RSREG PORTB
DEFINE LCD_RSBIT 1
DEFINE LCD_EREG PORTB
DEFINE LCD_EBIT 3
DEFINE OSC 4
contador var byte
ON INTERRUPT GOTO SUMA
INTCON.4 = 1
INTCON.7 = 1
OPTION_REG.6 = 1
CMCON=7
CVRCON=0
TRISB = %00000101
CONTADOR = 12
principal:
lcdout $FE,1,"ENCODER= ",#CONTADOR," VALOR "
pause 500
goto principal
DISABLE
end
SUMA:
IF PORTB.0 = 1 THEN
OPTION_REG.6 = 0
IF PORTB.2 = 1 THEN
contador = contador + 1
ENDIF
else
OPTION_REG.6 = 1
IF PORTB.2 = 1 THEN
CONTADOR = CONTADOR - 1
ENDIF
ENDIF
INTCON.1 = 0
RESUME
ENABLE
-
Re: decoding quadrature encoders
After interrupt you reset Option_Reg.6.
And then never set it again. Is it that what you want?
Ioannis
-
Re: decoding quadrature encoders
Code:
...
else
OPTION_REG.6 = 1
IF PORTB.2 = 1 THEN
CONTADOR = CONTADOR - 1
ENDIF
ENDIF
INTCON.1 = 0
OPTION_REG.6 = 0 'Here???
RESUME
ENABLE
My problem is the count never sub only add and add if the encoder turn left add and if the encoder turn right add too that's my problem
:(
I'm working with
http://www.bourns.com/data/global/pdfs/pec11.pdf
-
Re: decoding quadrature encoders
-
Re: decoding quadrature encoders
Thank you so much Ioannis!! :D
-
Re: decoding quadrature encoders
Hi mat I see you are using ASM to set up your config word, it is a greate idea, but I get a compiler error when I do it. I always edit the device include file 16F628.INC:
NOLIST
ifdef PM_USED
LIST
include 'M16F62x.INC' ; PM header
device pic16F628, intrc_osc, wdt_off, pwrt_off, mclr_off, lvp_off, protect_off
XALL
NOLIST
-
Re: decoding quadrature encoders
With PBP3 you no longer need to comment out the INC file.
BTW you should'nt edit it each and every time. Comment out the DEVICE and CONFIG lines in the INC file, save it... and set the fuses on your code period.
If you still experiment compiler error or warning, check out the syntax used.. is it PM or MPASM... PM need DEVICE line(s), while MPASM need __CONFIG line(s)
Better get familiar with MPASM syntax though... PBP3 use it, PM is no longer supported.
-
Re: decoding quadrature encoders
Hello,
I know this is an old thread but I have reached the extent of my knowledge with PBP. I have been testing the code published by several forum members and the code works wonderful. Using the asm interrupts provides very fast response and no missed counts. The issue I have is trying to display the results on LCD in 4 digit result (xx.xx). I'm using the following hardware:
16F876 @ 20MHz
US digital S4T-360 (360 CPR)
2x16 LCD connected to PortA
I'm using the encoder to measure angles from a center position (12 o'clock). It can turn +/- 90 degrees. I need to display the results as +/- any value between 0 and 90 such as 46.15 or -58.23 (cw,ccw respectively).
Since the count value is one byte, I can't figure out a way to get greater resolution for the count. I'm very new to programming period!!! I realize I have a lot to learn and I will continue to learn.
I've tried using sdec enc_Count/10, enc_count//10 in the LCD lines but the results are not giving me what I need.
Maybe I'm missing something really simple and I hope someone can point me in the right direction.
Here is the code I'm using:
Code:
'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 1000 ' just wait a bit to read splash screen
enc_counter = 0 ' set default encoder value
enc_counter_old = 0
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
' Here is were I struggle.....
'*********************************************************************************
lcdout $fe,$C0, SDEC2 enc_counter/10, ".", SDEC2 enc_counter//10 'display enc_counter value
'*********************************************************************************
low led 'turn off CCW led
low led2 'turn off CW led
endif
goto test
Thank you in advance,
cbrun17
-
Re: decoding quadrature encoders
Hi,
If I'd venture a guess it would be that with a 360CPR encoder and 4x decoding the counter variable will "tick" 4 counts per degree but your math assumes the resolution is 0.1 degrees.
As a simple workaround try multiplying your count value by 2.5 before you enter your display routine.
Code:
' Here is were I struggle.....
enc_counter = enc_counter */ 640 ' Multiply by 2.5
'*********************************************************************************
lcdout $fe,$C0, SDEC2 enc_counter/10, ".", SDEC2 enc_counter//10 'display enc_counter value
'*********************************************************************************
Obviously, the encoder only provides 0.25 degree resolution and no math can change that.
If it doesn't work properly then strip out the formating and display the raw value (pre or post multiplying with 2.5) and tell us between what values it ranges.
/Henrik.
-
Re: decoding quadrature encoders
Hi Henrik,
Thank you so much for the quick response. I will give your suggestion a try this evening and report back with the results.
Kind regards,
Chris
-
Re: decoding quadrature encoders
I changed the multiplier as suggested but I received very erroneous results. Using just "lcdout $fe,$C0, SDEC enc_counter"
The count results in 2,5,7,10,12,15,,,455. So 2 and 3 counts alternately.
Then if I multiply enc_counter by 250, I get exactly + 180 degrees which I think confirms 2 "ticks" Also note that by altering the value of enc_counter, I loose my negative count.
Counting enc_counter without multiplier results in ~ +/- 184.
To clarify, the shaft has a 12:00 o'clock "zero" position. Turning in either direction from center results in ~ +/- 184. Counting the number of pulses on my Fluke counter indicates the same count.
So, the way I understand this quadrature coding, it compares the A/B channels to determine the direction. So changing the post result would only be wiped away by any subsequent updates from the interrupt routine.
I would be satisfied with .25 degree increments if I could get the LCD output correctly. Showing +/- 90 in .25 increments in the form of xx.xx.
Thank you again for sharing your knowledge.
Chris
-
Re: decoding quadrature encoders
So you're getting ~360 counts for 180 degrees movement. Then the interrupt code must be doing 2x decoding instead of 4x - provided the encoder is actually 360cpr.
You loose the negative value because PBP doesn't handle the multiplication properly and you're of course better off copying the enc_counter variable to a second one for scaling and display purposes, sorry about all that.
Try this (untested):
Code:
Degrees VAR WORD
Sign VAR BIT
Degrees = enc_counter ' Degrees is now -180 to 180
Sign = Degrees.15 ' Save sign
Degrees = (ABS Degrees) * 5 ' Degrees is now 0 to 900
If Sign THEN Degrees = -Degrees ' Restore sign, value is now -900 to 900
/Henrik.
-
Re: decoding quadrature encoders
Hi Henrik,
No worries... I learn from mistakes as well. I appreciate all your help.
I believe we're getting very close. I have the following code:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
Counter = enc_counter
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
lcdout $FE,1, sdec Counter / 10, ".", sdec Counter //10
lcdout $FE,$C0, sdec Counter
First, the counter increments by 5.
Also, in the first LCD statement, the count increments to +90 as expected. However, the negative value shows as 6553.1, 6552.6, etc...
The second LCD out statement displays +/- 900 as you explained.
Seems the absolute value gets lost when trying to display negative after formatting.
Is there maybe a way I can parse the decimal values prior to display statement to maintain the value?
Chris
-
Re: decoding quadrature encoders
I made some changes to the formatting and I successfully display the +/- values. Displays +/- xx.x. I updated the code to:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
Counter = enc_counter
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
If NOT sign then
lcdout $FE,1, sdec Counter /10 , ".", sdec Counter // 10
else
lcdout $FE,1, "-", sdec -Counter /10 , ".", sdec -Counter // 10
endif
This takes care of displaying properly. Now is there a way I can bring the count down to .25 increments instead of .5?
Thanks again.
Chris
-
Re: decoding quadrature encoders
Hi,
Great job! The problem with the display routine is, again, that PBP doesn't handle math with negative numbers properly so the result of the Counter / 10 operation gets messed up when Counter is negative.
You can probably make that snippet a bit more efficient, somelike like this perhaps:
Code:
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
Counter = enc_counter
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
LCDOUT $FE, 1 ' Clear screen
IF Sign THEN
LCDOUT "-" ' Print a minus sign if the value is negative
ENDIF
LCDOUT SDEC Counter / 10, ".", SDEC Counter // 10 ' now print the value
I must admit that I can't really follow the ASM code for the interrupt but since it's using Interrupt on change I think it "should" do x4 decoding so you "should" get 1440 counts per revoultion on a 360CPR encoder. I don't know why that's not happening. Hopefully someone else is able to help with that part.
/Henrik.
-
Re: decoding quadrature encoders
Thank you Henrik.
I couldn't have made it this far without you're help. The displayed number is perfect. As you said, the counts aren't making much sense. I'll continue to experiment with this to see if I can figure out how to get the correct resolution.
Thanks again and kind regards....
Chris
-
Re: decoding quadrature encoders
you have some isr issues
1.
; Not Necessary for Chips with >2k of Codespace
; movwf wsave
; swapf STATUS, W
; clrf STATUS
; movwf ssave
; movf PCLATH, W
; movwf psave
; Not Necessary for Chips with >2k of Codespace what ?
yet at the end of your isr pclath and status are restored
all isrs need to save and restore mcu status
2.
the chip you are using has more than 1 code pages ,your isr is located in page 0 ,
pclath should be set accordingly
USUAL
ISR BEGIN
movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave
CLRF PCLATH
YOUR CODE
.............
3.
a RE stream moving clockwise goes phase 1 - 2 - 3 - 4 -1 -2 .......
where phase 1=00
2=10
3=11
4=01
your code
;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
say enc_old=01 ,enc_new=00 ; ie clockwise move
your xor would yield 0b01000000
and bit 7 is clear so no move is recorded , wrong it did move .it may also cause the 0 point to creep as direction
changes occur
the test should be is the result 0 not is bit7 0
4. bank sel ?
your isr forces bank0 the chip has more than one bank available
all vars used in isr need to be assigned to bank 0
ie enc_old VAR BYTE bank0 .
etc
5. enc_counter a word a non atomic entity
any non atomic entity that can be changed in an isr and is used outside the isr needs to be handled
correctly to prevent glitches caused by rollover/under of the low byte.
best way is to use a buffered copy or that entity
eg
enc_counter VAR WORD bank0
buffered_enc_counter VAR WORD
gosub get_enc_count
get_enc_count:
intcon.3=0 ; rbc_int off
buffered_enc_counter=enc_counter
intcon.3=1 ; rbc_int on
return
-
Re: decoding quadrature encoders
Hi Richard,
Thank you very much for the information. It's a little over my head at this point with regard to asm. I'm still very new to asm coding, but I'm going to try working through your suggestions and see what I can accomplish.
Chris
-
Re: decoding quadrature encoders
Hi Richard,
Here is what I've done so far. Since I'm just learning asm (and coding in general), I made the changes you suggested as I understand them (haven't tried to compile yet). The part I am still not too sure about is the reference to is testing bit7. Are you saying I should not be testing bit7 (btfsc _enc_old,7)? How do I reference this in the asm code?
I've tried going through this step by step and it's starting to make a little sense. I guess until I've done this a few times, I'll have to ask some lame questions....
Again, I really appreciate your input.
Code:
'Read Quadrature Encoder and display value on LCD. for ****pic16f876****
'************************ 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 bank0
enc_counter_old VAR WORD bank0
enc_scaler VAR WORD bank0
'*********************** 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
; ****Changed from original isr initialization settings****
;movwf wsave
;swapf STATUS, W
;clrf STATUS
;movwf ssave
;movf PCLATH, W
;movwf psave
; Changed from original isr initialization settings
movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave
CLRF PCLATH
;====== 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. ************Not sure what needs to happen here**************
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 ************Not sure what needs to happen here**************
;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 1000 ' just wait a bit to read splash screen
enc_counter = 0 ' set default encoder value
enc_counter_old = 0
' Initial display
LCDOUT $FE,1,"ENCODER" ' Initial display
LCDOUT $FE,$C0, "00.00"
INTCON = %10001000 ' Enable PortB Change Interupts
'************************** TESTING ROUTINES HERE **************************
test:
if buffered_enc_counter <> enc_counter_old then 'see if value has changed
'enc_counter_old = enc_counter 'move new value to old ******Removed this line and replaced with*******
gosub get_enc_count '******this line******************************
Counter = buffered_enc_counter
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
If NOT sign then
LCDOUT $FE,1, sdec Counter /10 , ".", sdec Counter // 10
else
LCDOUT $FE,1, "-", sdec -Counter /10 , ".", sdec -Counter // 10
endif
'*********************************************************************************
low led 'turn off CCW led
low led2 'turn off CW led
endif
goto test
get_enc_count:
intcon.3=0 ; rbc_int off
buffered_enc_counter=enc_counter
intcon.3=1 ; rbc_int on
return
-
Re: decoding quadrature encoders
Quote:
;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. ************Not sure what needs to happen here**************
goto Continue ;no movement exit isr
this should correct the quadrature decoding ,the big problem with this technique for quadrature decoding is that its is completely intolerant to noise.
given that you encoder is optical I would expect noise problems would be minimal
Code:
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 STATUS,Z ;if result is not zero, encoder moved.
goto Continue ;no movement exit isr
-
Re: decoding quadrature encoders
Hey guy's, Why not just use 2 external interrupt lines (int0 & int1) in high priority mode, and just increment or decrement a counter? I have used this approch for years on my mill and I haven't lost a count yet. I have attached a URL for the front end circuit I used. I have NO problem a tracking 1000 Rpm, 720 CPR encoder using LONG's.
Here is the URL: http://electronicdesign.com/analog/s...ation-encoders
I eliminated the caps and just use 10k resistors. Read the artical as it is informative.
-
Re: decoding quadrature encoders
Because INT0 and INT1 trips on EITHER the rising OR falling edge which means that you either get x1 decoding or that you need to reconfigure the interrupt edge every interrupt. The IOC feature handles that automatically since it interrupts on change - any change.
The circuit in your link provides up/down count but still only x1 decoding, see the pink trace only pulses on the rising edge of the yellow trace, same thing for the green trace. It's effectively using only 25% of the encoders available resolution. A 720CPR encoder, when "properly" read will give you 2880 "pulses" per revolution.
If that's enough resolution it's perfectly fine but in that case I probably wouldn't use interrupts to count the pulse. I'd have hardware timer/counters in the PIC count the pulses and periodically update the position register.
/Henrik.
-
Re: decoding quadrature encoders
Thank you Richard. I will test this and let you know the results.
Also, though the encoder is optical, I am going through a 74C14 hex Schmitt trigger with rc filtering to remove any stray noise. all cabling from the encoder is shielded. Monitoring the output results in clean square wave with fast rise and fall times.
Chris
-
Re: decoding quadrature encoders
Hi Dave,
I did see this article, however, as Henrik mentioned, the circuit described does not interpret quadrature encoding (grey code) to gain accurate angle measurement. I also looked at another solution using an LS7183 which does interpret the output to two separate clock signals (see datasheet here: http://www.lsicsi.com/pdfs/Data_Shee...083_LS7084.pdf), this looks promising if I can't successfully make my code work properly.
I truly appreciate your suggestion.
Chris
-
Re: decoding quadrature encoders
Hi Richard,
The asm change you suggested worked like a charm. I now get the correct counts!
Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.
I can't express how grateful I am for all the help from both you and Henrik.
Here is the working code. I hope others will find this useful.
Code:
'Read Quadrature Encoder and display value on LCD in .25 increments. for pic16f876
'************************ 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 '
CCP1CON = 0
CCP2CON = 0
TRISA = %00000000 ' output ports
TRISB = %11000000 ' set input and output ports
TRISC = %00100000 ' output ports
portB.4 = 0
portB.5 = 0
'************************ PROGRAM VARIABLES HERE ***************************
symbol ledUp = portC.2 ' status led
symbol ledDown = portC.3 ' status led2
symbol lcdbkl = portC.4 ' lcd pannel backlight
symbol sw1 = portC.5 ' encoder switch
symbol ledTest = portC.6
enc_old VAR BYTE
enc_new VAR BYTE
enc_tmp VAR byte
enc_counter VAR word bank0
enc_counter_old VAR word bank0
enc_scaler VAR word
enc_dir VAR enc_old.bit7
Counter VAR word bank0
enc_SIGN VAR counter.bit15
Sign VAR BIT
'*********************** 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
;movwf wsave
;swapf STATUS, W
;clrf STATUS
;movwf ssave
;movf PCLATH, W
;movwf psave
;CLRF PCLATH
;====== 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 STATUS,Z ;if result is not zero, 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 _ledDown ;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 _ledUp ;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
INTCON = %10001000 ' Enable PortB Change Interupts
LCDOUT $FE,1 'Init The LCD
pause 500 'wait for LCD to start
lcdout $FE,$80, "TEST ANGLE DISP " 'display splash screen
LCDOUT $FE,$C0, "Initializing... "
high lcdbkl ' turn on backlight
'************************** SET DEFAULT SETTINGS HERE **********************
pause 2000 ' just wait a bit to read splash screen
lcdout $FE,1
enc_counter = 0 ' set default encoder value
enc_counter_old = 0
Counter = 0
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE: "
'************************** TESTING ROUTINES HERE **************************
test:
If portC.5 = 0 then clearall
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
Counter = enc_counter '(enc_counter */ 250)/2
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
If not sign AND (-Counter > 0 or Counter < 0) then
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", "-",sdec Counter /100 , ".", sdec Counter // 100, " "
else
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", " ", sdec -Counter /100 , ".", sdec -Counter // 100, " "
endif
low ledUp 'turn off CCW led
low ledDown 'turn off CW led
endif
goto test
ClearAll:
enc_counter = 0
enc_counter_old = 0
Counter = 0
clear
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", " 0.00 "
high ledtest
pause 1000
low ledtest
goto test
@INT_RETURN
-
Re: decoding quadrature encoders
Hi Richard,
The asm change you suggested worked like a charm. I now get the correct counts!
Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.
I can't express how grateful I am for all the help from both you and Henrik.
Here is the working code. I home others will find this useful.
Code:
'Read Quadrature Encoder and display value on LCD in .25 increments. for pic16f876
'************************ 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 '
CCP1CON = 0
CCP2CON = 0
TRISA = %00000000 ' output ports
TRISB = %11000000 ' set input and output ports
TRISC = %00100000 ' output ports
portB.4 = 0
portB.5 = 0
'************************ PROGRAM VARIABLES HERE ***************************
symbol ledUp = portC.2 ' status led
symbol ledDown = portC.3 ' status led2
symbol lcdbkl = portC.4 ' lcd pannel backlight
symbol sw1 = portC.5 ' encoder switch
symbol ledTest = portC.6
enc_old VAR BYTE
enc_new VAR BYTE
enc_tmp VAR byte
enc_counter VAR word bank0
enc_counter_old VAR word bank0
enc_scaler VAR word
enc_dir VAR enc_old.bit7
Counter VAR word bank0
enc_SIGN VAR counter.bit15
Sign VAR BIT
'*********************** 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
;movwf wsave
;swapf STATUS, W
;clrf STATUS
;movwf ssave
;movf PCLATH, W
;movwf psave
;CLRF PCLATH
;====== 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 STATUS,Z ;if result is not zero, 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 _ledDown ;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 _ledUp ;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
INTCON = %10001000 ' Enable PortB Change Interupts
LCDOUT $FE,1 'Init The LCD
pause 500 'wait for LCD to start
lcdout $FE,$80, "TEST ANGLE DISP " 'display splash screen
LCDOUT $FE,$C0, "Initializing... "
high lcdbkl ' turn on backlight
'************************** SET DEFAULT SETTINGS HERE **********************
pause 2000 ' just wait a bit to read splash screen
lcdout $FE,1
enc_counter = 0 ' set default encoder value
enc_counter_old = 0
Counter = 0
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE: "
'************************** TESTING ROUTINES HERE **************************
test:
If portC.5 = 0 then clearall
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter 'move new value to old
Counter = enc_counter '(enc_counter */ 250)/2
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
If not sign AND (-Counter < 0 or Counter > 0) then
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", "-",sdec Counter /100 , ".", sdec Counter // 100, " "
else
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", " ", sdec -Counter /100 , ".", sdec -Counter // 100, " "
endif
low ledUp 'turn off CCW led
low ledDown 'turn off CW led
endif
goto test
ClearAll:
enc_counter = 0
enc_counter_old = 0
Counter = 0
clear
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE:", " 0.00 "
high ledtest
pause 1000
low ledtest
goto test
@INT_RETURN
-
Re: decoding quadrature encoders
Quote:
Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.
Direction is a matter of interpretation.
changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7 OR
changing the direction test from "btfsc _enc_old,6" to "btfsc _enc_old,6 OR
swaping the "A","B" connections ,will all cause the "direction " to reverse
I notice you are still incorrectly not saving the mcu context upon isr entry
-
Re: decoding quadrature encoders
Hi Richard,
Not sure why my last post went in twice... Sorry.
When I added the mcu context save routine the display crashed. It will ~ work if I move the shaft slowly, however, moving quickly caused the code to trip over itself and lockup the screen.
My first guess was that when jumping out of the ISR, the next return in the main program uses the most recent return address on the stack and returns to the point the interrupt occurred, crashing the program.
However, I noticed my up/down LED indicators are still operational which suggests the code is still working okay and its either the formatting or calculations that are causing some latency. if I perform a display reset, the counting resumes normally.
So I made two changes which seem to have resolved the issue.
I removed the two LCD defines:
DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
DEFINE LCD_DATAUS 60 ' Set Data delay time in uS
And added a 50ms pause after the display routine to give the display time to settle.
I know there must be a more efficient way to format the display to reduce the number of instructions necessary to accomplish the xx.xx by .25 increment, but this seems to smooth out he results.
Thanks again for you feedback.
Chris
-
Re: decoding quadrature encoders
Hi Chris,
In your test routine loop you're re-Writing alot of static text on the LCD and you're doing some "strange" things to get the correct formating. Here's an idea which probably is a bit more efficient:
Code:
' Print static information on LCD once, then leave it alone.
lcdout $FE,$80, "Measurement "
lcdout $FE,$C0,"TST ANGLE: "
'************************** TESTING ROUTINES HERE **************************
test:
If portC.5 = 0 then clearall
if enc_counter <> enc_counter_old then 'see if value has changed
enc_counter_old = enc_counter ' move new value to old
Counter = enc_counter
Sign = Counter.15 ' Save sign
Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
' Not doing this here, later (or not at all).
'If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
LCDOUT $FE, $CB 'Position cursor at 11th character on 2nd row.
IF SIGN THEN
LCDOUT "-"
ELSE
LCDOUT " "
ENDIF
LCDOUT DEC Counter / 100, ".", DEC Counter // 100, " "
' To save execution cycles you're better off not using HIGH/LOW
low ledUp 'turn off CCW led
low ledDown 'turn off CW led
' If the only purpose of the Counter variable is to display the value
' the there's no need to restore the sign of it so the following line
' could be deleted.
If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
endif
goto test
/Henrik.