ADC with bargraph display on the O

I needed to use the ADC in single-shot mode at full 10 bit resolution to measure battery voltage and ran into a gotcha which cost me a frustrating couple of hours. After power up, the ADC would always return the same reading (correct the first time) on successive calls. After staring at the datasheet and trying everything that I could think of, I found the answer in a post on AVRFreaks: You must read the ADC result low byte first (by design, see atmega168 datasheet, p. 242).

For example the C code

int result;
result = (ADCH<<8) | ADCL;

will work correctly only once, on power up. The following code correctly returns the latest result each time it is executed:

result = ADCL;
result |= (ADCH<<8);

For 8 bit resolution you can initialize the ADC for a left justified result and just read ADCH.

To illustrate ADC usage, I added to the LCD routines a bargraph display subroutine, modified from Pascal Stang’s AVRLIB. The length of the bar is controlled by the microscopic pot (red arrow on photo below) on the Orangutan, thus providing a GUI for your O!

Cheers, Jim

The bargraph routine, which also shows how to store program data in flash memory, is a bit long for posting and may be downloaded from
uoxray.uoregon.edu/orangutan/lcd_bargraph.c
while the main routine, which follows the photo, can also be downloaded: adc_bar.c

/*
ADC input and bar graph display on the Orangutan, ATmega168, 8 MHz clock
S. James Remington  
sjames_remington at yahoo dot com

This program reads the tiny pot attached to ADC channel 7 on the Orangutan and displays the result as an integer and a bar graph on the LCD display. 

Yes, a GUI for your O!

*/

#include <avr/io.h>
#include <stdlib.h>

#define F_CPU 8000000UL  //CPU clock frequency for delays
#include <util/delay.h>

void ADC_init()
{
	ADMUX =  7;						//set to right adjust, reference is AREF and ADC channel to 7
	ADCSRA = (1<<ADEN) |(6<<ADPS0); //6=ADC clock division factor 64 (8MHz / 64 = 125kHz). Use 7 for baby O.
									//interrupts disabled
}

unsigned int ADC_get(unsigned char channel)
{
	unsigned int ret;
	ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);
	ADCSRA |= (1<<ADSC);				//Start conversion
	while (bit_is_set(ADCSRA,ADSC));	//wait for ADC to finish conversion
	ret = ADCL;							//result MUST be read low byte first!!
	ret |= (ADCH<<8);					//or in high byte
	return  ret;
}

//include the LCD display routines
#include "lcd_bargraph.c"

int main(void)
{ 
	unsigned int i,adcval;
	char buf[8];
// Initialize LCD display and ADC
	LCDInit();
	ADC_init();

	while (1) 
	{
		adcval=ADC_get(7);		
		itoa(adcval,buf,10);						//convert integer to ascii
		LCDSendCommand(LCD_Line1);					//display it
		LCDPrintString(buf);
		LCDSendCommand(LCD_Line2);
		LCDBar(adcval,1024,8);
		for(i=1; i<20; i++) _delay_ms(10);	//wait a bit
		}
return 0;
}
6 Likes