My Books‎ > ‎AVR GUIDE‎ > ‎

ANALOG INPUTS (ANALOG TO DIGITAL CONVERTER)


INTRODUCTION:

    Digital inputs are great, but sometimes devices output an analog signal.  An analog signal is a signal of varying voltage levels.  Temperature sensors, light sensors, potentiometers and flex sensors are just some of the devices that output analog voltages that the AVR could read.  Analog sensors are generally a resistor that changes its value when a given condition is met (light level, temperature, flexibility, stretching ... etc.).

    This is by far my favorite part of the AVR because you could find so many uses for it.





HARDWARE:


Figure 1: ATmega8 - ADC Pins



    Different AVRs have a different number of analog Inputs the ATmega8 and the ATmega168/328 has 6 different device pins but only 1 ADC (Analog to Digital Converter) converter.  The 6 input pins are connected to a multiplexer which basically allows a single input to be connected to the ADC in order to get a reading.  So we have to be smart about how to use our ADC and how to prioritize our readings.

    The data sheet on the ADC can be a bit overwhelming, it talks about timing, noise cancellation, filters, uses inductors and is 10 bit ... 10 bit! I mean come on.  The truth is that it is not as complicated as it may seem.  Most of us hobbyists do not need a NASA level of precision.  Lets do some quick math, 90% of the time we will use 5V, the ADC is a 10bit ADC so this means it can do 1024 bits of resolution.  5V / 1024 ~= 0.005V.   We will never need that kind of accuracy.  All we really need is a small capacitor or 2 and we are good to go.


Figure 2: Recommended ADC Power Hookup


    If you want to be fancy you can add a 10uH inductor for extra filtering between the AVCC pin and Vcc (5V on the schematic).  Most hobbyist's however, don't bother.  Lastly, most hobbyist's don't bother with with C2 and simply leave AREF disconnected.  I however, have had a lot of luck with a nice stable ADC using the schematic above. 


Figure 3: 3-pin Analog Sensor Hookup


    In order for an analog sensor to work, it must create some sort of voltage divider.  3 pin type sensors that have a GND, VCC and Output connection have a voltage divider build in and can be connected to an ADC pin directly as per Figure 3.  


Figure 4: 2-pin Analog Sensor Hookup


    2 pin type sensors on the other hand work by changing their resistance and will not work without the use of a resistor (because you need a minimum of 2 resistors to build a voltage divider network.  You could use any value for R1 but you have to make sure that you do not cause a short circuit and/or that it doesn't snuff out (for a lack of a better term) your sensor.  



2 Pin Resistive Type Sensor Calculations:

    Ok this section has a bit of math, so if your not interested here is a short hand rule.  Make R1 the same value as the highest value of the sensor.  This gives you the best power to sensor voltage range ratio.  The rest of this subsection just proves that statement.

    I use 2 calculations to make sure the Resistor value will work out well.  Max current draw and the Difference in voltage between sensor min and max.

    If you think back to V=IR, dropping the resistance will cause your current to rise if your voltage stays the same.  So if we add the value of R1 and the lowest value of our sensor together we will find the highest current draw for our sensor.   We also have to figure out the difference in voltage between the highest and lowest sensor resistance value to figure to figure out your window,  the bigger the window the more data the higher your resolution.    

    So lets use the Mini Photocell from Sparkfun as our sensor. It ranges form 1k - 10k Ohms:


 Using 1k Resistor Using 10k Resistor Using 1 Resisotor
 V = I * R
 I = V / R
 I = V / (R1 + R_sensorlow)
 I = 5 / (1k + 1k)
 I = 5 / 2k
 I = 2.5 mA
 V = I * R
 I = V / R
 I = V / (R1 + R_sensorlow)
 I = 5 / (10k + 1k)
 I = 5 / 11k
 I = 0.45 mA
 V = I * R
 I = V / R
 I = V / (R1 + R_sensorlow)
 I = 5 / 1 + 1k
 I = 5 / 1001
 I = 4.5mA
 
    Notice how we use 5 times less power if we use the 10k resistor over the 1k, and the 1 Ohm uses 10 times more power then the 10k.  Another thing to note, is that if your using 1/8th watt resistors you will be limited to 25mA @ 5V so knowing how much current your sensors draw isn't a stupid idea.

    Now for the resolution calculation.  We do some hard math or we could just create 2 voltage divider networks, 1 using the min value of the sensor and the other using the max values, and then subtract the difference to get our working voltage range.

 1k Resistor Min Sens R 1k Resistor Max Sens R Conclusion
Vout = R2 / (R1 + R2) * Vcc
Vout = 1k / (1k+1k) *5V
Vout = 1k / 2k * 5V
Vout = 2.5V
Vout = R2 / (R1 + R2) * Vcc
Vout = 10k / (1k + 10k) * 5V
Vout = 10k / 11k * 5V
Vout = 4.5V
Using a 1k Resistor the Photocell has a 2V difference between Min and Max.

Min 2.5V sensed
Max 4.5V sensed 


 10k Resistor Min Sens R 10k Resistor Max Sens R Conclusion
Vout = R2 / (R1 + R2) * Vcc
Vout = 1k / (10k+1k) *5V
Vout = 1k / 11k * 5V
Vout = 0.45V
Vout = R2 / (R1 + R2) * Vcc
Vout = 10k / (10k + 10k) * 5V
Vout = 10k / 20k * 5V
Vout = 2.5V
Using a 10k Resistor the Photocell has a 2V difference between Min and Max.

Min 0.45V sensed
Max 2.5V sensed 

    Finally lets do something crazy, lets use a 1M resistor with the sensor and see what we get.

 1M Resistor Min Sens R 1M Resistor Max Sens R Conclusion
Vout = R2 / (R1 + R2) * Vcc
Vout = 1k / (1M+1k) *5V
Vout = 1k / 1001k * 5V
Vout = 0.0049V
Vout = R2 / (R1 + R2) * Vcc
Vout = 10k / (1M + 10k) * 5V
Vout = 10k / 1010k * 5V
Vout = 0.049
Using a 1M Resistor the Photocell has a 0.04V difference between Min and Max.

So our resolution has really shrunk



    I was alway thought in school to have a conclusion for complicated or long example, so here it is, If your resistor is too low, you risk an overload and use more power, if its too high you will loose resolution.  Using the min value of your resistive sensor will come up to the same resolution as using your max value, so use the max value (since higher R means less I).  



Signal Noise:

    Our air filled with Electra-Magnetic noise;  Radio waves, wireless networks power lines all emit electro-magnetic noise.  This noise can be picked up by the ADC because the traces on the circuit board and any  wires between the board and sensor will act like an antenna and pickup this noise.  The longer the antenna the greater the noise.  So when your designing your circuit board make sure to keep the traces from the ADC as short as possible to reduce noise.  But what if your sensor needs to be far away from your board? Well you could use some fancy programming to try to "guesstimate" the true signal of you could use a shielded cable.  

    Shielded cables have a ... well shield around the conductors that will keep the noise from being felt by the conductors.  The shield is a metal braid (or foil) material that surrounds the wires. Any magnetic noise that comes from the air will be captured by the shield.



Figure 5: Shielded Cable


    In order to make this work, you have to connect the shield to the ground BUT only one 1 end.  Connecting both ends of a shield to ground could cause ground loop faults.   If you ever hooked up a high end stereo system and have heard hum coming from the speakers, you have heard a ground fault loop in action.


Using the ADC For Extra Buttons:

    This is a really cool trick, Say you want to build something, but you don't have enough Digital Inputs.  If you build a voltage divider network and use the buttons to short out the resistors you could read use the ADC to figure out what button was pressed.  The downsides to doing this is that it takes a lot more time to read an ADC then it does to read a Digital Input and that you could not read multiple keys being pressed at the same time.

Figure 6:  Multiple Buttons On the ADC





THEORY OF OPERATION:

 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
ADMUXREFS1REFS0 ADLAR MUX3 MUX2 MUX1 MUX0 
ADC Multiplexer Selection Register


 REFS1REFS0 Voltage Reference Selection 
 0AREF, Internal Vref turned off 
 0AVcc with external capacitor on AREF pin 
 1Reserved 
 1Internal 1.1V (ATmega168/328) or  2.56V on (ATmega8)
REF Bits


MUX 3...0Single Ended Input
 0000ADC0 
 0001ADC1 
 0010ADC2 
 0011ADC3 
 0100ADC4 
 0101ADC5 
 0110ADC6 
 0111ADC7 
 1000Temp Sensor (ATmega168/328 only) 
 1001 - 1101     (reserved)
 11101.1V (ATmega168/328) 1.30V (ATmega8)
 11110V (GND) 
MUX Bits


If ADLAR in the ADMUX Register is LOW (0)
 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
 ADCHADC9 ADC8 
 ADCL ADC7ADC6 ADC5 ADC4 ADC3 ADC2 ADC1 ADC0 
ADC Data Register


ADLAR in the ADMUX Register is HIGH (1)
 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
 ADCHADC9 ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 
 ADCL ADC1ADC0 
ADC Data Register


  7 bit6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
 ADCSRAADEN ADSC ADFR* ADIF ADIE ADPS2 ADPS1 ADPS0 
NOTE * ADATE in ATmega168/328, ADFR in ATmega8
ADC Control and Status Register A


 ADPS2ADPS1 ADPS0 Division Factor 
0002 *
001
010
011
10016 
10132 
11064 
111128 
ADPS Bits
            * not a typo, check datasheet if you don't believe me :P

Reference Voltage Sources:

    The first thing we need to do is select a reference source (this is the high voltage) using the REFS1 and REFS0 bits in the ADMUX register. We could chose to use the voltage on the AREF pin, this voltage has to be a minimum of 2.0V on the ATmega8 or 1.0V on the ATmega168/328 and a max of VCC.  So when would you use this?  Say your using a 5V to power your AVR.  And you're sensors are on a 3.3V power supply.  If you want to use the full range you could hook up the 2nd power supply to the AREF pin.  How do I know that the min/max on the AREF pin?  It's buried in the datasheet under the "ADC Characteristics" table in the "Electrical Characteristics" section of the Datasheet.  The other References are AVcc or an internal test voltage.  Most of the time you will find yourself using the voltage on AVcc for two reasons.  First of all most analog devices are low power consumption devices (so you really don't need to run them off of another power supply for any reason) and secondly because using a reference on VREF prevents us from using the interior sources.


ADC Multiplexer Source:

    Because we only have a single ADC but 6 ADC pins to choose from we need to feed the signal into the ADC using the built in multiplexer.  To do this by setting the MUXn bits in the ADMUX register.  The neat thing is that if you change these values while the conversion is running, the conversion will finish before the change takes (so if you ever find yourself having problems with bad data you might be switching before the ADC finishes).


ADC Data Register Control:

    This really got me when I first started playing with micro controllers.  Here is the deal.  The ADC has 10bit of resolution. However, the AVR is an 8 bit micro controller, so we are always working with 8 bit registers.  So in order to get 10bit data we have to split it into 2 registers ADCH and ADCL.  We can use a 16 bit register or we could use an 8 bit register and dump the bottom 2 bits (which are the least significant bits anyways).  If you choose to use the full 10 bits resolution you should leave ADLAR LOW(0) and make sure you read the ADCL register first because reading the ADCH causes to ADC to update.  If you only want to use 8 bit resolution you will want to set the ADLAR to HIGH(1), this way you only have to read ADCH.  I guess I could have said, the ADLAR bit lets you control how the AVR is going to output the data.  Set ADLAR HIGH(1) if you only want to 8 bit of resolution. 


ADC Conversion Timing:

    The next thing requires a bit of math (and for once its not bit math).  In order to get the best conversion results the ADC clock needs to be between 50k - 100k on the ATmega8 and 50K-200k on the ATmega168/328.  So take your processor clock frequency divide by 100k on the ATmega8 (or 200k on the ATmega168/328).  When you get the result move the the next highest Division Factor.


ATmega8:

Processor Clock Speed / 100k = x(round up to next division factor)

eg.  Using a 8Mhz Clock
8MHrz / 100k = 
8,000,000 / 100,000 = 80
next highest = 128


ATmega168/328:

Processor Clock Speed / 200k = x  (round up to next division factor)

eg.  Using a 1Mhz Clock
1MHrz / 200k = 
1,000,000 / 200,000 = 5
next highest = 8


Modes of Operation:

    Now we need to figure out what kind of mode we want to run our ADC in.  We can choose to do a single conversion or free running mode.  In single mode we do 1 conversion and we are done.  In free running mode we run continually, when one conversion finishes we automatically start the next.  The default is single conversion mode so we don't have to do a thing.  To use Free running mode set the ADFR bit HIGH (1) in the ADCSRA register on the ATmega8 or set the ADATE bit in the ADCSRA register on the ATmega168/328 and leave the ADSTn bits LOW (0) in the ADCSRB register (more on this register later on).

    There are advantages and disadvantages of both modes.  In single scan mode, we tie up the processor while the ADC is doing its conversion, but its simple to use with multiple Analog Inputs because we trigger one at a time.  In free running mode, we have to have a bit of extra code to figure out what Analog input just finished and have to make sure we switch the MUX at the right time,  it's not too bad but it is not as simple as single scan mode.


ADC Interrupt: 

    Whatever mode you choose to use can be used with, or without interrupts, which will trigger the ADC vector.


Special Modes of Operation for the ATmega168/328:


 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
 ADCSRBACME ADTS2 ADTS1 ADTS0 
ADC Control and Status Register B


 ADTS2ADTS1 ADTS0 Trigger Source
 0Free Running 
 0Analog Comparator 
 0External Interrupt Request 0 
 0Timer/Counter0 Compare Match A 
 1Timer/Counter0 Overflow 
 1Timer/Counter1 Compare Match B 
 1Timer/Counter1 Overflow 
 1Timer/Counter1 Capture Event 
ADTS bits


    Isn't this cool, on the ATmega168/328 we can use the Timer/Counters and the Analog Comparator as a trigger source to start the ADC conversions.  Man does this ever cut down on the coding.  Set the ADATE bit in the ADSCRA register to HIGH (1) and set the ADTSn bits in the ADCSRB register the the values in the Table and we are good to go.


 7 bit6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 bit 
DIDR0ADS5D ADC4D ADC3D ADC2D ADC1D ADC0D 
Digital Input Disable Register 0


    This register is designed to reduce power consumption of the AVR by giving you the ability to turn of the Digital Input circuitry on the 6 ADC pins while your using the ADC.  If power consumption is a problem set the bits to HIGH (1) if not leave them at LOW (0).





SOFTWARE:


ADC on The ATmega8:

    Lets write a bit of code.  The two most common ways you will use the ADC is to do a single conversion at a given time in the program and/or to do continues scanning and have it trigger a an interrupt when its done.   I'll demonstrate both for each AVR type.


ATmega8 & ATmega168/328 Code:
// this code scans ADC1 for an analog signal upon request, using 8Mhz processor clock

#include <avr/io.h>
#include <stdint.h>       // needed for uint8_t


int ADCsingleREAD(uint8_t adctouse)
{
    int ADCval;

    ADMUX = adctouse;         // use #1 ADC
    ADMUX |= (1 << REFS0);    // use AVcc as the reference
    ADMUX &= ~(1 << ADLAR);   // clear for 10 bit resolution
    
    ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);    // 128 prescale for 8Mhz
    ADCSRA |= (1 << ADEN);    // Enable the ADC

    ADCSRA |= (1 << ADSC);    // Start the ADC conversion

    while(ADCSRA & (1 << ADSC));      // Thanks T, this line waits for the ADC to finish 


    ADCval = ADCL;
    ADCval = (ADCH << 8) + ADCval;    // ADCH is read so ADC can be updated again

    return ADCval;
}

    

int main(void)
{
    int ADCvalue;

    while (1)
    {
            ADCvalue = ADCsingleREAD(1);
            // ADCvalue now contains an 10bit ADC read
    }
}


ATmega8 Code:
// this code continually scans ADC0 for an analog signal, using 8Mhz processor clock


#include <avr/io.h>
#include <stdint.h>       // needed for uint8_t

#include <avr/interrupt.h>


volatile uint8_t ADCvalue;    // Global variable, set to volatile if used with ISR


int main(void)
{

    ADMUX = 0;                // use ADC0
    ADMUX |= (1 << REFS0);    // use AVcc as the reference
    ADMUX |= (1 << ADLAR);    // Right adjust for 8 bit resolution

    ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescale for 8Mhz
    ADCSRA |= (1 << ADFR);    // Set free running mode
    ADCSRA |= (1 << ADEN);    // Enable the ADC
    ADCSRA |= (1 << ADIE);    // Enable Interrupts 

    ADCSRA |= (1 << ADSC);    // Start the ADC conversion


    while (1)
    {
        // main loop
        
    }
}


ISR(ADC_vect)
{
    ADCvalue = ADCH;          // only need to read the high value for 8 bit

}



ADC on the ATmega168/328:

    The ATmega168/328 code isn't much different then the ATmega8 code.  If you remember the only difference is that we have a ADATE (ADC Auto Trigger Enable) bit in the ADCSRA register that we use in order that we could then use to set different trigger types (such as Free running mode).  For single conversion the code is exactly the same so please read above.


ATmega168/328 Code:
// this code continually scans ADC0 for an analog signal, using 16Mhz processor clock


#include <avr/io.h>
#include <stdint.h>       // needed for uint8_t

#include <avr/interrupt.h>


volatile uint8_t ADCvalue;    // Global variable, set to volatile if used with ISR


int main(void)
{

    ADMUX = 0;                // use ADC0
    ADMUX |= (1 << REFS0);    // use AVcc as the reference
    ADMUX |= (1 << ADLAR);    // Right adjust for 8 bit resolution

    ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescale for 16Mhz
    ADCSRA |= (1 << ADATE);   // Set ADC Auto Trigger Enable
    
    ADCSRB = 0;               // 0 for free running mode

    ADCSRA |= (1 << ADEN);    // Enable the ADC
    ADCSRA |= (1 << ADIE);    // Enable Interrupts 

    ADCSRA |= (1 << ADSC);    // Start the ADC conversion

    sei();    // Thanks N, forgot this the first time =P


    while (1)
    {
        // main loop
        
    }
}


ISR(ADC_vect)
{
    ADCvalue = ADCH;          // only need to read the high value for 8 bit
    // REMEMBER: once ADCH is read the ADC will update
    
    // if you need the value of ADCH in multiple spots, read it into a register
    // and use the register and not the ADCH

}



Changing MUX While In Free Running Mode With Interrupts:

    The last thing I want to cover is what if you have sensors on ADC0, ADC1 and ADC2 and you want to run in free running mode.  The problem is that you only have 1 ADC and only 1 Interrupt vector. So when a interrupt triggers you need to know what sensor is being read AND you need to switch to the next sensor.  This could all be done in the ISR routine, so I will just post below.   And don't forget to make the Sensor Data Registers volatile.


ATmega8 & ATmega168/328 Code:
ISR(ADC_vect)
{
    uint8_t tmp;            // temp register for storage of misc data

    tmp = ADMUX;            // read the value of ADMUX register
    tmp &= 0x0F;            // AND the first 4 bits (value of ADC pin being used) 

    ADCvalue = ADCH;        // read the sensor value

    if (tmp == 0)
    {
        // put ADCvalue into whatever register you use for ADC0 sensor
        ADMUX++;            // add 1 to ADMUX to go to the next sensor
    }
    
    else if (tmp == 1)
    {
        // put ADCvalue into whatever register you use for ADC1 sensor
        ADMUX++;            // add 1 to ADMUX to go to the next sensor
    }
    else if (tmp == 2)
        // put ADCvalue into whatever register you use for ADC2 sensor
        ADMUX &= 0xF8;      // clear the last 4 bits to reset the mux to ADC0
    } 
}


    That's it, its over man, that's all I know about ADCs.  It's bed time.


Cheers
Q
Comments