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
|For novel ideas about building embedded systems (both hardware and firmware), join the 40,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype and no vendor PR. Click here to subscribe.|
By Jack Ganssle
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 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.
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 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.
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.
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 ***********************************************