PDA

View Full Version : decoding quadrature encoders



ice
- 6th April 2005, 08:39
Hello,
is there a sample code for interfacing quadrature encoders[a/b channels]
I need to determine position/direction...
i searchd for posts in the pbp forum,but they were all for 1 channel

Thank you
-Gerry

mat janssen
- 6th April 2005, 10:50
Here is a small example how to count up and down a position counter.
How to initialise the position counter you must do by yourself.

A_INPUT VAR PORTA.0
B_INPUT VAR PORTA.1

HULP1 VAR BIT
HULP2 VAR BIT

COUNTER VAR WORD

Clear
COUNTER = 32768

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

Luciano
- 6th April 2005, 10:59
Hi!

Maybe these two links will help you out.

Nuts and Volts:
http://www.parallax.com/dl/docs/cols/nv/vol1/col/nv8.pdf

See State machine: two-bit quadrature encoder
http://www.emesystems.com/BS2fsm.htm#twobit%20encoder

Luciano

ice
- 8th April 2005, 07:39
mat janssen>>
Thank yoiu very much for the code snip..but i cant understand whats going on in the code ,esp the "&~" part
I tried using the code...however ,whatever maybe the direction the motor is running ....i get the same reading ,say always "left"



Luciano>>
Thanks for the links..
i ported the foll code from nv8 [http://www.parallax.com/dl/docs/col...ol1/col/nv8.pdf --pg7]


let new = pins & %11000000 ' Mask off all but bits 6 & 7
start:
let old = new & %11000000 ' Mask bits and copy new into old.
again:
let new = pins & %11000000 ' Copy encoder bits to new.
if new = old then again ' If no change, try again.
let directn = bit6 ^ bit15 ' XOR right bit of new w/ left bit of old.
if directn = 1 then CW ' If result=1, encoder turned clockwise.
serout 0,n2400,(I,left) ' If result=0, counterclock (scroll left).
goto start ' Do it again.
CW:
serout 0,n2400,(I,right) ' Clockwise (scroll right).
goto start ' Do it again.



To pbp




old VAR BYTE
new VAR BYTE
direct VAR BIT

quad:
new= PORTC && %00000011

start:
old=new && %00000011

again:
new= PORTC && %00000011
Pause 10
LCDOut $fe,1,"no movement"
IF new=old Then again
direct=new.bit1 ^ old.bit0
IF direct=0 Then GoTo cw
LCDOut $fe,1,"one dir"
Pause 10
GoTo start

cw:
LCDOut $fe,1,"2nd dir"
Pause 10
GoTo start


with the above code...there is alot of fluctuation between the 2 directions..

..anything going wrong in my code?.i sat on it the whole day..still cant figure out the problem

mat janssen
- 8th April 2005, 08:29
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

Luciano
- 8th April 2005, 11:31
Hello ICE!

You need the Bitwise Operator:
&

Not the logical Operator AND:
&& (AND and && are the same)


From the PBP manual:

4.17.14. Bitwise Operators

Bitwise operators act on each bit of a value in boolean fashion.
They can be used to isolate bits or add bits into a value.


4.19. Logical Operators

Logical operators differ from bitwise operations. They yield a true/false
result from their operation. Values of 0 are treated as false. Any other
value is treated as true.


Luciano

ice
- 8th April 2005, 12:35
Luciano,
I tried it with & [bitwise operators]...actually everything..
still no go...

mat janssen>>
are u by any chance using a dual D f/f between the encoder and the PIC,because im not..
Thanks for ur code snip

mat janssen
- 8th April 2005, 12:54
No, this small programm reacts as a D flipflop.
see attached drawing what happens

Luciano
- 8th April 2005, 14:06
Hello ICE,

From the PDF file page 3:
http://www.parallax.com/dl/docs/cols/nv/vol1/col/nv8.pdf

In the case of the encoder sequence, it turns out that for
any given sequence, XORing the righthand bit of the old value
with the lefthand bit of the new value tells you the direction
of rotation.
For example, take the clockwise sequence 01 00: 1 XOR 0 = 1.
Now the counter-clockwise sequence 01 11: 1 XOR 1 = 0.
This relationship holds for any pair of numbers in either direction.

* * *
The code for the above is:

direction_bit = old.bit0 ^ new.bit1 ' 1 if CW, 0 if CCW

* * *
In your code:

Pause 10 '= 10 milliseconds

* * *
This will help you to debug your code.

Remove the line
LCDOut $fe,1,"no movement"

Replace the lines
LCDOut $fe,1,"one dir" with LCDOut $fe,1,"Last direction was CCW"
LCDOut $fe,1,"2nd dir" with LCDOut $fe,1,"Last direction was CW"


Luciano

mister_e
- 8th April 2005, 22:13
be sure you have pull-up or pull down attach to both output of your encoder.

depending of you rotary encoder model, you can try this thread (http://www.picbasic.co.uk/forum/showthread.php?t=778&highlight=rotary+encoder) and monitor the 'value' variable. or modify it easily to have CW and CCW flag in one shot....

Luciano
- 9th April 2005, 13:44
File attachment = Incremental quadrature encoder direction drawing.

Luciano

ice
- 12th April 2005, 09:58
Luciano>>
Thanks for the file..i figured out the logic of the "XOR"..

i included this in my code...[the counting up and counting down lines]




quad:
new= PORTC & %00000011

start:
old=new & %00000011

again:
new= PORTC & %00000011
IF new=old Then again
direction_bit=old.bit1 ^ new.bit0
IF direct=0 Then cw
LCDOut $fe,1,"Last was CCW"
counter=counter+1
LCDOut $fe,$c0,DEC counter
Pause 5
GoTo start

cw:
LCDOut $fe,1,"Last was CW"
counter=counter-1
LCDOut $fe,$c0,DEC counter
Pause 5
GoTo start


...I am able to see the counter going up and down as the motor changes it's direction..however the LCD line "last was cw/ccw"..keeps on flipping and never remains the same..even if the motor has moved in the same direction..

Im still working on it..and will keep u all posted...



monitor the 'value' variable. or modify it easily to have CW and CCW flag in one shot....

Thanks for the link M_e
..i'll be doing tht to take care of the above problem

Luciano
- 12th April 2005, 11:00
Try this code. (Not tested).

(Problem in your code: you use 'direction_bit' but also 'direct').

Luciano



old_val VAR BYTE
new_val VAR BYTE
direction_bit VAR BIT
enc_counter VAR WORD

enc_counter = 10000
new_val = PORTC & %00000011
start:
old_val = new_val
again:
new_val = PORTC & %00000011

IF new_val = old_val Then again

direction_bit = old_val.bit0 ^ new_val.bit1

IF direction_bit = 1 Then

LCDOut $fe,1,"Last was CW"
enc_counter = enc_counter + 1
LCDOut $fe,$c0,DEC enc_counter

else
LCDOut $fe,1,"Last was CCW"
enc_counter = enc_counter - 1
LCDOut $fe,$c0,DEC enc_counter

endif

GoTo start

ice
- 18th April 2005, 08:31
Thank you for all your help.
Theres a small problem though.

The encoder program counts faster in one direction and slower in the other...

is it because ...
When im counting in one of the directions ,im utilizing the ADC[ADCIN and an IF statement]

..whereas in the other direction,im not.
The program logic is the same as above.

Luciano
- 18th April 2005, 08:55
I don't understand.
If you need help post your code.

Luciano

ice
- 18th April 2005, 12:26
before i go ahead with my code....ill explain wht i have to do

I need to control the position of a DC motor[geared],using a potentiometer

I have to first detect the endpoints of a gear assembly.
say, i start with a count of 32500 and the count at one end of the gear assembly is 32600
..now i rotate it to the other side ,say the count at this point is 32400

The above was just the calibration routine.Using the above values ,i have to
scale the 200 counts[32600-32400] to a potentiometer value[0 to 255]

How am i detecting the end points?
..im using the LMD18200T motor driver .Using the current sense feature of the chip,i detect wheteher the gear assembly has reached an end point.The current sense pin is connected to an ADC port..portA.1 namely


my program flow...
1> set counter to 32500
2> move motor in one direction
3> count pulses..if CW,increment.if CCW decrement
...say it is CW and counter increments to 32600 till the end
4>counter_1 has 32600
5>move the motor to the other side,count the pulses..it is decrementing now
6>so counter_2 has 32400
7>now i have to scale the gear assembly range to a potentiometer
difference=counter_1-counter_2
=32600-32400
=200
8> scale to a pot value
pot_val holds the pot value connected to analog portA.0

hence required count value=[difference/256]*pot_val+counter_2
=[difference*pot_val]/256+counter_2 (rearranging)

example...if potvalue in 128,then
required count value=(200*128)/256+32400
=32500

now i dont know where to go from here..how do i get the motor to move to position 32500,when the pot is moved to 128

...this is a long post..but its the best i could do to explain wht im trying to do.
-Gerry

Luciano
- 18th April 2005, 22:08
If you are losing encoder counts, then use an interrupt service routine.
Four of PORTB’s pins, RB<7:4>, have an interrupt-onchange feature.
Use two of these pins for the A/B encoder signals.

Luciano

ice
- 3rd May 2005, 12:58
ok,heres my program ,using interrupts..

the problems i'm encountering are below the code
..



DEFINE LOADER_USED 1


led VAR PORTB.7
old VAR BYTE
new VAR BYTE
direction_bit VAR BIT
counter VAR WORD
counter_1 VAR WORD
counter_2 VAR WORD
direction_store VAR BIT
adval VAR WORD ' Create adval to store result
potval VAR WORD
servo_val VAR WORD
difference VAR WORD


counter=32500

HPwm 0,150,20000
OPTION_REG = $7f
ON INTERRUPT GoTo myint ' Define interrupt handler
INTCON = $90 ' Enable INTE interrupt



loop: High led ' Turn LED on

LCDOut $fe,1,"count:"
LCDOut $fe,$c0,DEC counter
'Pause 10

GoTo loop ' Do it forever




' Interrupt handler
Disable ' No interrupts past this point
myint: Low led ' If we get here, turn LED off


enc: new= PORTB & %00000011


start_1:
old=new & %00000011

again:

new= PORTB & %00000011 'load new value of A/B channels into new by anding with HEX3


'IF new=old Then again
direction_bit=old.bit1 ^ new.bit0 'check direction


IF direction_bit = 1 Then
LCDOut $fe,1,"Last was CW"
counter = counter + 1 'increment counter
LCDOut $fe,$c0,DEC counter," ",BIN direction_bit

Else
LCDOut $fe,1,"Last was CCW"
counter = counter - 1 'decrement counter
LCDOut $fe,$c0,DEC counter," ", BIN direction_bit

EndIF
Pause 10



INTCON.1 = 0 ' Clear interrupt flag
Resume ' Return to main program
Enable



1>The interrupts are worng fine,however the code is still unable to distinguish between CW and CCW,because of which the counting goes awry..

2>if i remove the "if then " statement for CW/CCW direction and only put a
counter=counter+1,it works well


The whole problem seems to be the direction decoding part..
..i cant seem to find a solution to it..

Luciano
- 3rd May 2005, 19:17
Hi!

1.
You are using the wrong interrupt.
Four of PORTB’s pins, RB<7:4>, have an interrupt-onchange feature.
Use two of these pins for the A/B encoder signals.

2.
Use assembly code for the interrupt.
(See PicBasic Pro manual).

3.
In your code the variables "new" and "old" will have
most of the time the same values.

In your code you are doing that:

new= PORTB & %00000011 'get encoder A/B
old=new & %00000011 'old is now same as new
new= PORTB & %00000011 'get encoder A/B again

When you get A/B again, it is very likely that the encoder is
still in the same position, therefore the variables new and old
will have the same values.

* * * *

Did you try the code I posted the 12th April 2005, 11:00?
Does it work if you turn the encoder slowly?

Luciano

ice
- 4th May 2005, 10:48
Luciano,

when i try the program u recommended [12th april] as below,
there are alot of instances when the program gets confused with direction,
so it sometime increments and decrements.Im not using interrupts

when i move the motor slowly,the problem still persists.



old_val VAR BYTE
new_val VAR BYTE
direction_bit VAR BIT
enc_counter VAR WORD
adval VAR WORD ' Create adval to store result
potval VAR WORD
servo_val VAR WORD
difference VAR WORD

TRISB=%11111111
enc_counter=32500
HPwm 0,100,20000



new_val = PORTB & %00000011
start:
old_val = new_val


again:
new_val = PORTB & %00000011
'LCDOut $fe,1,"no move"
IF new_val = old_val Then again

direction_bit = old_val.bit0 ^ new_val.bit1

IF direction_bit = 1 Then

LCDOut $fe,1,"Last was CW"
enc_counter = enc_counter + 1
LCDOut $fe,$c0,DEC enc_counter

Else
LCDOut $fe,1,"Last was CCW"
enc_counter = enc_counter - 1
LCDOut $fe,$c0,DEC enc_counter

EndIF
GoTo start



Thank you for your responses

Luciano
- 4th May 2005, 17:08
Hi!

I need more info.

The version of the PIC used. (16F628, 16F628A, ...).

If you are using a commercial board for the PIC, I need
the URL where I can see the board.

The brand and model number of the encoder and the URL where I can
download the data sheet.

I also need to know how you have connected the encoder to
PORTB.0 and PORTB.1. (Pull-up resistors? If yes, values).


Luciano

ice
- 5th May 2005, 06:59
Luciano,


The version of the PIC used. (16F628, 16F628A, ...).

im using the PIC16F873A



If you are using a commercial board for the PIC, I need
the URL where I can see the board.

I made my own dev board..has an LCD,A/D..the usual features..
works on a 4Mhz crystal..
The board works perfectly as i have run accelerometers,D/A converters from the same board



The brand and model number of the encoder and the URL where I can
download the data sheet.
Maxon 22578
500 counts per turn
http://www.maxonmotor.com/docsx/Download/catalog_2005/Pdf/05_238_e.pdf


I also need to know how you have connected the encoder to
PORTB.0 and PORTB.1. (Pull-up resistors? If yes, values).
Pull up resistors
with value 1.8K


Thanks for all your help

ice
- 5th May 2005, 08:23
ok,a little progress,
seems like i'm loosing counts ,because of the LCDout commands

if i make my code tighter[remove the LCDcommands],i am able to monitor direction ,but a bit of interference still persists.
I'm on a 4Mhz..i'll switch to a 20Mhz tomorrow and post my progress

Luciano
- 5th May 2005, 21:52
When you try the example code with the LCD, turn the encoder very slowly
with your fingers.(About 1 degree per second).The example is just to see
that the decode technique works.

For the real code you need to use the interrupt-onchange feature, and the
code has to be written in assembly otherwise you will lose encoder counts.
(Read the PicBasic Manual about interrupts in Basic and assembly).

The encoder and pull-up resistors are OK.

Luciano

ice
- 6th May 2005, 07:36
yes works beautifully at 3 degrres per second,im using PWM thru a motor driver...the decode program works well

ill have to work on the interrupts..it will take time...
is there an example or "guide" code in assembly..it's a bit confusing in assembly,.
Ill try it anyway,will keep you posted.

Luciano
- 6th May 2005, 09:12
Hello,

Probably you already know that.An interrupt in PicBasic
language will not work for the rotary encoder.
You must use an interrupt in assembly.

The assembly code for the interrupt will be simple.
In addition of what you have to save and restore
for the correct functioning of PicBasic, the assembly
code for the rotary encoder is just detect its direction
and increase or decrease the counter variable and maybe set
a flag for the direction if you need this info in the basic program.
The rest is done by the basic program once you exit the assembly
interrupt routine.

Read section 29 Instruction Set of the Mid-Range MCU Family Reference Manual.
http://ww1.microchip.com/downloads/en/DeviceDoc/33023a.pdf

To do:

Understand the differences between interrupts in PicBasic and
interrupts in assembly and understand why you need an assembly
interrupt for your rotary encoder. (PicBasic Manual).

How to declare a variable in basic and use
it in the interrupt assembly code. (Share variables
between basic and assembly, see PicBasic Manual).

Understand what you need to save and restore when
you enter and exit the assembly interrupt. (PicBasic Manual).

In PicBasic language, try first the interrupt-onchange feature.
The A/B signals of the encoder will be connected of two of
these PINs that have the interrupt-onchange feature.
(This is not the interrupt on RB0).
Once the interrupt-onchange feature works in basic then
you will be ready to implement that in assembly.

RB<7:4>, have an interrupt-onchange feature.

Four of PORTB’s pins, RB<7:4>, have an interrupt-onchange
feature. Only pins configured as inputs can
cause this interrupt to occur (i.e., any RB<7:4> pin configured
as an output is excluded from the interrupt-onchange
comparison). The input pins (of RB7:RB4) are
compared with the old value latched on the last read of
PORTB. The “mismatch” outputs of RB7:RB4 are
OR’ed together to generate the RBIF interrupt (flag
latched in INTCON<0>).

INTCON = %10001000 'Enable RBIE
INTCON.0 = 0 'Clear Port Change Interrupt Flag bit

Read section 8 of the Mid-Range MCU Family Reference Manual.
http://ww1.microchip.com/downloads/en/DeviceDoc/33023a.pdf

AN566 - Using the PORTB Interrupt on Change as an External Interrupt.
http://ww1.microchip.com/downloads/en/AppNotes/00566b.pdf

Best regards,

Luciano

ice
- 6th May 2005, 12:06
Thanks Luciano,
Im read up the pbp manual and others for interrupts...
..however my test program isnt compiling..



wsave VAR BYTE $20 system 'bank0
wsave1 VAR BYTE $a0 system 'bank1
'wsave2 VAR BYTE $120 system 'bank2
'wsave3 VAR BYTE $1a0 system 'bank3
ssave VAR BYTE bank0 system
psave VAR BYTE bank0 system
led VAR PORTB.1

GoTo start_0 ' Skip around interrupt handler


DEFINE INTLHAND myint ' DEFINE INTERRUPT handler
Asm
myint: bsf _led ; Turn on LED (for example)
bcf intcon.0

retfie
EndAsm



' Assembly language INTERRUPT handler



start_0:

TRISB=%11111101
Low led ' Turn LED off
INTCON = %10001000 ' Enable INTERRUPT ON PORTB.0


loop: GoTo loop ' Wait here till interrupted



1> compile error "DEFINE INTHAND myint"
....undefined symbol myint
however if i relpace it with DEFINE INTLHAND myint,(which is only for th 18x series),it complies ok..

2>im using a 16F873A,which has a 4K flash,meaning 4 banks...
according to the PBP manual, i have to define wsave,wsave1,wsave2,wsave3...
it takes in wsave and wsave1...but when i declare wsave2 and wsave3..it gives a RAM_END 255 error

3>i cannot get the LED to blink either by using the int0 or the Interr On change<4:7>

ice
- 7th May 2005, 08:09
Hmmm..
I got the program to compile and work with interrupts on portB<4:7>

Ill try to implement the quadrature decoding in assembly now..

Luciano
- 7th May 2005, 08:55
Example from:
http://www.microengineeringlabs.com/resources/pbpmanual/9_0.htm

Luciano



' Assembly language interrupt example

led var PORTB.1

wsave var byte $20 system
ssave var byte bank0 system
psave var byte bank0 system


Goto start ' Skip around interrupt handler

' Define interrupt handler
define INTHAND myint

' Assembly language interrupt handler
asm
; Save W, STATUS and PCLATH registers
myint movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave

; Insert interrupt code here
; Save and restore FSR if used

bsf _led ; Turn on LED (for example)

; Restore PCLATH, STATUS and W registers
movf psave, W
movwf PCLATH
swapf ssave, W
movwf STATUS
swapf wsave, F
swapf wsave, W
retfie
endasm

' PICBASIC PRO™ program starts here
start: Low led ' Turn LED off

' Enable interrupt on PORTB.0
INTCON = %10010000

loop: Goto loop ' Wait here till interrupted

ice
- 7th May 2005, 10:13
Thanks Luciano,
But it's the same program as in the datasheet...
Im now trying to write the decoding program in assembly..

Luciano
- 9th May 2005, 11:32
Rotary encoder in assembly for 16F84:
http://emp.byui.edu/fisherr/EET255/encdr.asm

Luciano

ice
- 11th May 2005, 10:37
Luciano,
I added the code from the lin you posted..
I'm using interrupt on INT0 connected to channel A..


I'm able to count and detect directions..with 1 big problem

i cannot monitor or dispaly the counts either using LCDout or software serial to hyperterminal.
it's good for 2 seconds and then the whole LCD screen corrupts
...is it because the LCDout line is being interrupted by the encoder
...if i disable/enable interrupts within the LCDout line..i lose encoder counts


in the code..i use 2 leds connected to portB.6,7 to show direction.
LOOP is my main program

my code is below


GoTo start ' Skip around interrupt handler


DEFINE INTHAND display ' DEFINE INTERRUPT handler
Asm
display
movwf wsave
swapf STATUS, W
clrf STATUS
movwf ssave
movf PCLATH, W
movwf psave


;;;bsf _led ; Turn on LED (for example)
;new=PORTB & %11000000

;Get intitial input from ENCODER & put the value in OLD.
movf PORTB,W
movlw _old

;Strip off all but the last 2 LSBs in OLD.
movlw 0x03 ;Create bit mask for 2 LSBs (bits 6 & 7). 11000000
andwf _old,F ;Zero bits 0 thru 6.

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

;Strip off all but the 2 LSBs in OLD.
movlw 0x03 ;Create bit mask for 2 LSBs (bits 6 & 7). 11000000
andwf _new,F ;Zero bits 0 thru 6.





movf _new,W ;Move the contents of NEW to TEMP and OLD to W
movwf _temp ;in order to compare them with XOR.
movf _old,W
xorwf _temp,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 0.

GoTo rtrn ;Result is 0. Previous inputs equal latest
;inputs. Rotary encoder did not move. Return
;to loop

;Result is 1. Rotary encoder moved. Determine direction.
bcf STATUS,C ;Clear the carry bit in the status register and
rlf _old,F ;left shift it into OLD to align bit0 of OLD
;with bit 1 of NEW.
movf _new,W ;Move the contents of NEW to W in order to XOR.
xorwf _old,F ;XOR previous inputs (in OLD) with latest
;inputs (in W) to determine CW or CCW.
btfsc _old,1 ;Test bit 1 of result (in OLD). Skip next line
;if it is 0 (direction is CCW).
GoTo Up ;Bit is 1 (direction is CW). Go around Down



Down
;Decrements COUNTER because encoder moved CCW.

decf _enc_counter,F
bsf _led
GoTo rtrn ;Branch around UP.

Up
;Increments COUNTER because encoder moved CW.

incf _enc_counter,F
bsf _led2
;and increment counter.


rtrn bcf intcon.1 ; clear interrupt flag

; Restore PCLATH, STATUS and W registers
movf psave, W
movwf PCLATH
swapf ssave, W
movwf STATUS
swapf wsave, F
swapf wsave, W
retfie
EndAsm

start:

'SerOut PORTC.0,T9600,[#enc_counter,10,13]
TRISB=%00111111
TRISC=%00000000
Low led
Low led2 ' Turn LED off
INTCON = %10010000 ' Enable INTERRUPT ON portb INT0
option_reg=%01110000

loop:
'INTCON = %00000000
'LCDOut $fe,1,DEC enc_counter
'Pause 4
'INTCON = %10010000
Low led
Low led2
GoTo loop

Luciano
- 11th May 2005, 13:26
Hi!

If you use INT0 you will lose encoder counts.
See my Incremental quadrature encoder direction drawing.
(File encoder.zip posted the 9th April 2005, 13:44).

The two encoder signals A/B have to be connected to two of
the PINs RB<7:4> which have an interrupt-onchange feature.

* * *
When I post a code sample, the code is well formatted and
there are no dead commented statements.(I do my best).
Do you think that people on this forum will spend time
reading your code if it is not formatted?

* * *

See thread LCDOUT & interrupts.
http://www.picbasic.co.uk/forum/showthread.php?t=1559&highlight=lcdout+interrupt

* * *

Luciano

ice
- 12th May 2005, 12:20
Luciano,
I switched to portB.6 and 7 for interrupt on change and modified the code

However i cannot decode direction now.I use LEDs to display direction..Now only one of them lights for both directions.Thanks.

**
i'm sorry abt my code being all garbled.Ive done my best this time.
**




movf PORTB,W
movlw _old

;Strip off all but the last 2 MSBs in OLD.
movlw 0xc0
andwf _old,F

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

;Strip off all but the 2 MSBs in NEW
movlw 0xc0
andwf _new,F

movf _new,W ;Move the contents of NEW to TEMP and OLD to W
movwf _temp ;in order to compare them with XOR.
movf _old,W
xorwf _temp,F ;XOR previous inputs (in W) with latest inputs
;(in TEMP) to see if they are equal.
btfss STATUS,Z ;Test result and skip next line if 0.

GoTo rtrn ;Result is 0. Previous inputs equal latest
;inputs. Rotary encoder did not move. Return
;to loop

bcf STATUS,C ;Clear the carry bit in the status register and
rlf _old,F ;left shift it into OLD to align bit6 of OLD
;with bit7 of NEW.
movf _new,W ;Move the contents ofNEW to W in order to XOR.
xrwf _old,F ;XOR previous inputs (in OLD) with latest
;inputs (in W) to determine CW or CCW.
btfss _old,7 ;Test bit 1 of result (in OLD). Skip next line
;if it is 0 (direction is CCW).
GoTo Up ;Bit is 1 (direction is CW). Go around Down

Down
;Decrements COUNTER because encoder moved CCW.
decf _enc_counter,F
bsf _led ;light CCW LED
GoTo rtrn ;Branch around UP.

Up
;Increments COUNTER because encoder moved CW.
incf _enc_counter,F ;and increment counter.
bsf _led2 ;light CW LED

rtrn bcf intcon.0 ;clear interrupt flag on portBinT

Luciano
- 12th May 2005, 22:18
Hi,

Below is one part of the interrupt assembly code.
(I have tested the code with the MPLAB simulator).

This is not the end of the difficulties.

I can help (the forum can help) if you do the following:

Write a PicBasic program that displays on the LCD the
rotary encoder count. The program will use an
interrupt-onchange in assembly for the encoder.
(Please write a new well formatted program).

The assembly code I have posted today is only one
part of the code you will need in the interrupt routine.
BEFORE my code you will have to save the processor context.
AFTER my code you will have to restore the processor context.
See PicBasic manual '9.3. Interrupts in Assembler' for
everything else you will need in order to use an interrupt
in assembly.

For now do not modify my code. Add everything, also the
two comment lines. (Cut & Paste).
(;====== BEGINNING OF THE ROTARY ENCODER CODE ========)
(;========= END OF THE ROTARY ENCODER CODE ===========)

If you like you can add the code for the two LEDs CCW and CW.
See comments in the code for the LEDs.

In my code I use 3 variables that you will have to declare
in your PicBasic program. These variables have new names.
(Not the same names you have used in your code).
I don't like names like 'new' because 'new' can be a
reserved word for some compiler.

The 3 variables are:

enc_new VAR BYTE
enc_old VAR BYTE
enc_counter VAR WORD

Set to 0 (zero) the variable enc_old when you power up
the PIC, before you enter the main loop of your PicBasic
program. This is not necessary but it will help you to
understand how this variable is used.


* * *

From your post of the 6th May 2005, 12:06

....im using a 16F873A,which has a 4K flash,meaning 4 banks...
according to the PBP manual, i have to define wsave,wsave1,wsave2,wsave3...
it takes in wsave and wsave1...but when i declare wsave2 and wsave3..it gives a RAM_END 255 error


From the PicBasic manual
2.2. Your First Program

If you don’t tell it otherwise, the PicBasic Pro Compiler defaults to
creating code for the PIC16F84. To compile code for PICmicro MCUs
other than the PIC16F84, simply use the -P command line option
described later in the manual to specify a different target processor. For
example, if you intend to run the above program, BLINK.BAS, on a
PIC16F877, compile it using the command:

PBP -p16f877 blink

So verify what you need for the 16F873A.

Luciano



;====== 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 B'11000000' ;Create bit mask (bits 7 & 6).
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.

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.

Continue

;Assign the latest encoder inputs (in _enc_new) to _enc_old.

movf _enc_new,W
movwf _enc_old

;============ END OF THE ROTARY ENCODER CODE =====

ice
- 13th May 2005, 09:52
Thank you so much Luciano...

The code is..
working good ,
direction decoding ok
counting on LCD[no garbage,dispays properly]


A few questions
1>You never clear the Int on PortB change flag in the interrupt.is it because your reading from portB,which is one of the ways to clear the flag.

2>retfie.. Neither is the retfie used to return from where the program left.

3>why didnt my program work ,when i used the INT0 interrupt.I cant figure it out.


4>I use CodeDesigner Lite as an IDE.I get t0 select the chip from a drop down menu.I select 16f873a.I even put the line PBP -p16F873A in the command line option,but the error on using wsave2 and wsave3,still persists.


Working with enc_counter on 16 bits is a bit difficult to work on the current project.I need 32 bits.so back to my code.

Luciano
- 14th May 2005, 00:24
====================================

Question 1


(From data sheet PIC16F87XA).

INTCON REGITER

bit 0 RBIF: RB Port Change Interrupt Flag bit

1 = At least one of the RB7:RB4 pins changed state; a mismatch condition will continue to set
the bit. Reading PORTB will end the mismatch condition and allow the bit to be cleared
(must be cleared in software).

0 = None of the RB7:RB4 pins have changed state

* * *

Yes, you must clear RBIF in software.


======================================

Question 2

Yes, you need 'retfie' before 'endasm'.
Just do what is in the PicBasic Pro manual.

======================================

Question 3

The encoder outputs that in CW direction:

Step 1 A=0 B=0
Step 2 A=1 B=0
Step 3 A=1 B=1
Step 4 A=0 B=1

If you use the interrupt INT0, you can monitor
only one signal of the encoder. If you monitor
the encoder signal A with the PIN RB0 (INT0) the
interrupt is raised when the encoder goes from
step 1 to step 2. (0 to 1).
From step 2 to step 3 the interrupt is not raised
because the encoder signal A is '1' in step 2 and
also '1' in step 3.

======================================

Question 4

You will have to fix that. This is a ticking bomb.
I don't use PicBasic. I cannot answer this question.
Do what I told you in my last post and ask the forum
for help.

======================================

From your last post:
I need 32 bits.so back to my code.

* * *

The PicBasic Pro compiler only supports a maximum
variable size of 16 bits.

What kind of 32-bit operations do you need?

Best regards,

Luciano

ice
- 14th May 2005, 08:22
Luciano,
Thanks for answering the questions

when i use "retfie" to return to the main program from where it left..the PIC "freezes" and both the LEDs light up.
It's all good when i dont use retfie

I can count only from 0 to 65535 with 16bits..which is not enough.hence i need 32 bit addition.

mytekcontrols
- 14th June 2005, 23:25
ICE
I may have another solution for you (or then again not). Here is a link to something I have been working on which utilizes a timer interrupt to sample an encoder, and then increment or decrement a 6 digit counter (includes prescaler). Since everything to do with sampling and updating are via the assembly interrupt there is no danger of missing pulses. Also picking the minimum sampling frequency necessary for a particular application tends to add additional debounce to the encoder inputs. And last but not least, using the byte per digit counter approach allows for any counter length desired (not limited to a 16 bit word to hold your value).

Check it out:1886
(Scroll down towards the bottom)

jmen
- 3rd August 2005, 22:17
Hello ICE, hi Luciano, hello everyone.
I try to use an PIC 18F452 with the Code posted from ICE and Luciano. The Code I have posted does not work and I dont understand wy but to tell the truth: I never tried to use Assembler and I dont have experience with Interrupts so maybe I´v done totaly... but maybe someone can help me?!

The Quad-Encoder is connected to portB.6 and 7.
Is the way I saved and restored the Registers right?
At the Assembler Code I changed the "rlf" statement to "rlcf" is this OK (I think the 18F452 do not support rlf)?
Is the declaration of the variables for the assembler code right?

Sorry for the spacing at the code, but I dont know how to remove it... :-(

Thanks in advance,
Jan



<div class="smallfont" style="margin-bottom:2px">Code:</div>
<pre style="margin:0px; padding:2px; border:1px inset; width:640px; height:434px; overflow:auto">DEFINE OSC 40
'hserout requires an rs-232 driver
'Set transmit register to transmitter enabled (normal value "20h" / highSpeed "24h")
DEFINE HSER_TXSTA 24h
'Set receive register to to receiver enabled
DEFINE HSER_RCSTA 90h
'Set baud rate
DEFINE HSER_BAUD 19200

'---VARIABLES required for the ASSAMBLER code
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

enc_old VAR BYTE
enc_new VAR BYTE

enc_counter VAR WORD

'---Other Vriables

enc_counter_old VAR WORD
maxcount con 1800
startcount con 1000

led0 VAR PORTA.0 'leds for showing the direction
led1 VAR PORTA.1
led2 VAR PORTA.2

enc_counter = startcount

TRISA = %00000000 ' Makes the pins of PortA output
TRISB = %11111111 ' Makes the pins of PortB input


GOTO start
' Define interrupt handler
define INTHAND myint
' Assembly language interrupt handler
Asm
; Save W, STATUS and PCLATH registers, if not done previously
myint 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 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 B'11000000' ;Create bit mask (bits 7 & 6).
andwf _enc_new,F ;Zero bits 5 thru 0.

;Determine the direction of the Rotary encoder.
rlcf _enc_old,F ;was rlf - 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

;code for the CCW LED.
bcf _led0 ; If interrupt, turn off LED
goto Continue ;Branch around UP.
Up
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

;code for the CW LED.
bcf _led1 ; If interrupt, turn off 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

retfie ; Return from interrupt
endasm

start:
INTCON = %10001000 ' Enable INTERRUPT "on change" ON PORTB and clears FLAGS

pause 30
portA = %11111111
HSEROUT [#enc_counter,10,13] 'only for debugging

loop:

if enc_counter_old <> enc_counter then 'looks if counter has changed
HSEROUT [#enc_counter,10,13]
enc_counter_old = enc_counter
endif

goto loop
END
</pre>

psenek
- 14th August 2005, 15:30
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?

J_Brittian
- 17th October 2005, 21:31
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.

Luciano
- 26th October 2005, 14:31
You're welcome!

Luciano

J_Brittian
- 28th October 2005, 20:29
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.

Luciano
- 29th October 2005, 10:26
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

mytekcontrols
- 29th October 2005, 15:07
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):


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:


;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:


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:


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:


;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:


;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.


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:


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:


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,

Luciano
- 29th October 2005, 21:44
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

mytekcontrols
- 30th October 2005, 15:10
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?

RodSTAR
- 10th October 2007, 07:02
File attachment = Incremental quadrature encoder direction drawing.

Luciano

Luciano: five stars!

ietcyberwolf
- 19th April 2011, 17:51
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

Ioannis
- 20th April 2011, 09:26
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

ietcyberwolf
- 26th April 2011, 19:24
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:



'************************************************* ***************
'* 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

Ioannis
- 26th April 2011, 19:48
After interrupt you reset Option_Reg.6.

And then never set it again. Is it that what you want?

Ioannis

ietcyberwolf
- 26th April 2011, 20:05
...
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

Ioannis
- 26th April 2011, 22:12
Try this:

http://www.picbasic.co.uk/forum/content.php?r=144-New-approach-to-Rotary-Encoder

Ioannis

ietcyberwolf
- 26th April 2011, 22:44
Thank you so much Ioannis!! :D

ChrisMicro
- 11th August 2011, 00:54
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

mister_e
- 11th August 2011, 01:00
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.

cbrun17
- 6th February 2017, 16:03
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:




'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

HenrikOlsson
- 6th February 2017, 17:31
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.

' 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.

cbrun17
- 6th February 2017, 19:03
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

cbrun17
- 7th February 2017, 01:11
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

HenrikOlsson
- 7th February 2017, 07:38
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):



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.

cbrun17
- 7th February 2017, 16:11
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

cbrun17
- 7th February 2017, 17:00
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

HenrikOlsson
- 7th February 2017, 17:42
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:

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.

cbrun17
- 8th February 2017, 00:25
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

richard
- 8th February 2017, 02:40
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

cbrun17
- 8th February 2017, 22:20
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

cbrun17
- 9th February 2017, 18:00
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.



'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

richard
- 10th February 2017, 03:51
;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



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

Dave
- 10th February 2017, 12:01
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/single-chip-circuit-delivers-direction-information-encoders

I eliminated the caps and just use 10k resistors. Read the artical as it is informative.

HenrikOlsson
- 10th February 2017, 12:55
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.

cbrun17
- 10th February 2017, 14:40
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

cbrun17
- 10th February 2017, 15:17
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_Sheets/LS7083_LS7084.pdf), this looks promising if I can't successfully make my code work properly.

I truly appreciate your suggestion.

Chris

cbrun17
- 11th February 2017, 04:07
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.




'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

cbrun17
- 11th February 2017, 04:17
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.




'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

richard
- 11th February 2017, 05:17
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

cbrun17
- 11th February 2017, 17:34
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

HenrikOlsson
- 11th February 2017, 18:31
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:

' 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.

cbrun17
- 11th February 2017, 19:26
Hi Henrik,

I changed the display routine as you described and it does work much better. I sure do over think some of this stuff... I did have to increase the pause to 250ms to allow for the display to update since it seemed to trip over itself a little more frequently this way.

I believe moving to either serial LCD or 7 seg LED might be the better solution.

Thanks again for your expert advise.

Chris

Dave
- 11th February 2017, 22:02
Excuse me Henrik, If you read the spec. sheet you can set the interrupt edge for the INT0 and INT1. It is not required to set it each time. Also the interrupt routine look like:
'************************************************* ********************
INC_C: 'INTERRUPT SERVICE ROUTINE FOR EXTERNAL INTERRUPT-0
'************************************************* ********************
POSITION = POSITION + 1
@ INT_RETURN

'************************************************* ********************
DEC_C: 'INTERRUPT SERVICE ROUTINE FOR EXTERNAL INTERRUPT-1
'************************************************* ********************
POSITION = POSITION - 1
@ INT_RETURN

That's all there is to it.

richard
- 12th February 2017, 08:45
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.

I have not tried your code but can assure you that it is essential to save the mcu context upon isr entry and to restore it on exit . restoring the context without saving it can only lead to tears, not saving it can only lead to tears.

so good luck with that


dave
the chip used here in this somewhat hijacked thread is a 16F876 it has only one "INT" pin your suggestion just cannot work in that case.
afaik no pic16 chips can respond to rising and falling levels on an INT int pin simultaneously
its one or the other. as henrik says to do a full quadrature decode every edge needs to cause an interrupt.
RBC int can do it a single INT int cannot , the intx edge would need to be toggled on every interrupt .

secondly that circuit with all its extra bits cannot use the full resolution of a encoder its a 1x decode.

thirdly since the portb rbc pins on a 16F876 are schmidt trigger type inputs and if the chip is close enough to
the re (opto type not mechanical)no other components are required at all to do a full 4x decode , why complicate it ?

HenrikOlsson
- 12th February 2017, 09:35
Excuse me Henrik, If you read the spec. sheet you can set the interrupt edge for the INT0 and INT1. It is not required to set it each time. Also the interrupt routine look like:
Dave, I think you're misunderstanding what I'm saying.
The circuit you linked to is x1 decoder, effectively provoding 1/4 of the encoders available resolution. Your simply counting the individual lines of the A and B channels respectively. If that's enough, perfect go right ahead but if you need the full resolution of the encoder (as is the case here) then it's not going to cut it.

To use the full resolution of the encoder each and every edge of the two signals needs cause the counter the change up or down. If the encoder is connected directly to INT0 and INT1 it means that the edge causing the interrupt needs to changed on every interrupt so that it "catches" the falling edge after a rising edge etc. It's certainly doable if the PIC allows you to do it (not all do) but it does complicate things.

By using interrupt on change this is handled automatically because ANY change on ANY pin causes an interrupt so you will "catch" every edge (rising AND falling) automatically and you just need to decode the gray code in the ISR. From any given state (there's only four) you can only get to two other (valid) states, one is forward the other is backwards.

Look at this NI page (http://www.ni.com/tutorial/7109/en/) on qudrature encoders, about half way down is a comparison of x1, x2 & x4 decoding.

/Henrik.

Dave
- 12th February 2017, 14:18
Henrik and Richard, I understand what it is you are trying to accomplish. The problem becomes noise and the ability of the micro to intercept the interrupts. When you are trying to capture each and every edge in a mechanically noisy environment you will undoubtedly miss an edge or two. The key to properly designing an encoder into a mechanical design is the encoder resolution. Trying to get 4x the resolution from an encoder is indeed one way to fail if the encoder is of a higher resolution than is required. I have been designing encoders into mechanical fixtures for almost 40 years. I would rather have the processor executing code rather than constantly servicing interrupts in a mechanically noisy environment. It's just my way of thinking. Good luck...

HenrikOlsson
- 12th February 2017, 15:18
If you need a certain resolution and a certain speed then it's going to result in a certain number of "counts" per second (ie a certain number of interrupts per second) no matter if you're doing x1 or x4 decoding, won't it?

The difference is that with the former you need an encoder with 4 times the number of cycles compared to the latter. As far as the number of interrupts that micro needs to service there's no difference.

In this particular case we don't know what the speed is but we know that Chris needs (or wants) a resolution of the 0.25 degrees, which his 360CPR encoder will provide with x4 decoding. He could exchange his encoder for one with 1440CPR and do x1 decoding but given that the speed at which the enocder is being moved is the same there will be an equal number of "counts" per second. Doing the x4 decode in software is however a bit more complicated than x1, I'll give you that :-)

As far as noise goes I don't see the difference in running a 360CPR encoder doing x4 decoding and running a 1440CPR encoder doing x1 decoding.

And if using an external decoder circuit, like the one you linked to, there's no need for any interrupts at all, just have two counters count the up/down pulses and differentiate periodically.

/Henrik.

picster
- 16th February 2017, 14:19
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

Out of curiosity, what R/C values did you find to be optimal on your schmitt triggers?

Picster

picster
- 21st February 2017, 17:42
Out of curiosity, what R/C values did you find to be optimal on your schmitt triggers?

Picster

Ok, I did some trials myself and ended up with R1=100k, R2=33k, C=0.1uF working well with Schmitt trigger inputs on the PIC I'm using (see circuit attached). That's with a detented encoder, using the same values for the two rotary outputs AND the built-in pushbutton.
8376

Ioannis
- 22nd February 2017, 13:50
I would use 10th of this values.

Ioannis

picster
- 22nd February 2017, 19:38
I would use 10th of this values.

Ioannis

You certainly could. Yet this works great in my application, for which I am using switch polling, not interrupts. I would imagine it also depends on the characteristics of the encoder.

Ioannis
- 22nd February 2017, 21:20
Of course it does. But I am worried about the input impedance of the PIC and leakage currents.

Ioannis

picster
- 23rd February 2017, 14:21
Hey Ioannis,

I'm hoping you can shed some additional light on this for me.

I know the "design standard" is to use a 10k pullup for PIC inputs that switch to ground - which from my understanding is really not so much about leakage, but more about noise on the "antenna" that an input wire or trace can become. The internal weak pullups tend to work on a breadboard or on a PC board, as long as the system isn't subject to much electrical noise, but "guaranteeing" that environment is far from possible in real world applications. The internal "weak pullups" have equivalent impedance ranges from 200k to 16k, per PIC specs. Meanwhile, input impedance for ports works out to around 50Mohms per specs. I agree, it is always good to have a "solid" and unquestionable pull-up instead of something where spurious glitches can put you into nebulous range, particularly with an input that's NOT configured as a schmitt trigger and you can get all kinds of undefined questionable results. Absolutely makes sense to use "tried and true" 10k in these situations.

However, despite the above, part of the benefit of using a capacitor on the input (again, only with the schmitt trigger config) is that it will help to eliminate spurious noise, effectively shunting spikes to ground and only letting relatively persistent voltage levels through. Hence the reason, I believe, why these values may be demonstrated to work for a schmitt trigger input configuration. I picked these values around the 0.1uF capacitor since I have a ton of them.

Does this make sense, or am I missing something else that I should be considering?

picster
- 27th February 2017, 13:28
Hey Ioannis,

I'm hoping you can shed some additional light on this for me.

I know the "design standard" is to use a 10k pullup for PIC inputs that switch to ground - which from my understanding is really not so much about leakage, but more about noise on the "antenna" that an input wire or trace can become. The internal weak pullups tend to work on a breadboard or on a PC board, as long as the system isn't subject to much electrical noise, but "guaranteeing" that environment is far from possible in real world applications. The internal "weak pullups" have equivalent impedance ranges from 200k to 16k, per PIC specs. Meanwhile, input impedance for ports works out to around 50Mohms per specs. I agree, it is always good to have a "solid" and unquestionable pull-up instead of something where spurious glitches can put you into nebulous range, particularly with an input that's NOT configured as a schmitt trigger and you can get all kinds of undefined questionable results. Absolutely makes sense to use "tried and true" 10k in these situations.

However, despite the above, part of the benefit of using a capacitor on the input (again, only with the schmitt trigger config) is that it will help to eliminate spurious noise, effectively shunting spikes to ground and only letting relatively persistent voltage levels through. Hence the reason, I believe, why these values may be demonstrated to work for a schmitt trigger input configuration. I picked these values around the 0.1uF capacitor since I have a ton of them.

Does this make sense, or am I missing something else that I should be considering?

Note: 10k/3.3k/0.1uF also works with the switch I'm using, so I've changed to this configuration just to stay "standard", and I need not bother with larger caps.

Ioannis
- 28th February 2017, 09:02
I just realized that my reply post to your #92 never showed up... A mystery!

Anyway, there are no standard values and all depends on the circuit itself. If it works reliable under all worst conditions then this is your standard.

On the other hand I am worried having large pull-ups (or downs accordingly) near large electric fields of any kind.

Ioannis