2013年8月30日 星期五

ADC of ATmegaXXX of AVR

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 ATmega169/329, ADFR in ATmega8, ATmega128
ADC Control and Status Register A


 ADPS2ADPS1 ADPS0 Division Factor 
000
001
010
011
10016 
10132 
11064 
111128 
ADPS Bits


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

工作於CTC模式時,不管是OC1A還是OC1B,都隻能通過設置OCR1A來控制,如果OC1AOC1B都打開的話,兩個通道得到的波形是一樣的。如果你隻想用OC1B,但不設置OCR1A,而隻設置OCR1B時,OCR1B會沒有波形輸出!

    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 (Single Conversion Mode)

#include
#include        // 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));   // 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, ATmega128Code:
// this code continually scans ADC0 for an analog signal, using 8Mhz processor clock (Free Running Mode)


#include
#include       // needed for uint8_t

#include

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 (Free Running mode)


#include
#include        // needed for uint8_t

#include

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
    } 
}

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。