PDA

View Full Version : Can PBP read optical encoder damn fast? :)



CuriousOne
- 1st June 2020, 15:34
Hello.

Say we have optical encoder from inkjet printer. It has 60 pulses per 1cm of movement. So say it is moving with 20cm per second speed, this is 60x20=1200 readings per second. So can be software written in PBP, which will reliably count number of pulses and direction too? Output will be fed into AD9833, chip won't be doing anything else in realtime. So is this task doable with PBP, and which MCU I should consider?

Ioannis
- 1st June 2020, 19:47
Well a very fast chip at 64MHz may help.

But using another approach I guess it is possible with slower PIC also. You may use the CCP module to capture the rising and falling edge of the pulses in regard with the Timer so the PIC will capture the timer readings. Either just waiting or doing other stuff while the ISR will just transfer the timer value to your variable.

With 1200 readings you will have to count double that. Rising and falling edge so is 2400 reading and I guess double that also, for the quadrature readings. Finally your pulse to pulse time may be about 200usec. Not too demanding but fast.

Ioannis

HenrikOlsson
- 1st June 2020, 20:03
Yeah, 1200 counts per second really isn't THAT much. How to go about it depends - as always - on what else the PIC is supposed to do while keeping count (ie should it output to the AD9833 and if so, how, while counting pulses or how it's supposed to work) and on what peripherals and interrups sources are available.

If counting is one state and outputting is another then a tight loop polling the inputs will most likely do just fine for 1200 transistions per second.

It's important to determine if the 60 counts are with 4x decoding or not and if that is needed or not. Not doing 4x decoding is a lot easier than doing it.

The 18F2431 family contains a QEI module that will happily keep track of an encoder producing counts up in the MHz

CuriousOne
- 2nd June 2020, 03:22
Well the task is as follows. On power up, ribbon is stopped, AD9833 is set to output certain frequency. Depending on ribbon movement direction afterwards, frequency is increased or decreased. Movement can be fast or slow or even non-linear speed.

sayzer
- 2nd June 2020, 07:21
You may have a timer in counter mode in background; so you can do things in foreground while it counts in background.

CuriousOne
- 2nd June 2020, 09:15
I tried to start it at all, even at slow speeds, and looked forum for rotary encoder debug code. Most examples have things like this in it:
OLDR = PORTB & $30 and similar, current hardware config limiting code.
Is there a way to have "universal" rotary encoder code, which will work, if say one pin is going to PORTD.4 and another to PORTA.2 ? I'm not sure how all this XORing and ANDs can be performed across the various ports.

HenrikOlsson
- 2nd June 2020, 09:36
Do you have hardware already on which this has to run? If that's the case then please tell us what that is which PIC and which pins are available.

Having the encoder pins spread across two different port register will slow it down and is not ideal, two adjacent pins on the same port is prefferable. If those pins have IOC capabilities that's one way to skin it.

You say the encoder have 60 pulses, is that 60 pulses per channel (240 edges per cm) or 15 pulses per channel (60 edges)? If it's the former and you're fine with 60 Counts per cm that makes it easier since it's just a mater of sampling channel B on the Rising edge of channel A. No qudrature decoding needed.

I take it you've already figured out how to talk to the AD9833 or is that going to be the follow up question?

CuriousOne
- 2nd June 2020, 09:47
I have not went with AD9833 yet, because if I can't run encoder, then there is no need for DDS. And by the way, some DDS code I see around the forum, so I think I can re-use it. For the encoder, this is simple encoder from inkjet printer, HEDS-9730. The ribbon has plain strips, 60 per centimeter (I've counted it under microscope). Before going with this encoder, I want to have some code running with simple, rotary, 3 pin, incremental encoder. My prototyping system has PIC16F886 on it now, and while now I can connect both pins to same port - say PORTA.1 and PORTA.2, if doing own code, I'd like to have a code, which will not be limited by same port.

Ioannis
- 2nd June 2020, 09:53
If on different port, then you will spend time on transferring the data to a temp variable and do the XOR/AND on that temp byte.

Ioannis

CuriousOne
- 2nd June 2020, 10:02
While I know how XOR AND and other logical operations work, I can't get idea, how they are used over time based domain, as in case of encoder. I mean, where is that component, which defines the frequency of readout and pulse width and so on.

CuriousOne
- 2nd June 2020, 10:10
I came up with this code idea (have not tested it yet)



STATE: 'WAIT FOR ENCODER TO BE MOVED (STATE CHANGE)
X=PORTB.1
Y=PORTB.2
PAUSE 10
X1=PORTB.1
Y1=PORTB.2
IF X<>X1 OR Y<>Y1 THEN GOTO NEXTSTEP
GOTO STATE

NEXTSTEP:

SOMELOOP: 'COUNTER LOOP FOR PULSE LENGTH COUNTING
IF PORTB.1=1 THEN XIN=XIN+1 'X INCREMENT
IF PORTB.2=1 THEN YIN=YIN+1 'Y INCREMENT
IF PORTB.1=0 OR PORTB.2=0 THEN GOTO ANALYZE 'EXIT AND COMPARE LENGTHS
GOTO SOMELOOP

ANALYZE: 'DETERMINE DIRECTION AND INCREMENT CORRESPONDING VARIABLE
IF XIN>YIN THEN
CCW=CCW+1
ELSE
CW=CW+1
ENDIF
XIN=0 'RESET VARIABLES
YIN=0
GOTO STATE

microcnc05
- 2nd June 2020, 11:29
Check out https://www.microchip.com/wwwproducts/en/PIC18F4431 this series has a hardware quadrature encoder feedback module that runs on it's own in the back ground.

louislouis
- 2nd June 2020, 11:50
For optical encoder I use this:


; PIC18F4520

DEFINE OSC 4

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

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

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

; encoder ch A PortB.6
; encoder ch B PortB.7

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

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
enc_new = 0
enc_old= 0
enc_counter = 0
Flag = 0
@ INT_ENABLE RBC_INT ; enable external (INT) interrupts

Main_Loop:
Lcdout $fe, 1
lcdout Dec enc_counter
goto Main_Loop

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.
rlcf _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

CuriousOne
- 2nd June 2020, 12:31
Yes I checked that PIC datasheet already, but I don't see any ways to implement it in PBP, we don't have statement for encoder reading, like we have for LCDOUT or OWIN.

CuriousOne
- 2nd June 2020, 12:39
I've modified my code and it works ok, but sometimes I get false readings too - say for 20 pulses of CW, I will have 2-3 pulses of CCW and vise versa



STATE: 'WAIT FOR ENCODER TO BE MOVED (STATE CHANGE)
'Lcdout $Fe, $1, "WAIT..." , DEC ENX, " ", DEC ENY
X=ENX
Y=ENY
PAUSE 1
X1=ENX
Y1=ENY
IF X<>X1 OR Y<>Y1 THEN GOTO SOMELOOP
GOTO STATE


SOMELOOP: 'COUNTER LOOP FOR PULSE LENGTH COUNTING
IF ENX=1 THEN X1IN=X1IN+1 'X INCREMENT
IF ENY=1 THEN Y1IN=Y1IN+1 'Y INCREMENT
IF ENX=0 and ENY=0 THEN GOTO ANALYZE 'EXIT AND COMPARE LENGTHS
PAUSE 1
GOTO SOMELOOP


ANALYZE: 'DETERMINE DIRECTION AND INCREMENT CORRESPONDING VARIABLE
IF X1IN>Y1IN THEN
CCW=CCW+1
ELSE
CW=CW+1
ENDIF
LCDOUT $FE, $1, "RAW", DEC Y1IN, " ", DEC X1IN, " "
LCDOUT $FE, $C0, "VAL ", DEC CCW, " ", DEC CW, " "
PAUSE 1
X1IN=0 'RESET VARIABLES
Y1IN=0
GOTO STATE

CuriousOne
- 2nd June 2020, 12:41
This is simple 3 pin incremental encoder, middle pin is tied to VCC, A and B are tied to PORTB.3 PORTB.4, which are also pull down with 10K resistors. If I rotate shaft slowly, then no false readings. What can be reason, some wiring issue or code inefficiency?

Ioannis
- 2nd June 2020, 13:05
Yes I checked that PIC datasheet already, but I don't see any ways to implement it in PBP, we don't have statement for encoder reading, like we have for LCDOUT or OWIN.

What stops you from writing directly to the registers?

Ioannis

Ioannis
- 2nd June 2020, 13:12
This is simple 3 pin incremental encoder, middle pin is tied to VCC, A and B are tied to PORTB.3 PORTB.4, which are also pull down with 10K resistors. If I rotate shaft slowly, then no false readings. What can be reason, some wiring issue or code inefficiency?

Well, your LCD may be slowing down the response. And since you use PAUSE and LCDOUT it is expected that program is not the most efficient. The encoder is best done in ISR and even better using hardware module when available.

Ioannis

CuriousOne
- 2nd June 2020, 13:34
I don't see how to avoid use of pause statement. We have time changing, things are happening within time, so how not to consider time? In final device, there will be no LCDOUT, if that is the problem.

Ioannis
- 2nd June 2020, 13:42
ISR then.

Ioannis

HenrikOlsson
- 2nd June 2020, 18:46
If the encoder strip has 60 lines per cm then you can get a resolution of 240 counts per cm by counting all four edges of the quadrature cycle.

If you're OK with 60 it makes it a lot easier (compared to 4x decoding) since, again, all you need to do is poll channel B on the rising edge of channel A and you have your count direction. Using a coupe of external NAND gates (or perhaps the CLC module if your PIC has one) you can decode that into two discrete signals, up & down. Feed those into two counters on they'll count for you while you PAUSE.

If counting with software, polling the inputs, then using PAUSE, LCDOUT or any other command that takes longer to execute than the shortest time between two pulses WILL ruin the day - in which case resorting to interrupts or hardware counter is the only way. This is why I asked if the the counting should go on WHILE outputting to the DDS.

/Henrik.

CuriousOne
- 2nd June 2020, 19:43
As said above, there will be no LCDOUT or any other statements in the loop, except DDS updating. Regarding the precision, have not decided it yet, because have not build and have not heard :D maybe 10 readings per CM also will be ok....

CuriousOne
- 2nd June 2020, 20:23
I've found this: https://hackaday.io/project/122039-i2c-encoder-v2 - Ready firmware for the above mentioned PIC with I2C output. So what if I use this chip with that firmware to get data from encoder, and then use my PIC to send required data to DDS?

Ioannis
- 2nd June 2020, 21:07
If I get it right, you will use a PIC to read a quadrature encoder, then communicate with I2C to another PIC and the second PIC will send the data to your DDS?

Does this sound logical and efficient?

You have a PIC then just do it! Use some lines in ISR and get the job done.

After all, it is a good programming exercise! And the bonus is that you will be thrilled when finished! Don't be afraid of the ISR. A bit of work but you can do it!

Ioannis

P.S. what is your current PIC?

CuriousOne
- 3rd June 2020, 04:45
I'm result-oriented person :D
And knowing my programming skill, literally saying, I'd prefer to buy wheel, instead of inventing it :)
I have many different PICs, but no software knowledge that deep to handle all this stuff :D

HenrikOlsson
- 3rd June 2020, 06:24
Have you read the specs for that HackADay thing?

•It's support the standard rotary encoder and the RGB encoder
•It's possible to set all the 7bit of the I2C address trough a SMD jumper
•Dimension of 25x25mm or 0.98x0.98in
•With the castellated holes is possible to connected several boards on the 4 sides
•Possibility to solder the pull-up resistor on the I2C bus
•3 General Purpose pins. (GP pins)
•256byte of internal EEPROM divided in the 2 bank of 128 byte
•Advanced configuration respect the first version
•The maximum frequency of the A/B signal is 150Hz.

Anything that sticks out?

Ioannis
- 3rd June 2020, 07:27
I am a terrible programmer but have done some ISR routines that also do things fast.

If I can do it, everyone can do it! Try it and see how it goes. After all we are still here...

I'd select a PIC of relatively recent 18F series so the famous DT INTS-18 will support it, follow the instructions for the DT INTS and do some test. See here: http://dt.picbasic.co.uk/INT16/DTINTS-18

Nothing to be afraid of.

Ioannis

CuriousOne
- 3rd June 2020, 12:58
Yes I see 150Hz limitation. But as said above, I'd like to test idea first, if it is working at all.

HenrikOlsson
- 4th June 2020, 21:08
I couldn't help myself, had to see what I could do with just some simple, basic (no pun intended) coding. No interrupts, no hardware assist, no inline assembly language, just 33 line loop of straight PBP.

On an 18F4520 running at 20MHz it's seems solid as a rock at 20kHz. Above 20kHz it misses the odd count when subjected to long bursts. This is with a function generetor generating bursts of "perfect" signals with 50% dutycyle so in real life it might be a little less.
Connecting an optical encoder works fine but a mechanical one does not due to contact bounce. Using a 16bit WORD for the position counter and simple 1x decoding, pissing away 3/4 of the resoultion but that was what was asked for.

It bit-bangs SPI data to an AD9833 but there's no math to calculate actual frequency.

The LCD code is there for my debug purposes but I left it there just in case.



DEFINE LOADER_USED 1
DEFINE OSC 20

DEFINE LCD_DREG PORTD 'LCD data port
DEFINE LCD_DBIT 0 'LCD data starting bit 0 or 4
DEFINE LCD_RSREG PORTA 'LCD register select port
DEFINE LCD_RSBIT 3 'LCD register select bit
DEFINE LCD_EREG PORTA 'LCD enable port
DEFINE LCD_EBIT 1 'LCD enable bit
DEFINE LCD_RWREG PORTA 'LCD read/write port
DEFINE LCD_RWBIT 2 'LCD read/write bit
DEFINE LCD_BITS 8 'LCD bus size 4 or 8
DEFINE LCD_LINES 2 'Number lines on LCD
DEFINE LCD_COMMANDUS 3000 'Command delay time in us
DEFINE LCD_DATAUS 40 'Data delay time in us


FSYNC VAR LATB.3
SCLK VAR LATB.4
SDATA VAR LATB.5
Ch_A VAR PortB.6
Ch_B VAR PortB.7

Position VAR WORD ' Encoder position
Frequency VAR WORD '
AD9833 VAR WORD[5] ' Array for data to be sent to the AD9833
BitsToSend VAR BYTE ' Number of bits -1 to send to the AD9833 (max 127)
Ch_A_Old VAR BIT ' Memory bit for edge-detection


'-------------------------------------------------------------
'------------------------- Init ------------------------------
'-------------------------------------------------------------
CMCON = 7
ADCON1 = %00001111 ' No analog inputs.
TRISB = %11000001

Frequency = 0
Position = 0
BitsToSend = 255


PAUSE 1000
LCDOUT $FE, 1, "AD9833..."


' Initialize the AD9833 as per example in AN-1070.
' Just to verify that we're good to go.
' This should make it output 400Hz with a 25MHz base clock
AD9833[4] = $2100
AD9833[3] = $50C7
AD9833[2] = $4000
AD9833[1] = $C000
AD9833[0] = $2000

FSYNC = 0

For BitsToSend = 79 to 0 Step -1
SDATA = AD9833.0[BitsToSend]
SCLK = 0
PAUSEUS 20
SCLK = 1
PAUSEUS 20
NEXT

FSYNC = 1


Main:
'-------------------------------------------------------------
'--------------Poll encoder and keep count -------------------
'-------------------------------------------------------------
IF Ch_A = 1 THEN
IF Ch_A_Old = 0 THEN
IF Ch_B = 1 THEN
Position = Position + 1
ELSE
Position = Position - 1
ENDIF
Ch_A_Old = 1
ENDIF
ELSE
Ch_A_Old = 0
ENDIF
'-------------------------------------------------------------


'-------------------------------------------------------------
'------------- If needed, prepare to update DDS --------------
'-------------------------------------------------------------
IF Position <> Frequency THEN ' Has the count changed since last update of the DDS?
IF BitsToSend.7 THEN ' Are we free to update?
Frequency = Position

' See datasheet for AD9833, this is just for basic testing.
AD9833[2] = %0010000000000000
AD9833[1] = %0100000000000111
AD9833[0] = Frequency

AD9833.0[15] = 0
AD9833.0[14] = 1

BitsToSend = 47 ' 3*16 = 48 but we're using this variable to index the bits so minus 1

ENDIF
ENDIF
'-------------------------------------------------------------


'-------------------------------------------------------------
'---If DDS is to be updated, do it one bit per loop iteration----
'-------------------------------------------------------------
IF NOT BitsToSend.7 THEN ' Still bits left to send ?
FSYNC = 0 ' Chip select
SDATA = AD9833.0[BitsToSend] ' Setup data
SCLK = 0 ' Clock low
BitsToSend = BitsToSend - 1 ' Index next bit
SCLK = 1 ' Clock high
ELSE
SCLK = 1
FSYNC = 1 ' Chip select
ENDIF


' This is for debugging purposes.
' If button is pressed and LCD updated while the encoder is moved
' counts will obviously be missed.
IF PortB.0 = 0 THEN
LCDOUT $FE, 1, DEC Position, " ", DEC Frequency, " ", DEC BitsToSend
ENDIF

Goto Main

Ioannis
- 4th June 2020, 22:09
Excellent job Henrik.

At the expense of a little slower response, a couple of 100nF will help a lot debouncing mechanical encoders. I used that as I did not want to make a software debouncing at that time.

Ioannis

CuriousOne
- 6th June 2020, 05:52
IF A=1 or B=1 AND A<>B

Is this proper replacement for XOR statement?

richard
- 6th June 2020, 07:39
You could always write a little program to test your supposition and find out , you never know you may learn something




TOUCH var byte
A VAR TOUCH.0
B VAR TOUCH.1
RES var byte


touch = 4
Debug 9,"XOR TRUTH TABLE"
Debug ,13 ,10,"A",9,"B",9,"A^B",9,"CUT",13 ,10
while touch
touch=touch-1
touch=~touch
;CODE UNDER TEST
IF A = 1 or B = 1 AND A <> B THEN
RES=1
ELSE
RES=0
ENDIF
DEBUG #A,9,#B,9," ",#A^B ,9," ",#RES ,13 ,10
touch=~touch
WEND

Ioannis
- 6th June 2020, 11:07
And although the little debug program of Richard's might not give you a clue, the keyword is "priority".

Machines do not follow your thoughts. Need more detailed explanations, so parenthesis will help. See page 87 of the manual to understand why your expression is way out of what you intended to.

Ioannis

HenrikOlsson
- 6th June 2020, 11:22
If not pissing away 3/4 of the encoder resolution is something worth doing here's a replacement routine that does 4x decoding. It might not be the most optimized or elegant way but is works. Surprisingly (to me) it seems to work fine up to around 8kHz which means it does 32000 counts per second when being fed "perfect" quadrature signals my function gen.



' Added variables:
Encoder VAR BYTE
Old_Enc VAR BYTE
Test VAR BYTE
Dir VAR WORD ' Must be same size as Position variable


'-------------------------------------------------------------
'--------------Poll encoder and keep count -------------------
'-------------Alternative routine with 4x decoding------------
'-------------------------------------------------------------

Encoder.0 = Ch_A
Encoder.1 = Ch_B

Test = Encoder ^ Old_Enc

Dir = 0

If Test = 1 THEN
If Old_Enc = 0 THEN Dir = 1
IF Old_Enc = 1 THEN DIR = -1
IF Old_Enc = 2 THEN Dir = -1
If Old_Enc = 3 Then Dir = 1
ENDIF

IF Test = 2 THEN
IF Old_Enc = 0 THEN DIR = -1
IF Old_Enc = 1 THEN Dir = 1
IF Old_Enc = 2 THEN Dir = 1
IF Old_Enc = 3 THEN Dir = -1
ENDIF

Position = Position + Dir

Old_Enc = Encoder

Ioannis
- 6th June 2020, 11:47
To me looks elegant enough! Wow, 32000 counts/s? I suppose on a fast 18F device, right?

Ioannis

HenrikOlsson
- 6th June 2020, 12:56
Same setup as before, 18F4520 @20MHz so not really that fast.
I don't know why it seems faster than the 1x version from earlier, perhaps I made a mistake when measuring either one of them.

richard
- 7th June 2020, 09:15
Henrik assuming i have implemented your ideascorrectly it works not at all well for a ky040 re in real life, i get about 400 counts per indent and rarely does it get direction correct.
my old code previously posted works near perfectly in the same rig either as isr or polled even a couple of .1uf across the pins does not help.





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


DEFINE OSC 32
OSCCON=$70
ANSELA=0
OPTION_REG.6=0
ENCODER VAR BYTE ;THE STATE OF THE ENCODER
DIR VAR BYTE ;POSN
Old_Enc VAR BYTE ;LAST MOVE
TEST VAR BYTE
Position VAR WORD
TRISA = %11111110
lata.0=1 ;DEBUG
Ch_A VAR PORTA.4
Ch_B VAR PORTA.5


DEFINE DEBUG_REG PORTA
DEFINE DEBUG_BIT 0
DEFINE DEBUG_BAUD 38400
DEFINE DEBUG_MODE 0
pause 2000
Debug "Start",13 ,10
Position =0
Old_Enc=0

mainloop:
' Encoder.0= Ch_A ;WON'T WORK AT ALL MOST TIMES
' Encoder.1= Ch_B
Encoder = (PORTA&48)>>4
Test = Encoder ^ Old_Enc
Dir = 0
If Test = 1 THEN
If Old_Enc = 0 THEN Dir = 1
IF Old_Enc = 1 THEN DIR = -1
IF Old_Enc = 2 THEN Dir = -1
If Old_Enc = 3 Then Dir = 1
ENDIF
IF Test = 2 THEN
IF Old_Enc = 0 THEN DIR = -1
IF Old_Enc = 1 THEN Dir = 1
IF Old_Enc = 2 THEN Dir = 1
IF Old_Enc = 3 THEN Dir = -1
ENDIF
Old_Enc = Encoder
Position = Position + Dir
IF DIR THEN
debug 13,10, "C",SDEC Position
ENDIF
goto mainloop



my take [no pic18 worky or shitty old chips with no wreg access]


'************************************************* ***************'* Name : RE16.BAS *
'* Author : RICHARD *
'* Notice : *
'* : All Rights Reserved *
'* Date : 5/06/2020 *
'* Version : 1.0 *
'* Notes : ONLY PIC16'S AND 12'S WITH WREG *
'* : IN THIS INCARNTATION NO SHITTY OLD CHIPS *
'************************************************* ***************


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


DEFINE OSC 32
OSCCON=$70
ANSELA=0
OPTION_REG.6=0
ev VAR BYTE bank0 ;THE STATE OF THE ENCODER
cnt VAR WORD bank0 ;POSN
TMP VAR BYTE bank0 ;LAST MOVE IS 1, 0, -1
TRISA = %11111110
lata.0=1 ;DEBUG

DEFINE DEBUG_REG PORTA
DEFINE DEBUG_BIT 0
DEFINE DEBUG_BAUD 38400
DEFINE DEBUG_MODE 0
pause 2000
Debug "Start",13 ,10
CNT=0


mainloop:
ASM
bcf STATUS,C
RLF _ev ,f ;shift last reading into position
bcf STATUS,C
RLF _ev ,f
MOVF PORTA,W ;read enc pins 4,5
ANDLW 48 ;mask off others
SWAPF WREG ,w ;shift NEW reading into position
IORWF _ev,F ;combine this read with last read
MOVF _ev,W ;save ev
ANDLW 15
L?CALL enc_lut ; decide to inc , dec or ignore
MOVWF _TMP ;save decision
BTFSC _TMP,7
GOTO myDEC16
BTFSC _TMP,0
GOTO myINC16
enc_exit ; exit
GOTO OUT
myINC16
INCF _cnt ,f
BTFSC STATUS,Z
INCF _cnt +1 ,f
GOTO enc_exit
myDEC16
MOVF _cnt ,w
BTFSC STATUS,Z
DECF _cnt +1, f
DECF _cnt , f
GOTO enc_exit
OUT
ENDASM
IF TMP THEN
debug 13,10,SDEC CNT
ENDIF
goto mainloop


enc:
ASM
enc_lut ; the decision matrix
addwf PCL, F
retlw 0
retlw 255
retlw 1
retlw 0
retlw 1
retlw 0
retlw 0
retlw 255
retlw 255
retlw 0
retlw 0
retlw 1
retlw 0
retlw 1
retlw 255
retlw 0
ENDASM

HenrikOlsson
- 7th June 2020, 10:09
I have it running on the bench, works perfectly fine with an optical encoder (Scancon 2RMHF-500, 500 lines / 2000 edges per rev) - IRL :-)
Like I said previously I do not expect it to work reliably with a mechanical encoder unless it is properly debounced in hardware. Why your ASM version works with the same encoder I do not know :-(

You have a DEBUG statement within the loop - that will obviously mess up the timing but I know you know that.

richard
- 8th June 2020, 04:57
silly me i had the dir var as byte, it now works with this added change

' Encoder.0 = Ch_A
' Encoder.1 = Ch_B
encoder =(porta&48)>>4

reading the pins individually wont work for me @32mhz

HenrikOlsson
- 8th June 2020, 06:51
I made that exact mistake initially, hence the comment where the DIR variable is declared - yet I missed it when looking at you code...

I wonder why reading the pins individually doesn't work in your case. I only did it that way because CuriousOne wanted to be able to have the signals placed arbitrarily. I suspect though that reading them individually might be faster than masking and shifting.

I've been playing around with the math to go from encoder counts to actual frequency and I'm down to 110 cycles for calculating and stuffing the bits into the array. Had to resort to a tiny bit of ASM, getting the high word of a 16*16bit multiplication back from PBP without resorting to actually using LONGs.

richard
- 8th June 2020, 07:07
encoder=0

:o

richard
- 8th June 2020, 07:16
Had to resort to a tiny bit of ASM, getting the high word of a 16*16bit multiplication back from PBP without resorting to actually using LONGs.


did you and dt make this ? , i use it if req

;32 bit multiply result


A32bitVar var word[2]
Dummy var word


ASM
GetMulResult macro Dword
MOVE?WW R2, Dword ; Low Word
MOVE?WW R0, Dword + 2 ; High Word
endm
ENDASM


Dummy = 1000
Dummy = Dummy * Dummy
@ GetMulResult _A32bitVar

HenrikOlsson
- 8th June 2020, 08:41
Thanks, hadn't seen (or did not remember seeing, certainly can't take credit for it) that macro but that's pretty much what I did except I copied the value byte-by-byte instead of using the MOVE?WW macros.

Ioannis
- 8th June 2020, 14:33
Henrik,

I tested your last code with a mechanical quadrature encoder and works just fine with a couple of 100nF debouncing capacitors and internal pull ups of the PIC.

I noticed though, a jump on the position variable due to mechanical problem of the encoder. Say you are at the beginning with position at 0. One click on the right increments to 4 and one to left gets back to 0. Sometimes due to little more movement of the axis of the encoder you may get a 65535 then back to 0. This of course is not a software problem, rather a pure mechanical, but a user may perceive it as a program error.

Ioannis

HenrikOlsson
- 8th June 2020, 18:15
Yes, it's because your particular encoder has 4 counts per detent and, as you say, sometimes it will "overshoot" the detent slightly causing the code register one count before it "springs back" into the detent position. The important thing is that the code DOES register it "springing back" so that it doesn't lose position.

These mechanical encoders exists with either 4 counts (one quadrature cycle) per detent or 1 count (1/4 quadrature cycle) per detent. Using 1x decoding with an encoder that has 1 count per detent means you'd have to turn it 4 clicks for each increment. However, with the type of encoder you have using 1x decoding might be better.

Ioannis
- 8th June 2020, 18:49
Absolutely agree. Code works as expected and is fast enough for manual rotation. Have not tested for faster resposne.

The encoder indeed has a detent that produces 4x pulses so yeah. It is not appropriate for that code.

Ioannis

Ioannis
- 8th June 2020, 21:03
Not the best programming techniques but uses interrupts and is fast enough for x4 encoders like ALPS with push switch used to reset the position counter.

Based on Henrik's example and tested on 16F886.



intcon=%10001000
iocb=7

Encoder VAR BYTE
Old_Enc VAR BYTE
Test VAR BYTE
Dir VAR WORD ' Must be same size as Position variable
position var word
ch_a var portb.0
ch_b var portb.1
detent var byte
flag var bit

clear

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

wsave var byte $70 system

'::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler RBC_INT, _rotary, PBP, yes
endm
INT_CREATE
ENDASM

@ INT_ENABLE RBC_INT ; Enable PortB.0 interrupt for signal reception

goto main

rotary:
'-------------------------------------------------------------
'--------------Poll encoder and keep count -------------------
'-------------Alternative routine with 4x decoding------------
'-------------------------------------------------------------

Encoder.0 = Ch_A
Encoder.1 = Ch_B

Test = Encoder ^ Old_Enc

Dir = 0

If Test = 1 THEN
If Old_Enc = 0 THEN Dir = 1
IF Old_Enc = 1 THEN DIR = -1
IF Old_Enc = 2 THEN Dir = -1
If Old_Enc = 3 Then Dir = 1
ENDIF

IF Test = 2 THEN
IF Old_Enc = 0 THEN DIR = -1
IF Old_Enc = 1 THEN Dir = 1
IF Old_Enc = 2 THEN Dir = 1
IF Old_Enc = 3 THEN Dir = -1
ENDIF
if !portb.2 then 'Reset counter
position=0
flag=1
endif
if Old_Enc <> Encoder then
Old_Enc = Encoder
detent=detent+1 'Increment position after 4 pulses (one detent on ALPS encoder)
if detent = 4 then
Position = Position + Dir
detent=0
flag=1
endif
endif
@ INT_RETURN


main:
while 1
if flag then
hserout [#position,13,10]
flag=0
endif

'other code stuff here

wend

END


Ioannis