![]() |
|
In a recent project I implemented angular feedback using an incremental encoder. The intent of this article is not to rehash a well known concept, but to show the particulars of my implementation. Additionally, I provide the source code to implement the system as this ties directly to the type of feedback required in many robots.
Personal robotics like many other hobbies suffers greatly from monetary constraints. To address this issue, I choose a single chip implementation. Initial designs centered around the issues of resolution lines vs. Frequency and speed of logic required to decode the same. I settled on a lower resolution encoder with a high mechanical input ratio. This again minimized cost as high line count encoders come with a hefty price tag built in as an added feature. One of the key observations made was to implement the mechanical gain ratio (gear pair) through a single linkage to minimize slop and backlash. Additionally, the use of split pre-loaded matched pair gear further helps to reduce errors incurred through the mechanical linkage.
My first thoughts were to develop a custom FPGA to handle the high speed single chip implemenation. Money constraints specifically those involved with the tremendous amount of debug time pointed me back to the PIC processor by MicroChip. Other design considerations focused around the number of pins required to interface to the encoder (2) and the monitoring processor system (8). Still further investigation revealed the need to determine the maximum number of instructions that could be executed between the phase changes of the encoder. With the simple timing cycles presented by the RISC architecture style, this was easily accomplished, by literally counting them up during the preliminary code layout sessions.
Software on the system consists of a state machine which continuously runs at the maximum speed of the processor. (20MHz) Continuous cycling contrast sharply to a solid state system which only cycles when a state change occurs. This cycling was mandated by the need to keep up with the polled communications protocol of the controlling processor. The design called for readings to be taken before the motion, during the motion (both minimum and maximum values), and final resting position. To implement this, the counter runs continuously. The system copies the value of the counter into four separate two byte registers when instructed to do so by the controlling processor. These registers being Zero1 & 2, High1 & 2, Low1 & 2 and Cur1 & 2. An additional design constraint is imposed that the maximum and minimum values can only be recorded once the encoder has passed across the zero reference point. A simple flag (record) is set to 0 upon system reset. The two byte counter is continuously checked in both the increment and decrement routines for the zero condition. Once it has been detected, the record flag is turned on. With the record flag on, the maximum and minimum values are updated as well as the counter.
![]() |
State machine
Increment Counter
|
The microcomputer that receives the input from the encoder system, interfaces through an eight bit interface. In the state cycle above, the first step is to check the control byte from the master processor. Control is passed to the PIC processor through port RB0..7. This control byte can tell the system to perform one of the following functions:
During these operations, the state machine continues to operate such that there is never a state loss. Before each data transfer, the controlling microprocessor requests a self test byte, a preset value which is composed of 01010101b. This byte lets the main processor know that the system is operating properly and following this, the request for the appropriate data byte or function may be made. If the self test byte 01010101b is not transmitted by the state system, then main processor system can identify that an error has occurred and can signal the user to perform the measurements over again. Each of the system data transfers is a two byte transfer and there are six transfers to be made. The self test value is checked between each transfer which leads to the chance for error being, conservatively stated, small.
To apply this code to provide feedback from the wheels of your robot real time, all one must do is remove the flag checking on the count up and count down subroutines. They prefilter the counting mechanism to make sure that certain conditions particular to this project had been met. Specifically, these filters make sure that the counter passes back across the zero point before the minimum and maximum functions are enabled. If you have no need for the minimum and maximum function, simply use the code as is, and you will be able to read current counter high and low byte as well as having access to the reset function. Just a note, resetting the system writes 0x8088 to the counter rather than 0x0000.
;----------------------------------------------------
; Foam1.src
; Pic 16C55 state machine code to de-encode
; 4,000 line incremental two line encoder
; attached to port A and communicate with
; 8031ah based host through ports B & C
; By:
; Kenneth Y. Maxon
; On:
; 3/12/97 - 3/18/97
;----------------------------------------------------
org 8
recflg ds 1
acc ds 1
cur1 ds 1 ; stored low byte of counter var
cur2 ds 1 ; stored high byte of counter var
zero1 ds 1
zero2 ds 1
high1 ds 1
high2 ds 1
low1 ds 1
low2 ds 1
p1 = ra ; point to port of encoder attachment
p2 = rc ; point to the command port
p3 = rb
buffer = 010h ; shared data ram
org 0
device pic16c55,hs_osc,wdt_off,protect_off
reset begin
begin mov !ra,#00001111b ;define input only low four bits hooked up
mov !rb,#0 ;define output, 0's are outputs
mov !rc,#00001111b ;define some input, 0's are outputs
mov recflg,#0
goto record
;----------------------------------------
state0 lcall disptch
mov acc,p1
AND acc,#00000011b
cjne acc,#00000001b,atate1
goto state0 ;rem were here in state 0 and nothing has changed
atate1 cjne acc,#00000011b,atate2
lcall cntup ;rem were here in state 0 and we're going to state #1
goto state1
atate2 cjne acc,#00000000b,atate3
lcall cntdwn ;rem were here in state 0 and we're going to state #3
goto state3
atate3 mov p3,#11h
goto atate3 ;were here because a state error has occured
;----------------------------------------
state1 lcall disptch
mov acc,p1
AND acc,#00000011b
cjne acc,#00000011b,btate1
goto state1 ;rem were here in state 1 and nothing has changed
btate1 cjne acc,#00000010b,btate2
lcall cntup ;rem were here in state 1 and we're going up to state #2
goto state2
btate2 cjne acc,#00000001b,atate3
lcall cntdwn ;rem were here in state1 and we're going down to state #0
goto state0
btate3 mov p3,#011h
goto btate3 ; were here because a state error has occured
;----------------------------------------
state2 lcall disptch
mov acc,p1
AND acc,#00000011b
cjne acc,#00000010b,ctate1
goto state2 ;rem were here in state 2 and nothing has changed
ctate1 cjne acc,#00000000b,ctate2
lcall cntup ;rem were here in state 2 and we're going up to state #3
goto state3
ctate2 cjne acc,#00000011b,ctate3
lcall cntdwn ;rem were here in state 2 and we're going down to state #1
goto state1
ctate3 mov p3,#011h
goto ctate3 ;were here because some sort of state error has occured
;----------------------------------------
state3 lcall disptch
mov acc,p1
AND acc,#00000011b
cjne acc,#00000000b,dtate1
goto state3 ;rem were here in state 3 and nothing has changed
dtate1 cjne acc,#00000001b,dtate2
lcall cntup ;rem were here in state 3 and we're going up to state #0
goto state0
dtate2 cjne acc,#00000010b,dtate3
lcall cntdwn
goto state2 ;rem were here in state 3 and we're going down to state #2
dtate3 mov p3,#011h
goto dtate3 ;were here because an error has occured
;----------------------------------------
;come here to increment the current count
;----------------------------------------
cntup setb p2.6
clrb p2.6
clc
add cur1,#01h
addb cur2,c ;add bit to fr (btfsc fr,b incf fr,1)
cjne recflg,#00,ctupyep ;check to see if we're recording
cjne cur1,zero1,ctdnnada ;turn it on?
cjne cur2,zero2,ctdnnada
mov acc,p2 ;will the 8031 let us record yet
and acc,#00001111b
cjne acc,#00000001b,ctdnnada
mov recflg,#01h
ctupnada setb p2.7
nop
nop
nop
clrb p2.7
ret ;record flag cannot come on in count up mode
;----------------------------------------
;okay here were recording, so check the max values
;if cur2 > high2 then update
;if cur2 < high2 then return
;fall through if cur2 = high2
;if cur1 > hgih1 then update
;return
;----------------------------------------
ctupyep setb p2.7
clrb p2.7
cja high2,cur2,ctupdt ;first is above second ?
cjb high2,cur2,ctupdn ;first is below second ?
cja high1,cur1,ctupdt ;first is above second ?
setb p2.6
nop
clrb p2.6
ctupdn ret ;count_up_done
ctupdt mov high1,cur1 ;count_up_update
mov high2,cur2
setb p2.7
nop
clrb p2.7
ret
;----------------------------------------
;come here to decrement the current count
;----------------------------------------
cntdwn setb p2.6
clrb p2.6
clc
sub cur1,#01h
subb cur2,c
cjne recflg,#00,ctdnyep ;check to see it we're recording
cjne cur1,zero1,ctdnnada ;turn it on?
cjne cur2,zero2,ctdnnada
mov acc,p2 ;will the 8031 let us record yet
and acc,#00001111b
cjne acc,#00000001b,ctdnnada
mov recflg,#01h
ctdnnada setb p2.7
nop
nop
nop
clrb p2.7
ret
;----------------------------------------
;okay were recording, so check the min values
;if cur2 < low2 then update
;if cur2 > low2 then return
;fall through if cur2 = high2
;if cur1 < low1 then update
;else return
;----------------------------------------
ctdnyep setb p2.7
clrb p2.7
cjb low2,cur2,ctdndt ;second below?
cja low2,cur2,ctdndn ;second above?
cjb low1,cur1,ctdndt ;second below?
setb p2.6
nop
clrb p2.6
ctdndn ret ;count_down_done
ctdndt mov low1,cur1 ;count_down_update
mov low2,cur2
setb p2.7
nop
clrb p2.7
ret
;----------------------------------------
;main dispatch table
disptch mov acc,p2
and acc,#00001111b
cjne acc,#00000000b,disp2
goto cntrst
disp2 cjne acc,#00000001b,disp3
ret
disp3 cjne acc,#00000010b,disp4
goto outhigh
disp4 cjne acc,#00000011b,disp5
goto outhigl
disp5 cjne acc,#00000100b,disp6
goto outlowh
disp6 cjne acc,#00000101b,disp7
goto outlowl
disp7 cjne acc,#00000110b,disp8
goto outzerh
disp8 cjne acc,#00000111b,disp9
goto outzerl
disp9 cjne acc,#00001000b,disp10
goto outcurh
disp10 cjne acc,#00001001b,disp11
goto outcurl
disp11 cjne acc,#00001111b,disp12
goto pmode
disp12 ret
;----------------------------------------
;the record routine begins with find state
record mov acc,p1
AND acc,#00000011b
cjne acc,#00000000b,fstate1
goto state3
fstate1 cjne acc,#00000001b,fstate2
goto state0
fstate2 cjne acc,#00000010b,fstate3
goto state2
fstate3 goto state1
;----------------------------------------
;this routine zeros out the counters
cntrst mov zero1,#88h ;arbitrarily choosen zero value fill
mov zero2,#80h ;in with appropriate values later...
mov cur1,#88h
mov cur2,#80h
mov high1,#88h
mov high2,#80h
mov low1,#88h
mov low2,#80h
mov recflg,#0 ;turn of the record function
ret
;----------------------------------------
outhigh mov p3,high2
ret
outhigl mov p3,high1
ret
;----------------------------------------
outlowh mov p3,low2
ret
outlowl mov p3,low1
ret
;----------------------------------------
outzerh mov p3,zero2
ret
outzerl mov p3,zero1
ret
;----------------------------------------
outcurh mov p3,cur2
ret
outcurl mov p3,cur1
ret
;----------------------------------------
pmode mov p3,#01010101b
ret
Since building the original measuring apparatus two months ago, I've used this code in three separate projects with only minor code modifications. I hope that this code can help you in your next robotics project.