Skip to main content
Engineering LibreTexts

26.1: Introduction

  • Page ID
    35905
  • Just as we would like to read the state of simple on/off switches, we also need to read continuously variable (i.e. analog) data. Usually this means the output voltage caused by some form of sensor such as a temperature sensor, force sensor, light sensor, etc. Very often simple passive devices such as CdS cells or FSRs are placed into a resistive bridge network, the output voltage of which will shift with changes in the environment. An even simpler application would be the use of a potentiometer hooked up to a fixed voltage supply. The position of the pot would be controlled by the user and could represent a setting of almost any conceivable parameter such as volume, brightness, time delay, frequency, etc. In order to read analog quantities, the ATmega 328P contains a single 10 bit analog-to-digital converter multiplexed across six input channels. On the Arduino Uno board, the inputs to these ADCs are found at the pins labeled A0 through A5. The Arduino development environment contains two useful functions to access theses, namely analogRead() and analogReference(). The on-line function descriptions are repeated below:

    Figure \(\PageIndex{1}\): analogRead and analogReference docs

    analogRead()1

    Description

    Reads the value from the specified analog pin. The Arduino board contains a 6 channel (8 channels on the Mini and Nano, 16 on the Mega), 10-bit analog to digital converter. This means that it will map input voltages between 0 and 5 volts into integer values between 0 and 1023. This yields a resolution between readings of: 5 volts / 1024 units or, 0.0049 volts (4.9 mV) per unit. The input range and resolution can be changed using analogReference().

    It takes about 100 microseconds (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second.

    Syntax

    analogRead( pin )

    Parameters

    pin: the number of the analog input pin to read from (0 to 5 on most boards, 0 to 7 on the Mini and Nano, 0 to 15 on the Mega)

    Returns

    int (0 to 1023)

    Note

    If the analog input pin is not connected to anything, the value returned by analogRead() will fluctuate based on a number of factors (e.g. the values of the other analog inputs, how close your hand is to the board, etc.).

    See also

     

    analogReference()2

    Description

    Configures the reference voltage used for analog input (i.e. the value used as the top of the input range). The options are:

    • DEFAULT: the default analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
    • INTERNAL: an built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328 and 2.56 volts on the ATmega8 (not available on the Arduino Mega)
    • INTERNAL1V1: a built-in 1.1V reference (Arduino Mega only)
    • INTERNAL2V56: a built-in 2.56V reference (Arduino Mega only)
    • EXTERNAL: the voltage applied to the AREF pin (0 to 5V only) is used as the reference.
    Syntax

    analogReference( type )

    Parameters

    type: which type of reference to use (DEFAULT, INTERNAL, INTERNAL1V1, INTERNAL2V56, or EXTERNAL).

    Returns

    None.

    Note

    After changing the analog reference, the first few readings from analogRead() may not be accurate.

    Warning

    Don't use anything less than 0V or more than 5V for external reference voltage on the AREF pin! If you're using an external reference on the AREF pin, you must set the analog reference to EXTERNAL before calling analogRead(). Otherwise, you will short together the active reference voltage (internally generated) and the AREF pin, possibly damaging the microcontroller on your Arduino board.

    Alternatively, you can connect the external reference voltage to the AREF pin through a 5K resistor, allowing you to switch between external and internal reference voltages. Note that the resistor will alter the voltage that gets used as the reference because there is an internal 32K resistor on the AREF pin. The two act as a voltage divider, so, for example, 2.5V applied through the resistor will yield 2.5 * 32 / (32+5) = ~2.2V at the AREF pin.

    The analogRead() function is best used as a single conversion “snapshot”. This is good for simple user interface and basic sensor applications. Generally speaking, analogReference() is called once during the setup and initialization phase of the program. Also, unless there is a compelling reason to do otherwise, the default mode is the best place to start. This will yield a 5 volt peak to peak input range with a bit resolution of just under 4.9 millivolts (5/1024). It is important to note that this range runs from 0 volts to 5 volts, not −2.5 volts to +2.5 volts. Depending on the amplitude and frequency range of the sensor signal, some input processing circuitry may be required to apply a DC offset, amplify or reduce signal strength, filter frequency extremes and so forth.

    The code for analogReference() is about as simple as it gets. It just sets (and hides) a global variable which will be accessed by the analogRead() function:

    uint8_t analog_reference = DEFAULT;
    
    void analogReference(uint8_t mode)
    {
        analog_reference = mode;
    }
    

    The analogRead() function is a bit more interesting (actually, 10 bits more interesting). From the preceding chapter we saw that there several registers associated with the ADC system. For single conversion operation the important registers include ADCSRA (ADC control and status register A) and ADMUX (ADC multiplexer selection register). The register bits are repeated below for convenience:

    Table \(\PageIndex{1}\): ADC bits.
    Register Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
    ADCSRA ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
    ADMUX REFS1 REFS0 ADLAR - MUX3 MUX2 MUX1 MUX0

    Also recall that the ADCH and ADCL registers contain the high and low bytes of the result, respectively. For ADCSRA, setting ADEN enables the ADC. Setting ADSC starts a conversion. It will stay at one until the conversion is complete, the hardware resetting it to zero. ADIF and ADIE are used with an interrupt-based mode not discussed here. ADATE stands for AD Auto Trigger Enable which allows triggering of the ADC from an external signal (again, not discussed here). ADPSx are pre-scaler bits which set the conversion speed. See the previous chapter for details.

    For ADMUX, REFSx sets the reference voltage source where 00 = use AREF pin, 01 = use VCC, 10 = reserved, 11 = use internal 1.1 volt reference. Setting ADLAR left-adjusts the 10 bit word within the ADCH and ADCL result registers (i.e. it left-justifies, leaving the bottom six bits unused). Clearing this bit leaves the result right justified (i.e. top six bits are unused). The four MUXx bits select which input channel is used for the conversion. For example, to read from channel 5, set these bits to the pattern 0101.

    Other registers are available such as ADSRB and ACSR which are useful for other modes of operation. They are not necessary for the current purpose, though.

    The code follows, cleaned up for ease of reading. The original code contains a considerable number of #ifdefs so it works with different microcontrollers.

    int analogRead(uint8_t pin)
    {
        uint8_t low, high;
    
        if (pin >= 14) pin -= 14; // allow for channel or pin numbers
    
        // set the analog reference, input pin and clear left-justify (ADLAR)
        ADMUX = (analog_reference << 6) | (pin & 0x07);
    
        // start the conversion
        sbi(ADCSRA, ADSC);
    
        // ADSC is cleared when the conversion finishes
        while (bit_is_set(ADCSRA, ADSC));
    
        // read low and high bytes of result
        low = ADCL;
        high = ADCH;
    
        // combine the two bytes
        return (high << 8) | low;
    }
    

    Let’s take a closer look. First, two unsigned bytes are declared to hold the contents of the high and low result registers; then the pin argument is translated. Note the undocumented usage of either channel or pin numbers. Always be cautious about using undocumented features.

    uint8_t low, high;
    
    if (pin >= 14) pin -= 14; // allow for channel or pin numbers
    

    At this point the reference is set (note how the analog_reference global is shifted up to the REFSx bits). The pin number is ANDed with 0x07 for safety and ORed into ADMUX. Note that the result will be right-justified as ADLAR is not set.

    ADMUX = (analog_reference << 6) | (pin & 0x07);
    

    The conversion is initiated by setting the AD Start Conversion bit. sbi() is a macro that reduces to a single “set bit” instruction in assembly. We then wait until the ADSC bit is cleared which signifies the conversion is complete. The while loop is referred to properly as a “busy-wait” loop; the code simply “sits” on that bit, checking it over and over until it is finally cleared. This causes the loop to exit. Again, bit_is_set() is a macro that returns true if the bit under test is set to one and false if it’s clear.

    // start the conversion
    sbi(ADCSRA, ADSC);
    
    // ADSC is cleared when the conversion finishes
    while ( bit_is_set(ADCSRA, ADSC) );
    

    The conversion is now complete. ADCL must be read first as doing so locks both the ADCL and ADCH registers until ADCH is read. Doing the reverse could result in spurious values. The two bytes are then combined into a single 16 bit integer result by ORing the low byte with the left-shifted high byte. As the data in the result registers were right-justified, the top six bits will be zeros in the returned integer, resulting in an output range of 0 through 1023.

    low = ADCL;
    high = ADCH;
    
    // combine the two bytes
    return (high << 8) | low;
    

    Given the default prescaler values and function call overhead, the maximum conversion rate is approximately 10 kHz which is perfectly acceptable for a wide variety of uses. The function is fairly bare-bones and straightforward to use as is. Use this function as a guide if you wish to produce left-justified data (set ADLAR bit) or other simple modifications. If time is of the essence and machine cycles cannot be wasted with the busy-wait loop seen above, an interrupt-based version or free-running version may be a better choice.

     


    1. http://arduino.cc/en/Reference/AnalogRead
    2. http://arduino.cc/en/Reference/AnalogReference
    • Was this article helpful?