Samstag, 21. Mai 2016

Analog Digital Conversion (ADC)

Introduction

In many applications, analog voltages are involved, and the actions of the microcontroller should depend on a given analog voltage level (for example a data logger). In order to convert it into a digital value (i.e. a binary number), analog to digital converters (ADCs) are used. The ADC compares the measured voltage to a reference voltage. The resulting number will be the ratio of the measured voltage and the reference voltage, where a ratio of 1 or greater would be mapped to the highest possible 10-bit value (0b1111111111), and 0 or smaller would be mapped to the lowest value (0b0000000000). The ATMEGA8 has an integrated 10-bit ADC that is connected to 6 of the pins in the DIP package (marked red in the following picture). Here I present a small setup and some C code that demonstrates how the ADC can be used.

Setup

A schematic drawing of the setup is shown in the next picture. Relevant pins of the ATMEGA are labeled. The connections to the programmer (also serving as power supply) are shown in blue. Note that the programmer can stay connected all the time and will not interfere with the operation of the circuit.
The voltage divider is connected to the ADC5 pin. 10 LEDs are used to display the binary value of after the analog to digital conversion. The AVCC pin has to be powered in this example (connected to VCC). Previous examples, not using the ADC, didn't need that pin. If low noise is important, the AVCC power rail should be a separate extra low noise power supply.

There are 3 sources for the ADC reference voltage for the ATMEGA8: the supply voltage, a voltage provided at the AREF pin, or an internal 2.56V reference.
I will use the internal reference. In this case a capacitor has to be connected tot the AREF pin. If you build that circuit, I encourage you to disconnect that capacitor and see what happens to the output: it will be completely random.

Firmware

As always, making use of the ADC requires to write the correct values to the right registers, and as always, everything is well documented in the ADC section of the datasheet. The following program will measure the voltage from a variable resistor divider with the ADC and use the value to switch some LEDs that will show the binary representation of the measured ADC value.

The relevant section of the ATMEGA8 datasheet starts on page 189. There are two registers that need to be manipulated to get the ADC running: ADCSRA and ADMUX (described on pages 199 & 200 in the datasheet)



The program will do the ADC setup and enter an infinite loop, in which it measures the voltage at ADC5 pin and displays the value on the LEDs. The LED on pin PC3 has the most significant bit of the 10-bit value, and PB0 has the least significant bit.

Setting the ADEN bit in ADCSRA register to 1 enables the ADC subsystem
ADCSRA |= (1<<ADEN);

The voltage reference is selected by the two bits REFS1 and REFS0 in the ADMUX register. Table 74 on page 199 of the datasheet shows, that both bits have to be set in order to get the internal voltage reference.
ADMUX |= ( (1<<REFS1) | (1<<REFS0) );

Since I connected my analog signal to ADC5, the number 5 has to be written to the MUX[0:3] part of the ADMUX register
ADMUX |= 5;

A conversion is started by setting ADSC bit to 1 in the ADCSRA register. If the conversion is done, the ADC sets this bit back to 0.
ADCSRA |= (1<<ADSC);        
while(ADCSRA & (1<<ADSC)); // wait until conversion is done

The result is then available in the registers ADCL and ADCH, which can be combined to a single 10-bit value (of course stored in a 16-bit integer).
uint16_t value;
value = ADCL; 
value |= (ADCH<<8); 

The complete program

#include <avr/io.h>
#define F_CPU 1000000UL  // 1 MHz
#include <util/delay.h>

int main(void)
{
  // enable ADC
  ADCSRA |= (1<<ADEN);
  ADMUX |= ( (1<<REFS1) | (1<<REFS0) ); // internal reference
  ADMUX |= 5;                           // select ch. 5 (pin ADC5)
 
  // LED setup
  // PORTB[0:5] as output
  DDRB  = 0x3f; // 0011 1111
  // PORTC[0:2] as output
  DDRC  = 0x0f; // 0000 1111
 
  for (;;) 
  {
    uint16_t value;

    ADCSRA |= (1<<ADSC);        // start single measurement
    while(ADCSRA & (1<<ADSC));  // wait until measurement done

    // read result bytes (low and high)
    value  = ADCL;
    value |= (ADCH<<8);
  
    // display number
    PORTB = value;
    PORTC = value>>6;
  }
}

Finally, some pictures of my setup, showing three different settings of the voltage divider. A multimeter is connected to the ADC input voltage to verify the ATMEGA8 measurement. The 10-bit value can be seen on the right hand side with the least significant bit on the lowest LED and the most significant bit on the top LED.




More

This example is intended to get you started. The ATMEGA8 ADC can do more. For example the sampling frequency can be adjusted using the ADPS[0:2] bits in the ADCSRA register. The ADC can be configured to trigger interrupts and it can operate in a noise cancellation mode. All this is described in detail in the ADC section of the datasheet.

Keine Kommentare:

Kommentar veröffentlichen