Forum Menu




 


Log in Problems?
New User? Sign Up!
AVR Freaks Forum Index

Post new topic   This topic is locked: you cannot edit posts or make replies.
View previous topic Printable version Log in to check your private messages View next topic
Author Message
penquissciguy
PostPosted: Nov 16, 2007 - 08:45 PM
Newbie


Joined: Sep 09, 2006
Posts: 7
Location: Maine

I wanted to learn more about the ADC on my ATMega128, so I dug into the docs and taught myself a couple things. I thought since there wasn't a tutorial on this topic I'd write one. Hope this helps someone out. Comments, clarifications and constructive criticism welcomed!

Newbie's Guide to the AVR ADC
© Ken Worster


No part of this document is to be redistributed without the copyright holder's express permission.

What is an ADC?

An ADC, or Analog to Digital Converter, allows one to convert an analog voltage to a digital value that can be used by a microcontroller. There are many sources of analog signals that one might like to measure. There are analog sensors available that measure temperature, light intensity, distance, position, and force, just to name a few.

Introduction – The AVR ADC

The AVR ADC allows the AVR microcontroller to convert analog voltages to digital values with few to no external parts. The author wrote this tutorial with the ATMega128 in mind, though other AVRs use similar hardware. The ADC built into the ATMega128 is capable of 10 bit resolution. The ATMega128 microcontroller has 8 ADC channels, allowing up to 8 analog sources to be attached to the microcontroller. The 8 ADC channels are connected to the internal DAC through a device called a multiplexer. The multiplexer connects the 8 ADC channels (the 8 pins of Port F on the ATMega128) to the internal ADC. One channel at a time is passed through the multiplexer to the ADC. The ADC has its own power supply, labeled AVCC, on the ATMega128. This pin needs to be connected to a power source within .3 volts of the chip's VCC supply. Most of the time, you would wire this to VCC. With the 10 bit DAC, this allows measuring voltages from 0 to 5 volts with a resolution of 5/1024 volts, or 4.88 mV.

The ADC channels in the ATMega128 can be configured in several different ways. In this tutorial, the channels are being used in “single-ended” mode. In this mode, the analog voltages presented on the ADC channels are compared to ground. There are several selectable voltage references, which determine the range of the ADC conversion. In this tutorial, AVCC is used as the voltage reference. The ADC can also be set to run continuously (the free-running mode) or to do only one conversion. The first example in this tutorial uses the free-running mode to continuously update the ADC reading.

Part 1 – A Simple Free-Running ADC Example

This example uses the simplest variable voltage source I could think of – a potentiometer. I wired up a 10k potentiometer on a breadboard as in the example below.



The two outside terminals were attached to 5 volts and ground, with the center terminal attached to the first ADC channel. The two 330 ohm resistors protect the microcontroller pin from being shorted to ground or to 5 volts at the edges of the potentiometer's travel. With this setup, turning the potentiometer will give you a range of .15 volts to 4.85 volts between ground and ADC0. In order to read the voltage of this circuit, its ground and the ground of the microcontroller need to be connected.

To give an indication of the value the ADC is reading, two LEDs are hooked to the microcontroller. We can toggle these to give us a “high” or “low” indication. Here is the pseudocode for this example:

Code:
Set up output LEDs
Configure ADC Hardware
Enable ADC
Start A2D Conversions

WHILE Forever
   IF ADC Value High, Turn on LED1
   ELSE Turn on LED2
END WHILE


To simplify this example, we will set up the ADC to continuously measure the voltage on ADC0. We will then poll the value in an endless loop and change the LEDs' statuses as we need to. The skeleton code for our example would then be

Code:
#include <avr/io.h>

int main (void)
{
   DDRE |= (1 << 2); // Set LED1 as output
   DDRG |= (1 << 0); // Set LED2 as output

   // TODO:  Configure ADC Hardware
   // TODO: Enable ADC
   // TODO: Start A2D Conversions

for(;;)  // Loop Forever
   {
      // TODO: Test ADC Value and set LEDs
   }
}


Setting up the LEDs is outside the topic of this tutorial, so the code to set them up is shown above without explanation. You can use any unused i/o line for the LEDs. Check out the “Programming 101” tutorial on the AVRFreaks forum for more information on this if you need it.

The next step is to configure the ADC hardware. This is done through setting bits in the control registers for the ADC. First, let's set the prescalar for the ADC. According to the datasheet, this prescalar needs to be set so that the ADC input frequency is between 50 KHz and 200 KHz. The ADC clock is derived from the system clock. With a system frequency of 16 MHz, a prescaler of 128 will result in an ADC frequency of 125 Khz. The prescaling is set by the ADPS bits in the ADCSRA register. According to the datasheet, all three ADPS bits must be set to get the 128 prescaler.

Code:

ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


Next, let's set the ADC reference voltage. This is controlled by the REFS bits in the ADMUX register. The following sets the reference voltage to AVCC.

Code:

ADMUX |= (1 << REFS0);


To set the channel passed through the multiplexer to the ADC, the MUX bits in the ADMUX register need to be set accordingly. Since we are using ADC0 here, which corresponds with all 5 MUX bits being zero, we don't need to set anything here.

In order to put the ADC into free-running mode, set the aptly-named ADFR bit in the ADCSRA register:

Code:

ADCSRA |= (1 << ADFR);


One last settings change will be made to make reading the ADC value simpler. Though the ADC has a resolution of 10 bits, this much information is often not necessary. This 10 bit value is split across two 8 bit registers, ADCH and ADCL. By default, the lowest 8 bits of the ADC value are found in ADCL, with the upper two being the lowest two bits of ADCH. By setting the ADLAR bit in the ADMUX register, we can left align the ADC value. This puts the highest 8 bits of the measurement in the ADCH register, with the rest in the ADCL register. If we then read the ADCH register, we get an 8 bit value that represents our 0 to 5 volt measurement as a number from 0 to 255. We're basically turning our 10 bit ADC measurement into an 8 bit one. Here's the code to set the ADLAR bit:

Code:

ADMUX |= (1 << ADLAR);


That completes the setup of the ADC hardware for this example. Two more bits need to be set before the ADC will start taking measurements. To enable the ADC, set the ADEN bit in ADCSRA:

Code:

ADCSRA |= (1 << ADEN);


To start the ADC measurements, the ADSC bit in ADCSRA needs to be set:

Code:

ADCSRA |= (1 << ADSC);


At this point, the ADC would begin continuously sampling the voltage presented on ADC0. The code to this point would look like this:

Code:

#include <avr/io.h>

int main (void)
{
   DDRE |= (1 << 2); // Set LED1 as output
   DDRG |= (1 << 0); // Set LED2 as output

   ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Set ADC prescalar to 128 - 125KHz sample rate @ 16MHz

   ADMUX |= (1 << REFS0); // Set ADC reference to AVCC
   ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit reading

   // No MUX values needed to be changed to use ADC0

   ADCSRA |= (1 << ADFR);  // Set ADC to Free-Running Mode

   ADCSRA |= (1 << ADEN);  // Enable ADC
   ADCSRA |= (1 << ADSC);  // Start A2D Conversions

   for(;;)  // Loop Forever
   {
      // TODO: Test ADC Value and set LEDs
   }
}


The only thing left to do is test the ADC value and set the LEDs to display a high / low indication. Since the ADC reading in ADCH has a maximum value of 255, a test value of 128 was chosen to determine whether the voltage was high or low. A simple IF/ELSE statement in the FOR loop will allow us to turn the correct LED on:

Code:

if(ADCH < 128)
      {
         PORTE |= (1 << 2); // Turn on LED1
         PORTG &= ~(1 << 0); // Turn off LED2
      }

      else
      {
         PORTE &= ~(1 << 2); // Turn off LED1
         PORTG |= (1 << 0); // Turn on LED2
      }


Again, if the notation used above is unclear, the “Programming 101” tutorial at AVRFreaks forum gives a great explanation.

Here's the finished program with comments. When compiled and downloaded to an ATMega128, LED1 will be lit for roughly half the rotation of the potentiometer, indicating a “low” voltage reading. Near the halfway point of the potentiometer's rotation, LED1 will go out and LED2 will light. Indicating a “high” voltage reading. By changing the tests in the FOR loop, one could get different voltage indications with two or more LEDs.

Code:

#include <avr/io.h>

int main (void)
{
   DDRE |= (1 << 2); // Set LED1 as output
   DDRG |= (1 << 0); // Set LED2 as output

   ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Set ADC prescalar to 128 - 125KHz sample rate @ 16MHz

   ADMUX |= (1 << REFS0); // Set ADC reference to AVCC
   ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit reading

   // No MUX values needed to be changed to use ADC0

   ADCSRA |= (1 << ADFR);  // Set ADC to Free-Running Mode
   ADCSRA |= (1 << ADEN);  // Enable ADC
   ADCSRA |= (1 << ADSC);  // Start A2D Conversions

   for(;;)  // Loop Forever
   {
      if(ADCH < 128)
      {
         PORTE |= (1 << 2); // Turn on LED1
         PORTG &= ~(1 << 0); // Turn off LED2
      }

      else
      {
         PORTE &= ~(1 << 2); // Turn off LED1
         PORTG |= (1 << 0); // Turn on LED2
      }

   }

}


Part 2 – An Interrupt-Driven Example

Let's improve the first example so that we can run the LED IF loop “in the background”. It takes 13 cycles of the ADC clock to perform one A2D conversion in free-running mode according to the datasheet. With a prescaler of 128 as in the previous example, there are 13x128, or 1664, system clock cycles between each A2D conversions. If our short IF loop can be run only after an A2D conversion, this allows considerable processing time to be dedicated to other tasks.

Microcontrollers allow this kind of program execution using something called interrupts. Certain pieces of hardware within the microcontroller can signal that a certain task has been completed. Normal program execution can be “interrupted” when one of these signals (the interrupt) is asserted. Depending on the interrupt signal, different user-defined programs, called interrupt service routines or ISRs, can be run. After the ISR completes, normal program execution resumes.

The AVR ADC has an interrupt associated with it that is asserted when an A2D conversion completes. There are several changes that need to make to the first example to utilize this interrupt. First, let's write the ISR that will be run when the interrupt is asserted.

The first step in using interrupts in our application is to add the standard library header avr/interrupt.h. This file defines functions and macros needed to utilize interrupts on the AVR. The following line should be added below the io.h define in our original program.

Code:

#include <avr/interrupt.h>


Next, we'll define the ISR itself. To do this, we need the name of the interrupt we are connecting to the ISR. Referring to the datasheet, we find the name of the interrupt we want to use – ADC, which is asserted when an A2C conversion completes. Here is the proper format for an ISR using the ADC interrupt:

Code:

ISR(ADC_vect)
{
   // Code to be executed when ISR fires
}


Now, we place the IF statement originally in the infinite loop inside the ISR, so it will only be run when the ADC interrupt indicates a conversion has been completed:

Code:

ISR(ADC_vect)
{
   if(ADCH < 128)
      {
         PORTE |= (1 << 2); // Turn on LED1
         PORTG &= ~(1 << 0); // Turn off LED2
      }

      else
      {
         PORTE &= ~(1 << 2); // Turn off LED1
         PORTG |= (1 << 0); // Turn on LED2
      }    
}


At this point, the program has an ISR defined. However, the ISR will never execute. Interrupts on the AVR need to be enabled before they will run. This is done in two steps. First, the interrupt capability of the microprocessor needs to be enabled. This is done with the sei() function call, defined in interrupt.h to simplify this process. Next, the ADC interrupt needs to be enabled. This is done by setting the ADIE bit in the ADCSRA register. The following two lines enable the ADC interrupt:

Code:

ADCSRA |= (1 << ADIE);
sei();


We can now combine the new interrupt code with our first example. We will insert the ISR after the main loop. The interrupt enable lines will be inserted before we start the A2D conversions. The FOR loop is now empty, as the code we had there originally has been moved to the ISR. Other code could be inserted here to run between ISR calls. The full code is shown below.

Code:

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

int main (void)
{
   DDRE |= (1 << 2); // Set LED1 as output
   DDRG |= (1 << 0); // Set LED2 as output

   ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Set ADC prescaler to 128 - 125KHz sample rate @ 16MHz

   ADMUX |= (1 << REFS0); // Set ADC reference to AVCC
   ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit reading

   // No MUX values needed to be changed to use ADC0

   ADCSRA |= (1 << ADFR);  // Set ADC to Free-Running Mode
   ADCSRA |= (1 << ADEN);  // Enable ADC

   ADCSRA |= (1 << ADIE);  // Enable ADC Interrupt
   sei();   // Enable Global Interrupts

   ADCSRA |= (1 << ADSC);  // Start A2D Conversions

   for(;;)  // Loop Forever
   {
   }
}

ISR(ADC_vect)
{
   if(ADCH < 128)
   {
      PORTE |= (1 << 2); // Turn on LED1
      PORTG &= ~(1 << 0); // Turn off LED2
   }
      
   else
   {
      PORTE &= ~(1 << 2); // Turn off LED1
      PORTG |= (1 << 0); // Turn on LED2
   }
}


Last edited by penquissciguy on Nov 17, 2007 - 05:53 AM; edited 1 time in total
 
 View user's profile Send private message  
Reply with quote Back to top
ttownfire
PostPosted: Nov 16, 2007 - 09:53 PM
Rookie


Joined: Oct 03, 2007
Posts: 37
Location: Tucson, AZ

Awesome! Mind if I add this to the starter guide?

_________________
Michael

Dragon Slayer... no not that one...This one!
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Nov 16, 2007 - 10:00 PM
Moderator


Joined: Jan 23, 2004
Posts: 10218
Location: Melbourne, Australia

Very well done - I do like your writing and example style. It reminds me heavily of my own tutorials, which many people like.

Looking forward to more tutorials from you in the future!

- Dean Twisted Evil

_________________
Make Atmel Studio better with my free extensions. Open source and feedback welcome!
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
penquissciguy
PostPosted: Nov 16, 2007 - 11:01 PM
Newbie


Joined: Sep 09, 2006
Posts: 7
Location: Maine

ttownfire wrote:
Awesome! Mind if I add this to the starter guide?


Please do. Thanks!

abcminiuser wrote:
Very well done - I do like your writing and example style. It reminds me heavily of my own tutorials, which many people like.


Very Happy

I intentionally copied the structure of your tutorials. I really like how yours flow from one topic to the next. Having a common tutorial structure might also help some know where to find certain information in a new tutorial and make code examples easier to follow.

abcminiuser wrote:
Looking forward to more tutorials from you in the future!


Thanks, I appreciate that. I'm hoping to expand this one in the near future.

Ken
 
 View user's profile Send private message  
Reply with quote Back to top
penquissciguy
PostPosted: Nov 17, 2007 - 05:56 AM
Newbie


Joined: Sep 09, 2006
Posts: 7
Location: Maine

I've added a simple interrupt-driven example. Comments welcome!

Ken
 
 View user's profile Send private message  
Reply with quote Back to top
starwarts
PostPosted: Nov 27, 2007 - 09:27 AM
Newbie


Joined: Nov 23, 2007
Posts: 1


I have only starting dabbling with AVR coding.. this tutorial was very useful. Thanks.
 
 View user's profile Send private message  
Reply with quote Back to top
kioanakos
PostPosted: Nov 27, 2007 - 12:51 PM
Newbie


Joined: Jan 28, 2005
Posts: 1


Thanks for your tutorial!

I can't see your images...
Maybe the image hosting service you used is down.
 
 View user's profile Send private message  
Reply with quote Back to top
penquissciguy
PostPosted: Nov 27, 2007 - 07:05 PM
Newbie


Joined: Sep 09, 2006
Posts: 7
Location: Maine

kioanakos wrote:
Thanks for your tutorial!

I can't see your images...
Maybe the image hosting service you used is down.


The image is hosted on my personal webspace, so I think it should be OK. I reloaded the page on my machine and everything seems to be OK.

Ken
 
 View user's profile Send private message  
Reply with quote Back to top
JohanEkdahl
PostPosted: Nov 27, 2007 - 07:27 PM
10k+ Postman


Joined: Mar 27, 2002
Posts: 22030
Location: Lund, Sweden

No dice here. The progress bar in the browser takes a while, and then it fails to load the images, shown by with a small frame with a red X in IE, a dimmed image icon in Firefox. Trying the direct url to one of the images (http://www.intergate.com/~scienceguy/avr/varvolts.png) results in a non-responsive server error.
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
clawson
PostPosted: Nov 27, 2007 - 09:10 PM
10k+ Postman


Joined: Jul 18, 2005
Posts: 71229
Location: (using avr-gcc in) Finchingfield, Essex, England

The only image above is this isn't it?

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
JohanEkdahl
PostPosted: Nov 27, 2007 - 09:29 PM
10k+ Postman


Joined: Mar 27, 2002
Posts: 22030
Location: Lund, Sweden

Well Cliff, either the problem is not global or you have just proved that your cache is working. Still can't see it in the OP.
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
bloody-orc
PostPosted: Nov 27, 2007 - 09:56 PM
Posting Freak


Joined: Dec 17, 2005
Posts: 1602
Location: Europe- Estonia- Tallinn

I can see it in OP post with FF and XP. No problems (And it's my first time here so no cache).

And nice tutorial indeed... At first I though it was Dean, who wrote it, but then I saw his name on the first comment and had to look again and surprise- surprise, it was penquissciguy. hehe Wink
 
 View user's profile Send private message Send e-mail  
Reply with quote Back to top
cgilson33
PostPosted: Dec 07, 2007 - 01:42 AM
Newbie


Joined: Oct 08, 2007
Posts: 19
Location: Omaha, ne, usa

great tutorial very helpful...

i'm actually using a similar functionality in a project. should i turn the ADC off somehow when i'm not using it? if so whats the best way...

i am curious because i am trying to save power. is this a problem?

basic idea of project: user control =
1) remotely turn leds on or off
2) use the adc (via rssi) to control the leds

if the user uses the adc then goes back to manual mode the adc will still be running correct? what about interrupting? is it constantly interrupting if it's turned on (the ADC that is)?
 
 View user's profile Send private message  
Reply with quote Back to top
clawson
PostPosted: Dec 07, 2007 - 10:42 AM
10k+ Postman


Joined: Jul 18, 2005
Posts: 71229
Location: (using avr-gcc in) Finchingfield, Essex, England

Well it's the ADEN bit that turns it on, so clearing that will turn it off.

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
albertob3
PostPosted: Dec 17, 2007 - 11:54 PM
Newbie


Joined: Jan 31, 2007
Posts: 18


Thanks, this tutorial is very usefull.
 
 View user's profile Send private message  
Reply with quote Back to top
MR BLONDE
PostPosted: Dec 19, 2007 - 06:20 PM
Hangaround


Joined: May 13, 2004
Posts: 382
Location: north west uk

Hello

Easy to follow very instructive with well placed useable material.

Thank you
Stephen Wink

_________________
Codevisionavr & Avrstudio 4.18
Easyavr5A-Jtagicemk1

Call me Pedantic, But not after 9.

if Milk_Brilliant
else Codevision_Avrs==Better
 
 View user's profile Send private message  
Reply with quote Back to top
vicky3413
PostPosted: Jan 14, 2008 - 07:11 AM
Wannabe


Joined: Mar 12, 2007
Posts: 54


mega 16 adc chanels are multiplexed.it means at a given time there is only one conversion.
what in case we are using several sensors to guide a robot motion?
 
 View user's profile Send private message  
Reply with quote Back to top
clawson
PostPosted: Jan 14, 2008 - 11:48 AM
10k+ Postman


Joined: Jul 18, 2005
Posts: 71229
Location: (using avr-gcc in) Finchingfield, Essex, England

Does the rate really need to be faster than the "round robin" rate you can achieve using the multiplexer? If so then use a number of Tiny's/small Mega's in parallel and "network" them using I2C or SPI or similar.

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
MakeeK
PostPosted: Jan 22, 2008 - 09:18 PM
Rookie


Joined: Dec 01, 2007
Posts: 45


Thanks for the tutorial. It only destroyed my atmega8 Smile

_________________
I'm a Newbie! ... and it's not my fault!!
 
 View user's profile Send private message  
Reply with quote Back to top
TeonHarasymiv
PostPosted: Jan 25, 2008 - 06:24 AM
Newbie


Joined: Jan 01, 2008
Posts: 2


Thanks for the tutorial! Much appreciated!
 
 View user's profile Send private message  
Reply with quote Back to top
Display posts from previous:     
Jump to:  
All times are GMT + 1 Hour
Post new topic   This topic is locked: you cannot edit posts or make replies.
View previous topic Printable version Log in to check your private messages View next topic
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits