The analog digital converter (ADC) on the ATMega328p and ATMega2560

15 Jul 2021 - tsp
Last update 15 Jul 2021
Reading time 25 mins

Analog digital converter?

First of - what is an analog digital converter (ADC)? An ADC allows one to convert an analog voltage into a digital value. Basically it’s a voltage measurement (in relation to a reference voltage) and one of the easier to use peripherals on the ATMega AVR series.

The ATMega328p and the ATMega2560 offers a single analog digital converter that is connected to six different input pins (16 on the Mega2650) via a programmable multiplexer - so one can measure on multiple input pins, but only one at a time. The maximum resolution is 10 bits with about $\pm 2$ bits absolute accuracy. For 5V reference voltage that would be a resolution of about $\pm 20 mV$. The ADC can run either in single conversion mode in which it’s performing one measurement and then waiting again for the next trigger or in free running mode in which it performs one measurement after each other.

If one really requires high precision measurements one should of course go the route of an external ADC - possibly also using some external bandgap voltage reference. The cheapest (and slowest) method is to use some kind of Delta-Sigma ADC chip on the I2C port.

The reference voltage for the ATMega can be either:

Usually one requires an capacitor connected between AREF and GND - one should really read the datasheet or at least skim the schematic provided. In most of my setups I use a $0.1 \mu F$ capacitor towards ground and connect AVCC directly to the $5V$ VCC line. Note that AREF is connected via a switch to the internal reference pin. If one’s using an external bandgap reference one cannot use any other mode since the internal sources would be shorted through one’s own reference. So if you plan to use the bandgap reference do not connect the AREF pin.

On the ATMega2560 one can not only use single channels but use an differential ADC between a variety of pins with variable gains.

There are two additional inputs to the multiplexer - an on die temperature sensor as well as the bandgap reference. The input channel is selected via the ADMUX register. In case one’s using the internal temperature sensor one also has to use the internal bandgap reference as reference voltage and thus has to leave AREF open. Note the internal temperature sensor is not particularly useful - its usually working around $\pm 1^\circ C$ - but it’s absolute value is only accurate to about $\pm10^\circ C$ due to manufacturing differences.

Generally the ADC is enabled by ADEN in ADCSRA. The result of the last successful conversion is stored in two registers - ADCH and ADCL. One always has to read the low register first, then the high one. After reading the low register the hardware automatically blocks the ADCs access to the high register to allow an atomic read in which case the result of the conversion would be lost. This is usually not a problem when just storing data inside an ISR but might be a problem in free running mode when doing data storage from within a loop that’s interrupted by other operations.

To start a single conversion:

Note that any changes to the selected channel will only take effect after the currently running conversion has finished. This allows one to swap the selected channel on the multiplexer immediately after starting the conversion to provide uninterrupted rolling measurements.

Additionally one can use a variety of sources to trigger the conversion - this is controlled by the ADTS bits in ADCSRB. This is particularly useful in case one uses the ADIF - the interrupt flag of the ADC - as a trigger source. In this mode the ADC automatically starts a new conversion whenever it’s own interrupt occurs. This means it would perform an endless loop of performing conversions. The first conversion has to be started by writing a logical one to the ADSC bit in ADCSRA register.

How to calculate voltages from ADC values

This is pretty simple - the ADC always samples in relation to the voltage range from $0$ to the selected reference voltage $V_{ref}$. This range is divided into the 10 bits of the ADC. Since $2^{10} = 1024$ we have 1024 equally spaced divisions - so the ADC counts $n_{ADC}$ can simply be converted by a simple multiplication:

[ V_{adc} = \frac{V_ref}{1024} * n_{ADC} ]

Note that the lower reference point is always the ground potential at $0 V$.

Clock source

The ADC is clocked from it’s own prescaler. 10 bit resolution is possible for up to $200 kHz$. Higher frequencies are possible when one is satisfied with a lower resolution. One should never set a clock frequency below $50 kHz$, this might also lead to unstable operation.

Results are stored 1.5 clock cycles after the start of a normal conversion or around 13.5 cycles during the initial one. The internal bandgap voltage reference that can be used to calibrate for the external reference voltage might also be used - but it might take a few measurements to stabilize after multiplexer configuration.

Registers

ATMega328p

ADMUX - ADC multiplexer selection register

The ADMUX register allows one to select the reference voltage and left / right adjusting of the data register as well as most of the input sources.

Bit Name Content
7 REFS1 Voltage reference selection
6 REFS0 Voltage reference selection
5 ADLAR Left adjusting
4 - Reserved
3:0 MUX3:0 Input multiplexer

The selectable voltage references are:

REFS1 REFS0 Selected reference
0 0 AREF pin, internal reference voltage turned off
0 1 AVCC with external capacitor on AREF
1 0 -
1 1 Internal 2.56 bandgap reference with external capacitor on AREF

The ADLAR bit can left adjust (1) or right adjust (0) the measurement result.

The MUX selection works together with the sixth bit in ADCSRB:

MUX3:0 Single ended input
0000 ADC0
0001 ADC1
0010 ADC2
0011 ADC3
0100 ADC4
0101 ADC5
0110 ADC6
0111 ADC7
1000 Temperature sensor
1001 -
1010 -
1011 -
1100 -
1101 -
1110 Internal 1.1V bandgap reference
1111 0V (GND)

ADCSRA - ADC Control and Status Register A

Bit Name Content
7 ADEN ADC enable. If set to 1 the ADC is enabled.
6 ADSC Start conversion. One can start a conversion / the first conversion by writing a one.
5 ADATE ADC auto triggering enable. If set the ADC will trigger on the specified trigger source.
4 ADIF Interrupt flag. If one doesn’t use the interrupt vector one has to clear the interrupt flag by writing a logical 1.
3 ADIE Interrupt enable. If this bit is set the ISR will be executed when the ADC sets ADIF.
2:0 ADPS2:0 Prescaler selection

The prescaler selection configures the ADC clock. This prescaler is directly applied to the system clock - the generated ADC clock should lie somewhere between $50 kHz$ and $200 kHz$. If one requires less precision by discarding lower bits one can select up to $1000 kHz$. If one has a system clock of for example $16 MHz$ and wants to run the ADC at around $125 kHz$ one can calculate the optimal value:

[ \frac{16 * 10^8}{125 * 10^3} = 128 ]

As one can see one should set all bits ADPS2:0 to one. This would result in a conversion speed of $9.6 kHz$ (1 conversion taking 13 ADC cycles - the first one takes 25 ADC cycles).

ADPS2:0 Scaling factor
000 2
001 2
010 4
011 8
100 16
101 32
110 64
111 128

ADCSRB - ADC Control and Status Register B

This register contains trigger selection and the comparator multiplexer enable bit that is set to zero for ADC usage:

Bit Content
7 -
6 ACME - Multiplexer enable for comparator output. Set to zero for ADC usage
5 -
4 -
3 -
2:0 ADTS2:0 - Trigger selection for ADC

The trigger selection can choose between one of the following sources:

ADTS2:0 Trigger source
000 Free running mode
001 Analog comparator
010 External interrupt request 0
011 Timer/counter 0 compare/match A
100 Timer/counter 0 overflow
101 Timer/counter 1 compare/match B
110 Timer/counter 1 overflow
111 Timer/counter 1 capture event

DIDR0 - Digital input disable register 0

This register allows one to disable the digital input logic on the pins used by the ADC. This is especially useful on the ATMega328p since the ADC pins are shared with other digital pins.

Bit Content
7 -
6 -
5 ADC5D - Disable digital I/O on ADC5 if set to 1
4 ADC4D - Disable digital I/O on ADC4 if set to 1
3 ADC3D - Disable digital I/O on ADC3 if set to 1
2 ADC2D - Disable digital I/O on ADC2 if set to 1
1 ADC1D - Disable digital I/O on ADC1 if set to 1
0 ADC0D - Disable digital I/O on ADC0 if set to 1

ADCL and ADCH - ADC data registers

This is the data register. As usual one has to read ADCL before ADCH. Reading ADCL places a lock on modification of ADCH to allow consistent reading of both registers. When ADLAR is set to zero the bits 7:0 in ADCL contain bits 7:0 of the data, Bits 1:0 the remaining 9:8. In case ADLAR has been set to one all bits are left aligned which might be interesting if one wants to discard the lower bits.

ATMega2560

ADMUX - ADC multiplexer selection register

The ADMUX register allows one to select the reference voltage and left / right adjusting of the data register as well as most of the input sources.

Bit Name Content
7 REFS1 Voltage reference selection
6 REFS0 Voltage reference selection
5 ADLAR Left adjusting
4:0 MUX4:0 Input multiplexer (one more bit in ADCSRB)

The selectable voltage references are:

REFS1 REFS0 Selected reference
0 0 AREF pin, internal reference voltage turned off
0 1 AVCC with external capacitor on AREF
1 0 Internal 1.1V bandgap reference with external capacitor on AREF
1 1 Internal 2.56 bandgap reference with external capacitor on AREF

The ADLAR bit can left adjust (1) or right adjust (0) the measurement result.

The MUX selection works together with the sixth bit in ADCSRB:

MUX5:0 Single ended input Positive differential Negative differential Gain
000000 ADC0      
000001 ADC1      
000010 ADC2      
000011 ADC3      
000100 ADC4      
000101 ADC5      
000110 ADC6      
000111 ADC7      
001000   ADC0 ADC0 10
001001   ADC1 ADC0 10
001010   ADC0 ADC0 200
001011   ADC1 ADC0 200
001100   ADC2 ADC2 10
001101   ADC3 ADC2 10
001110   ADC2 ADC2 200
001111   ADC3 ADC2 200
010000   ADC0 ADC1 1
010001   ADC1 ADC1 1
010010   ADC2 ADC1 1
010011   ADC3 ADC1 1
010100   ADC4 ADC1 1
010101   ADC5 ADC1 1
010110   ADC6 ADC1 1
010111   ADC7 ADC1 1
011000   ADC0 ADC2 1
011001   ADC1 ADC2 1
011010   ADC2 ADC2 1
011011   ADC3 ADC2 1
011100   ADC4 ADC2 1
011101   ADC5 ADC2 1
011110 1.1V bandgap      
011111 GND      
100000 ADC8      
100001 ADC9      
100010 ADC10      
100011 ADC11      
100100 ADC12      
100101 ADC13      
100110 ADC14      
100111 ADC15      

ADCSRB - ADC Control and Status Register B

Bit Name Content
7   0
6 ACME Analog Comparator Multiplexer Enable - used for comparator operation
5   0
4   0
3 MUX5 See multiplexer register above
2 ADTS2 Trigger select
1 ADTS1 Trigger select
0 ADTS0 Trigger select

The ACME bit is not used during normal analog digital conversion operation.

The trigger select selects the trigger source for the ADC to start conversions:

ADTS2:0 Trigger source
000 Free running mode
001 Analog comparator output
010 External interrupt request 0
011 Timer0 Compare Match A
100 Timer0 Overflow
101 Timer1 Compare Match B
110 Timer1 Overflow
111 Timer1 Capture Event

ADCSRA - ADC Control and Status Register A

Bit Name Content
7 ADEN ADC enable (1) or disable (0). Running conversions will be terminated when disabling
6 ADSC Start conversion. In single mode writing 1 starts a single conversion, in free running mode starts the first conversion
5 ADATE Auto trigger enable. If set the ADC will automatically start on the selected trigger
4 ADIF Interrupt flag. Set after complete conversion.If ADIE is set will raise an interrupt - else one has to manually clear this flag by writing 0.
3 ADIE Interrupt enable. If set the ADIF will raise an interrupt.
2:0 ADPS2:0 Prescaler selection (see below)

The prescaler selection configures the ADC clock. This prescaler is directly applied to the system clock - the generated ADC clock should lie somewhere between $50 kHz$ and $200 kHz$. If one requires less precision by discarding lower bits one can select up to $1000 kHz$. If one has a system clock of for example $16 MHz$ and wants to run the ADC at around $125 kHz$ one can calculate the optimal value:

[ \frac{16 * 10^8}{125 * 10^3} = 128 ]

As one can see one should set all bits ADPS2:0 to one. This would result in a conversion speed of $9.6 kHz$ (1 conversion taking 13 ADC cycles - the first one takes 25 ADC cycles).

ADPS2 ADPS1 ADPS0 Division factor
0 0 0 2
0 0 1 2
0 1 0 4
0 1 1 8
1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128

DIDR0 to DIDR2

These three registers allow one to disable the digital input logic to be disabled for analog input pins. This should be done when using pins only for analog input not requiring any digital functions such as interrupts, etc. There may be situations where one wants to use both - for example for special trigger scenarios, etc.

DIDR0 Bit Content
7 ADC7D - Disable digital input logic on ADC7
6 ADC6D - Disable digital input logic on ADC6
5 ADC5D - Disable digital input logic on ADC5
4 ADC4D - Disable digital input logic on ADC4
3 ADC3D - Disable digital input logic on ADC3
2 ADC2D - Disable digital input logic on ADC2
1 ADC1D - Disable digital input logic on ADC1
0 ADC0D - Disable digital input logic on ADC0
DIDR1 Bit Content
7 -
6 -
5 -
4 -
3 -
2 -
1 AIND1 - Disable digital input logic on AIN1
0 AIND0 - Disable digital input logic on AIN0
DIDR2 Bit Content
7 ADC15D - Disable digital input logic on ADC7
6 ADC14D - Disable digital input logic on ADC6
5 ADC13D - Disable digital input logic on ADC5
4 ADC12D - Disable digital input logic on ADC4
3 ADC11D - Disable digital input logic on ADC3
2 ADC10D - Disable digital input logic on ADC2
1 ADC9D - Disable digital input logic on ADC1
0 ADC8D - Disable digital input logic on ADC0

ADCL and ADCH - ADC data registers

This is the data register. As usual one has to read ADCL before ADCH. Reading ADCL places a lock on modification of ADCH to allow consistent reading of both registers. When ADLAR is set to zero the bits 7:0 in ADCL contain bits 7:0 of the data, Bits 1:0 the remaining 9:8. In case ADLAR has been set to one all bits are left aligned which might be interesting if one wants to discard the lower bits.

Differential measurements are presented in one complements notation.

Example and snippets

Polling manually triggered measurement

When one just wants to synchronously fetch an ADC measurement after manually triggering one can use something like the following snippet:

void adcInit() {
	/*
		First disable interrupts while modifying the ADC settings.
		Though not really necessary in all cases there are some where
		it's required
	*/
	uint8_t oldSreg = SREG;
	cli();

	/*
		Disable power saving features for ADC
	*/
	PRR0 = PRR0 & ~(0x01);

	/*
		AVCC reference voltage   01xxxxxx
		MUX 0                    xxx00000
		right aligned            xx0xxxxx
	*/
	ADMUX = 0x40;

	/*
		Free running trigger mode                 xxxxx000
		highest mux bit on ATMega2560 set to 0    xxxx0xxx
		Disable comparator multiplexer            x0xxxxxx
	*/
	ADCSRB = 0x00;

	/*
		Enable ADC                      1xxxxxxx
		Do not start conversion now     x0xxxxxx
		Disable autotriggering          xx0xxxxx
		Clear interrupt flag            xxx1xxxx
		Disable interrupt               xxxx0xxx
		Prescaler select (/128)         xxxxx111
	*/
	ADCSRA = 0x97;

	/*
		Restore interrupt flag
	*/
	SREG = oldSreg;
}
uint16_t adcMeasure() {
	ADCSRA = ADCSRA | 0x40; /* Trigger ADSC */

	while((ADCSRA & 0x40) != 0) {
		/* Busy wait till measurement is done ... */
	}

	return (uint16_t)(ADC & 0x3FF);
}

Manually triggered, ISR based

A better approach to busy waiting is most of the time the usage of an synchronous interrupt service routine that’s triggered whenever a measurement has finished. This is the ADC_vect vector. To use the interrupt service routine one just has to set the ADIE bit in ADCSRA, the remaining operation stays the same:

void adcInit() {
	/*
		First disable interrupts while modifying the ADC settings.
		Though not really necessary in all cases there are some where
		it's required
	*/
	uint8_t oldSreg = SREG;
	cli();

	/*
		Disable power saving features for ADC
	*/
	PRR0 = PRR0 & ~(0x01);

	/*
		AVCC reference voltage   01xxxxxx
		MUX 0                    xxx00000
		right aligned            xx0xxxxx
	*/
	ADMUX = 0x40;

	/*
		Free running trigger mode                 xxxxx000
		highest mux bit on ATMega2560 set to 0    xxxx0xxx
		Disable comparator multiplexer            x0xxxxxx
	*/
	ADCSRB = 0x00;

	/*
		Enable ADC                      1xxxxxxx
		Do not start conversion now     x0xxxxxx
		Disable autotriggering          xx0xxxxx
		Clear interrupt flag            xxx1xxxx
		Enable interrupt                xxxx1xxx
		Prescaler select (/128)         xxxxx111
	*/
	ADCSRA = 0x9F;

	/*
		Restore interrupt flag
	*/
	SREG = oldSreg;
}
void adcStartMeasurement() {
	ADCSRA = ADCSRA | 0x40; /* Trigger ADSC */
}

The ISR will then handle any available data:

uint16_t currentADCValue;

ISR(ADC_vect) {
	/*
		Do whatever (short) operation you want using ADC
		register. In this sample simply store it somewhere.
		One might switch channels, trigger another measurement,
		push the measurement into some serial port, etc.
	*/

	currentADCValue = ADC;
}

Fully automatic triggering, ISR based

The only modification one needs from interrupt based manually triggered mode to fully free running mode is enabling auto triggering by setting ADATE in ADCSRA. Setting this bit starts a conversion whenever the specified trigger source is activated. If one sets free running mode as trigger the ADC instantaneously triggers whenever the ADC is available (i.e. after the previous measurement has finished). In case one switches the input multiplexer after each measurement one has to notice that a change in ADMUX only applies during the startup phase - whenever the ISR is triggered the MUX value already has been latched by the ADC so it will only take effect after the next interrupt.

The following sample code is used by me in an simple electron gun controller for example to sample 8 channels in a round robin fashion:

void adcInit() {
	/*
		First disable interrupts while modifying the ADC settings.
		Though not really necessary in all cases there are some where
		it's required
	*/
	uint8_t oldSreg = SREG;
	cli();

	/*
		Disable power saving features for ADC
	*/
	PRR0 = PRR0 & ~(0x01);

	/*
		AVCC reference voltage   01xxxxxx
		MUX 0                    xxx00000
		right aligned            xx0xxxxx
	*/
	ADMUX = 0x40;

	/*
		Free running trigger mode                 xxxxx000
		highest mux bit on ATMega2560 set to 0    xxxx0xxx
		Disable comparator multiplexer            x0xxxxxx
	*/
	ADCSRB = 0x00;

	/*
		Enable ADC                      1xxxxxxx
		Do not start conversion now     x0xxxxxx
		Enable autotriggering           xx1xxxxx
		Clear interrupt flag            xxx1xxxx
		Enable interrupt                xxxx1xxx
		Prescaler select (/128)         xxxxx111
	*/
	ADCSRA = 0xBF;

	/*
		Restore interrupt flag
	*/
	SREG = oldSreg;

	/*
		Start first conversion now. This will latch the current
		MUX bits. This is done by setting ADSC.
	*/
	ADCSRA = ADCSRA | 0x40;

	/*
		Now already advance the MUX
	*/
	ADMUX = (ADMUX & 0xE0) | ((ADMUX & 0x1F) + 1);
}
/*
	Note that one has to provide a interrupt safe method to
	access these values.
*/
uint16_t currentADCValues[8];

ISR(ADC_vect) {
	uint8_t oldMux = ADMUX;

	/*
		Advance MUX to the next input - this will take
		effect on the measurement after the next one.
		The logical and with 0x07 provides an easy way to
		iterate over the first 8 channels (implements modulo 8)
	*/
	ADMUX = (oldMux & 0xE0) | (((oldMux & 0x1F) + 1) & 0x07;

	/*
		Store the value at the (oldMux & 0x1F) - 1 entry.
		To prevent having to work with negative numbers
		one can simply add 7 and perform again a modulo 8.
	*/
	currentADCValues[((oldMux & 0x1F) + 7) & 0x07] = ADC;
}

This article is tagged: DIY, Electronics, Basics, Measurements, AVR, ANSI C, Microcontroller


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://jugujbrirx3irwyx.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support