ADC (Analog To Digital Converter) of AVR Microcontroller

Most of the physical quantities around us are continuous. By continuous we mean that the quantity can take any value between two extreme. For example the atmospheric temperature can take any value (within certain range). If an electrical quantity is made to vary directly in proportion to this value (temperature etc)
then what we have is Analogue signal. Now we have we have brought a physical quantity into electrical domain. The electrical quantity in most case is voltage.To bring this quantity into digital domain we have to convert this into digital form. For this a ADC or analog to digital converter is needed. Most modern MCU
including AVRs has an ADC on chip.
An ADC converts an input voltage into a number. An ADC has a resolution. A 10 Bit ADC has a range of 0-1023. (2^10=1024) The ADC also has a Reference voltage(ARef). When input voltage is GND the output is 0 and when input voltage is equal to ARef the output is 1023. So the input range is 0-ARef and digital output is 0-1023.

Fig: ADC Theory

Inbuilt ADC of AVR

Now you know the basics of ADC let us see how we can use the inbuilt ADC of
AVR MCU. The ADC is multiplexed with PORTA that means the ADC channels are shared
with PORTA. The ADC can be operated in single conversion and free running more.
In single conversion mode the ADC does the conversion and then stop. While in
free it is continuously converting. It does a conversion and then start next
conversion immediately after that.

ADC Prescaler.

The ADC needs a clock pulse to do its conversion. This clock generated by system
clock by dividing it to get smaller frequency. The ADC requires a frequency
between 50KHz to 200KHz. At higher frequency the conversion is fast while a
lower frequency the conversion is more accurate. As the system frequency can
be set to any value by the user (using internal or externals oscillators)( In
xBoard™ a 16MHz crystal is used). So the Prescaler is provided to
produces acceptable frequency for ADC from any system clock frequency. System
clock can be divided by 2,4,16,32,64,128 by setting the Prescaler.

ADC Channels

The ADC in ATmega32 has 8 channels that means you can take samples from eight
different terminal. You can connect up to 8 different sensors and get their
values separately.

ADC Registers.

As you know the registers related to any particular peripheral module(like
ADC, Timer, USART etc.) provides the communication link between the CPU and
that peripheral. You configure the ADC according to need using these registers
and you also get the conversion result also using appropriate registers. The
ADC has only four registers.

Using the ADC.

In this sample we will setup and use the ADC in single conversion mode. We
will connect a LDR( light dependent resistor) which is a light sensor to input.
The result will be shown in LCD.

Initialization.

We have to configure the ADC by setting up ADMUX and ADCSRA registers. The
ADMUX has following bits.

ADMUX Register.

REFS1 REFS0 selects the reference voltage. See table below –

REFS1

REFS0

Voltage Reference Selection

0

0

ARef internal Vref Turned off

0

1

AVCC

1

0

Reserved

1

1

Internal 2.56 Voltage Reference

We will go for 2nd option, i.e. Our reference voltage will be Vcc(5v). So we
set

ADMUX=(1<<REFS0);

The ADCSRA Register.

ADEN – Set this to 1 to enable ADC

ADSC – We need to set this to one whenever we
need adc to do a conversion.

ADIF – This is the interrupt bit this is set to
1 by the hardware when conversion is complete. So we can wait till conversion
is complete by polling this bit like

//Wait for conversion to complete
while(!(ADCSRA & (1<<ADIF)));

The loop does nothing while ADIF is set to 0, it exits as soon as ADIF is
set to one, i.e. conversion is complete.

ADPS2-ADPS0 – These selects the Prescaler for
ADC. As I said the ADC frequency must be between 50KHz to 200KHz.

We need to select division factor so as to get a acceptable frequency from
our 16Mhz clock. We select division factor as 128.So ADC clock frequency = 16000000/128
= 125000 = 125KHz (which is in range of 50KHz to 200KHz). So we set ADCSRA as

Hardware

Fig: LDR Connected to ADC of AVR

You have to connect a LDR (light dependant resistor) as shown above. After burning
the code on chip use a light source to throw some light on LDR, the ADC will show
a value between 0-1024 depending on light. For dark the value should be close
to 0 while for bright condition the value will become close to 1000.

Hi
Yes, you can define voltage reference at any time on A/D registers. First time you configure A/D voltage reference on AVCC pin and then read A/D conversion then you configure A/D voltage reference on AREF pin and again read A/D conversion and finally you may like to use inside voltage reference that you can define it on A/D register.

Actually there is only one ADC inside the chip and the Input to it is multiplexed. So setting of different reference voltage for individual pins is not possible. But you can set the Reference voltage of ADC to required before taking the input for the desired pin.

For example set
ref=2.56 before sampling PA1
ref=VCC before sampling PA2
and ref=VCC before sampling PA3

“If the user has a fixed voltage source connected to the AREF pin, the user may not use
the other reference voltage options in the application, as they will be shorted to the
external voltage. If no external voltage is applied to the AREF pin, the user may switch
between AVCC and 2.56V as reference selection. The first ADC conversion result after
switching reference voltage source may be inaccurate, and the user is advised to dis-
card this result.
”

So you can easily switch between internal reference of 2.56 and 5.00 volts. But if you also need a third i.e. Custom Voltage applied to Vref it is not possible unless you have more onchip/off chip ADCs

“it means that only two ref voltages can be used either 2.56v or 5v ………..but what to do if we want any voltage other than those two …”

its simple , see the table in ADMUX register description above . In this example we have gone for 2nd option (bold italics) but to use any other volatge go for first option. Then you can apply any voltage to Aref pin (its pin 21 on mega8) and it will be the reference voltage.

Hi Avinash,
your tutorials are very helpful especially for newbies,my question is how can use your adc test for two channels.What i want to do is to measure voltages from two adc channels say 0 & 1 and display the result on the LCD.
Thank you.

Hi Avinash,
I am reading voltage from six adc channels of atmega32 in round-robin fashion and displaying the voltage on LCD. But I am getting different voltage of all channels in diferent round means I am not getting constant voltage in different rounds. With Voltmeter I checked the voltage at microcontroller pins, it is constant. I am feeding Unity gain opamp output to ADC inputs.
Please tell me the solution.

hi everybody.
i have no good knowledge about ADC conversion. at my projects i just can use only 1 channel ADC on ATmega32, but actually i want to use 4 channel ADC in the same time. please help me how to use 4 channel ADC?

Hello i´m making a voltmeter with assembler, my question is if i want to send to the lcd the value of the voltage instead of the binary number what do i have to do?
I think i have to compare the result of the ADC and then assigned the value that i want in the LCD but i have to compare several values and i think i lost time comparing , or what do you think?

Hello Avinash!
Great tutorials! A question regarding “Fig: LDR Connected to ADC of AVR”. What I see there is 100 kohm resitor going to ground. Is that a standard resitor value for all types of ADC connections? I have seen 10 kohm resitor values as well in schematics. What I try to say is: should it always be a resistor placed (to ground) like that when using AVR adc?

i am connecting a LDR to a PIC. i want to compare the value that the LDR to the preset value. how do i represent this preset value in hex? for example, if the value of LDR is less than 3V, then do the following commands. how to concert the 3V to hex value? thanks.

3v is 60% of 5v (Ref Voltage) so 60% of 1024 (Max Value for 10BIT A/D) is required value. 60% of 1024 is 614.4 = 614 and its HEX equivalent is 266. You must write 0x266 (prefix by 0x) in ‘C’ so that compiler knows it is a HEX and not decimal.

Hey Avinash,
Where do you buy all your electronics stuff in India? I live near Dehradun and for me it seems impossible to get any stuff, no hobby shops around!! Is there any safe online shop which deals with it and do you know any place around Delhi or preferably Lucknow?
I was able to get in touch with ATmega16 there in Lucknow for my surprise!! :p

Hi, I’ve build a filtering ciruit and attached it to the adc, connected to the adc is a resistor, on one side of this i’m getting 2.5v on the micro side im getting 0v. if I remove the micro, this goes up to 2.5v. The signal is at approx 0.2mA is this enough? What do i need to set the DDR to for the adc port? I want to use some of the other portbits as digital inputs. Thanks for any help!

Hi
l am just starting to work with atmega16 controller and got a hang first tutorials and desided to try this one to but then l connect everything together but then l start a program a result l get is always 1023 and l tryed hooking up a pontiometer insted a LRD and turning it but always l get only 1023 is there something l do wrong with my software? maybe with registers? l used exactly a same code as your posted in this tutorial and l got nothing. Connections is ok

good question. I recommend using the other pins of ADC PORT as INPUT only. And use some other I/O port for OUTPUT purpose. Once I tried to do the same thing. One ADC was used as ANALOG IN while the other pins carried medium frequency DIGITAL OUTPUT signal (to switch seven segment displays). In this configuration the ADC result were not constant, it was fluctuating. When I made the other PINs of ADC free and used some other PORts the problem was solved!
🙂
So this info may be useful for you.

thanks for d tutorials… they were very helpful
the atmega32 i am using shows highly fluctuating values even when the input to the pin is constant… wht cud be the problem?
and can we read negative voltages using atmega32 adc?

does this mean that you are asking it to perform ADC on the first three channels, although you only need it from channel 0 because this is where you are reading the result from, so it could really be ch=ch&0b00000001;? Or have I drastically misunderstood this? So if i wanted to read if from channel 7 i could put ch=ch&0b10000000; and then change the last part to adc_result=ReadADC(7);?

this line limits the channel number to a valid value. If you pass ch between 0-7 it remains unchanged but if you pass for example 11 it will be converted to 3. To get how it works you must be familiar with binary numbers and bit wise logical operation using bitwise logical operators like &.

ReadADC(0) just reads channel 0! The ADC can only do one conversion at one time (unless you’ve got a fancy chip with more than one ADC multiplexer on)

One way I did this was I wrote a conversion complete interrupt, which on conversion of a channel, stored it in to a variable (an array to be precise) and then started on the next conversion.

In the main loop, where ever I wanted to read more tha one ADC value, i just read the values out of the array, which were contstantly being updated by the interrupt! You can have my code if you like, just reply and let me know!

Hello Avinash,
your tutorials are really wonderful, they are very easy to understand and implement.
I want to read voltage values from 8 sensors using 8 adc channels on Atmega32 MC and then write these values to an SD card memory continuously, I have all the hardware required.
Can you suggest me an easy way to do this or do you have any tutorials related to this application. please help me out.
thanks.

The LDR tutorial is good and works well too, when I tried it out on the XBoard MINI. The 100K resistor measures low light (tubelight) accurately but is swamped when exposed to sunlight.
I have been told that using a 1K resistor will measure sunlight more accurately. Will there be any problems for the ADC if I were to use a 1K resistor. I am told that ADC can take a max of only 40 milliamps? Thanks.

Hi, I had some serious problems using this code for multiple inputs, but the following substitution fixed the error:
use:
ch=(ADMUX & 0xf0)|(ch& 0x0f);
ADMUX=ch;
instead of:
ch=ch&0b00000111;
ADMUX|=ch;

Thanks! The solution seems to work just fine with multiple inputs.But the thing is I’m unable to find the difference between

ch=(ADMUX & 0xf0)|(ch& 0x0f); //Correct
ADMUX=ch;

(AND)

ch=ch&0b00000111; //Bug
ADMUX|=ch;

could you please elaborate.

Regards

By Smith
-
March 25, 2012 2:47 am

Also Avinash kindly correct the bug in the original code to avoid misleading people.

Regards

By Chupo_cro
-
January 30, 2013 2:13 am

Here’s the explanation:

Imagine you want to read from ADC1 (ch = 0b0001) and afterwards from ADC2 (ch = 0b0010). Because of the bug, 0b0010 would be ORred with the previous value of the MUX3:0 bits (0b0001) so the result would be 0b0011 (ADC3) instead of 0b0010 (ADC2). That’s why you first have to reset the lower 4 bits of the data held by the register (ADMUX&0b00001111) and only then OR with the channel selection value. ch&0b00001111 is as a precaution so ch greater than 15 wouldn’t touch the upper 4 bits of the ADMUX.

By Avinash
-
January 31, 2013 4:59 pm

@Chupo_cro,

Thanks a lot for your time to post a useful comment 🙂

By Chupo_cro
-
January 30, 2013 2:16 am

A correction:

Instead of:
That’s why you first have to reset the lower 4 bits of the data held by the register (ADMUX&0b00001111)…

there should be:
That’s why you first have to reset the lower 4 bits of the data held by the register (ADMUX&0b11110000)…

By Chupo_cro
-
February 1, 2013 1:52 am

@Avinash:

You’re welcome 🙂

If you are able to edit the comments, you may edit my first comment (January 30, 2013 at 2:13 am) and correct the binary value according to the correction in my second comment (January 30, 2013 at 2:16 am). Then you may delete my second comment so it would be easier to read the discussion.

@Avinash, im not sure that that would work. If you divided by 1023, then the answer would be one as it cant deal with decimal fractions unless your using the float data type. If you only need 1% accuracy, why not grab the 7most significant bits from adc times by 100 and then divide by 127. That way you can advoid floats, and your code should be significantly faster (because your advoiding floats, and using smaller numbers!)

I apologize for my ignorance. I wasn’t asking for an arithmetic lesson, just help with the C code which I am a beginner. I was enjoying your website and tutorials until I was ridiculed. What’s the sense of publishing tutorials for beginners if you are clearly offended by a beginner’s questions. Phill, thanks for being open minded and not blinded by ego.

hi, thanks for the great tutorial…..it is really very helpful for beginners like me.
i had a doubt….in your ReadADC routine when you say :
ch=ch&0b00000111;
ADMUX|=ch;
we are also setting the REFS1 and REFS2 to 0…..which means we are no more using AVCC as our reference voltage(which we were supposed to do).
Please correct me if i am going wroing somewhere.

There is a function named LCDInit(LS_BLINK|LS_ULINE) in the code given above but, the actual name of the function in lcd.h supplied by your web-site in LCD usage tutorial has the function InitLCD(LS_BLINK|LS_ULINE).

Please consider changing this and make this to be in sync with that tutorial.

Hi, I’m having a problem regarding with getting the percentage of the adc reading. I am using this equation percent=(adc_result1*100)/1023. I get my percentages right, but the problem is that after 64 percent it jumps to 1 percent again, I was wondering if you could help me figure out the problem.

Hello.I have a problem understanding this line of code:
ADMUX=(1<<REFS0);
I know that "<<" is the bitshift operator. The operation x<<n shifts the value of x left by n bits. If REFS0 is initial 0 then 1 is shifted to the left by 0 positions. So in the right-hand side we have the bit value 1 and this value is assign to ADMUX(a register with 8 bits).Is this possible? I'll appreciate if you could help me figure out this problem.
Thanks.

Sir thank you very much for your great support through these tutorials.Please post tutorials on arduino development boards.
I hope that in future Arduino Uno will be there in your online shop.
Thank you.

these tutorials are great. you are great.
i will be very happy if you add one tutorial for me
how to convert 8bit and 16bit adc value to seven segment
desplay.
or make a complete programe 0 to 30volt 4digit volt-meter.i shall be very thankfull to you.
thanks
mr.rizwan ahmed.
2.04.2012 12pm

What is the material that is used for binding the wires to the LCD display? I have soldered pins to the LCD display…this looks like something else…is it suitable replacement for soldering, where heating the element is not permitted ?

You need to be “master” of bitwise operator, numbering system and patience to read in order to understand that!

So do you know the following?

1)operator &
2)operator |

By masterofdisguise
-
December 19, 2012 12:12 am

@avinash:
sorry for the late reply.. yeah i do understand the operators & and |.These are bitwise and or operators.Actually i understand c but embedded c appears to be bit different may be i m new to this field that’s why?.I want to know why are we masking,the lower order bits for the variable ch(I mean making them one). And how the ADC register ADMUX is able to select the input channel.

sir I used your lcd lib code (that is lcd.c,lcd.h and myutils) with atmega48 to write code on temperature monitor and switch relay connected in portd as well display the value in lcd. the code works very well but my problem is the relay is not switching at any value of adc i want it to switch. so my question is did you use portd in your lcd lib code or the lcd lib code is not for atmega48. please how do I solve this problem

Use your right brain, take some help from the left part too. Then think of what is a LCD and what it shows, then think what is float. Then prepare an algorithm to solve this problem. Then try to bring to life the concept from your brain to this physical world. If it doesn’t work, try to collect the inputs and use them as a feedback to correct the first. Repeat the process until you die or get the solution. Contact me when you have at-least repeated the loop 10 times without success. Understood?

What if i want to display the adc value as a float type like adc/12.5 = 1023/12.5= 81.84 on lcd display.
What would be the code for this purpose???i have tried this code
adc_result=ReadADC(0)/204.6;
sprintf(lcd,”%0.2f”,adc_result);
LCDWriteStringXY(0,0,lcd);

lcd buffer has been also declared before.But the code doesn’t work.Shows garbage data on lcd.Please give a valuable advise of yours.Thanks in advance.

sir i have found a mistake in your ReadADC() function.
suppose i want to first read ADC channel 1 and then read ADC channel 2.

so when i write ReadADC(1);
so ADMUX will be 0b01000001

now if i write ReadADC(2);
ADMUX |=ch;
as u have done OR operation
so ADMUX=ADMUX | ch;
so ADMUX=0b01000001 | 0b00000010;
so ADMUX=0b01000011;
so ADMUX will read Channel 3 not Channel 2;
So i think OR operation is wrong here.

this is a simple program for adc which i have written,working perfectly with atmega16,but when i write the same for atmega8 it doesn’t work…
#include
#define F_CPU 16000000UL
#include
int main()
{
DDRD=0xFF;
PORTD=0×00;
ADMUX=(1<<REFS0);
ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
while(1)
{
ADCSRA|=(1<<ADSC);
while(!(ADCSRA & (1<<ADIF)));
ADCSRA|=(1<<ADIF);
PORTD=ADCW;
_delay_ms(50);
}
}
plz..tell me ,what are the changes needed to be made ???

Dear George,
You cannot get that resolution over the whole range. If you use a 10 bit ADC you have 2^10=1024 different values. The resolution and accuracy depend on the measuring hardware and the signal conditioning before it reaches the ADC on the AVR.

I think a scale is unlikely to be 1g accurate in the 30kg range, because of the typical quality of sensors. Even if you average out many values, the measurement might not be repeatable, i.e. have low precision.

Sir,
using your codes I’ve read many sensors but this time something wrong. I’m working on At-8, two ADC channel using. While I change the value of 5th ADC channel the value of 4th channel is automatically changed, and no vice-versa means there is no change by 4th even not in 4th output. Hardware is correct. What will be the reason . I’ve applied delay.

hai,
I am connecting 2 ldrs to two adc pins,and want to compare the output power of two ldrs.so how should i go with it?
as far as i know the ports read voltage/current of the ldrs.so how do I make it read the resistance and estimate their power?

Hi Chet,
The short answer is: No, you don’t want to do that. 😉 What is the purpose?

The analog pins only measure voltage relative to the reference voltage, not current. Google ADC. To measure a varying voltage due to the LDR, google for example Arduino LDR. You would need a voltage divider or other circuit.

If you really want to measure power you can for example measure the resistance of the LDR using a multimeter under different lighting conditions and under those same conditions you measure your analog input value from a voltage divider. Tabulate those for approximations. Then you know voltage and resistance and can calculate power. Don’t expect to get accurate measurements unless you do it properly (which is too much to explain here).
BR, Thomas H.

hi avinash,
can you tell me what is the maximum input current one can give to adc pin of atmega 16,
and tell the method of scaling down the current of 2amps to the current that can be given as input to adc input of atmega16.
plz help me with this

Hi Dheeraj,
I think you have misunderstood the relationship between voltage, current and resistance. Read up.

The ADC has very high input impedance (resistance), maybe on the order of 100 megaohms, and the current draw of the analog pin will be on the order of nanoamps (ideally 0). The ADC measures voltage, not current.

If you need to measure current you can for example use a shunt resistor or other circuitry. Try googling that. If the shunt resistance is too high you will affect the operation of the circuit.
BR,
Thomas H.

Hi Avinash
please help me
I can not set the rejister adc i want to start adc in timer0 and stop the adc in inttrupt adc and then read adc so store the array . i dont no please help!!!!
EXcuse me sir!!!
I dont have facebook so i cant downloade sample code for adc .i need for help you.
thanks alot

Hi,
Firstly, thank you for an excellent tutorial.
it was of great help in understanding the basics of ADC.
I also need to understand differential adc and how to use gain of adc in atmega 16.
however with almost nothing on the web explaining it,
it is getting really difficult.
it would be of great help if u make a tutorial explaining this.
Thank you.

hi sir.can you help me how to calculate frequency by adc unit of atmega32 ??do you khnow any source about it??I write and debug a schematic and program in code vision to calculate and show voltage of an analog signal but I want to add a part to my program to calculate frequency of signal and show it.thanks alot

I have a problem with STK600 ATMEGA2560 and board A08-0431.A
I need to change the reference voltage for the ADC I need 2.56 and 1.1 the one after the other. When I change it it seems that the 1.1V is never applied. I tried to insert delay between these two succesive measurements and after 2s of delay the 1.1V finally was the reference for my ADC. Does anybody else experienced such a problem?

Hi,
This is really nice tutorial for beginners like me.
Just a small spelling mistake in below line:
“If an electrical quantity is made to vary directly in proportion to this value (temperature etc) then what we have is Analogue signal.”

Actually I have bought the 40 pin avr development board from extreme Electronics store. But I am not getting any reading from the lm35 sensor.Is your development board is suitable for adc conversion or not!? Or I have to make external connection for this AREF pin. .