PDA

View Full Version : how to read ky40 mechanical encoder ?



iw2fvo
- 11th October 2015, 20:29
Good day to all of you in this forum.
I used an optical encoder in the past with pbp and I had good result.
Now I have a mechanical encoder similar to the ky40 or ky-040 that is widely available on the net.
I am not able to make it working correctly and still having bad results.
I did filtered the A and B signals with two 0.1uf and 10k resistors pull_up: no solution up to now.
Any help on the this matter please ? Or any sample code available ?
Thanks
Ambrogio

Archangel
- 11th October 2015, 21:16
Is your encoder like this ?
http://www.ebay.com/itm/400964660325?_trksid=p2060353.m1438.l2649&ssPageName=STRK%3AMEBIDX%3AIT

These encoders are (supposedly) destroyed by pullup resistors.

Or is your encoder just a noisey old mechanical switch type?
Comparators are supposed to be good buffers for mech switches.

OK I found my answer, Mech. as stated in your post :D
You are needing to "debounce" these.

iw2fvo
- 12th October 2015, 06:31
this is my ecoder:
http://www.ebay.com/itm/1PCS-KY-040-Rotary-Encoder-Module-Brick-Sensor-Development-AVR-PIC-for-Arduino-/171411843977?hash=item27e8f12b89

Any help , please ?
Thanks
Ambrogio

Archangel
- 12th October 2015, 10:06
CAUTION: UNTESTED CODE


main:

A var portb.6
B var portb.7
state var byte
if(( A == 1)&&(B==1)) then state = 1
if(( A == 1)&&(B==0)) then state = 2
if(( A == 0)&&(B==1)) then state = 3
if(( A == 0)&&(B==0)) then state = 4
;pause 100
if state = 1 then portC = %00000001
if state = 2 then portc = %00000010
if state = 3 then portc = %00000100
if state = 4 then portc = %00001000

debug " state = ",#state


goto main

richard
- 12th October 2015, 10:19
I found this interesting.
http://makeatronics.blogspot.it/2013/02/efficiently-reading-quadrature-with.html

is your "re " a 20 count per turn with indents ? the 10 for a $1 ones I got from ebay have enough contact bounce to start a spring factory but the inherent software filtering of the above code does an amazing job on them 1 (4k7 pu r's and .1uFs included). I have not ported this to pbp but have for mikro c and xc8 if that helps

http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino/comment-page-1#comments
this link has some other insights

basically you just need to adjust your re count using this current value / old value matrix (illegal values [bounces] are just ignored )

pedja089
- 12th October 2015, 10:42
One of possible ways to implement filter(Not tested)

OutputValue=0
Select Case OldValue
Case 0
If Current=1 then OutputValue=-1
If Current=2 then OutputValue=1
Case 1
If Current=3 then OutputValue=-1
If Current=0 then OutputValue=1
Case 2
If Current=0 then OutputValue=-1
If Current=3 then OutputValue=1
Case 3
If Current=2 then OutputValue=-1
If Current=1 then OutputValue=1
EndSelect

iw2fvo
- 17th October 2015, 08:00
still not able to make my panel encoder working .
Could someone post the complete code applicable to mechanical encoder like my one please ?
Thanks in advance.
regards,
Ambro

richard
- 17th October 2015, 11:10
post the code you are trying , and a bit of info about your re setup .

does it have detents ?
how many edges per turn ?
pull ups ?
filter caps ?
pic type ?
pins used ?


still not able to make my panel encoder working is not much to work with

iw2fvo
- 17th October 2015, 13:07
Thanks for the assistance.
My encoder has detents and has about 24 edges per turn.
I used a pull up of 2x 10 k on both encoder phases and a 2x 0.1uF to ground.
I am using pic18f452 , xtal=10 Mhz, ppl X 4 to get 40 Mhz clock speed.
I used RB0 for the "A" phase as shown on the code.
At the end of the world I am now able to make it working well using the following picbasic program by including Darrel Taylor interrupt routine.
The program prints counter results on a terminal @ 115200 baud rate 8,n,1.
Regards, Ambro


DEFINE OSC 40 ' XTAL= 10 MHZ CLOCK= 40 MHZ
INCLUDE "MODEDEFS.BAS"
INCLUDE "DT_INTS-18.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ; Include if using PBP interrupts
INTCON2.BIT6=0 ' interrupt from 1 down to 0 on RB0

CNT VAR WORD ' COUNTER TO BE DISPLAYED ON TERMINAL
CNT= 32000
CNTOLD VAR WORD
CNTOLD=32000

B VAR PORTD.1 ' encoder B PHASE
A VAR PORTD.0 ' encoder A PHASE

' DEBUG INTERFACE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\

'' Set Debug pin port ________________________________________
DEFINE DEBUG_REG PORTD
'' Set Debug pin bit _________________________________________ PIC_PIN_30
DEFINE DEBUG_BIT 7
'' Set Debug baud rate _________________________________________
DEFINE DEBUG_BAUD 115200
'' Set Debug mode: 0 = true, 1 = inverted ______________________
DEFINE DEBUG_MODE 1

DEBUG 13,10,13,10, " START OF PROGRAM ", 13, 10, 13, 10

ADCON1=7 ' all digital !

ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler INT_INT, _ToggleLED1, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM

@ INT_ENABLE INT_INT ; enable external (INT) interrupts

Main:
PAUSE 1
IF CNT <> CNTOLD THEN
DEBUG DEC CNT,13,10
CNTOLD=CNT
ENDIF

iw2fvo
- 17th October 2015, 13:10
sorry for not having paste the complete code...



DEFINE OSC 40 ' XTAL= 10 MHZ CLOCK= 40 MHZ
INCLUDE "MODEDEFS.BAS"
INCLUDE "DT_INTS-18.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ; Include if using PBP interrupts
INTCON2.BIT6=0 ' interrupt from 1 down to 0

CNT VAR WORD ' COUNTER TO BE DISPLAYED ON TERMINAL
CNT= 32000
CNTOLD VAR WORD
CNTOLD=32000

B VAR PORTD.1 ' encoder B PHASE
A VAR PORTD.0 ' encoder A PHASE

' DEBUG INTERFACE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\

'' Set Debug pin port ________________________________________
DEFINE DEBUG_REG PORTD
'' Set Debug pin bit _________________________________________ PIC_PIN_30
DEFINE DEBUG_BIT 7
'' Set Debug baud rate _________________________________________
DEFINE DEBUG_BAUD 115200
'' Set Debug mode: 0 = true, 1 = inverted ______________________
DEFINE DEBUG_MODE 1

DEBUG 13,10,13,10, " START OF PROGRAM ", 13, 10, 13, 10

ADCON1=7 ' all digital !

ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler INT_INT, _ToggleLED1, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM

@ INT_ENABLE INT_INT ; enable external (INT) interrupts

Main:
PAUSE 1
IF CNT <> CNTOLD THEN
DEBUG DEC CNT,13,10
CNTOLD=CNT
ENDIF

GOTO Main

'---[INT - interrupt handler]---------------------------------------------------
ToggleLED1:
if b=0 then cnt=cnt-1
IF B=1 THEN CNT=CNT+1
@ INT_RETURN

richard
- 18th October 2015, 13:31
ToggleLED1:
if b=0 then cnt=cnt-1
IF B=1 THEN CNT=CNT+1
@ INT_RETURN

I found that strategy gave quite disappointing results , was jerky missed counts and had sudden and unexpected direction reversals. there are better ways

iw2fvo
- 18th October 2015, 14:45
I am sorry Richar for having posted the file that is not the final one:
please change the following:

A VAR PORTD.0 ' encoder A PHASE
shAll be:
A VAR PORTB.0

This is the interrupt input !

I would be happy to know ...
Thanks and regards,
Ambrogio

iw2fvo
- 18th October 2015, 15:27
I did capture the output of the terminal printout for a better check.
There are about 200 changes of the cnt obtained by "manually" turning the encoder knob on my control panel. Probably sometims cw and ccw.
I noted very few spikes : I am not shure if they are due to the time required for the serial port output or to the encoder software.
Any improvement to the program is very much appreciated.
I am really searching for a good solution.
Thanks
regards,
Ambrogio

richard
- 18th October 2015, 20:51
I do it this way

enc pins must be sequential and on same port
both enc pins must be able to generate an interrupt (ioc on neg and pos edge is best , ioc, rac,rbc on neg or pos edge will work as will int0 int1)
enc pins must be pulled up (10k) and have 0.1uF filter caps

I have an asm version that's 100 bytes smaller and has about 1 tenth of the irq latency and is much faster too



'************************************************* ***************
'* Name : rotary encoder.BAS *
'* Author : richard *
'* Notice : Copyright (c) 2013 *
'* : All Rights Reserved *
'* Date : 9/4/2013 *
'* Version : 1.0 16f1825 *
'* Notes : enca porta.2 encb porta.3 *
'* : note mclr disabled ,full quad decode *
'************************************************* ***************

#CONFIG
__config _CONFIG1, _FOSC_INTOSC & _CP_OFF & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CLKOUTEN_OFF
__config _CONFIG2, _PLLEN_ON & _LVP_OFF
#ENDCONFIG


include "dt_ints-14.bas"
include "REENTERPBP.bas"

asm
INT_LIST macro
INT_HANDLER IOC_INT, _enc_isr,pbp,NO
endm
INT_CREATE
ENDASM

DEFINE OSC 32




OSCCON=$70
ANSELA=0

OPTION_REG.6=0

ev VAR BYTE bank0
cnt VAR WORD bank0
TMP VAR BYTE bank0
enca VAR porta.2
encb VAR porta.3
TRISA = 111110

lata.0=1
IOCAP=12 ;enca porta.2 encb porta.3 pos edge trigger
IOCAN=12 ;enca porta.2 encb porta.3 neg edge trigger


pause 4000

serout2 PORTA.0,84,[ "ready",13,10]
IOCAF=0

INTCON=$C8



mainloop:

serout2 PORTA.0,84,[13,10, "C",#CNT]
pause 1000

goto mainloop




enc_isr:


ev=ev<<2 ;shift last reading into position
ev=ev|((porta&12)>>2) ;read enc pins 2,3 mask off others then combine this reading with last reading
LOOKUP (ev&$f),[0,255, 1, 0, 1, 0, 0, 255, 255, 0, 0, 1, 0, 1, 255, 0],tmp ;lookup the decision matrix
if tmp then ; decide to inc , dec or ignore
if tmp.7 then
cnt=cnt-1
else
cnt=cnt+1
endif
endif
intcon.0=0 ; clean up and exit
iocaf=0
@ INT_RETURN

iw2fvo
- 21st October 2015, 07:29
Thanks Richard for the code.
I am just a little bit busy on these days and so I plan to try your code on next days.
As you have seen I am using pic18f452: Is your code applicable to this micro ?
If no, what should I modify or adapt ?
Thanks
Regards,
Ambrogio

richard
- 21st October 2015, 08:03
for a pic18f452 you need to use RBC_INT for best results and pins portb.4,5 or portb.6,7 or portb.5,6

this line needs to match chosen p1ns


ev=ev|((porta&12)>>2) ;read enc pins 2,3 mask off others then combine this reading with last reading

for pins portb.4,5 it would be


ev=ev|((portb&48)>>4)


INT_LIST macro
INT_HANDLER IOC_INT, _enc_isr,pbp,NO
endm
INT_CREATE

would be

INT_LIST macro
INT_HANDLER RBC_INT, _enc_isr,pbp,NO
endm
INT_CREATE




IOCAP=12 ;enca porta.2 encb porta.3 pos edge trigger
IOCAN=12 ;enca porta.2 encb porta.3 neg edge trigger
IOCAF=0
INTCON=$C8



would be


dummy_var = portb
INTCON=$C8



any references to IOCAF need to be deleted , trisb must be appropiate

iw2fvo
- 24th October 2015, 10:32
Hi Richard,
I am going to modify my previous program with your indications.
Probably I did something wrong and it is not working.
I am attaching the full code : could you please have a look of it ?
Thanks again for the assistance
regards,
Ambro

richard
- 24th October 2015, 11:47
I would do it like this


'************************************************* ***************
'* Name : pic controlled power supply *
'* Author : [select VIEW...EDITOR OPTIONS] *
'* Notice : Copyright (c) 2015 [select VIEW...EDITOR OPTIONS] *
'* : All Rights Reserved *
'* Date : 11/09/2015 *
'* Version : 1.0 *
'* Notes : pic18f452 10mhz x 4 pll = 40 MHZ *
'************************************************* ***************
';Program Configuration Register 1H
@ __CONFIG _CONFIG1H, _OSCS_OFF_1H & _HSPLL_OSC_1H
';Program Configuration Register 2L
@ __CONFIG _CONFIG2L, _BOR_OFF_2L & _BORV_20_2L & _PWRT_ON_2L
';Program Configuration Register 2H
@ __CONFIG _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H
';Program Configuration Register 3H
@ __CONFIG _CONFIG3H, _CCP2MX_OFF_3H
';Program Configuration Register 4L
@ __CONFIG _CONFIG4L, _STVR_OFF_4L & _LVP_OFF_4L & _DEBUG_OFF_4L
';Program Configuration Register 5L
@ __CONFIG _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L
';Program Configuration Register 5H
@ __CONFIG _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
';Program Configuration Register 6L
@ __CONFIG _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
';Program Configuration Register 6H
@ __CONFIG _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
';Program Configuration Register 7L
@ __CONFIG _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
';Program Configuration Register 7H
@ __CONFIG _CONFIG7H, _EBTRB_OFF_7H
' ************************************************** *************************

DEFINE OSC 40 ' XTAL= 10 MHZ CLOCK= 40 MHZ
INCLUDE "MODEDEFS.BAS"
include "dt_ints-14.bas"
include "REENTERPBP.bas"


CNT VAR WORD ' COUNTER TO BE DISPLAYED ON TERMINAL
CNT= 32000
CNTOLD VAR WORD
CNTOLD=32000
counter VAR WORD
counter= 32000
' DEBUG INTERFACE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\
'' Set Debug pin port ________________________________________
DEFINE DEBUG_REG PORTD
'' Set Debug pin bit _________________________________________ PIC_PIN_30
DEFINE DEBUG_BIT 7
'' Set Debug baud rate _________________________________________
DEFINE DEBUG_BAUD 115200
'' Set Debug mode: 0 = true, 1 = inverted ______________________
DEFINE DEBUG_MODE 1
DEBUG 13,10,13,10, " PROGRAM IS STARTING...", 13, 10, 13, 10
ADCON1=7 ' all digital !
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler RBC_INT, _enc_isr, PBP, NO
endm
INT_CREATE ; Creates the interrupt processor
ENDASM


'@ INT_ENABLE RBC_INT ; enable external (INT) interrupts
ev VAR BYTE BANK0
TMP VAR BYTE BANK0
enca VAR portb.4
encb VAR portb.5
TRISA = 255

lata.0=1
tmp = portb ;; read portb to establish pin change status
INTCON=$C8 ; enable RBC_INT and clear rbcif
Main:
PAUSE 1000
gosub get_cnt:
IF counter <> CNTOLD THEN
DEBUG DEC CNT,13,10
CNTOLD=counter ENDIF

GOTO Main
enc_isr:

ev=ev<<2 ;shift last reading into position
ev=ev|((portb&48)>>4) ;read enc pins 4+5 mask off others then combine this reading with last reading
LOOKUP (ev&$f),[0,255, 1, 0, 1, 0, 0, 255, 255, 0, 0, 1, 0, 1, 255, 0],tmp ;lookup the decision matrix
if tmp then ; decide to inc , dec or ignore
if tmp.7 then
cnt=cnt-1
else
cnt=cnt+1
endif
endif
intcon.0=0 ; clean up and exit
@ INT_RETURN



get_cnt: ; cnt is not atomic this is needed to avoid rollover miss reads
intcon.3=0
counter=cnt
intcon.3=1
return

iw2fvo
- 24th October 2015, 17:40
Thanks Richard,
Your program compiles well.
I do have the follwing :

PROGRAM IS STARTING...

32009
32005
32009
32013
32017
32021
32033
32042
32073
32113
32153
32173
32197
32209
32233
32257
32221
32199
32161
32129
32101
32065
32061
32057
32053
32045
32041
32040
32037
32013
31999
31997

I have changed the encoder too but not working...
Thanks
Ambro

richard
- 24th October 2015, 19:08
the isr counts edges , an indented re has 4 edges per indent . you would expect to increment counter by 4
don't forget the display routine and the isr are asynchronous .
if you are trying to count indents then




cnt_f var bit

Main:
PAUSE 10

IF cnt_f THEN

gosub get_cnt:
DEBUG DEC counter,13,10

ENDIF

GOTO Main





enc_isr:
ev=ev<<2 ;shift last reading into position
ev=ev|((portb&48)>>4) ;read enc pins 4,5 mask off others then combine this reading with last reading
LOOKUP (ev&15),[0,255, 1, 0, 1, 0, 0, 255, 255, 0, 0, 1, 0, 1, 255, 0],tmp;lookup the decision matrix
if tmp then ; decide to inc , dec or ignore
if tmp.7 then
cnt=cnt-1
cnt_f=1
else
cnt=cnt+1
cnt_f=1
endif
endif
intcon.0=0 ; clean up and exit
@ INT_RETURN




get_cnt :
intcon.3=0
counter=cnt/4
cnt_f=0
intcon.3=1
return

iw2fvo
- 25th October 2015, 16:22
Richard:
for each one click ( or detent ) it prints four numbers.
Thanks