Ris97
Ris97

Reputation: 111

Unable to use ADC on atmega328

I am trying to make a proximity sensor using ATmega328P. I am using the onboard ADC to convert the voltage value and if it is higher than the ambient, an LED is lit.

The voltage that is being sensed is according to this circuit: enter image description here

In the circuit, the VOUT is going to ADC channel 3 and should be sensed (think of the led on the right as the IR Sensor).

When the program starts, it senses 30 readings and takes their average to be used as the ambient setting. If any subsequent measurement is higher than this value, the LED should be lit.

But the LED does not light even if I place my hand above the sensor.

I have tested with just the LED to see if the IR sensor is ok. It is ok by the way.

The code for the microcontroller is as follows:

/*
 * Proximity Sensor IR.c
 *
 * Created: 6/3/2017 2:35:33 PM
 * Author : Rishav
 */ 

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

#define F_CPU 16000000UL
#include <util/delay.h>

int calibration()
{
    unsigned int sum = 0;

    for (int i=0; i<30; i++)
        {
            ADCSRA |= (1<<ADSC);
            while(!(ADCSRA & (1<<ADIF)));

            ADCSRA |= (1<<ADIF);

            sum += (ADCH<<8)|ADCL;
        }

    return (sum/30);
}

int main(void)
{
    unsigned int val = 0;

    ADMUX |= (0<<REFS1)|(1<<REFS0)|(0<<MUX3)|(0<<MUX2)|(1<<MUX1)|(1<<MUX0);     //setting the multiplexer to ADC3
    ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

    DDRB = 0b00000010;

    DDRD |= (1<<PCINT22);
    PORTD |= (1<<PCINT22);

    int calib_value = calibration();

    while (1) 
    {       
        ADCSRA |= (1<<ADSC);
        while(!(ADCSRA & (1<<ADIF)));

        val = (ADCH<<8)|ADCL;

        ADCSRA |= (1<<ADIF);

        if (val > calib_value)
            PORTB = 0b00000010;
    }
}

I think there is some problem in the code. Please help.

Upvotes: 0

Views: 1881

Answers (2)

Bence Kaulics
Bence Kaulics

Reputation: 7281

You have to enable the ADC first and select channel and reference voltage afterwards. It is easy to skip this fact in the datasheet.

The ADC is enabled by setting the ADC Enable bit, ADEN in ADCSRA. Voltage reference and input channel selections will not go into effect until ADEN is set. Datasheet page 238.

I did not check all of your settings but I am pretty sure that this must be your issue.

Example order:

void init_adc()
{
    ADCSRA |= (1<<ADEN);                            // enable ADC
    ADMUX |= (1<<MUX1) | (1<<MUX0);                 // channel selection ADC3 - PB3
    ADMUX &= ~(1<<REFS0);                           // VCC as reference
    ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // setting prescaler to 128
}

As mentioned a you should read ADCL first to:

ADCL must be read first, then ADCH, to ensure that the content of the Data Registers belongs to the same conversion

I suggest to move this part into a separate function like:

uint16_t read_adc()
{
    ADCSRA |= (1<<ADSC);

    while(!(ADCSRA & (1<<ADIF)));

    uint8_t adcl = ADCL;
    uint8_t adch = ADCH;

    ADCSRA |= (1<<ADIF);

    return (adch<<8) | adcl;
}

Upvotes: 1

tofro
tofro

Reputation: 6073

Some things that come to mind when looking at your code:

  1. You are really not completely initializing the ADMUX and ADCSRA registers - everything you put in there is just 'ORed'-in. (ADLAR in ADMUX is not in a defined state, for example, ADCSRA has even more undefined bits).
  2. After setting the reference voltage source in the ADMUX register, you are supposed to wait for the chip to switch, but don't. Most probably, your first measurement in calibration will be way off. The simplest way to address this is to do one first measurement whose result you simply ignore. (or wait some ms after you have set up ADC).
  3. You are supposed to always read ADCL before ADCH (the AVR locks the ADC for writing further results to the result register when ADCL is read until ADCH is read as well). Your current code has an undefined read order of those 2 registers.

Upvotes: 1

Related Questions