Position
Encoders
Copyright 1991,
Jack G. Ganssle
Abstract
Encoders transmit
position or frequency info to the computer. Here's a few ways to make life
with them easier.
Published in
Embedded Systems Programming, February 1991
One of the most
exciting embedded systems I worked on was a huge guage designed to measure
the thickness of metal in a steel mill. A frighteningly radioactive cesium
source, encased in a ton of lead, shot gamma rays through up to 4 inches of
steel. An ion chamber measured just how much of the radiation made it through
the steel, providing the raw material of a thickness calculation. Two embedded
PDP-11 minicomputers and a handful of Z80s drove the 7 ton sensor assembly
back and forth on a railroad track, so the guage could read the steel's thickness
at any point across the plate's 14 foot width. Without question, debugging
the code that ran the sensor back and forth on the track was the most fun
of the project! One software bug sent the monstrous assembly through an electronics
cabinet, causing no end of recriminations...
A problem we
faced on this project was feeding the sensor's position on the railroad track
back into the computer. Inaccurate positional information would invalidate
all of the thickness data. The end-customer was forking over better than $2
million for the system; they expected correct data all of the time. In this
case the solution was fairly simple: we put a shaft encoder on one of the
wheels. The encoder transmitted a 12 bit binary code representing position
back to the computer.
Measuring position
is important in most factory control environments, the home of a lot of embedded
systems. More often than not some sort of encoder provides all of the location
data to a computer.
An encoder is
a mechanical device coupled to a rotating shaft that provides a digital representation
of the shaft's position. Modern encoders almost exclusively use optical techniques
to convert the shaft's angle to digital form. A beam of light shines though
a disk fixed to the spindle. Photocells detect marks on the disk. Depending
on the type of encoder, these marks will represent either an absolute or relative
position. Shaft Encoders
Shaft encoders
convert position information into an absolute binary code. A 12 bit encoder
will output 0000 with the shaft at zero degrees. At 90 degrees the code will
be one quarter of the full scale reading, or 400 hex. 180 degrees is 800h,
until just before 0 degrees FFFh is output. It's pretty easy to compute the
shaft's angular position via a formula or lookup table given only the number
of encoder bits.
Sometimes binary
is less than ideal. The science of Information Theory teaches us that straight
binary eats up a lot of "channel capacity". As the encoder slowly
rotates the binary value increases monotonically, thankfully keeping the software
that reads it simple. Unfortunately, with each code change only one bit might
be different (say, from 000 to 001), or many bits might change (from 7FF to
800). When lots of bits change at once trouble can result. For example, many
switching lines can cause crosstalk in the cable.
Gray code is
a variation of binary that eliminates much of the problem. A Gray code encoder
will change only one bit at a time as the shaft rotates. Table 1 shows the
relationship between Gray and binary. Note that for each sequential entry
in the table, the Gray code changes only by a single bit. This reduces the
demands made on the transmission channel, whether they are simply wires or
a radio link.
(As an aside,
Information Theory underlies much of the computer world. It's interesting
philosophically as well, as the Theory relates how entropy, the amount of
disorder in the universe, effects just how fast one can send data over a communications
link. See "Information Theory" in the December 1987 Byte for a quick
summary of the subject.)
While Gray code
might be ideal for an encoder's output, it's pretty hard to use in internal
computations. Standard practice is to convert Gray to binary using a table
translation scheme.
Computing absolute
position is easy if we know the encoder's resolution (in distance per revolution).
One unknown still exists: what does the "zero" position correspond
to?
The "parked"
or "zero" position of any mechanical system is when it is all the
way at one extreme or the other. One crude way to calibrate the encoder is
to have a technician manually rotate it to give a reading of 000 when the
system is parked. Then, the computer can figure distance just in terms of
offset from the 000 position.
If anything in
the mechanical part of the beast slips the position data will contain errors.
It's far better to add a limit switch that is asserted when the sensor is
parked. Then the software can read the encoder whenever the limit is detected,
and apply this reading as an offset to all position calculations. It saves
the tedious calibration step, and reduces errors.
Have the computer
regularly park the sensor and re-read the encoder offset if things are prone
to mechanical slipping. If the application can stand having it offline once
in a while, this will remove all doubt about the positional accuracy.
A shaft encoder
implicitly contains direction information. The software will know if the shaft
is rotating clockwise or counterclockwise by examining the direction of the
code change. This is important in bidirectional systems, particularly when
the shaft might be controlled by external forces other than the computer.
Whenever moving
parts are involved be wary of backlash. High quality encoders themselves have
no inherent backlash. In other words, if the direction of rotation changes
there will be no count uncertainty due to mechanical play in the unit. Unfortunately,
a perfect encoder might still see backlash from play in the rest of the mechanics.
When a motor starts spinning, play in the gearing might make the encoder not
see the first few millimeters of travel. Where accurate positioning is important
the software might have to make the system always approach a final position
from the same direction, thus always working with constant backlash errors.
It's better to use low backlash gears if you can convince the mechanical group
to go along with the extra cost.
Never put an
encoder on a powered wheel. If the motor's startup makes the wheel slip for
an instant before it grabs the track, then the encoder position will be wrong.
Always connect the encoder to an unpowered, coasting wheel. Toothed Encoders
Another type
of encoder gives a pulse stream as the shaft rotates. No absolute position
information is conveyed. The software must count the number of pulses and
infer position indirectly.
Before today's
scribed glass disks, encoders looked rather like toothed gears. The beam of
light was interrupted by the rotating teeth, giving rise to the name "toothed
encoders". The name stuck even as the technology passed the concept by.
Shaft encoders
with binary codes are ideal for some systems, but have a number of inherent
problems. Their resolution is limited. It's difficult to make an accurate
encoder with more than 12 bits of resolution - 4096 counts per revolution.
Sometimes this is just not enough. A toothed encoder can generate tens of
thousands of pulses per revolution.
In other cases
the encoder is used not so much to indicate position as to command the software
to read an I/O port. A simple analogy is the distributor in a car. A each
of 4 positions per revolution the distributor causes a contact to close, firing
off a spark plug. In the embedded world things are a bit more complex. A scanning
colorimeter might use a rotating diffraction grating to sweep thousands of
colors of light across a sample. The software must read reflected energy at
each color. If the grating's shaft is connected to a toothed encoder, then
each of the thousands of pulses can interrupt the CPU and make it read the
reflected light. In this case we don't care about the shaft's absolute position
so much as we need a "read data now" interrupt from the moving pieces.
Perhaps another
example is in order. Remember the bad old days of punched cards? Some readers
had a toothed encoder coupled to the shaft that moved cards through the scanner.
One revolution of the shaft corresponded to the length of the whole card.
80 separate pulses per revolution came from the encoder. Each one came as
a set of character holes were in position, and meant "read a character
now".
Most toothed
encoders come with twin outputs. One is the pulse stream indicating relative
position. The other is a "zero" pulse that is asserted only once
per revolution, indicating the start of a rotation. Using the zero and count
pulses the program can indeed come up with the same kind of absolute position
information output from a shaft encoder. If the encoder is calibrated just
like a shaft encoder, then the cheaper toothed version will give accurate
position information.
One downside
of computing position this way is that a toothed encoder gives no information
about the direction of rotation of the shaft. The pulse stream looks the same
either way. Further, if the mechanical assembly is moved when the computer
is turned off then the position will be incorrect. Unless, of course, the
computer recalibrates everything on power up. Fast Reads
Most of the systems
I've worked on required very fast response to each encoder pulse. Only rarely
can one afford the luxury of polling a port to find that a pulse is asserted.
All polled loops
are subject to varying degrees of latency. Consider the following polling
code:
loop: in a,port
; read pulse port
and a,80 ; isolate pulse bit
jz loop ; jump if no pulse
The loop will
fall through immediately if the bit becomes asserted just before the input
instruction is executed. If it goes high just AFTER this same instruction,
then it will execute the entire loop again, doubling the detection time. This
variable latency is sometimes deadly.
Again consider
the case of a car's distributor. Variable latency will make the engine run
rough, since the time of each spark plug ignition will dither. In the case
of a typical instrument collecting data every 50 microseconds (say), a 5 microsecond
dither represents 10% acquisition uncertainty.
In a high speed
encoder system minimizing latency becomes a sort of search for the holy Grail.
I've spent weeks yanking just a few microseconds out of the code to control
the dither.
One obvious solution
is to connect the pulse stream to the processor's interrupt input. As we all
know, an interrupt will immediately stop
the CPU and vector off to the interrupt service routine (ISR). Actually, "immediately"
is not quite true. Different instructions take different amounts of time to
execute. Sometimes the range is several orders of magnitude, particularly
when a MULTIPLY instruction is compared to a NOP. Conventional interrupt handlers
are no better than a polled loop in minimizing latency.
Worse, interrupts
are slow. When handling very fast data the interrupt structure just might
not be able to keep up. After all, a vectored interrupt requires an acknowledge
cycle to get the interrupt source, several PUSHes to stack a return address
and other context information, and an indirect read from memory. All of this
takes time - sometimes quite a few microseconds.
Some processors
support several types of interrupts. While the Z80 is usually used in its
most useful vectored configuration, it does have an oddball mode left over
from its 8008 heritage. On an interrupt, external hardware can jam an instruction
into the execution stream. This mode bypasses all conventional interrupt processing.
The following
code takes advantage of a jammed NOP instruction.
loop:
halt ; wait for interrupt
<process interrupt>
jmp loop
The interrupt
does nothing but exit the halt condition and execute a NOP. Latency is just
the processor's raw interrupt latency, which is usually only one or two machine
cycles. With no interrupt servicing overhead, the code runs about as fast
as possible.
The technique
can be improved a bit where speed is a real problem. Probably the interrupt
processing code will maintain a count of the number of pulses received, and
will exit the loop when the count is exceeded. Keep the count in the HL register
pair. Then, jam an INC HL instruction instead of NOP. This single byte opcode
will perform some useful work in the code, slightly increasing the system's
performance.
Hitachi's 64180
(called the Z180 by Zilog) has an even better way to process fast interrupts.
This is a CMOS processor that minimizes power consumption. It has a very low
power mode which is initiated by executing the SLP (sleep) instruction. SLP
is rather like HALT, except for the lower current drain.
SLP has an unusual
mode that is but poorly documented deep in the chip's hardware description.
Like HALT, only an interrupt will exit a sleep condition. Like HALT, SLP will
initiate conventional interrupt servicing if the processor is in vectored
interrupt mode. However, if the global interrupts are disabled (via a DI instruction),
but an individual peripheral interrupt enable bit is on, then that interrupt
will exit sleep mode without starting an interrupt acknowledge cycle. The
code will just flow past the SLP instruction into the next opcode, effecting
just the sort of fast response we need without having extra hardware to jam
a special instruction.
Most processors
are not so well endowed. The 80x88 family, for example, can only handle an
interrupt with the conventional slow service routine. Still, options exist
even in these cases. Connect external hardware that drives the processor into
a permanent WAIT condition when a particular I/O cycle occurs. Then, have
the encoder release the WAIT. The code will look like:
loop:
out al,dx ; assert a WAIT
<process data> ; come here after encoder pulse
jmp loop
Again, the latency
will be minimal and execution speed fast. Non-linear Encoders
We've been discussing
conventional toothed encoders where each pulse occurs exactly so much of a
revolution after the previous one. When
rotated at a constant speed, the pulse output will represent one constant
frequency.
On one project
we rotated a diffraction grating to produce a linear sweep of colors. Unfortunately,
the angle versus frequency of a grating is related to the sine-squared of
the grating's angular position. For a while we used a hideously expensive
and unreliable assembly of specially shaped cams to rotate it at the trigonometric
rates, keeping a linear color output. The grating's irregular motion and constant
accelerations burned up bearings in days. Then someone had a brilliant idea:
why not make a special encoder to linearize the data?
A mechanical
engineer removed the cams so the grating would rotate at a constant rate.
Then, we had a special encoder made that gave pulses at different rates depending
on the position of the shaft. These pulses were carefully spaced to compensate
for the grating's sine- squared frequency characteristic. By taking data whenever
a pulse arrived, the computer acquired an array which was linear over the
spectrum.
The moral is
that in the embedded world we have unique opportunities to solve complicated
problems creatively. That's what makes this business so much fun.
***********************************************
Gray Code Binary Code
0000 0000
0001 0001
0011 0010
0010 0011
0110 0100
0111 0101
0101 0110
0100 0111
1100 1000
1101 1001
1111 1010
1110 1011
1010 1100
1011 1101
1001 1110
1000 1111
Table 1: Gray and Binary Codes
***********************************************