Table of Contents
Introduction to Analog To Digital Conversion (ADC)
Microcontrollers as we know of today are digital devices that is they only understand digital signals but our world is not digital and produces many analog signals as well. Here comes the role of ADC. Analog to Digital converter as the name suggests is used to convert any analog signal to digital so that microcontroller can read that signal. AVR microcontroller have inbuilt ADC on all bits of PORTA.
In this blog we will be discussing about all the registers and bits necessary to use ADC on AVR.
ADC on AVR
Now let us use this ADC of AVR and convert analog signals to digital and read them.
Block Diagram for using ADC on AVR:
Let us discuss these steps in detail to understand use of ADC in AVR
STEP – I : Basic Setup
1) Setting up pre-scalar for ADC frequency
We know that digital signal are at fixed moment or instances of time while analog are continuous in time. So when we convert analog signal to digital signal we need to decide that on how many instances of time we need to take value of signal so that we can create a digital counterpart of signal.
In the image above the green line depicts analog signal and blue lines make up digital signal. Now we need to decide that for ADC how many blue lines we need. The no. of blue lines in a second is called sampling frequency. AVR has a counter which counts from 0 till 65536 and turns back to 0 and counts again. We will tell AVR that every time you count 16 times then put 1 blue line of digital signal according to analog signal. This thing is known as pre-scalar. If we set up a pre-scalar of 8 means we say to AVR that after every 8 times you count add 1 reading of digital signal. Preferred and generally used pre-scalar value is 16.
ADCSRA i.e. ADC Control and Status Register A has bits 2,1,0 which decide the prescalar for ADC. You can refer table below which is taken from datasheet to select value for ADC pre-scalar:
Lets see code for setting 16 bit pre-scalar. For 16 bit pre-scalar we need to only set bit ADPS2, so we will set 1 to ADPS2 bit in ADCSRA register.
ADCSRA |= (1<<ADPS2);
2) Setting up resolution for ADC
Resolution is nothing but a technical term for maximum value that we will be taking up as digital value. As we convert analog signal to digital we convert it to numbers where a number will represent the maximum analog input value i.e. if we set max input to 5v then digital value of all inputs equal to greater than 5v will be that number. Similarly a number is assigned to minimum value and the range between those numbers represent the values between minimum to maximum input voltage set.
Now 8bit resolution means 0-255. The question comes up how? It has a simple answers. 8 bit resolution means that the converted value will have 8 binary digits. So now minimum value of 8 digit binary number is 00000000 which is 0 in decimal and highest value is 11111111 which is 255 in decimal hence the 8 bit range is 0 – 255. Here 255 represent 5V (Maximum voltage input) and 0 represents 0V. This makes up 256 digits for 0 volt hence we can calculate:
5V/256 = 0.0195V
This implies that every number represents 0.0195 V. So 1 in digital value is equal to 1*0.0195V = 0.0195V. Similarly 25 means 25*0.0195V = 0.4875 V and so on…
Similarly 10 bit has 10 digits so range becomes from 0(0000000000) – 1023(1111111111). And now if we select maximum output voltage to be 5V then 1023 means 5V but for every count precision increases, so:
5V/1024 = 0.00488V
Hence every count represent 0.00488V and as shown above now 25 count will mean 25*0.00488 = 0.122V.
Enough theory!!! Now lets see how to write code and select the resolution (range) we need for our ADC. AVR has a 10bit ADC. We can obtain both 8 bit values and 10 bit values from ADC for AVR. To set resolution we need to use ADMUX register of our microcontroller. ADMUX stand for ADC Multiplexer selection register.
For 8 bit mode, set ADLAR bit of ADC to 1.
ADMUX |= (1<<ADLAR);
For 10 bit mode, clear ADLAR bit i.e. make it 0.
ADMUX &= ~(1<<ADLAR);
3) Setting up ADC reference voltage
Reference voltage is the voltage level which will be considered as maximum voltage by our ADC and at any voltage level equal to or greater than reference voltage, we will get the maximum value after conversion. The maximum value that we discussed above is same ad known as reference voltage as ADC takes this voltage as reference to work.
Reference voltage can be set using ADMUX register. Bit 7 and bit 6 decide the reference that we are going to use according to the following table fetched from the datasheet.
a) AREF(00) – We give the reference voltage to AREF pin, i.e. PIN 32.
b) AVCC(01) – The reference voltage is considered as power connected to VCC. AVCC is Analog VCC which is optional to give for better ADC functionality. If you leave it empty then also nothing happens.
It also suggest to add an external capacitor between AREF and GND so that there is no noise in ADC due to AREF pin.
c) Internal 2.56V(11) – This provides a fixed internal reference voltage of 2.56V to ADC. It also has suggestion of adding a capacitor in similar way as above so that there is no noise from AREF Pin.
For AREF mode, clear REFS0 and REFS1 i.e. make them both 0
ADMUX &= ~(1<<REFS0); ADMUX &= ~(1<<REFS1);
For AVCC mode, set REFS0
ADMUX |= (1<<REFS0);
For Internal 2.56V, set both REFS0 and REFS1 i.e. make them both 1
ADMUX |= (1<<REFS0);
ADMUX |= (1<<REFS1);
STEP – II : INTERRUPT ENABLE
First let us understand basically what are interrupts. Interrupts concept is generally particular to embedded systems. Basically our main code will run as usual but when we setup an interrupt then there will be some point of trigger or initiation that microcontroller will keep checking while running the main code normally. At the moment that it gets the trigger then it leaves everything and enters to a function called ISR which has an argument in it. This argument is called a vector. After entering the function it does all the interrupts of function and go back to the function from where it left off the code before entering in ISR.
Vector or the argument usually defines the trigger in a way that if following trigger has happened then enter this ISR function and hence there can be multiple ISR function defined with different vectors which work on their particular trigger only.
Vectors are in a predefined list and cannot be user defined in AVR. Following is the list of vectors (This whole list is not necessary to be understood for ADC and hence you can skip reading it if you want. Important segments are discussed below in setting up ISR segment):
1) Enabling global interrupt
To use any kind of interrupt in AVR we first need to tell AVR that we are going to use Interrupts. For this:
a) First we will include interrupts library
b) Second we use following function to enable global interrupts. This function should be called before any interrupt is triggered so that there are no chances of error. Generally it does not matter as such but it’s a good practice to define it before using or enabling any interrupt.
2) Enabling ADC Interrupt
Now we need to enable ADC interrupt so that our AVR keeps tracking our ADC and the moment ADC conversion gets complete it enters into ISR function defined for ADC and give us the value obtained.
For enabling ADC in AVR, we need to use ADCSRA register and set bit 3
ADCSRA |= (1<<ADIE);
3) ISR function
Now, one of the most important section. ISR function, function which will be triggered after ADC conversion gets completed. First of all, vector(argument) that we need. We will nw lookup the table mentioned above. As you can notice point no. 17 says ADC and tells that this vector is used for ISR which needs to be triggered when “ADC Conversion in Complete”.
So now we know the vector we need to use. Now to use a vector there is a rule i.e. any space in vector name is replaced by underscore (‘_’) and after vector is ended, we add ‘_vect’ in the end. For eg: for ADC, Source is written as ADC so the name of vector will be ‘ADC_vect’.
Now lets discuss what will come inside the function.
First we need to understand that size of 1 register in AVR is 8 bits hence it has bits 0-7. If any number is less than or equal to 8 bits it can be directly stored in 1 register and retrieved without any problem but if a number is bigger than 8 bit then we need to use 2 register and perform some operation to combine the result stored in 2 different registers and get data.
In AVR we have 2 registers in which result of ADC Conversion is stored after conversion is complete. ADCH and ADCL. Now lets discuss how to obtain data properly in AVR.
Here comes the role of setting resolution while using ADC that we studied in 2nd section of BASIC SETUP. If we select the 8 bit mode (ADLAR = 1) then whole data will be directly stored in ADCH register as follows.
Two values i.e. ADC1 and ADC0 shown in above image are least significant bits and can be discarded. We can directly assign value to any variable as follows:
uint8_t result = ADCH;
**Here uint8_t is a datatype which is an integer only but has memory only enough to store an 8 bit number and hence used to save wastage of memory. We can also use int instead of uint8_t but that is just wastage of memory.
If we select 10 bit mode (ADLAR = 0), then data would be stored as follows.
Now ADC9 and ADC8 which are Most significant bits are stored in ADCH and hence need to be added to result to get correct value and also these 2 bits become necessary as we need a 10 bit result. So first we will store value of ADCL in a variable and then left shift value of ADCH 8 times so that ADC8 and ADC9 come in front of ADC7 and then we will store them in a variable. After doing left shift data will be visible as follow:
Hence creating 10 bit data in correct order. We can assign value as follow:
int result = (ADCL) | (ADCH<<8);
**Here we have used int because 10 bit data wont fit in 8 bit data type and there is no particular data type for 10 bit data. We could also use uint16_t to save memory wastage.
Here we can see that first ADCL’s data is taken and ADCH data is shifted 8 bit and added (by using OR operation) to create the required and correct data and stored in variable. Here we can also use a global variable to use this value in program further.
*ISR stands for Interrupt Service Routine
STEP -III : PIN SELECTION
1) Selecting ADC Pin
Now we need to select pin on which we will connect sensor or analog signal to be taken as input. This can be simply done using ADMUX register. We can use bit 4, bit 3, bit 2, bit 1 and bit 0 to make 5 bit no. and hence used to select from pin 0-7 of PORT A as input i.e. PIN 33 to PIN 40. For selecting 7 pins we need only 3 bits but AVR also has options for ADC with gain and differential ADC inputs which can be used, however they are a concept to be dealt in different blog.
Currently we will only focus on bit 2, bit 1 and bit 0 and bit 4, bit 3 will be set to 0 to select simple ADC. Following table shows the value of bits in order bit4 , bit3 , bit2, bit1 , bit0 to select a pin on AVR:
Here ADC0 means pin0 of PORTA and so on..
Now, for example, for selecting pin 5 as input we need to set bit 2 and bit 0 so we will write following:
ADMUX |= (1<<MUX2)|(1<<MUX0);
STEP – IV : TURN ON ADC AND GET VALUES
1) Turning ON ADC
Now we will turn on ADC of AVR to tell that ADC is active on AVR. We will use ADCSRA register to enable ADC on AVR which is bit 7 of ADC.
ADCSRA |= (1<<ADEN);
2) Starting Conversion
Now we will command ADC to start taking in analog input and do conversion. Earlier step just turned ADC on. This step will start conversion of the analog input received at selected PIN.
This can be done using ADCSRA register by setting ADSC i.e. bit 6 of ADCSRA.
ADCSRA |= (1<<ADSC);
We can also use this statement in end of our ISR that is interrupt function so that as soon as we get the value of current conversion, next conversion will start immediately.
PHEWWWW!!!! We are now done with using ADC on AVR 🎉🎉
We have now completed basics of using ADC on AVR and fully equipped with code as well. Always remember to use the above mentioned flow so that you never forget any setting needed to use ADC.