circuitcellar.com
Magazine Support   Digital Library   Products & Services   Suppliers Directory 
 
 





 

Issue 127, February 2001
Working with AVR Microcontrollers


by Stuart Ball

Start The Hardware The Software Tools Design Considerations The Bottom Line Sources PDF

Design Considerations

Most of the AVR devices that have an ADC use an external reference voltage. This voltage can be between 0 V and the AVR supply voltage, Vcc. Analog input values can range between 0 V and the reference voltage. The simplest approach to generating a reference value is to connect the reference to Vcc. This eliminates the need for an external reference, but the precision of the conversion is limited to the accuracy of the Vcc regulation.

The ATtiny15 has an internal 2.56-V reference for its ADC. Analog inputs can range between 0 V and the reference voltage. According to the datasheet, the reference has a tolerance of about 6%, so this is the best accuracy you can achieve with no modifications.

You can apply your own reference to the ATtiny15, but one way to get better precision out of the internal reference is to apply a known, precise voltage to the chip when it’s programmed. If the software knows what the ADC value should be with the nominal 2.56-V reference, it can calculate a correction factor for the actual reference voltage using:

Correction factor = Expected ADC value/Measured ADC value

The correction factor is stored in EEPROM and multiplied by all subsequent ADC readings. Of course, this requires that a pin be available to put the device into the special calibration mode. You can do the same thing with an external reference voltage. This technique allows you to use a less precise (and less expensive) reference for your application.

You can use a similar technique with other sensors. Thermistors typically have a 5% or 10% resistance tolerance, which translates directly into a temperature measurement error. To use this technique on a thermistor, you have to hold the thermistor at a precisely known temperature for the calibration.

When you perform this type of calibration, you can get correction factors that are either greater or less than one. To avoid doing floating-point math in real time, you might want to derive a multiply-and-shift-right sequence to do the multiplication with integer math. Another alternative is to use a look-up table to correct the ADC values.

The watchdog timer uses its internal 1-MHz oscillator. The exact frequency of this oscillator varies with supply voltage and temperature, and it varies from one device to the next. Consequently, you should allow for variation in the value of the watchdog timeout. For instance, if you program the watchdog timer for a 2-ms timeout and reset it using a 2-ms interrupt, you may sometimes get a watchdog timeout even though the reset is working properly. Or worse, the condition may only occur at high temperatures and not on all units.

Figure 2 shows how programming the flash memory and EEPROM works. All AVR devices without an internal oscillator require that an external crystal or clock source be connected for programming. Devices with an internal oscillator, such as the ATtiny15, use that clock to time the program operations.

2102027-f2.gif (11144 bytes)
Figure 2—There are two methods for serial programming. (a) shows the low-voltage method while (b) shows the high-voltage method. c—During normal operation, if the AVR is an output, ground this point or tie it high during programming to keep the programming wave form from driving whatever logic connects here. If the AVR pin is an input, you can leave it floating during programming.

There are two methods for serial programming; low-voltage programming is enabled by grounding the RESET input and high-voltage programming is enabled by applying 12 V to the RESET input. Not all of the AVR devices support the high-voltage mode. The timing diagrams in Figure 2 are typical.

A problem you may run into with in-circuit programming is the dual nature of the pins. Ideally, you would leave the ISP pins free for programming, but this is not always possible. This can make in-circuit programming difficult.

One way around this problem is shown in Figure 2c. A 2.2-kW resistor is placed in series with the AVR pin. In normal operation, the AVR pin can drive external logic or it can function as an input from external logic. During programming, the control signals are directly connected to the AVR pin.

This scheme requires that the programming driver be able to drive both high- and low-logic levels into the 2.2-kW load, so a driver with suitable current capacity is needed. Obviously, if the AVR pin is being used as an output that has to sink or source significant current, this approach won’t work because the series resistor will limit the current available to whatever the AVR is driving.

An alternative way to handle this problem is to add logic external to the AVR device that multiplexes the pins between the normal function and the ISP function. Of course, this complicates the design of your circuit.

Sometimes you need more pins than you have. A means to handle this is shown in Figure 3. Here, one of the pins is pulled high with a resistor. In normal operation, this pin is used as an output. To put the device into Calibration mode, the pin is grounded before power is applied.

2102027-f3.gif (7772 bytes)
Figure 3—Sometimes you need more pins than the device has, so dual-purpose inputs are handy. (a) shows using a pin as a calibration input and (b) shows using an ADC input to read two switches.

To use this scheme, the software must check the state of the pin before it programs the direction register to make the pin an output. If the pin is high, the code executes normally. If the pin is low (having been externally grounded to enable calibration) the code enters the special Calibration mode.

This example used an external pull-up. You could accomplish the same thing with the internal pull-up on the port pin. However, these are large value resistors, so the rise time will be slow and the software may need to introduce a delay before reading the pin.

Figure 3b shows how a single AVR ADC input can be used to read two switches. The analog input voltage has four different values corresponding to the four possible states of the switches. The switches could be configuration switches or user pushbuttons.

The AVR timers fall broadly into two categories of 8- and 16-bit. All AVR devices have at least one 8-bit timer and some have two. The 16-bit timers have more functionality than 8-bit timers do. The timers can be clocked from the CPU clock using a programmable pre-scale value or from an external pin.

The simpler AVR timers are a counter that can be clocked by a (programmable) pre-scale value from the system clock or be clocked by an external clock. These counters can be loaded by software and generate an interrupt when they roll over from FF to 00. These timers have no provision for a repeatable clock other than divide by 256. If you want a divisor other than 256, you have to reload the timer each time the overflow interrupt occurs. If you are using a /1 or /8 clock pre-scale, this can make the time period vary by whatever your interrupt latency variation is, because the interrupt latency can delay interrupt servicing until one or more timer clocks have occurred.

Some AVR devices have more sophisticated 8-bit timers, which essentially add an 8-bit compare register. When the count reaches the compare value, the compare register can generate an output or interrupt the CPU. This allows you to have a regular, repeating interrupt generated in hardware.

Other AVR devices include 16-bit timers. These include a compare function like the more complex 8-bit counters. The 16-bit timers also have an input capture function that captures the value of the free-running counter when an input pin changes state. When using input capture, the 16-bit timer can roll over from FFFF to 0000. The software must take this into account.

The 16-bit timers can switch an output pin when the output compare occurs. And, they can generate a PWM output. Of course, any timer function that uses an external pin makes that pin unavailable for general-purpose I/O.

External clocking allows you to operate the timer from a timebase other than the CPU clock. The external clock is internally synchronized to the CPU clock, so the external clock cannot be any faster than the CPU clock. Some versions of the AVR allow you to run a timer from a 32.768-kHz crystal that is connected to two pins. This allows you to use the timer as a real-time clock.

Because the AVR is an 8-bit processor, the 16-bit timers must be loaded in two operations. This is accomplished with a temporary register. The software writes the most significant byte of the register values, then the hardware stores this value in the temporary register. The software then writes the least significant byte and the hardware writes both bytes simultaneously to the timer registers.

Be careful if interrupts are used. The 16-bit timers have three registers, the output compare, input capture, and counter registers. There is only one temporary register. So, if you use interrupts and if the interrupts access the 16-bit registers, there is a potential for a race condition when an interrupt occurs (see Figure 4).

2102027-f4.gif (9236 bytes)
Figure 4—Let’s say the non-interrupt code wants to write a value of 53D7 hex to the timer registers and the input capture register contains a value of 2245 hex. (a) shows how it’s supposed to work and (b) shows what happens when it doesn’t work.

The timer register gets loaded with the wrong value because the interrupt occurred between the two timer writes by the non-interrupt code. The result is that the interrupt code modified the temporary register before the non-interrupt code could update the timer registers. The way to avoid this is to disable interrupts before the non-interrupt code modifies the timer registers.

As with any design, make sure the power-up state of the port pins does not damage your hardware. For instance, if you have two port pins driving both transistors in an H-bridge, be sure that the power-up state doesn’t turn on both transistors (see Figure 5).

2102027-f5.gif (4056 bytes)
Figure 5—Take a look at the AVR driving an H-bridge. Make sure the power-up state of the pins doesn’t turn on both transistors.

In Figure 5, two port pins of the AVR drive two MOSFET transistors in one half of an H-bridge. At powerup, both outputs are pulled high by the pull-up resistors because the ports come up as inputs (high impedance). Consequently, both transistors turn on, making a short between Vcc and ground and probably destroying one of the transistors. The fix is easy, use an inverter to make one of the transistors turn on when the output is low. But it illustrates the potential problem. Similar cases would include two relays or two motors that must not be turned on at the same time.

As mentioned earlier, the AVR processors all have 32 internal registers. Even when using a device with additional SRAM, many applications can be implemented by using just the 32 registers. If you’re working in an HLL, the compiler typically assigns the registers for you. If you’re working in assembly, there are a few things you can do to make efficient use of the registers.

For example, when defining the registers you’ll use, identify variables that won’t be used with immediate instructions and assign those functions to the first 16 registers. Examples include counters that will be zeroed, incremented, and decremented, but never loaded with an immediate value.

Similarly, assign the registers that need to be used in immediate instructions to one of the upper 16 registers. If you have a general accumulator register for some operations, make it one of the upper 16 registers.

If you need several on/off flag values, don’t use a complete register for each flag. Instead, assign one of the upper 16 registers as a flag register, assign each bit to be a flag value, and use the bit set/clear/test instructions to manipulate individual bits. This can reduce the number of registers you need.

For fast context switching, dedicate registers for use in the ISR. By dedicating these registers, you avoid the time needed to push and pop registers from the stack. Of course, this won’t work if the ISR needs a lot of dedicated variables or if you have a lot of interrupts, because you will run out of registers.