AVR ADC Tutorial: Reading a Potentiometer and LM35 with ADMUX and ADCSRA
Most real-world quantities such as temperature, light level or the position of a knob are analog voltages. The ATmega328P, ATmega16 and ATmega32 all contain a 10-bit successive approximation analog-to-digital converter (ADC) that turns a voltage into a number from 0 to 1023. A multiplexer in front of it lets one ADC read several pins in turn.
In this tutorial you will configure the ADC with direct register access, read a potentiometer to set LED brightness, and measure temperature with an LM35 sensor using the internal 1.1 V reference for better resolution.
How the AVR ADC Works
The ADC compares the input against a reference voltage, VREF, and produces a result proportional to the ratio between them. The basic conversion formula is:
ADC = (Vin x 1024) / VREF Vin = ADC x VREF / 1024 With VREF = 5.00 V: 1 LSB = 5.00 / 1024 = 4.88 mV With VREF = 1.1 V: 1 LSB = 1.1 / 1024 = 1.07 mV
Each conversion needs 13 ADC clock cycles, and the first conversion after enabling takes 25 cycles because it also initialises the analog circuitry. For full 10-bit accuracy the datasheet recommends an ADC clock between 50 kHz and 200 kHz. At 16 MHz a prescaler of 128 gives 125 kHz, so one conversion takes about 104 µs.
Key registers
| Register | Important bits | Function |
|---|---|---|
| ADMUX | REFS1, REFS0 | Reference: 00 = AREF pin, 01 = AVCC, 11 = internal 1.1 V on ATmega328P |
| ADMUX | ADLAR | Left-adjusts the result so the top 8 bits are in ADCH |
| ADMUX | MUX3..MUX0 | Selects channel ADC0 to ADC7 (plus internal sources) |
| ADCSRA | ADEN | Enables the ADC |
| ADCSRA | ADSC | Write 1 to start; reads 1 until the conversion ends |
| ADCSRA | ADPS2..ADPS0 | Prescaler: 111 selects divide by 128 |
| ADCSRA | ADIE, ADIF | Conversion complete interrupt enable and flag |
| ADC (ADCL, ADCH) | - | 10-bit result; read ADCL before ADCH, or use the 16-bit ADC name |
| DIDR0 | ADC0D..ADC5D | Disables the digital input buffer on analog pins to save power |
Circuit
On the ATmega328P the analog inputs ADC0 to ADC5 are PC0 to PC5 (DIP pins 23 to 28, Uno A0 to A5). Connect AVCC (pin 20) to VCC even if you do not use the ADC, ideally through a small LC filter, and place a 100 nF capacitor from AREF (pin 21) to ground. Never connect an external voltage to AREF while using the AVCC or internal reference, because the internal reference would be shorted.
- Potentiometer (10 kΩ): ends to 5 V and GND, wiper to PC0 (ADC0).
- LM35: pin 1 to 5 V, pin 3 to GND, centre output pin to PC1 (ADC1). It outputs 10 mV per °C, so 30 °C gives 300 mV.
- LED with 330 Ω resistor on PB3 (OC2A) for brightness control.
Program: Pot Controls LED, LM35 Measures Temperature
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
#define REF_AVCC (1 << REFS0) /* 5 V reference */
#define REF_1V1 ((1 << REFS1) | (1 << REFS0)) /* internal 1.1 V */
static void adc_init(void)
{
ADMUX = REF_AVCC; /* AVCC ref, channel 0 */
/* Enable ADC, prescaler 128 -> 125 kHz ADC clock */
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
DIDR0 = (1 << ADC0D) | (1 << ADC1D); /* digital buffers off */
}
/* Read a channel with the given reference; returns 0..1023 */
static uint16_t adc_read(uint8_t channel, uint8_t ref)
{
uint8_t new_admux = ref | (channel & 0x0F);
if (ADMUX != new_admux) {
ADMUX = new_admux;
_delay_ms(2); /* let reference/input settle */
ADCSRA |= (1 << ADSC); /* dummy conversion */
while (ADCSRA & (1 << ADSC))
;
}
ADCSRA |= (1 << ADSC); /* start conversion */
while (ADCSRA & (1 << ADSC)) /* wait until done */
;
return ADC; /* reads ADCL then ADCH */
}
volatile int16_t temperature_c10; /* temperature x 10 */
int main(void)
{
/* Timer2 fast PWM on OC2A (PB3) for the LED */
DDRB |= (1 << DDB3);
TCCR2A = (1 << COM2A1) | (1 << WGM21) | (1 << WGM20);
TCCR2B = (1 << CS22);
adc_init();
while (1) {
/* Potentiometer: 0..1023 scaled to 0..255 */
uint16_t pot = adc_read(0, REF_AVCC);
OCR2A = pot >> 2;
/* LM35 with 1.1 V reference: average 16 samples */
uint32_t sum = 0;
for (uint8_t i = 0; i < 16; i++)
sum += adc_read(1, REF_1V1);
uint16_t raw = sum / 16;
/* T(degC x10) = raw x 1100 mV / 1024 (mV per count, x10 per degC) */
temperature_c10 = (int16_t)(((uint32_t)raw * 1100UL) / 1024UL);
_delay_ms(100);
}
}
Code Walkthrough
adc_init() selects AVCC as reference and sets the prescaler to 128. The adc_read() function writes ADMUX only when the reference or channel changes, then performs a throw-away conversion, because the first reading after switching the reference is often inaccurate while the reference capacitor on AREF charges or discharges.
The conversion is started by writing a one to ADSC, and the hardware clears ADSC when the result is ready, so the busy-wait loop is simple. The 16-bit ADC macro reads ADCL first and then ADCH, which is the order the hardware requires to keep both halves from the same conversion.
For the LM35, the 1.1 V reference gives about 1.07 mV per count. Since the sensor produces 10 mV per °C, each count is roughly 0.1 °C, and the result raw x 1100 / 1024 directly gives tenths of a degree. With the 5 V reference each count would be about 0.5 °C. The internal reference is not exactly 1.1 V on every chip; for accurate readings measure it at AREF with a good multimeter and use the measured value in the formula. Display temperature_c10 on an LCD or send it over USART as covered in the next tutorials.
Troubleshooting
- Reading always 1023 or 0: wrong channel in MUX bits, input not connected, or AVCC left unconnected.
- Noisy readings: add 100 nF on AREF, keep analog wires short, average several samples, or use ADC Noise Reduction sleep mode.
- LM35 reads high by a few degrees: the reference is not exactly 1.1 V. Calibrate against a known temperature or the measured AREF voltage.
- Readings from one channel affect the next: high source impedance. Keep the signal source below about 10 kΩ or add a small capacitor at the pin.
Summary
The AVR ADC is configured with ADMUX for reference and channel and ADCSRA for enable, start and prescaler. Keep the ADC clock between 50 and 200 kHz, start a conversion with ADSC, wait for it to clear and read the 16-bit ADC register. Choose the reference to match the signal range: AVCC for full-scale signals like a potentiometer, and the internal 1.1 V reference for small signals such as an LM35.