Atmega328 with ADC

In this project I tested the analog to digital converter (ADC) from the Atmega328p. It read’s out an analog pin and shows the value on an i2c lcd. There are 6 analog ports and 2 are already used for the lcd. To change the voltage at the analog pin I used a 10k potmeter. The ADC can be set to free running mode or single conversion mode. Free running mode means the ADC samples continuously and single conversion mode means, only sample when you order to do so. I use the single conversion mode in this project.

 

Atmega328 with ADC and LCD schematic.

 

 

The working of the ADC

The first thing that’s needed to be done is initializing the ADC. I used a separate function called adc_init() in the file adc.c to get this done. For a proper initialization a few registers have to be set. For single conversion mode not all registers are used therefor I only describe the used registers in this project.

The ADMUX register is dealing with the voltage reference and the analog pin(s) that will be used.


Bit 6 and 7 tells the Atmega328p against what reference the voltage on the pin should be measured. This is important for the precision of the 10 bit read value. For example, it is possible to put 2V as a reference on the AREF pin. In that case the 10 bits (value up to 1023) will be divided over 2V. It’s more presice. It also means that every voltage above 2V on the analog pin will be presented as 1023. In this project I used Avcc (5V) as a reference therefor bit REFS0 is set.



The last register I use for initializing is the ADCSRA register.


Bit 7 is the ADC enable and needs to be set to activate the ADC. Bit 6 is the ADC start conversion, this is used in the application every time you need to read a value from the analog pin. Bit 5-3 is used for interrupts and triggering on certain conditions. Not used in this project. Bit 2 – 0 sets a prescaler. The 16MHz external clock gets divided by this prescaler to set the sampling rate. In this project all 3 bits are set, which gives a division factor of 128. The Atmega328p ADC circuit needs a clock below 200 KHz to get a 10 bit resolution and by using a higher frequentie the resolution will only be 8 bits, but then you will have a faster sampling rate. In this project I use 16 MHz / 128 to get 125 KHz. This is below 200 KHz so it will provide the full 10 bits resolution.


The initialization function uses the below code to initialize the ADC for Single conversion mode.

void adc_init(){
	
	ADMUX = (1<<REFS0);
	ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

For reading a pin it’s necessary to set ADMUX again with the analog pin you want to use. After that the conversion gets started by setting the ADSC bit in the ADCSRA register. When the conversion is done this bit is automatically unset by the Atmega328p hardware so wait for this in a while loop. Last thing is returning the value to do something with it. Because it’s a 10 bit value the return value is 2 bytes placed in ADC.

uint16_t adc_read(int pin){
	if(pin < 0 || pin > 7){ //ADC0 - ADC7 is available
		return 1; //pin number is out of range
	}
	
	pin &= 0x7;
	ADMUX = (ADMUX & 0xF8)|pin;
	
	ADCSRA |= (1<<ADSC);
	
	while(ADCSRA & (1<<ADSC)); //as long as ADSC pin is 1 just wait.
	
	return (ADC);
}

 

The application1.c file contains the code that deals with reading the analog pin and writing the value to the lcd.

Application1.c

/*
 * application1.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat, show lcd clock and adc
 */ 

#include <avr/io.h>
#include <stdlib.h>
#include "i2c.h"
#include "i2cdisplay.h"
#include "timer4clocklib.h"
#include "adc.h"

#define ADCPIN 2
#define DELAYMILIS 150 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

void showTime(); // showing the time
void showAdcValue(); // showing the ADC value

/****************************************************************************
	Public vars
****************************************************************************/

extern int alarmCode;
extern int timeSecApp1;
extern int timeMinApp1;
int oldTime;


int timerSet4Adc = TIMER_IS_NOT_SET;
unsigned long int endTimeAdc = 0;


void Application1(void){

	if(timerSet4Adc == TIMER_IS_NOT_SET){
		endTimeAdc = msCounter + DELAYMILIS;
		timerSet4Adc = TIMER_IS_SET;
		showAdcValue();
	}
	if(endTimeAdc < msCounter){ // Timer is expired
		timerSet4Adc = TIMER_IS_NOT_SET; // reset timer
		return;
	}

	
	showTime();
	
}

void showTime(){
	int seconds;
	int minutes;
	
	
	seconds = timeSecApp1;
	minutes = timeMinApp1;
	
	if (oldTime == timeSecApp1){
		return;
	}
	if (seconds >= 10){
		setCursor(11,2);
		writeI2cDisplay(seconds/10+48, DATAREGISTER);
		setCursor(12,2);
		writeI2cDisplay(seconds%10+48, DATAREGISTER);
		if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	else if (seconds < 10){
		setCursor(12,2);
		writeI2cDisplay(seconds+48, DATAREGISTER);
		setCursor(11,2);
		writeI2cDisplay('0', DATAREGISTER);
		if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	oldTime = timeSecApp1;
}

void showAdcValue(){
	
	uint16_t adcReadedValue;
	uint8_t hundreds;
	uint8_t tens;
	
	adcReadedValue = adc_read(ADCPIN);
	setCursor(8,3);
	
	if(adcReadedValue > 999){
		writeI2cDisplay('1', DATAREGISTER);
		adcReadedValue -= 1000;
	}
	else{
		writeI2cDisplay('0', DATAREGISTER);
	}
	hundreds = adcReadedValue / 100;
	adcReadedValue %= 100;
	writeI2cDisplay(hundreds + 48, DATAREGISTER);
	tens = adcReadedValue /10;
	adcReadedValue %= 10;
	writeI2cDisplay(tens + 48, DATAREGISTER);
	writeI2cDisplay(adcReadedValue + 48, DATAREGISTER);
	
}

initialize.c

/*
 * initialize.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8
#define ADCPIN 0

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"
#include "i2cdisplay.h"
#include "adc.h"


void initialize(){
	
	char openingText[] = "www.hdhprojects.nl";
	char clockText[] = "Clock";
	char adcText[] = "ADC";
	int i;

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2cLcd_Init(); // initialize i2c lcd
	adc_init(); // initialize the ADC
	sei();//interrupts enable
	
	clear();
	setCursor(1,0);
	for(i=0; i<18; i++){		
		writeI2cDisplay(openingText[i], DATAREGISTER);
	}
	
	setCursor(1,2);
	for(i=0; i<5; i++){
		writeI2cDisplay(clockText[i], DATAREGISTER);
	}
	
	setCursor(10,2);
	writeI2cDisplay(':', DATAREGISTER);
	
	setCursor(1,3);
	for(i=0; i<3; i++){
		writeI2cDisplay(adcText[i], DATAREGISTER);
	}
	
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

adc.c

/*
 * adc.c
 *
 * Created: 4-12-2017 18:01:49
 *  Author: acer
 */ 

#include <avr/io.h>
#include "adc.h"

/****************************************************************************
	adc_init(), enables the ADC conversion and set's the prescaler
****************************************************************************/
void adc_init(){
	
	ADMUX = (1<<REFS0);
	ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

/****************************************************************************
	adc_read(uint8_t pin), returns a value from 0 - 1023
****************************************************************************/
uint16_t adc_read(int pin){
	if(pin < 0 || pin > 7){ //ADC0 - ADC7 is available
		return 1; //pin number is out of range
	}
	
	pin &= 0x7;
	ADMUX = (ADMUX & 0xF8)|pin;
	
	ADCSRA |= (1<<ADSC);
	
	while(ADCSRA & (1<<ADSC)); //as long as ADSC pin is 1 just wait.
	
	return (ADC);
}

adc.h

/*
 * adc.h
 *
 * Created: 4-12-2017 18:02:01
 *  Author: acer
 */ 


#ifndef ADC_H_
#define ADC_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/



/****************************************************************************
	Function definitions
****************************************************************************/
void adc_init();
uint16_t adc_read(int pin);




#endif /* ADC_H_ */