PDA

View Full Version : HEDS5540 optical encoder



louislouis
- 2nd October 2016, 20:48
Hello boys,
I'm new on the forum and beginner in programming. I try to create a simple digital readout for X-axis on to my small lathe.
For simplicity I want to use rotary encoder and rack and pinion drive to measure distance.
I read and try all suitable codes I have found on the forum but a little success. I try combine this pieces of codes, but no success.
Please can someone to help me how to implement this code to functional end. MCU is 16F628A thank You for help.
This is my code:


INCLUDE "DT_INTS-14.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP.bas" ; Include if using PBP interrupts


' PIC 16F628A
' PicBasic program to demonstrate operation of an LCD in 4-bit mode
' and reading an optical encoder in quadrature
'
' LCD should be connected as follows:
' LCD PIC
' DB4 PortA.0
' DB5 PortA.1
' DB6 PortA.2
' DB7 PortA.3
' RS PortA.4 (add 4.7K pullup resistor to 5 volts)
' E PortB.0
' RW Ground
' Vdd 5 volts
' Vss Ground
' Vo 20K potentiometer (or ground)
' DB0-3 No connect
; encoder ch A PortB.6
; encoder ch B PortB.7

#config
__config _HS_OSC & _WDT_ON & _PWRTE_ON & _MCLRE_OFF & _LVP_OFF & _CPD_OFF
#endconfig

DEFINE OSC 20

TRISA = %00000000 ' Make all PortA pins output
TRISB = %11100000

Define LCD_DREG PORTA
Define LCD_DBIT 0
Define LCD_RSREG PORTA
define LCD_RSBIT 4
define LCD_EREG PORTB
define LCD_EBIT 0
define LCD_BITS 4
define LCD_LINES 2
define LCD_COMMANDUS 2000
define LCD_DATAUS 50

Flag var bit
wsave VAR BYTE $70 SYSTEM ; alternate save location for W
enc_new VAR BYTE
enc_old VAR BYTE
enc_counter VAR WORD

; Set variable value @ startup
Flag = 0
enc_new = 0
enc_old= 0
enc_counter = 0


Lcdout $fe, 1 ' Clear LCD screen
Lcdout "Encoder test" ' Display message
Pause 500
@ INT_ENABLE RBC_INT ; enable external (INT) interrupts
goto main_loop


asm
INT_LIST macro ; IntSource, Label, Type, Resetflag?
INT_Handler RBC_INT, _enc, ASM, yes
endm
INT_CREATE ; Creates the interrupt processor
;================================================
endasm
enc:
asm

;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 =====
endasm
@ INT_RETURN

Main_Loop:

if Flag = 1 then
Lcdout $fe, 1
Lcdout Dec enc_counter ; display counter value on LCD
Flag = 0
endif
goto Main_Loop
end

richard
- 2nd October 2016, 22:11
if Flag = 1 then

nowhere in your code is flag ever set to 1

louislouis
- 2nd October 2016, 22:25
you're right, in the ASM code I not have defined the flag=1. The question is how to do that.

richard
- 2nd October 2016, 22:56
easiest way



Flag var byte
wsave VAR BYTE $70 SYSTEM ; alternate save location for W
enc_new VAR BYTE
enc_old VAR BYTE
enc_counter VAR WORD





asm

;Read latest input from PORTB & put the value in _enc_new.
MOVE?CB 1 ,_Flag
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

mark_s
- 2nd October 2016, 23:21
Richard's way is the best. Just for referece it could also be done without asm since its not time sensitive.



old_enc_counter var word
Main_Loop:

if enc_counter<> old_enc_counter then 'If change update LCD
Lcdout $fe, 1
Lcdout Dec enc_counter ; display counter value on LCD
old_enc_counter = enc_counter
endif
goto Main_Loop
end

louislouis
- 2nd October 2016, 23:26
hmm, I try it and no change, still only the "encoder test" message on the LCD.
This line is ok MOVE?CB 1 ,_Flag what means the question mark.

richard
- 2nd October 2016, 23:38
This line is ok MOVE?CB 1 ,_Flag what means the question mark.

NOTHING , MOVE?CB IS THE NAME OF A PBP MACRO that moves a constant to a byte var
advantage is that the macro does all the banksel for you

more proper would be FOR YOUR isr vars


Flag var bit bank0
enc_new VAR BYTE bank0
enc_old VAR BYTE bank0
enc_counter VAR WORD bank0

richard
- 2nd October 2016, 23:50
plus I would rearrange the code like this , the way you have it the interrupt is enabled before its created
which may not be a good thing




INCLUDE "DT_INTS-14.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP.bas" ; Include if using PBP interrupts

' PIC 16F628A
' PicBasic program to demonstrate operation of an LCD in 4-bit mode
' and reading an optical encoder in quadrature
'
' LCD should be connected as follows:
' LCD PIC
' DB4 PortA.0
' DB5 PortA.1
' DB6 PortA.2
' DB7 PortA.3
' RS PortA.4 (add 4.7K pullup resistor to 5 volts)
' E PortB.0
' RW Ground
' Vdd 5 volts
' Vss Ground
' Vo 20K potentiometer (or ground)
' DB0-3 No connect
; encoder ch A PortB.6
; encoder ch B PortB.7

Define LCD_DREG PORTA
Define LCD_DBIT 0
Define LCD_RSREG PORTA
define LCD_RSBIT 4
define LCD_EREG PORTB
define LCD_EBIT 0
define LCD_BITS 4
define LCD_LINES 2
define LCD_COMMANDUS 2000
define LCD_DATAUS 50

wsave VAR BYTE $70 SYSTEM ; alternate save location for W
enc_new VAR BYTE bank0
enc_old VAR BYTE bank0
enc_counter VAR WORD bank0
Flag var BYTE bank0

Lcdout $fe, 1 ' Clear LCD screen
Lcdout "Encoder test" ' Display message
Pause 500


asm
INT_LIST macro ; IntSource, Label, Type, Resetflag?
INT_Handler RBC_INT, _enc, ASM, yes
endm
INT_CREATE ; Creates the interrupt processor
;================================================
endasm
; Set variable value @ startup
Flag = 0
enc_new = 0
enc_old= 0
enc_counter = 0
@ INT_ENABLE RBC_INT ; enable external (INT) interrupts
Main_Loop:
if Flag = 1 then
Lcdout $fe, 1
Lcdout Dec enc_counter ; display counter value on LCD
Flag = 0
endif
goto Main_Loop
end


enc:
asm
;Read latest input from PORTB & put the value in _enc_new.
MOVE?CB 1,_Flag
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
INT_RETURN
;============ END OF THE ROTARY ENCODER CODE =====
endasm

richard
- 3rd October 2016, 07:57
tried your code on a 500 ppr opto encoder, it seems to be good

modified to run on 16f1825, full quad decode [2000 edges / rev]



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

OSCCON=$70
DEFINE OSC 32

INCLUDE "DT_INTS-14.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP.bas" ; Include if using PBP interrupts

' PIC 16F1825

; encoder ch A Porta.4
; encoder ch B Porta.5

enc_new VAR BYTE bank0
enc_old VAR BYTE bank0
enc_counter VAR WORD bank0
Flag var BYTE bank0

TRISA = %111111 ' Make all pins Input
trisc = %11111111 ;Make all pins Input
ANSELA=0
ANSELC=0
;SETUP IOC REGS
IOCAP = %110000 ;POS EDGE
IOCAN = %110000 ;NEG EDGE
IOCAF = 0 ;CLR INT FLAG
intcon.3 = 1 ;iocie
asm
INT_LIST macro ; IntSource, Label, Type, Resetflag?
INT_Handler IOC_INT, _enc, ASM, NO
endm
INT_CREATE ; Creates the interrupt processor

endasm
;debug --------------------------
TRISA.0 = 0
lata.0=1
pause 2000 ;debug
serout2 PORTa.0,84, ["ready v3",13,10 ] ;debug
;debug ------------------------------------

; Set variable value @ startup
Flag = 0
enc_new = 0
enc_old= 0
enc_counter = 0
@ INT_ENABLE IOC_INT
Main_Loop:
if Flag = 1 then
serout2 PORTa.0,84, [#enc_counter,13,10 ]
Flag = 0
endif
goto Main_Loop
end


enc:
asm
;Read latest input from PORTA & put the value in _enc_new.
MOVE?CB 1,_Flag
CHK?RP PORTA
movf PORTA,W
RST?RP
movwf _enc_new
;Strip off all but the 2 MSBs in _enc_new.
movlw B'00110000' ;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,5 ;Test bit 5 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
CHK?RP IOCAF
CLRF IOCAF
INT_RETURN
;============ END OF THE ROTARY ENCODER CODE =====
endasm

louislouis
- 3rd October 2016, 19:50
Thank You Richard for helping me. The your corrected code works, the routine counts up and down when I turn the encoder shaft cw or ccw, but if i mark the shaft starting position and turn on the MCU the reading is zero thats OK and when i slowly turn for example half turn and go back to marked position the encoder reading is not zero but less or more than zero. I think the routine not count correctly the encoder pulses, maybe the encoder send incorrect signals, I dont know.

richard
- 4th October 2016, 00:52
Thank You Richard for helping me. The your corrected code works, the routine counts up and down when I turn the encoder shaft cw or ccw, but if i mark the shaft starting position and turn on the MCU the reading is zero thats OK and when i slowly turn for example half turn and go back to marked position the encoder reading is not zero but less or more than zero. I think the routine not count correctly the encoder pulses, maybe the encoder send incorrect signals, I dont know.

I tried my setup again more carefully and verified it works ok, several back and forth motions always returns to the zero mark as zero in either direction, so the question is does rbc_int on your chip react to every edge generated by the encoder the same as ioc_int on a 16f1825 / perhaps not

louislouis
- 4th October 2016, 16:39
Yes, that's the problem. The RBC_INT react with pin RB.0 on port B, because this pin operate the LCD ENABLE pin. If I try to use only portA to operate the LCD (with internal 4MHz oscillator) and on the PORTB pin RB.6 and RB.7 hang only the encoder A and B channel, rest pins configured to output the reading is perfect. Always after turning up and down randomly, slower, quicker etc. when I return to marked starting position the reading is zero. Now, how to solve this problem, how to "tell" to MCU the only pinRB.6 and RB.7 trigger the interrupt, not the entire portB

richard
- 4th October 2016, 23:17
"
If I try to use only portA to operate the LCD (with internal 4MHz oscillator) and on the PORTB pin RB.6 and RB.7 hang only the encoder A and B channel, rest pins configured to output the reading is perfect.


yes , makes sense . unfortunately setting or clearing a bit on a pic port entails a read of that port first , any read of portb clears the mismatch condition state for the rbc interrupt.
so even though only the bits 4 to 7 that are set as inputs can cause an interrupt on change a write to your "e" pin can cause changes to be missed .
if it won't keep up with the encoder using the int osc then use an i2c lcd display or a different pic

louislouis
- 5th October 2016, 00:24
For testing and learning the 16F628A is seems to be OK. But now I try to convert the pulse reading to distance. One full encoder turn generates 1440 pulses. The wheel diameter is 25mm, the circumference is 157mm. I want reading precision to 0,1mm. I try basic math like:


Main_Loop:
if Flag = 1 then
let counter = (enc_counter/36) * 157 / 4
Lcdout $fe, 1
lcdout Dec counter ; display counter value on LCD
Flag = 0
endif
goto Main_Loop


But the precision is terrible, can you explain me the right way?

richard
- 5th October 2016, 01:16
But the precision is terrible, can you explain me the right way?


157/1440 = .109 mm per encoder tick

.109 * 256 * 10 = 279

distance * 10 = (counts * 279)/256

to use pbp's higest resolution integer functions



counter = enc_counter */ 279

lcdout Dec counter/10 ,".", dec counter//10 ; display counter value on LCD



problem here is that when enc_counter > 60000 [or thereabouts] the internal 32bit result will overflow and the result will be wrong
you could look at nbit math


ps you could also use div32



counter = enc_counter * 1000
counter = div32 917

lcdout Dec counter/10 ,".", dec counter//10 ; display counter value on LCD

louislouis
- 5th October 2016, 19:54
Thank You Richard for helping me. That's great, now the "core" of my distance meter works perfectly. Yes, the max. distance what I can measure is near six and a half meter which is enough for me. If I want to measure more than 6,5m simply zeroed the reading and start count from that point. Now I must implement negative reading when I go to opposite direction from zero the reading must be from -1..... to -655,3.
Your explanation helping me a lot, once again Thank You.

louislouis
- 9th October 2016, 23:26
I finally assembled my distance meter based on rotary optical encoder and precision wheel. But still I have problem with math and precision.
For example, wheel circumference is 157mm, and when I take a 30 revs the result must be 4710mm, but the MCU calculates only 4708,1mm which is 1,9mm difference. Is there a way to improve the precision?
I use Richard's math:
157/1440 = .109 mm per encoder tick
.109 * 256 * 10 = 279
distance * 10 = (counts * 279)/256


counter = enc_counter */ 279
lcdout Dec counter/10 ,".", dec counter//10 ; display counter value on LCD

The precision 0,1mm is enough for me, the max. measurable distance want be 6000mm. I try some change, if I improve the precision to 0.01mm then the max. measurable distance shortened to 600mm and this not enough.

richard
- 10th October 2016, 04:15
you could look at nbit math or use a pic18 and long vars

you can glean a bit more precision breaking the calc up a bit

where 43200 counts would give 4709.2



frac = enc_counter.lowbyte * 1090
posn1 = div32 100 ;mm*100 ;mv 2790
posn2 = (enc_counter.highbyte&15) * 2791 ; mm*100 mv 41865
frac = (enc_counter.highbyte>>4) * 44657
posn3 = div32 100 ;mm mv6698

posn3= posn3 + (posn1+posn2)/100 ;mm
frac= ((posn1+posn2)//100 +5)/10 ;round it up
serout2 PORTa.0,84, [13,10,#enc_counter,9,dec posn3,".",#frac ]

louislouis
- 10th October 2016, 22:41
Hello Richard, thank you again for help. Your code works and take a little improvement. I make some changes, first got a smaller precision wheel dia. 25mm (circ. 78,53975mm) and divide the encoder reading /2. Now the encoder reading is only 720 per one rev.
The code is as follows:


Main_Loop:
enc_counter = enc_counter /2
if Flag = 1 then
frac = enc_counter.lowbyte * 1090
posn1 = div32 100 ;mm*100
posn2 = (enc_counter.highbyte&15) * 2794 ; mm*100
frac = (enc_counter.highbyte>>4) * 44680
posn3 = div32 100 ;mm
posn3= posn3 + (posn1+posn2)/100 ;mm
frac= ((posn1+posn2)//100 +10)/10 ;round it up

Lcdout $fe, 1
lcdout "Dist: ",dec posn3,".",#frac
Flag = 0
endif
goto Main_Loop


Now I can measure up to 7100mm, but still more or less precise.
Next step will be order one of the PIC18x mcu and try long variables.
Here are suitable to use the same encoder reading code (written in ASM) and simple change the word var to long var?
I think its not that easy. When I have the 18x PIC I think, I will need more help with this project. Thanks.

richard
- 10th October 2016, 23:34
frac= ((posn1+posn2)//100 +10)/10 ;round it up
is incorrect , it will round the number up always


frac= ((posn1+posn2)//100 +5)/10 ;round it up

is the way to round the number up if the last digit is 5 or more

richard
- 11th October 2016, 07:47
using n-bit math

result


ready v3
turns 23 f 489
33609 3664.3145
turns 23 f 486
33606 3663.9875
turns 23 f 484
33604 3663.7694








Include "N-Bit_MATH.pbp"

enc_new VAR BYTE bank0
enc_old VAR BYTE bank0
enc_counter VAR WORD bank0
Flag var BYTE bank0
tmp var byte[4]
res var byte[4]
tmp1 var byte[4]
posn var WORD
turns var word
frac var WORD



; Set variable value @ startup
Flag = 1 ;preset a value for testing
enc_new = 0
enc_old= 0
enc_counter = 33609 ;preset a value for testing


Main_Loop:
if Flag = 1 then
turns= enc_counter /1440
frac= enc_counter //1440
serout2 PORTa.0,84, [13,10,"turns ",#turns,9,"f ",#frac ]
if frac then
@ MOVE?CP 157000, _tmp
@ MOVE?WP _frac, _tmp1
@ MATH_MUL _tmp1, _tmp, _res
@ MOVE?CP 1440000, _tmp
@ MATH_DIV _res, _tmp, _tmp1
@ MOVE?PW _tmp1, _posn
@ MOVE?CP 144, _tmp
@ MATH_DIV REG_Z, _tmp, _tmp1
@ MOVE?PW _tmp1, _frac
endif
posn=posn+turns*157
serout2 PORTa.0,84, [13,10,#enc_counter,9,dec4 posn,".",dec4 frac ]



Flag = 0
endif
goto Main_Loop
end

louislouis
- 11th October 2016, 12:51
I try to compile your code. I downloaded the N-Bit_MATH.pbp include, but the compiler stops with this error message:
"Bad data type"

If I try to compile only the N-Bit_MATH.pbp (only for test) and its do not compile. Stops on first line:
REG_X VAR BYTE[PRECISION] BANK0 SYSTEM
with the same error message MCU is the same 16F628A, For test I try also 16F688 with the same result. Something I'am doing wrong, I think.

HenrikOlsson
- 11th October 2016, 18:03
You need to define the constant PRECISION and assign it a value equal to the number of bytes to use for the values in the math routine. And you need to do this before including N-Bit. In this case it looks like Richard opted for 32bit values (4 bytes)

PRECISION CON 4 SYSTEM '32bit values
Include "N-Bit_Math.pbp"
If you open the N-Bit file you'll find a link to the thread on this forum: http://www.picbasic.co.uk/forum/showthread.php?t=12433

/Henrik.

louislouis
- 11th October 2016, 23:23
Yes, right. I forgot this line. After putting this line as you say the code compiles OK. Now I do some testing and trying to understand the code.