Programming AVR ADC module with avr-gcc

Most AVR microcontrollers have Analog to Digital Converter (ADC) integrated into to chip. Such a solution makes embedded designers’ life much easier when creating projects and programming them. With no need for external ADC, PCB takes less space, easier to create programs – it saves time and money. As an example, let’s take the Atmega8 microcontroller, which has up to 8 ADC inputs, most with a 10-bit resolution(excluding ADC4 and ADC5 inputs that are 8-bit). All features of AVR internal ADC can be found on official ATMEL AVR datasheets, but most important to mention are:

  • ±2 LSB accuracy – so measurements aren’t very accurate. If AREF voltage is 5V, then the error may reach ±0.04V, but this is still good results for most of the tasks;
  • Integral nonlinearity ±0.5 LSB;
  • Conversion speeds up to 15kSPS at maximum resolution. This is far not enough for 20kHz audio signal sampling.

ADC unit is powered with separate power supply pins AVCC with AGND, but AVCC must not differ ±0.3V of VCC. Also, ADC units can have different voltage reference sources selectable in the ADMUX register. References may be taken from AREF pin, AVCC with an external capacitor, or internal 2.56V voltage reference. All ADC inputs are multiplexed via a multiplexer. Each channel can be selected by changing 4 bits in the ADMUX register. ADC unit can operate in two modes:

  • Single conversion – when the ADC converter makes one conversion and then stops. As this mode require ADC initialization for each conversion, it takes 25ADC clock cycles;
  • Free running conversion – conversion is continuous. Once initialized, it takes 13 ADC cycles for a single conversion. In this mode, the ADC data register has to be read before the new value is written.

AVR ADC has a nice feature ADC noise reduction technique, which performs conversion with minimal noise induced from AVR core and io peripherals. It is simple – when noise canceling is enabled, MCU is put to sleep(CPU clock stops). After conversion completes, interrupt wakes the processor to read and process converted data.

All theory is nicely explained in AVR datasheets. Better let’s go to some practical issues. Connect photo resistor to ADC0 input and simple potentiometer to ADC2. As an example, let’s do single conversions with AVCC voltage as a reference. Let’s generate an interrupt when the conversion is complete. Interrupt service routine will output ADC value on LCD. Circuit is really simple – on Proteus simulator, it is even simpler:

AVR ADC example on LCD

There are three simple steps needed to achieve the desired results. First of all, we need to initialize ADC. This adc_init() function is written, which prepares reference voltage source (AVCC with external capacitor at AREF pin), then enables ADC peripheral and performs single dummy conversion to initialize ADC.

void adc_init(void)
{
//select reference voltage
//AVCC with external capacitor at AREF pin
ADMUX|=(0<<REFS1)|(1<<REFS0);
//set prescaller and enable ADC
ADCSRA|=(1<<ADEN)|(1<<ADIE);//enable ADC with dummy conversion
//set sleep mode for ADC noise reduction conversion
set_sleep_mode(SLEEP_MODE_ADC);
}

The next step is to convert the data itself. As we need to read values from two channels, there also multiplexing is needed.

void adc_start_conversion(uint8_t channel)
{
//remember current ADC channel;
ch=channel;
//select ADC channel
ADMUX=(ADMUX&0xF0)|channel;
//Start conversion with Interrupt after conversion
//enable global interrupts
sei();
ADCSRA |= (1<<ADSC)|(1<<ADIE);
}

If this conversion mode invokes an interrupt after conversion is complete, there also interrupt service routine is needed, which takes 10 ADC value and displays it on LCD:

ISR(ADC_vect)
{
}

And finally working main.c file contents.

//*****************************************************************************
//
// File Name	: 'main.c'
// Title		: ADC single conversion witn interrupt after conversion
//
//*****************************************************************************
#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd_lib.h"

//adjust LCDsendChar() function for stream
static int LCDsendstream(char c, FILE *stream);
//----set output stream to LCD-------
static FILE lcd_str = FDEV_SETUP_STREAM(LCDsendstream, NULL, _FDEV_SETUP_WRITE);


void delay1s(void);
void adc_init(void);
void adc_start_conversion(uint8_t);
static int LCDsendstream(char c , FILE *stream);
void init(void);

//current channel
uint8_t ch;
//strings for LCD stored in Flash memory
const uint8_t LDR[] PROGMEM="LDR:\0";
const uint8_t POT[] PROGMEM="POT:\0";
const uint8_t CLRROW[] PROGMEM="                \0";

const uint8_t *LCDXY[] ={
		LDR,	//for ADC0
		POT};	//for ADC1


//*****************************************************************************
//
//  ADC module initialization
//
//*****************************************************************************
void adc_init(void)
{
//select reference voltage
//AVCC with external capacitor at AREF pin
ADMUX|=(0<<REFS1)|(1<<REFS0);
//set prescaller and enable ADC
ADCSRA|=(1<<ADEN)|(1<<ADIE);//enable ADC with dummy conversion
//set sleep mode for ADC noise reduction conversion
set_sleep_mode(SLEEP_MODE_ADC);
}
//*****************************************************************************
//
//  ADC single conversion routine
//
//*****************************************************************************
void adc_start_conversion(uint8_t channel)
{
//remember current ADC channel;
ch=channel;
//set ADC channel
ADMUX=(ADMUX&0xF0)|channel;
//Start conversion with Interrupt after conversion
//enable global interrupts
sei();
ADCSRA |= (1<<ADSC)|(1<<ADIE);
}

//*****************************************************************************
//
//  delay 1s
//
//*****************************************************************************

//delay 1s
void delay1s(void)
{
	uint8_t i;
	for(i=0;i<100;i++)
	{
		_delay_ms(10);
	}
}

//*****************************************************************************
//
//  Set LCD stream function
//
//*****************************************************************************
static int LCDsendstream(char c , FILE *stream)
{
LCDsendChar(c);
return 0;
}

//*****************************************************************************
//
//  init AVR
//
//*****************************************************************************

void init(void)
{
	//init stream
	stdout = &lcd_str;
	//init LCD
	LCDinit();//init LCD bit, dual line, cursor right
	LCDclr();//clears LCD
	LCDGotoXY(0, 0);//Cursor Home
	//Init ADC
	adc_init();
}

//*****************************************************************************
//
//  ADC conversion complete service routine
//
//*****************************************************************************
ISR(ADC_vect)
{
	uint16_t adc_value;
	adc_value = ADCL;  
	/*shift from low level to high level ADC, from 8bit to 10bit*/
	adc_value += (ADCH<<8);
	CopyStringtoLCD(CLRROW, 0, ch );
	CopyStringtoLCD(LCDXY[(ch)], 0, ch );
	printf("%d", adc_value);
}

//*****************************************************************************
//
//  run analog digital converter, timer.
//
//*****************************************************************************
int main(void)
{
	init();
	printf("ADC Test");
	delay1s();
	while(1)//loop demos
	{
	//read LDR
	adc_start_conversion(0);
	_delay_ms(30);
	//read potentiometer
	adc_start_conversion(1);
	_delay_ms(30);
	//continue infinitely
	}
	return 0;
}

Comments are closed.