|
|
|
Embedded Microcontrollers, or Making Your Sensors Really Smart Sensor designers and OEMs will welcome this explanation of how to spend a little and accomplish a lot. Ed Ramsden, Cherry Electrical Products
There are two amazing trends in the world of microprocessors. The first is that top-of-the-line processors, such as those used in desktop and DSP applications, keep getting faster, even after nearly 30 years of development. The Pentium-based system on your desk is more powerful than the supercomputer of only two decades past. While this trend has been in the industry limelight, another, nearly invisible trend is equally exciting, especially to those of us who design sensors: the constant decline in the cost of a minimal microcomputer-on-a-chip. It is now possible to buy a processor--complete with RAM, user-programmable ROM, and various I/O capabilities--for less than $1.00 in moderate volumes. For a little additional expense you can also acquire onboard A/D and D/A converters, as well as communications capabilities (SPI, CAN, serial) and nonvolatile memory for calibration factors. Low-cost microcontrollers embedded into sensor products create opportunities for adding end-user value to those products. When the cost of incorporating a microprocessor into a sensor compares to that of adding an op amp and a few resistors, many new applications become cost effective. A microcontroller can be used either to replace existing analog-/discrete-logic functions, or, more ambitiously, to add enhanced functionality. As an example of the former case, consider a proximity sensor. A microcontroller with onboard A/D can be used to provide factory adjustable trip points, resulting in a product that is more consistent but functionally similar to a proximity detector made with traditional analog circuitry.
For an example of the latter, consider the same proximity sensor, but in this case the microcontroller takes a distance measurement, linearizes it against an in-sensor calibration table, and communicates this measurement over a serial link to a higher-level control system. The latter example provides a much higher level of sensor functionality than the former; the per-unit cost of the latter device, however, may not be significantly higher.
Anatomy of a Microcontroller Figure 1 is a block diagram of a typical microcontroller. The principal difference between this device and a general-purpose microprocessor is that it integrates memory and I/O capabilities onto a single silicon chip, whereas the general-purpose device requires external memory and I/O circuitry. The primary components are: CPU. A microcontroller's CPU is often very simple, with a limited instruction set, and is highly optimized to occupy a minuscule amount of chip real estate. Don't expect to find support for high-level operations such as floating-point arithmetic; these devices often work with 8-bit integers, and sometimes don't even provide integer subtraction as a primitive instruction. Nevertheless, there are many applications that don't require enormous computational power or finesse, and an inexpensive "minimal processor" is a good solution. Data Memory. This is the processor's read-write memory (RWM). Instead of being measured in megabytes, however, this onchip memory is measured in bytes, with fewer than 256 bytes common on many low-end devices. Because of the small number of memory locations available, this memory is also commonly referred to as the microcontroller's register file. In some processors a number of these registers may be implemented as EEPROM or flash memory, which retains its contents even when the power is turned off and is useful for storing customization and calibration data. Program Memory. The program code resides in this memory, which is almost always separate from the data memory. This feature allows the processor to fetch instructions while simultaneously fetching data. In some processor families, this separation allows the program memory width to differ from that of the data memory, permitting more efficient encoding of instructions. In either case, this memory is usually writeable only from outside the processor when the device is being programmed for a particular application. Digital I/O. The simplest microcontrollers provide a number of digital I/O lines that can be used to move digital data into and out of the device. With appropriate software, these lines can be used to talk to A/D and D/A converters and to perform serial communications in addition to sampling status flags. Timer/Counters. These peripherals can be used in a variety of ways, depending on their configuration options. As counters, they can be used to count external events or frequency. As timers they can be used to measure the time between events or to trigger the processor to perform actions periodically. A special "watchdog timer" can restart the processor if no activity occurs within a specified time interval, usually a few milliseconds. This is one function that is especially useful in sensors and other embedded systems, where there is no one around to hit the reset button should the system lock up. Finally, many timers can be configured to function as pulse-width modulators, which can be used as low-cost, high-resolution D/A converters.
A/D Converter. Many microcontrollers now have integral A/D converters, often with 48 input channels. This feature allows the microcontroller to interface to a variety of sensors, with little or no additional circuitry. While most of these devices offer 8 bits of resolution, with conversion times ranging from 2 ms to 100 ms, 10- and 12-bit converters are becoming more common.
Communications. Microcontrollers often integrate communication subsystems targeted at particular applications. SPI and MicroWire are serial protocols intended mainly for communications among chips on a single circuit board. Universal Asynchronous Receiver-Transmitters (UARTs) are commonly used as the basis for RS-232 and RS-422 protocols. Although less common at present, CAN and USB network interfaces are also featured on some microcontrollers. Software Structures With modern microcontrollers offering so much onchip hardware, it is possible in some applications to use them as single-component black boxes into which analog signals enter, and from which processed data and decisions exit. In many cases, the system hardware design will be nearly trivial, and the product's value added will derive almost solely from its software content. Software for a real-time embedded application can be considerably different from the applications you commonly run on your PC. While the objective of most software embedded into a sensor will be to read a set of inputs, do some computations, and output the results, there are many possible ways of organizing a program. A few of the most common approaches are:
Polled I/O. In the flowchart shown in Figure 2, the processing sequence is to read the inputs, do some calculations, output the results (which may include updating internal variables), and then repeat the process. While this scheme is simple, and appropriate for many applications, it can misuse processor time in that if nothing is happening, there may be no need to perform any calculations. This process can be improved; after reading the inputs, the processor looks to see if anything has changed; if not, it cycles back to the beginning. Only if there is a change does it perform the calculations. The benefit of having the processor run in a tighter loop isn't that it saves cycles, but rather that it improves the response time from a change on the inputs to a change on the outputs.
Interrupt Driven. A second common software organization, shown in Figure 3, is the interrupt-driven model. In this type of program, a foreground task, such as the polled I/O program described above, runs more or less continuously and is interrupted by various events called interrupts. An interrupt can be caused by a change of state on an I/O pin, the receipt of data through a communications port, or the tick of one of the timers. When an interrupt occurs, the CPU stops executing the foreground task and begins executing a section of code dedicated to servicing that interrupt (interrupt service routine). When this routine is finished, control transfers back to the foreground task at the point at which the interrupt occurred. The difference between an interrupt-driven program and the modified polled-I/O program is that the foreground task doesn't have to watch for interrupts; this function is provided for in the CPU's hardware. This provides significant performance benefits over polled-I/O programs. The downside of interrupts is that because they can redirect program execution at nearly any point, they can make the program more complicated to develop and thoroughly debug.
For many applications, however, particularly those that must deal with real-time asynchronous events such as serial communications and precision timing, the use of interrupts can actually simplify the organization of a program.
State Machines. State machines are a simple way of representing processes that evolve in time. In a state machine, the condition, or state, of a process is represented by the value of a variable. The process then moves from one state to another, with the transitions determined by the values of the inputs. Outputs are a function of the present state and the inputs. Mathematically, a state machine can be represented as: Next_state := f(present_state, inputs) Output := g(present_state, inputs) where f() and g() are functions, often based on lookup tables A common way to visually represent a state machine is to use a state-transition diagram (see Figure 4. The state machine represented here describes the behavior of a simple traffic light. In this case, the individual states are displayed as different light combinations. Note that several states have identical external behaviors but are represented by separate states. The reason is that although they appear the same (same-color lights), they need to be differentiated so that they can sequence to the proper "next state."
In addition to providing a powerful way of thinking about sequential processes, state machines are also easy to implement in software. Figure 5 shows a flowchart for a state-machine program organization. The first step in the program is a "gatekeeper" function that limits the rate and circumstances under which the state may be updated. This function often is controlled by a timer interrupt so that state updates occur at regular intervals. The next program step is to determine the present state by looking at the program's state variable and to branch to the appropriate state-handling routine. Finally, in the state-handling routine, the program looks at inputs, performs computations, and updates outputs. The program must also determine the next state and update the program's state variable. Once this is done it returns to the gatekeeper function to wait until it is allowed to update the state again.
In a practical program, polled-I/O, interrupt-driven, and state-machine techniques are often combined; each has its strengths and weaknesses, and a mix is usually required to provide acceptable performance. Sharp Tools
One major difference between programming for microcontrollers vs. desktop PCs is the environment--or more precisely, the lack of one in the case of a microcontroller. To put it simply, a $0.75 processor doesn't do Windows (at least not yet) and consequently doesn't provide any of the user interface amenities you may have come to expect from your desktop machine. Getting a program to function on one of these devices can be a trying experience and requires specialized programming tools.
First you will need to obtain language translators, such as assemblers, compilers, and linkers that do run on your PC. You will also need to get a device programmer. This is a box that hooks to your PC and sports a socket into which you insert the microcontroller. You use the device programmer to download the compiled program from your PC into the PROM in the microcontroller, a process that is colorfully known as "burning" the code into the chip. You can now try running your un-debugged software in your un-debugged hardware. This often results in the microcontroller's behaving in ways you did not anticipate, a process aptly called "crash and burn" debugging. Fortunately, various tools are available to make debugging an application easier, or, in many cases, simply possible at all. The first of these are simulators, which are programs that allow you to run your code on a simulated microcontroller residing on your desktop PC in the form of software. Simulators allow you to step through the program and stop it where you want and to look at the contents of the simulated microcontroller's memory.
The disadvantage of simulators is that they don't usually let you simulate the rest of the system into which the microcontroller will be embedded. To do this requires a device known as an emulator. These devices are pieces of hardware that connect to your PC and include a cable that plugs into the socket where your microcontroller will ultimately reside. From the standpoint of the rest of the system, the emulator looks just like a microcontroller sitting in its socket. The advantage, however, is that from your PC you can monitor and control the internal state of the microcontroller, looking at registers, stopping execution, and modifying memory locations. A good emulator can make simple development projects go much more smoothly and make complex ones possible.
Finally, no discussion of microcontroller software would be complete without at least touching on the use of assembler language vs. high-level languages for writing programs. A program written in assembler consists of a series of commands corresponding to the hardware-level instructions for the particular microcontroller you are intending to use. The advantage is that you get direct and unfettered control of the microcontroller hardware. The disadvantages are that the program will not be portable to other microcontrollers and that it takes a great deal of effort to write and debug. Not only is it much easier to write programs in high-level languages such as C or C++ than in assembler, but you also get a degree of code portability. The disadvantage is that you don't get quite the degree of control you get with assembler. For many simple applications, particularly on the very lowest end devices where memory space and processor cycles are at a premium, assembler may be the best solution, especially when the program length doesn't exceed a few hundred lines. For larger or more complex applications the bulk of the code will normally be written in a high-level language, such as C, with time-critical functions being hand-coded in assembler. DSP in the Slow Lane In addition to the general programming techniques presented above, one application for small microcontrollers, particularly those with built-in A/D converters, is to provide simple digital signal processing (DSP) functions. While specialized DSP processors are available for performing sophisticated operations on signals in the audio frequencies and higher, there are many sensor applications where signals are of low frequency or require very simple processing. Thresholding One of the simplest DSP applications is thresholding with hysteresis, where the microcontroller reads an analog transducer signal through its A/D port and compares it to preprogrammed thresholds (see Figure 6). If the value is higher than the on threshold, it sets an output bit; if it is lower than an off threshold, it clears the bit. While this operation can also be performed with a trivial amount of purely analog hardware, the use of a microcontroller offers the potential for additional sophistication; the thresholds could be made to adapt to the history of the input signal or to vary with other environmental influences such as temperature. For even the simple case of fixed thresholds, the ability to program the exact levels on a device-to-device basis may increase manufacturability enough to offset the difference in cost between the microcontoller and the analog solution. Finally, the flexibility offered by the microcontroller may allow you to provide a wide range of functionality from a single piece of hardware, simplifying inventory management. Linearization Linearization is another DSP operation commonly used on sensor data. Offsets, gain errors, and nonlinearities can be removed from a raw transducer's transfer curve by passing it though a customized linearization function. There are several ways of doing this with simple microcontrollers. For processors with low-to-moderate-resolution A/D converters (812 bits), the simplest solution is often straight table-lookup. The programming is simple, and the operation is very fast. The result of the A/D conversion is used as an index into an array that stores the "corrected" data points (see Figure 7). An 8-bit input requires a table of 256 entries; 12 bits, 4096 entries. The memory required to store tables of these sizes is often available on the microcontroller itself, or can be added externally for a small additional cost. If memory is limited, another linearization technique is linear interpolation. In this scheme, the correction function is represented as a connected series of linear segments, of which only the endpoints need to be stored. The following steps are then taken to linearize the raw transducer measurement, W: 1. Determine the segment number, N, on which the W falls. 2. Fetch X n , Y n , and X n+1 , Y n+1 to define the line segment. 3. Compute the linearized value, V: V = (WX n ) (Y n+1 Y n ) + Y n (X n+1 X n ) (1)
Note that this operation involves four add/subtracts, a multiplication, and a division. If memory is available, and more speed is required, two of the subtractions and the division can be precomputed for each segment and stored along with the X and Y data.
Filtering Another common DSP application is filtering. Simple filters such as the moving-average type (see Figure 8. can be easily implemented on microcontrollers. Figure 8 also lists pseudocode to implement this 4-point moving-average filter. The algorithm is tailored to work around the shortcomings of available processors in two ways. The first is by using a number of samples that is an integer power of 2 (2, 4, 8, 16, etc.). This allows the division to be implemented with a bit-shift operation (assuming unsigned values!). The second workaround is more subtle. Transferring the past-sample variables to one another is normally computationally inefficient; most DSP algorithms update index pointers to the past-sample data to avoid moving them around. Many low-end microcontrollers do not efficiently support access to arrays of data, and for moderately small sets of data, actually moving the data around can be faster than performing the computations to maintain index pointers needed to fetch the data from an array. A similar scheme can be used to implement general-purpose finite impulse response filters, which allow for low-pass, high-pass, and band-pass filtering functions. To do this requires the multiplication of each of the samples by a constant before they are added together. Most useful finite impulse response filters, however, require a significant number of samples (>20), and will be limited to low sampling rate applications when implemented on a low-end microcontroller. * Ed Ramsden is a Project Engineer, Cherry Electrical Products, 11200 88th Ave., PO Box 581913, Pleasant Prairie, WI 53158-0913; 414-942-6473, fax 414-942-6334, erams den@cherrycorp.com
| ||||||||||||||||||||||||||||||||
|
|