15 Jul 2021 - tsp
Last update 15 Jul 2021
25 mins
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:
AVCC
pin. This is the separate VCC
pin for the ADC that is
allowed to differ only by $\pm 0.3V$ from VCC
. This is used on the
most simple setups and also on most of the Arduino boards - in the latter
case the reference voltage is just the 5V power rail which of course is not
that reliable when using some cheap 5V power adapter or USB port of the
computer. But it’s the simplest reference to use.AREF
pin that provides reference voltage. This has to stay
within about $1V$ below VCC
. This is most useful in case one also wants
to use 5V reference voltage but wants to implement some special stabilization
like a bandgap reference (for example an LT1029 - these are basically devices
that work like Zener diodes that are connected to shunt and provide a stable
and more precise voltage reference with higher temperature stability.GND
to the reference voltage this places a tight
constraint on the input voltage of the ADC - but on the other hand
also increases the resolution. The $1.1V$ bandgap reference would allow for
a theoretical resolution of $1.075 mV$ which would be enough for simple
direct measurements using thermocouples when one requires to know only
tens (not tenth) of degrees.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:
PRADC
to zero to disable any power saving features if this is the
first time you’re initializing the ADC.ADMUX
, ADCSRB
and ADCSRA
to match your input selection,
trigger selection, prescaler configuration, etc.ADSC
. This bit
will stay high as long as the conversion is in progress. As soon as the bit
goes low the result can be read in case one does polling access. If one has
configured interrupt mode the ADC_vect
will be triggered as soon
as data is available.ADC
register pair (as usual when not using any macros
of your compiler or writing assembly code read ADCL
first since this
triggers an interlock to prevent modification of ADCH
till if has been
read)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.
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$.
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.
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) |
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 |
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 |
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 |
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.
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 |
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 |
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 |
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 |
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.
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);
}
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;
}
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
Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/