ADC - Daniel-Git-Hub/ELEC3042 GitHub Wiki

ADC, Analog to Digital Converter

The ATMEGA328P Analog to Digital converter allows us to read analog data (that data where the amount of voltage is variable) the classic example is a potentiometer.

The inbuilt ADC allows for a maximum of 10 bit precision (between 0 and 1023 (inclusive)) but only allows us to convert 1 port at a time (as all analog pins share 1 ADC)

Code can be found: https://github.com/Daniel-Git-Hub/ELEC3042/blob/main/ADC/ADCExample.c

Init

void ADC_init(){

ADMUX  |= 0b01000000; //Set the reference voltage to be our 5V (AVcc)
ADMUX  |= 0b00000011; //Set it to use ADC3 (Analog pin 3);

ADMUX  |= 0b00100000; //For ADLAR mode = 1 (default is 0) (right shifted)

ADCSRA |= 0b00000111; //Set to use 128 clock factor as per calculated

ADCSRA |= 0b10000000; //Enable the ADC

DDRB = 255; //set pins 8-13 as outputs

}

Clock Factor

As per page 249 of the datasheet we know that we need to have the ADC running at between 50kHz and 200kHz (50,000Hz < fadc < 200,000) for it to have maximum precision, we can run it outside of that range if we don't need all 10 bits of precision

as fadc = fosc/N where N is are prescaler and fosc is 16MHz

prescaler fadc
2 8MHz
4 4MHz
8 2MHz
16 1MHz
32 500kHz
64 250kHz
128 125kHz

As can be seen we need to use a clock factor of 128 for maximum precision

Reference Voltage

This is the value that represents a maximum reading (1023) typically we will use AVcc which on the Arduino Uno is shorted to Vcc pin (5V) (from the schematic (and must not vary from Vcc by more then +-0.3V) but we can also use either an internal 1.1V reference or an external reference voltage (connected from the AVref pin on the Arduino Uno)

In all these cases the output to the ADC buffer will be ADC = Vin*1024/Vref (this gives a range of 0 to 1023) if the value is higher** then it will give a reading of 1023

* this can be slightly different value compared to the 5V pin and is slightly more stable but this error margin is completly irrelevant for us and setting to AREF cna be seen as setting to the 5V pin

**This should obviously be avoided as much as possible

Pin select

The 3 least signifcant bits of ADMUX (ADC Multiplexer Selection Register) dictate which analog pin to take the reading from (sa there is only 1 ADC only 1 pin can be read at a time) the table for pins can be seen on page 258. Any change to these bits will only go into effect after the current conversion is complete

note that an Arduino Uno only has access to ADC0 to ADC5 and ADC8 is an internal analog temperature sensor (usefil for detecting if the chip is overheating)

ADLAR Mode 0 or 1, the data representation

IN AVR programming we are able to recieve the data in 2 different modes to select we set/clear the ADLAR (5th bit) in ADMUX

In either case the claculated data (from the ADC conversion) is stored in the ADC (ADC Data Register) The 8 high bits can be retreved from the ADCH register and the low bits from the ADCL register (c allows us to retreve all 16 from the ADC register)

this is from page 259 and 260

Mode ADLAR = 0

ADMUX &= 0b11011111; In this mode the data is right adjusted so the 6 empty bits are on the left The data looks like 0b000000JIHGFEDCBA Where A is the least significant bit and J is the most significant for example a reading of 745 would be stored as "0000 0010 1110 1001"

this form is more useful if we need all 10 bits of precision

Mode ADLAR = 1

ADMUX |= 0b00100000; In this mode the data is left adjusted so the 6 empty bits are on the right The data looks like 0bJIHGFEDCBA000000 Where A is the least significant bit and J is the most significant

for example a reading of 745 would be stored as "1011 1010 0100 0000"

this is useful if we only need 8 bits (0 - 255) of precision as we can just query ADCH and in the use case the formula would be ADCH = Vin*256/Vref

Main

int main(void) {
    serial_init(112500); //can be seen in https://github.com/Daniel-Git-Hub/ELEC3042/wiki/Serial
    ADC_init();
    
    while (1) {
        ADCSRA |= 0b01000000; //sets the ADCS bit to start a conversion
        while(ADCSRA & 0b01000000){;} //waits until the conversion is finished
        serial_transmit(ADCL);   //transmits other serial the result of the low bits then the high bits   
        PORTB = ADCH >> 2; //show 6 most important bits on port B (remeber that we can't use PB7 and PB8)
        serial_transmit(ADCH);
        _delay_ms(1000); //from util/delay, waits 1 second
    }
}

Note you should read the low byte (ADCL) before reading the high byte (ADCH) as ADC Data register isn't updated until the high byte is read (more on page 260). It is also okay just to query the 16 bit register, "ADC", instead.

The ADC Start Conversion bit (6th bit of ADCSRA (ADC Contorl and Status Register A)) is used for starting a conversion as well as telling us when a conversion is finished. Setting this bit starts the conversion and then the chip will automatically set this bit back to 0 when the conversion is finished

Interupts and auto start

⚠️ **GitHub.com Fallback** ⚠️