You are on page 1of 10

ADC AND DAC

I.

INTRODUCTION

In the real world, about everything is analog: voltages, currents, light levels, forces, etc., all take on
continuously variable values. Deep inside the microcontrollers, on the other hand, everything is binary: on or
off. Going from the analog world to the digital is the job of the analog-to-digital converter (ADC) hardware.
ADC allows one to convert an analog voltage to a digital value that can be used by a microcontroller.
There are many sources of analog signals that one might like to measure. There are analog sensors
available that measure temperature, light intensity, distance, position, and force, just to name a few.
ADC converts an analog signal into digital for. An embedded system uses the ADC to collect information
about the external world (data acquisition system.) The input signal is usually an analog voltage, and the
output is a binary number. The ADC has the following major characteristics:

The ADC precision is the number of distinguishable ADC inputs (e.g., 4096 alternatives, 12 bits).
The ADC range is the maximum and minimum ADC input (e.g., 0 to +3.3V).
The ADC resolution is the smallest distinguishable change in input (e.g., 3.3V/4096, which is about
0.81 mV). The resolution is the change in input that causes the digital output to change by 1.

II.

ADC PROGRAMMING IN THE AVR

Since the ADC is widely used in data acquisition, an increasing number of microcontrollers have an on-chip
ADC peripheral. An on chip ADC eliminate the need for an external ADC connection, which leaves more
pins for other I/O activities. As shown in Figure 2.1 The ATMega128 microcontroller:

Has 8 ADC channels, 10-bit successive approximation ADC, allowing up to 8 analog sources to be
attached to the microcontroller. The 8 ADC channels are connected to the internal DAC through a
device called a multiplexer. The multiplexer connects the 8 ADC channels (the 8 pins of Port F on the
ATMega128) to the internal ADC. One channel at a time is passed through the multiplexer to the
ADC. The ADC has its own power supply, labeled AVCC, on the ATMega128. This pin needs to be
connected to a power source within 0.3 volts of the chip's VCC supply. Most of the time, you would
wire this to VCC. With the 10 bit DAC, this allows measuring voltages from 0 to 5 volts with a resolution
of 5/1024 volts, or 4.88 mV.
The device also supports 16 differential voltage input combinations. Two of the differential inputs
(ADC1, ADC0 and ADC3, ADC2) are equipped with a programmable gain stage, providing
amplification steps of 0dB (1x), 20dB (10x), or 46dB (200x) on the differential input voltage before the
A/D conversion. Seven differential analog input channels share a common negative
terminal (ADC1), while any other ADC input can be selected as the positive input terminal. If 1x
or 10x gain is used, 8-bit resolution can be expected. If 200x gain is used, 7-bit resolution can be
expected.
The ADC contains a Sample and Hold circuit which ensures that the input voltage to the ADC is
held at a constant level during conversion.
The ADC has a separate analog supply voltage pin, AVCC. AVCC must not differ more than
0.3V from VCC.
Internal reference voltages of nominally 2.56V or AVCC are provided On-chip. The voltage
reference may be externally decoupled at the AREF pin by a capacitor for better noise performance.
The converted output binary data is held by two special function registers called ADCL (A/D Result
Low) and ADCH (A/D Result High)
Because the ADCH:ADCL registers give 16 bits and the ADC data out is only 10 bits wide, 6 bits of the
16 bits are used. The programmer has an option of making either the upper 6bits or the low 6 bits
unused.
The conversion time is dictated by the crystal frequency connected to the XTAL pins (Fosc) and
ADPS0:2 bits

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 1

Figure 2.1. Analog to Digital Converter Block Schematic

In AVR microcontrollers, five major registers are associated with the ADC. These are:
1. ADMUX (ADC Multiplexer selection register)
2. ADCH (High Data)
3. ADCL (Low Data)
4. ADCSRA (ADC Control and Status Register)
5. SPIOR (Special Function I/O Register)

2.1. ADMUX Register

Figure 2.2 shows the bits of the ADMUX Registers and their usage. The ADMUX Register contains the following
registers:

REFS1:0 (Bit 7:6) -Reference Selection Bits These bits select the reference voltage for the ADC
ADLAR (Bit 5) -ADC Left Adjust Results -This bit dictates either the left bits or the right bits of the result
registers ADCH:ADCL that are used to store the result. If one writes a HIGH to ADLAR, the result will be
left adjusted; otherwise, the result is right adjusted.

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 2

MUX4:0 (Bit 4:0) Analog Channel and Gain Selection Bits The value of these bits selects the gain for
the differential channels and also selects which combination of analog inputs are connected to the
ADC

Figure 2.2 ADMUX Register

2.1.1. Vref Source


The internal circuit of the Vref selection bit is shown in figure 2.3. As shown, the source reference voltage can
be selected according to three options, (a) AREF pin, (b) AVCC pin or (c) internal 2.56V source.

Figure 2.3 ADC Reference Source (Vref) Selection Options


Table 2.1 shows how bits REFS1 and REFS0 of the ADMUX register can be used to select the Vref source.

NOTE:

If VREF pin is connected to an external fixed voltage, it is impossible to use the other references options
in the application. They will be shorted with the external voltage.
Connecting a 100nF external capacitor between the VREF pin and GND will increase the precision
and stability of ADC, especially when an internal 2.56V source is used.

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 3

2.1.2. ADC Input Channel Source


Figure 2.4. shows the schematic of the internal circuitry of the input channel selection. As shown, either
single-ended or the differential input can be selected to be conveted to digital data.

If single ended input is selected, one can choose input channel among ADC0 to ADC7. In this case,
a singel pin is used as the analog line and the GND of the AVR chip is used as common ground.
Table 2.2 show the the values of MUX4-MUX0 bits for different single-ended inputs.
If differential input is selected, one can select the op-omp gain. The gain can be 1x, 10x or 200x. The
positive input of the op-amp can be selected to be one of the pins ADC0 to ADC7, and the
negative input of the op-amp can be any of ADC0, ADC1, or ADC2.

Figure 2.4. ADC Input Channel Selection Circuitry

2.1.3. ADLAR bit Operation


AVR microcontrollers have a 10-bit ADC, which means that the result is 10 bits long and cannot be stored in
a single byte. In AVR, two 8-bit registers are dedicated to the ADC result, but only 10 of the 16 bits are used
and 6 bits are unused. One can select the position of used bits in the bytes as discusses above. If one sets
the ADLAR bit in ADMUX register, the result bit will be left-justified; otherwise, the result bits will be rightjustified as shown in figure 2.5 below.

Figure 2.5. ADLA Bit and ADCx Registers

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 4

2.2.

ADCH:ADCL Registers

After the A/D conversion is complete, the result is stored in registers ADCL (A/D Result LOW byte) and ACDH
(A/D Result High byte). Since the ADC has a resolution of 10 bits, it requires 10 bits to store the result. Hence
one single 8 bit register is not sufficient. We need two registers ADCL and ADCH (ADC Low byte and ADC
High byte) as follows. The two can be called together as ADC.

2.3.

ADCSRA Register

The ADCSRA register is the status and control register of the ADC. Bits of this register contrrol or monitorr the
operation of the ADC. Figure 2.6 show the different bits and their discription.

Figure 2.6 A/D Control and Status Register A (ADCSRRA)

ADEN (Bit 7) ADC Enable This bit enables or diables the ADC. Setting this bit to HIGH will enable the
ADC and setting this bit to LOW will disable the ADC even while a conversion is in progress.
ADSC (Bit 6) -ADC Start Conversion- To start each conversion this bit must be set to high
ADATE (Bit 5) ADC Auto Trigger Enable Auto triggering of the ADC is enabled when you set this bit
to one.
ADIF (Bit 4) ADC Interrupt Flag This bit is set when an ADC convesion completes and the data
registers are updated.
ADIE (Bit 3) ADC Interrupt Enable Setting this bit to one enables the ADC conversion complete
interrupt.
ADPS2:0 (Bit 2:0) ADC Prescaler Select Bits These bits determine the division factorr between the
XTAL (clock) frequency and the input clock to the ADC.

As shown in Figure 2.7, using the ADPS2:0 bits of the ADCSRA register, it is possible to set the A/D conversion
time. To select the conversion time, one can select any of Fosc/2, Fosc/4, Fosc/8, Fosc/16, Fosc/32, Fosc/64
or Fosc/128 for ADC clock (Fosc is the speed of the crystal frequency connected to the AVR chip). Also note
that the multiplexer has 7 inputs since the option ADPS2:0=000 is reserved. For AV, the ADC requires an input
clock frequency of 50 to 200kHz for the maxium accuracy.

Figure 2.7 AVR ADC Clock Selection

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 5

EXAMPLE 1: An AVR is connected to an 8MHz crystal oscillator. Calculate the ADC frequency for
(a) ADPS2:0 = 001
(b) ADPS2:0 = 100
(c) ADPS2:0 = 11
SOLUTION
(a) If ADPS2:0 ==001,the clk/2 input will be activated. Then we have 8MHz/2 =2 MHz (This is geater than
200kHz; thus is not valid
(b) If ADPS2:0==100, the clk/8 input will be activated. The prescaled clock is 8MHz/16=500kHz. Again, this
is not a valid input
(c) If ADPS2:0==111, the clc/128 input is activated. The clock is thus 8MHz/128 =62kHz (A valid input since
it is less than 200kHz)
III.
STEPS IN PROGRAMMING THE A/D CONVERTER USING POLLING
To program the A/D converter of the AVR, the following steps must be taken:
1.
2.
3.
4.
5.
6.
7.
8.

Make the pin for the selected ADC channel an input pin
Turn on the ADC module of the AVR because it is disabled upon power-on reset
Select the conversion speed. Use ADPS2:0 register to select the conversion speed
Select the voltage reference and A/C input channel. Use REFS0 and REFS1 bits in the ADMUX register
to select voltage reference and MUX4:0 bits in ADMUX to select ADC input channel
Activate the start conversion bit by writing a one to the ADSC bit of the ADCSRA
Wait for the conversion to be completed by polling the ADIF bit in the ADCSRA register
After the ADIF bit has gone HIGH, read the ADCL and ADCH registers to get the digital data output.
Note that one must read ADCL beforre ADCH; Otherwise the result is invalid
If one wants to select another Vref source or input channel, he/she must go back to step 4.

EXAMPLE 2: ADC conversion of analog input connected to ADC0. The output is sent to PORTB and PORTD

#include <avr/io.h>
int main (void)
{
DDRB = 0xFF; // Make PORT B an input
DDRD = 0xFF; // Make PORT D an output
DDRA = 0; // Make PORTA an input for ADC input
// Make ADC enable and select clc/128
ADCSRA = 0x87;
ADMUX = 0xC0; // 2.56 Vref, ADC0 single ended input
// data will be right-justified
while (1)
{
ADCSRA|=(1<<ADSC); // Start conversion
// Wait for the conversion to finish
while((ADCSRA&(1<<ADIF))==0);
PORTD = ADCL ; // Give the low byte to PORTD
PORTB = ADCH ; // Give the high byte to PORTB
}
return 0;
}

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 6

EXAMPLE 3: A Simple Free-Running ADC Example


This example uses a potentiometer. I wired up a 10k potentiometer as shown below.

To give an indication of the value the ADC is reading, two LEDs are hooked to the microcontroller. We can toggle
these to give us a high or low indication. Here is the pseudocode for this example:
Set up output LEDs
Configure ADC Hardware
Enable ADC
Start A2D Conversions
WHILE Forever
IF ADC Value High, Turn on LED1
ELSE Turn on LED2
END WHILE
To simplify this example, we will set up the ADC to continuously measure the voltage on ADC0. We will then poll
the value in an endless loop and change the LEDs' statuses as we need to. The skeleton code for our example
would then be

#include <avr/io.h>
int main (void)
{
DDRE |= (1 << 2); // Set LED1 as output
DDRG |= (1 << 0); // Set LED2 as output
// TODO: Configure ADC Hardware
// TODO: Enable ADC
// TODO: Start A2D Conversions
while() // Loop Forever
{
// TODO: Test ADC Value and set LEDs
}
}
The next step is to configure the ADC hardware. This is done through setting bits in the control registers for the
ADC. First, let's set the prescalar for the ADC. According to the datasheet, this prescalar needs to be set so that
the ADC input frequency is between 50 KHz and 200 KHz. The ADC clock is derived from the system clock. With a
system frequency of 16 MHz, a prescaler of 128 will result in an ADC frequency of 125 Khz. The prescaling is set
by the ADPS bits in the ADCSRA register.
According to the datasheet, all three ADPS bits must be set to get the 128 prescaler.

ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


Next, let's set the ADC reference voltage. This is controlled by the REFS bits in the ADMUX register. The following
sets the reference voltage to AVCC.

ADMUX |= (1 << REFS0);

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 7

To set the channel passed through the multiplexer to the ADC, the MUX bits in the ADMUX register need to be set
accordingly. Since we are using ADC0 here, which corresponds with all 5 MUX bits being zero, we don't need to set
anything here.
In order to put the ADC into free-running mode, set the aptly-named ADFR bit in the ADCSRA register:

ADCSRA |= (1 << ADFR);


One last settings change will be made to make reading the ADC value simpler. Though the ADC has a resolution of
10 bits, this much information is often not necessary. This 10-bit value is split across two 8 bit registers, ADCH
and ADCL. By default, the lowest 8 bits of the ADC value are found in ADCL, with the upper two being the lowest
two bits of ADCH. By setting the ADLAR bit in the ADMUX register, we can left align the ADC value. This puts the
highest 8 bits of the measurement in the ADCH register, with the rest in the ADCL register. If we then read the
ADCH register, we get an 8 bit value that represents our 0 to 5 volt measurement as a number from 0 to 255.
We're basically turning our 10 bit ADC measurement into an 8 bit one. Here's the code to set the ADLAR bit:

ADMUX |= (1 << ADLAR);


That completes the setup of the ADC hardware for this example. Two more bits need to be set before the ADC will
start taking measurements. To enable the ADC, set the ADEN bit in ADCSRA:

ADCSRA |= (1 << ADEN);


To start the ADC measurements, the ADSC bit in ADCSRA needs to be set:

ADCSRA |= (1 << ADSC);


At this point, the ADC would begin continuously sampling the voltage presented on ADC0. The code to this point
would look like this:

#include <avr/io.h>
int main (void)
{
DDRE |= (1 << 2); // Set LED1 as output
DDRG |= (1 << 0); // Set LED2 as output
// Set ADC prescalar to 128 - 125KHz sample rate @ 16MHz
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADMUX |= (1 << REFS0); // Set ADC reference to AVCC
ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit reading
// No MUX values needed to be changed to use ADC0
ADCSRA |= (1 << ADFR); // Set ADC to Free-Running Mode
ADCSRA |= (1 << ADEN); // Enable ADC
ADCSRA |= (1 << ADSC); // Start A2D Conversions
while(1) // Loop Forever
{
// TODO: Test ADC Value and set LEDs
}
}
The only thing left to do is test the ADC value and set the LEDs to display a high / low indication. Since the ADC
reading in ADCH has a maximum value of 255, a test value of 128 was chosen to determine whether the voltage
was high or low. A simple IF/ELSE statement in the FOR loop will allow us to turn the correct LED on:

if(ADCH < 128)


{
PORTE |=
PORTG &=
}
else
{
PORTE &=
PORTG |=
}

(1 << 2); // Turn on LED1


~(1 << 0); // Turn off LED2

~(1 << 2); // Turn off LED1


(1 << 0); // Turn on LED2

Here's the finished program with comments. When compiled and downloaded to an ATMega128, LED1 will be lit for
roughly half the rotation of the potentiometer, indicating a low voltage reading. Near the halfway point of the
potentiometer's rotation, LED1 will go out and LED2 will light. Indicating a high voltage reading. By changing the
tests in the FOR loop, one could get different voltage indications with two or more LEDs.

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 8

#include <avr/io.h>
int main (void)
{
DDRE |= (1 << 2); // Set LED1 as output
DDRG |= (1 << 0); // Set LED2 as output
// Set ADC prescalar to 128 - 125KHz sample rate @ 16MHz
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADMUX |= (1 << REFS0); // Set ADC reference to AVCC
ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit reading
// No MUX values needed to be changed to use ADC0
ADCSRA |= (1 << ADFR); // Set ADC to Free-Running Mode
ADCSRA |= (1 << ADEN); // Enable ADC
ADCSRA |= (1 << ADSC); // Start A2D Conversions
while (1) // Loop Forever
{
if(ADCH < 128)
{
PORTE |= (1 << 2); // Turn on LED1
PORTG &= ~(1 << 0); // Turn off LED2
}
else
{
PORTE &= ~(1 << 2); // Turn off LED1
PORTG |= (1 << 0); // Turn on LED2
}
}
}

EXAMPLE 4 A simple Light Meter


In this example, a simple light meter is created. To measure light, a Light Dependent Resistor (LDR) is used.
This sensors (also called photocells or photoresistors) provide a resistance that deceases as a more and
more light falls on them.

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 9

#include <avr/io.h>
#include <util/delay.h>
#define LED_PORT
PORTB
#define LED_PIN
PINB
#define LED_DDR
DDRB
#define loop_until_bit_is_clear(sfr, bit) do { } while (bit_is_set(sfr, bit))
static inline void initADC0(void);
int main(void)
{
uint8_t ledValue;
uint16_t adcValue;
uint8_t i;
initADC0();
LED_DDR = 0xff;
while (1)
{
//start ADC conversion
ADCSRA |= (1 << ADSC);
// wait until done
loop_until_bit_is_clear(ADCSRA, ADSC);
// read ADC in
adcValue = ADC;
/* Have 10 bits, want 3 (eight LEDs after all) */
ledValue = (adcValue >> 7);
/* Light up all LEDs up to ledValue */
LED_PORT = 0;
for (i = 0; i <= ledValue; i++)
{
LED_PORT |= (1 << i);
}
_delay_ms(50);
} /* End event loop */
return (0); /* This line is never reached */
}
static inline void initADC0(void)
{
/* reference voltage on AVCC */
ADMUX |= (1 << REFS0);
/* ADC clock prescaler /8 */
ADCSRA |= (1 << ADPS1) | (1 << ADPS0);
ADCSRA |= (1 << ADEN); /* enable ADC */
}

University of Rwanda

EEE3421- Embedded Systems

2015 Kizito N-N

Page | 10

You might also like