Thursday, May 24, 2018

Analog to Digital Converter (ADC) library for AVR microcontrollers

This library provides a quick and easy way to set up an ADC on AVR microcontrollers and retrieve the values in 8-bit or 10-bit format in an interrupt driven fashion.

Nowadays even the cheapest microcontroller has a build-in ADC (Analog to Digital Converter). An ADC converts analog signals into digital signals and can be used in a wide range of applications like recording a signal from a microphone into a digital format, reading light sensors like an LDR (light dependent resistor), measuring current consumption, reading temperature or humidity sensors, etc. All these requires voltage measurements that an ADC can do.



Library Structure

In version 2 I have added support for newer types of AVR microcontrollers that have new and exciting features. I will be referring to them as UPDI and non-UPDI devices because older architectures such as ATmega328 don't have an UPDI interface, whereas newer models, such as ATiny402, are programmable via UPDI interface. Given the major differences between the two classes of microcontrollers, there are library functions that are specific to only one class.The library refers to non-UPDI devices as Class 0 and UPDI devices as Class 1.

ADC Characteristics


ADC Channels

Most microcontrollers have only one ADC peripheral and multiple analog input pins, and each one can be connected to different analog sources, but only one at a time can be internally sampled, by means of multiplexing. This pins can also be used as digital pins. No resistor is required when taking voltage measurements. In case resistors are used, the datasheet recommends that the impedance should not exceed 10k. Care must be taken that the voltage to this pins must not exceed VCC. If the microcontroller is powered from 5V, don't put more than 5V on the pins. In case that higher voltages are to be measured resistor dividers should be used.

An ADC channel is not necessarily an analog pin. It can also be an internal source, such as a temperature sensor (provided that the microcontroller has one), ground or a voltage reference. Consult the datasheet to know what channel number to use when using the library.

ADC channel table
Example from ATtiny402 datasheet

Calculating resistor values for voltage divider with filter cap (optional)

Calculating output impedance for ADC involves a bit of math especially when adding a filter capacitor, so I have made a spreadsheet for LibreOffice for this purpose. Select a value for top resistor (R1) and the calculator will recommend a value for R2 and show the output impedance.

A filter capacitor can be used to remove potential noise, but that can be done in software by averaging samples and on newer AVR microcontrollers, even in hardware.

Calculator - Voltage Divider With Filter Cap, Impedance and sampling Frequency

The formula is from an answer to this stackexchange question:

ADC power supply and voltage reference pin

The ADC has it's own pins for power supply named AVCC and AGND. The AVCC pin is recommended to be connected to VCC through an LC filter for noise suppression. 

The AREF pin can be used in two ways and both require a 100n decoupling capacitor to ground:

  1. By default the ADC register is configured to use an external reference voltage from this pin. If you apply a voltage to this pin you must avoid setting the ADC to use an internal reference as this will cause a short circuit between them.
  2. The second method is to use one of the internal voltage references such as VCC, 2.56V and 1.1V. Not all avr microcontrollers have all of them. Consult the datasheet to see which ones are available for your device.
AVR ADC power connections with LC filter and analog ground plane
ADC power connections with LC filter and analog ground plane

Important thing to know when using ATmega324PB ADC and the ADC values are always at maximum value or 0x03FF

By default the AREF pin on ATmega324PB is disabled and only internal voltage references can be used. To enable the AREF pin, the GPIOEN pin in the ADCSRB register must be set to 0 because the default value is 1. Many people including myself have spent hours trying to figure out why the ADC is not working and here's why.

Here is the datasheet for ATmega324PB revision DS40001908A

and here is the datasheet with revision DS40001892A

ATmega324PB datasheet revision DS40001892A ADCSRB ADC register

The GPIOEN bit is missing in the newer revision. The library will set this bit low for you if using ATmega324PB and external reference voltage.

ADC values

Most AVR microcontrollers have 10 bit ADC resolution but 8 bit resolution can be used also. Suppose that the voltage reference is set to VCC 5V and you measure a potentiometer with 10 bit ADC resolution. The minimum value returned by the ADC will be 0 - meaning ground - and the maximum value will be 2^10-1 = 1023 (meaning 5V). The returned value will be between 0 and 1023. This value can be converted to a voltage using the following formula, but most of the time this is not needed.

V = ADCvalue * (Vref / ADCresolution)

To find out the minimum voltage that can be measured depending on the ADC resolution, divide voltage reference by the maximum ADC value. For 10 bit resolution and 5V Vref

Vref / 2^10
 5 / 1024 = 0.0048V (4.8mV)

Calculating values for INITDLY and SAMPLEN

There are places in the datasheet where they recommend minimum values for Initialization Delay (INITDLY) and Sample Length (SAMPLEN) like this:

INITDLY ≥ 32 µs × fCLK_ADC 

SAMPLEN ≥ 32 µs × fCLK_ADC

You might wonder how to calculate the register value. The formula for this is:


Example: F_CPU is 3.33MHz (default clock frequency on UPDI devices) and the ADC prescaler is 4, then the ADC clock is: 3333333 / 4 = 833333 Hz.

Register value is then: 32E-6 (convert microseconds to seconds) * 833333Hz = 26.66. The closest Initialization Delay register value is 32.

When calculating Sample Length you need to subtract 2 from the calculated value because by default, the sampling time is two CLK_ADC cycles and this register value adds up to those 2 default cycles.

Library Usage

ADC Setup

Used to set the prescaler, and to enable ADC interrupt, the ADC peripheral and global interrupts. The prescaler is used by the ADC peripheral to generate the ADC clock, by dividing the main clock by the prescaler value which can be one of the following macros that are equal to a register value not a prescaler number:


Prescaler 256 is not available on all microcontrollers. Check the datasheet for your particular device.

The prescaler value depends on the F_CPU (main clock frequency) and the desired ADC clock frequency that gives the ADC sampling rate. First decide on what the ADC clock frequency must be. When using only 8-bit resolution, the ADC clock can be higher than when using 10-bit. Here is what the datasheets recommend:

"By default, the successive approximation circuitry requires an input clock frequency between 50kHz and 200kHz to get maximum resolution. If a lower resolution than 10 bits is needed, the input clock frequency to the ADC can be higher than 200kHz to get a higher sample rate." - from ATmega328PB datasheet.

"The ADC requires an input clock frequency between 50 kHz and 1.5 MHz for maximum resolution. If a lower resolution than 10 bits is selected, the input clock frequency to the ADC can be higher than 1.5 MHz to get a higher sample rate." - from ATtiny202 -402 datasheet.

Suppose the F_CPU is 16MHz and desired ADC clock 200kHz. To find the closest prescaler divide the main clock by the ADC clock:

16000000 / 200000 = 80

Since there is no prescaler with the value 80, we need to find the closest one which is 64. Using a 64 prescaler the ADC clock will be:

16000000 / 64 = 250000

If 250kHz is not acceptable select the 128 prescaler which will give 125kHz ADC frequency.

void adcSetup(uint16_t adc_prescaler)



Must be one of the provided macros that represent a register value.

Set ADC voltage reference

The internal voltage reference must be decoupled by an external capacitor at the AREF pin to improve noise immunity. If the user has a fixed voltage source connected to the AREF pin, the user may not use the other reference voltage options in the application, as they will be shorted to the external voltage. If no external voltage is applied to the AREF pin, the user may switch between internal voltage references. The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result.

void adcReference(uint8_t vref)



Can be one of following macros, provided it is supported by your microcontroller:


These macros represent a register value and not an integer equal to the voltage they suggest.

Set ADC resolution

ADC resolution in bits.

void adcResolution(uint8_t res)



The ADC resolution that can be an integer 8 or 10, or a macro: ADC_8bit, ADC_10bit. Default value is 10-bit.

Start ADC conversion

Start an ADC conversion on the given channel. When available, disables digital input for the given ADC pin for a more accurate sampling. Uses ADC Noise Reduction Sleep Mode if enabled.

On UPDI devices the digital input buffer should be disabled by the user as the datasheet recommends:

"The digital input buffer should be disabled on the pin used as input for the ADC to disconnect the digital domain from the analog domain to obtain the best possible ADC results. This is configured by the port I/O Pin Controller."

For example if the analog pin is on port A pin 0:

void adcStartConversion(uint8_t channel)



ADC channel that can be an analog pin or the internal temperature sensor.

Disable ADC

Disables the ADC to reduce power consumption.

void adcDisable(void)

Enable ADC

Re-enable the ADC to increase power consumption.

void adcEnable(void)

Freerun mode

Enable the FREERUN mode. adcStartConversion() must be used once to set the ADC channel and trigger the conversion.

void adcFreeRun(uint8_t freerun)



Set 1 to enable FREERUN mode or 0 to disable. Available macros:


Check conversion complete

Returns 1 if ADC conversion is complete.

uint8_t adcIsReady(void)

Read ADC value

Returns the ADC value stored in a buffer written by the ADC interrupt routine.

uint16_t adcReadValue(void)

Return: if ADC resolution is 8-bit the value will be between 0-255, or 0-1023 when 10-bit resolution is used.

ADC to Volts

Convert an ADC value to a value in Volts.

float adcToVoltage(uint16_t adc_value, float vref)



The ADC value.


ADC voltage reference.

Return: converted value in Volts.

Measure power supply (VCC) without a pin

Measure power supply voltage without a pin. Especially useful in battery powered applications. Instead of an ADC pin to measure the power supply voltage, the ADC measures the internal 1.1 voltage (Vbg) and the measured voltage - VCC - is used as a reference voltage. Since one voltage is known (1.1V) the other one (VCC) will be calculated.

The ADC needs to be configured before using his function. ADC reference voltage is set inside the function as VCC.

This function is based on: Microchip - AN2447 - Measure VCC/Battery Voltage Without Using I/O Pin on tinyAVR and megaAVR.

float adcMeasureVCC(uint8_t adc_channel, float vref)



Internal 1.1V reference channel number. For example on ATmega328PB this is 0b1110 which converted to hex is 0xE.


Reference voltage which is 1.1 in most cases but some microcontrollers have 1.3 instead.

Return: measured power supply in volts.

ADC Noise Reduction Sleep Mode

Enable/disable ADC Noise Reduction Sleep Mode. The function will set up a flag and when the adcStartConversion() will be used, the CPU will be put into sleep mode from which it will exit when the conversion is complete. Only available on non-UPDI devices.

Before using adcStartConversion() make sure that UART is done transmitting, otherwise after the CPU exits from sleep mode the resumed UART will be out of sync.

"The ADC features a noise canceler that enables conversion during sleep mode to reduce noise induced from the CPU core and other I/O peripherals."

"If no other interrupts occur before the ADC conversion completes, the ADC interrupt will wake up the CPU and execute the ADC Conversion Complete interrupt routine. If another interrupt wakes up the CPU before the ADC conversion is complete, that interrupt will be executed, and an ADC Conversion Complete interrupt request will be generated when the ADC conversion completes. The CPU will remain in active mode until a new sleep command is executed."

"The ADC will not be automatically turned off when entering other sleep modes than Idle mode and ADC Noise Reduction mode. The user is advised to write zero to ADCRSA.ADEN before entering such sleep modes to avoid excessive power consumption."

void adcUseNoiseReduction(uint8_t sleep)



Available macros:


ADC Auto Trigger Source

Selects ADC auto trigger source. Only available on non-UPDI devices. UPDI devices features the EVSYS module. adsStartConversion() should be used to select the ADC channel. It is important to include the ISR with the appropriate interrupt vector in dependence of the trigger source. For example if Timer0 Compare Match A is selected then include ISR(TIMER0_COMPA_vect){ } in your code.

void adcAutoTrigger(uint8_t trig_source)



One of the available macros that represent register values:

ADC_TRIG_NONE			// Automatic Triggering Disabled
ADC_TRIG_AC			// Analog Comparator
ADC_TRIG_EXT_INT0		// External Interrupt Request 0
ADC_TRIG_TIM0_CMPA		// Timer/Counter0 Compare Match A
ADC_TRIG_TIM0_OVF		// Timer/Counter0 Overflow
ADC_TRIG_TIM1_CMPB		// Timer/Counter1 Compare Match B
ADC_TRIG_TIM1_OVF		// Timer/Counter1 Overflow
ADC_TRIG_TIM1_CAPEV		// Timer/Counter1 Capture Event

Set Initialization Delay

Set the initialization/start-up delay before the first sample when enabling the ADC or changing to an internal reference voltage. Setting this delay will ensure that the reference, MUXes, etc. are ready before starting the first conversion. The initialization delay will also take place when waking up from deep sleep to do a measurement. The delay is expressed as a number of CLK_ADC cycles.

void adcSetInitDelay(uint16_t init_delay)



Available macros:

ADC_DLY0	// Delay 0 CLK_ADC cycles
ADC_DLY16	// Delay 16 CLK_ADC cycles
ADC_DLY32	// Delay 32 CLK_ADC cycles
ADC_DLY64	// Delay 64 CLK_ADC cycles
ADC_DLY128	// Delay 128 CLK_ADC cycles
ADC_DLY256	// Delay 256 CLK_ADC cycles

Set Sampling Length

Extend the ADC sampling length in a number of CLK_ADC cycles. By default, the sampling time is two CLK_ADC cycles. Increasing the sampling length allows sampling sources with higher impedance. The total conversion time increases with the selected sampling length.

void adcSetSampleLength(uint8_t sample_length)



Integer from 0 to 31 representing ADC clocks.

Sample Accumulation

The ADC supports sampling in bursts where a configurable number of conversion results are accumulated into a single ADC result (sample accumulation). Further, a sample delay can be configured to tune the ADC sampling frequency associated with a single burst. This to tune the sampling frequency away from any harmonic noise aliased with the ADC sampling frequency (within the burst) from the sampled signal. An automatic sampling delay variation feature can be used to randomize this delay to slightly change the time between samples.

More information on this subject can be found in the application note from Microchip: Noise Countermeasures for ADC Applications - AN2551.

void adcSetSampleAccumulation(uint8_t samples_nr, uint8_t samp_dly, uint8_t samp_dly_auto)



Number of samples for the accumulator to take. Available macros:

ADC_SAMP_ACC_NONE	// No accumulation
ADC_SAMP_ACC_2		// 2 results accumulated
ADC_SAMP_ACC_4		// 4 results accumulated
ADC_SAMP_ACC_8		// 8 results accumulated
ADC_SAMP_ACC_16		// 16 results accumulated
ADC_SAMP_ACC_32		// 32 results accumulated
ADC_SAMP_ACC_64		// 64 results accumulated


Sampling Delay Selection. These bits define the delay between consecutive ADC samples. The programmable Sampling Delay allows modifying the sampling frequency during hardware accumulation, to suppress periodic noise sources that may otherwise disturb the sampling. The SAMPDLY field can also be modified automatically from one sampling cycle to another, by setting the ASDV bit. The delay is expressed as CLK_ADC cycles. The sampling cap is kept open during the delay.

Integer from 0 to 15 or one of the available macros:



Automatic Sampling Delay Variation. Writing this bit to '1' enables automatic sampling delay variation between ADC conversions. The purpose of varying sampling instant is to randomize the sampling instant and thus avoid standing frequency components in the frequency spectrum. The value of the SAMPDLY bits are automatically incremented by one after each sample. When the Automatic Sampling Delay Variation is enabled and the SAMPDLY value reaches 0xF, it wraps around to 0x0.

Available macros:


Select Sample Capacitance

Select the sample capacitance, and hence, the input impedance. The best value is dependent on the reference voltage and the application's electrical properties.

void adcSelectSampleCapacitance(uint8_t sample_capacitance)



Available macros:

ADC_SAMP_CAP_10pf	// Recommended for reference voltage values below 1V.
ADC_SAMP_CAP_5pf	// Reduced size of sampling capacitance. Recommended for higher reference voltages.

Set ADC Duty Cycle

Set the duty cycle of the ADC clock. ADCclk > 1.5 MHz requires a minimum operating voltage of 2.7V.

void adcSetDutyCycle(uint8_t duty_cycle)



Available macros:

ADC_DUTY_CYCLE_50	// 50% Duty Cycle must be used if ADCclk > 1.5 MHz
ADC_DUTY_CYCLE_25	// 25% Duty Cycle (high 25% and low 75%) must be used 
			// for ADCclk < or = 1.5 MHz (default register value)

In this case, the default register value is 1 not 0, thus the default duty cycle is 25%.

Set Window Comparator Mode

The ADC can raise the WCOMP flag in the Interrupt and Flag register (ADCn.INTFLAG) and request an interrupt (WCOMP) when the result of a conversion is above and/or below certain thresholds. The available modes are: 

• The result is under a threshold 

• The result is over a threshold 

• The result is inside a window (above a lower threshold, but below the upper one) 

• The result is outside a window (either under the lower or above the upper threshold)

When accumulating multiple samples, the comparison between the result and the threshold will happen after the last sample was acquired. Consequently, the flag is only raised once, after taking the last sample of the accumulation.

void adcWindowComparator(uint8_t mode, uint16_t winlt, uint16_t winht)



Enables and defines when the interrupt flag is set in Window Comparator mode. RESULT is the 16-bit accumulator result. WINLT and WINHT are 16-bit lower threshold value and 16-bit higher threshold value, respectively. Available macros:

ADC_WINDOW_NONE		// No Window Comparison (default)


Window Comparator Low Threshold.


Window Comparator High Threshold.

Run in Standby

Configure whether the ADC needs to run when the chip is in Standby Sleep mode.

void adcRunInStandBy(uint8_t run_stby)



Available macros:


Get Accumulator Size

Returns the number of samples that the accumulator is set to take. Useful to multiply the winlt and winht arguments for adcWindowComparator() with the returned value, or to divide the ADC value by.

uint8_t adcAccSize(void)

Code example for non-UPDI devices

In this example the ADC is configured in auto triggering mode where timer 0 triggers an ADC conversion in hardware every 1ms. The time interval can be changed according to your project. For example you could measure an external temperature sensor every 1 second without CPU intervention. The CPU is only used to check when a conversion is ready and to do something with the ADC value.

// Not necessary in this project but useful.
// See
#ifndef F_CPU
    #warning	"F_CPU not defined. Define it in project properties."
#elif F_CPU != 16000000
    #warning	"Wrong F_CPU frequency!"

#include <util/delay.h>
#include "adc.h"
#include "uart.h"

int main(void){
    // Local variables
    uint16_t adc_val = 0;
    float voltage = 0.0;
    // USART Initialization
    UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT);
    // ADC initialization
    // 16000000 / 128 = 125kHz ADC clock
    // 10-bit ADC resolution
    // Enable/Disable ADC Noise Reduction Sleep Mode
    // Select voltage reference
    // Select Timer/Counter0 Compare Match A as an auto trigger
    // Select ADC channel as ADC0 pin
    // Configure Timer0 to trigger a compare match interrupt every 1ms
    TCCR0A = 1 << WGM01;		// CTC mode
    TCCR0B = (_BV(CS01) | _BV(CS00));	// 64 prescaler
    OCR0A = ((F_CPU / 64) / 1000) - 1;	// 1000 is the frequency in Hz
    TIMSK0 = (1 << OCIE0A);		// Enable interrupt
        // Wait for ADC conversion complete
	    // Read the ADC value
	    adc_val = adcReadValue();
	    // Convert ADC to V. Reference voltage is VCC 5V
	    voltage = adcToVoltage(adc_val, 5);
	    // Send value over UART
	    UART_sendString(&uart0, "V = ");
	    UART_sendFloat(&uart0, voltage, 3);
	    UART_sendString(&uart0, ", ADC = ");
	    UART_sendFloat(&uart0, adc_val, 3);
	    UART_send(&uart0, '\n');
	    // Start a new conversion on ADC0
	    // Not necessary in FREERUN mode or auto trigger mode

// Necessary for auto trigger mode
    //PORTD |= 1<<7;
    //PORTD &= ~(1<<7);

Code example for UPDI devices

// Not necessary in this project but useful.
// See
#ifndef F_CPU
    #warning	"F_CPU not defined. Define it in project properties."

#elif F_CPU != 3333333
    #warning	"Wrong F_CPU frequency!"

#include <util/delay.h>
#include "adc.h"
#include "uart.h"

int main(void){
    // Local variables
    uint16_t adc_val = 0;
    float voltage = 0.0;
    // Set TX pin as output
    PORTA.DIR |= PIN6_bm;
    // The digital input buffer should be disabled on the pin used as input for the ADC to disconnect the digital
    // domain from the analog domain to obtain the best possible ADC results. This is configured by the port - I/O Pin Controller.
    // USART Initialization
    UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT);
    // ADC initialization
    // 10-bit ADC resolution
    // Use internal 4.3V voltage reference
    // Initialization delay (optional)
    // Sampling length (optional)
    // Reduced size of sampling capacitance. Recommended for reference voltages higher than 1V.
    // Number of samples in a burst that will be averaged.
    // Useful for noise filtering. All done in hardware.
    // Configure the window comparator. The ADC interrupt will only be triggered
    // when the ADC result is inside this window: greater than 780 or smaller than 800.
    // The size of the accumulator must be taken into account which is returned by adcAccSize(),
    // and in this example is 8. Don't use 780 * ADC_SAMP_ACC_8.
    adcWindowComparator(ADC_WINDOW_INSIDE, 780 * adcAccSize(), 800 * adcAccSize());
        // Wait for ADC conversion complete
	    // Read the ADC value
	    adc_val = adcReadValue();
	    // Convert ADC to V. Reference voltage is 4.3
	    // Do not use ADC_VREF_INTERNAL_4V3 instead of 4.3
	    voltage = adcToVoltage(adc_val / adcAccSize(), 4.3);
	    // Send value over UART
	    UART_sendString(&uart0, "V: ");
	    UART_sendFloat(&uart0, voltage, 3);
	    UART_sendString(&uart0, ", ADC: ");
	    UART_sendFloat(&uart0, adc_val / adcAccSize(), 3);
	    UART_send(&uart0, '\n');
	    // Start a new conversion on pin 2


Changelog and license can be found at the beginning of the header files
adc v2.1


Other Links

UART library project page Useful to display ADC values on a serial terminal.

AVR internal temperature sensor library
v2.1 31-01-2024:
- included function adcMeasureVCC() for measuring VCC without a pin.
v2.0 29-01-2024:
- added support for modern AVR (UPDI devices). Tested on tinyAVR402.

No comments:

Post a Comment