ADC for Atmega32u4

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone.

I have some problem with reading of ADC value from Atemega32u4.
I want to read value from atmega32u4, pin PD6 (#26) which have ADC9.
My Vref is internal, and I don't have connect anything to this pin.

I have written two functions.
- initializeADC9, who just initliaze registers. e.g. as prescaler, mux and control/status register.
- readAnalogADC9, who just starting the conversation and read values from ADC9.

The problem is: I don't get any sensible values at all!
my return value (result) just jumping around, even when I connecting this pin to VCC or GND!

Can anybody see what I doing wrong?

* I using this code at Arduino! Thats why I have access to Serial.println();
* I have already tried with different data registers! ADC, ADCH or ADCL.
* I dont want to use interrupt! I just want to read ADC value when I need it.

Thanks...

int main(void){
  initializeADC9();
  while(1){
    int value =  readAnalogADC9();
    Serial.print("ADC(9) - ");
    Serial.println(value);
    delay(2000);
  }
}

void initializeADC9(){
  //PRESCALER
  //DIVIDING BY 128 (DUE 16KHZ FREQUENCY) 
  ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
  
  //ADC MSB VALUE
  //DATA/VALUE WILL BE THEN 8BIT (NOT 10BIT)
  ADMUX |= _BV(ADLAR);
  
  //ADC internal Vref
  ADMUX = ~_BV(REFS1) | ~_BV(REFS0);
  
  //ADC MUX channel 9 (ADC9)
  ADCSRB |= _BV(MUX5);
  ADMUX |= ~_BV(MUX4) | ~_BV(MUX3) | ~_BV(MUX2) | ~_BV(MUX1) | _BV(MUX0);
  
  //ADC FREE RUNNING
  ADCSRB = ~_BV(ADTS3) | ~_BV(ADTS2) | ~_BV(ADTS1) | ~_BV(ADTS0); 
  
  //ADC ENABLE
  ADCSRA |= _BV(ADEN);
  
  //ADC AUTO-TRIGGER
  ADCSRA |= _BV(ADATE);

  //ADC START CONVERSATION
  ADCSRA |= _BV(ADSC);
}

int readAnalogADC9(){
  //ADC START CONVERSATION
  ADCSRA |= _BV(ADSC);
  while(ADCSRA & _BV(ADSC));
  
  return ADCH;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

perhaps this setting two bits to zero:
//ADC internal Vref
ADMUX = ~_BV(REFS1) | ~_BV(REFS0);

should be:
//ADC internal Vref
ADMUX &= (~_BV(REFS1) | ~_BV(REFS0) );

With the top version, all the bits in ADMUX are cleared to zero. In the bottom version, only REFS1 and REFS0 are cleared, the other bits are unchanged.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If they are both zero, why write code to set them to zero?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To set internal, both bits have to be 11.
This code isn't exactly what you need, it running from Timer, you should mod some settings:

	/* Setup ADC */
        ADMUX  = ((1<<REFS1)|	// Reference Selection Bits
		  (1<<REFS0)|	// 2.56V Internal
		  (0<<ADLAR)|	// ADC Left Adjust Result
	          (1<< MUX4)| 
		  (0<< MUX3)|	// Gain / Channel Selection Bits 
		  (1<< MUX2)|
		  (0<< MUX1)|   // (0) 10100 ADC1<->ADC4 Gain = 1x.		
		  (0<< MUX0));		
	
	ADCSRA = ((1<< ADEN)|	// 1 = ADC Enable
		  (0<< ADSC)|	// 1 = ADC Start Conversion 
		  (1<<ADATE)|	// 1 = ADC Auto Trigger Enable
		  (0<< ADIF)|	// ADC Interrupt Flag
		  (0<< ADIE)|	// ADC Interrupt Enable
		  (1<<ADPS2)|
		  (1<<ADPS1)|	// ADC Prescaler Selects adc sample freq.
		  (1<<ADPS0));

	ADCSRB = ((1<<ADHSM)|	// High Speed mode select
         	  (0<< MUX5)|   // 0 (10100) ADC1<->ADC4 Gain = 1x.
		  (0<<ADTS3)|	
		  (1<<ADTS2)|   // Sets Auto Trigger source Timer/Counter1 Compare Match B
		  (0<<ADTS1)|
		  (1<<ADTS0));

        DIDR0  = ((1<<ADC0D)|   // Turn Off Digital Input Buffer.
                  (1<<ADC1D)|
                  (1<<ADC4D)|
                  (1<<ADC5D)|
                  (1<<ADC6D)|
                  (1<<ADC7D));  
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  ADMUX |= ~_BV(MUX4) | ~_BV(MUX3) | ~_BV(MUX2) | ~_BV(MUX1) | _BV(MUX0);

An "interesting" line. What do you think it achieves? If the plan was, as I guess, to clear those mux bits I think you'll get more mileage from &= than |= ;-)

(BTW you go overboard with the use of |=, use = when you should use = and |= when you should use |=, don't just try to use |= all the time!).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I think you'll get more mileage from &= than |=
But not much more mileage as the result would still be wrong. @arain88, you need to read the Programming 101 thread in the Tutorials section.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone.

Thanks a lot.

The was two problem with my code!
- Yes. I do a lot of wrong bitwise operations! So I fixed that.
- Yes. Internal Vref, was wrong too! both REFS1 and REFS0 have to be high!

Here is the final code, and it works pretty fine!
Just to mention, I have hardcode ADC9 into this code!
If you want to use different ADC, you have to change MUX5, MUX4, MUX3, MUX2, MUX1 and MUX0, in order to your atmega datasheet.

void initializeADC(){
  ADCSRA  = ((1<<ADEN) | (0<<ADSC) | (1<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0));
  ADCSRB &= ((~(1<<ADTS3)) | (~(1<<ADTS2)) | (~(1<<ADTS1)) | (~(1<<ADTS0)));
  ADCSRB |= (1<<MUX5);
  ADMUX   = ((1<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (0<<MUX4) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (1<<MUX0));
}

int readAnalogADC(){
  uint8_t low, high;
  //ADC START CONVERSATION
  ADCSRA |= (1<<ADSC);
  
  //TO GET 10-bit resolution
  low  = ADCL;
  high = ADCH;
  return (high << 8) | low;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can read hi and low in one line with return ADC. You can selectchannel n with ADMUX = 0x40+n; Don't forget to add the line that waits for ADSC to go lo before reading the data.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
You can read hi and low in one line with return ADC. You can selectchannel n with ADMUX = 0x40+n; Don't forget to add the line that waits for ADSC to go lo before reading the data.

Hi.
Can you explain the "waiting for ADSC" a little bit more!
I tried with

while(ADCSRA & (1<<ADSC));

but I just getting stock and dont get anything out anymore!

When I remove it, I get at least values..

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

By "getting stock" I presume you mean "getting stuck"? How are you determining this? As long as the ADC is enabled (ADEN) and the prescaler is set then some time after setting ADSC it will clear.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I connect a pot from 5V to gnd, run the wiper to the a/d input. Read the a/d and write the result out the serial port followed by a carriage return. Turn the pot from ccw to cw. Number should increase from 0 to 1023. You get this result?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes. I getting stuck when I used this line:

while(ADCSRA & (1<<ADSC));

But after some research here, I found out its because I have enabled "free running" mode. So I just changed it and enabled "Analog Comparator" to disable "free running".
So the code being executed.
I guess it works pretty good.

Here is the final code..
Any though about it?

void initializeADC(){ 
  ADCSRA  = ((1<<ADEN) | (0<<ADSC) | (1<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0));
  ADCSRB &= ((~(1<<ADTS3)) | (~(1<<ADTS2)) | (~(1<<ADTS1)));
  ADCSRB |=  ((1<<ADTS0) | (1<<MUX5));
  ADMUX   = ((1<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (0<<MUX4) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (1<<MUX0));

} 

int readAnalogADCValue(){
  uint8_t low, high;
  //ADC START CONVERSATION
  ADCSRA |= (1<<ADSC);
  while(ADCSRA & (1<<ADSC));
  //TO GET 10-bit resolution
  low  = ADCL;
  high = ADCH;
  return (high << 8) | low;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Didn't like my suggestion to read the result with one line? [return ADC;]

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
Didn't like my suggestion to read the result with one line? [return ADC;]

Your suggestion was very good and I appreciate it.
How I return ADC value is just inspired from Arduino analogRead() function,
but you have right. It’s the same actually.

But I have one question about this line:

ADMUX = 0x40+n;

Can you explain it a little bit more?
And one other consideration, the fifth bit have to be stores at ADCS!
And this line calculates just for the ADMUX register.
How is that possible with this line?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Can you explain it a little bit more?

Well Bob's suggestion does not actually deliver what you were asking for so can probably be ignored but if you want an explanation: if you cannot picture 0x40 immediately in your mind actually write it out in binary: 0b01000000. So that's bit 6 set. Therefore in the line:

ADMUX = 0x40 + n;

it means set bit 6 and also set whatever value "n" is. I'm guessing n is going to be a 0..7 channel selection number. So say n=3 this will be:

ADMUX = 0x43;

Well 3 sets the MUX0..MUX4 bits to select channel 3 and bit 6 set is "REFS0". So if you look at the 16/32U4 datasheet and read table 24-3 you will see that ADMUX with REFS1=0 and REFS0=1 selects "AVCC with external capacitor on AREF pin". So the code is setting channel 3 and selecting AVcc as the ADC reference.

Quote:
How is that possible with this line?

If, as you said in the original post you are trying to read ADC9 then you are right there needs to be writes to both ADMUX (MUX4..0) and ADCSRB (MUX5). Personally I'd write that (assuming it is Avcc you want a reference) as:

ADMUX = (0b01 << REFS0) | (1 << MUX0);
ADCSRB |= (1 << MUX5);

EDIT: Actually you said "Vref Internal" so the ADMUX line becomes:

ADMUX = (0b11 << REFS0) | (1 << MUX0);
ADCSRB |= (1 << MUX5);

which is:

ADMUX = 0xC0 | 0x01;
ADCSRB |= 0x20;

In conclusion I think I'd write your code as:

void initializeADC(){
  // Enable, slowest prescale (/128), Internal Ref, channel 9
  ADCSRA  = (1 << ADEN) | (0b111 << ADPS0);
  ADCSRB =  (1 << MUX5);
  ADMUX   = (0b11 << REFS0) | (1 << MUX0));
} 

int readAnalogADCValue(){
  //ADC start conversion
  ADCSRA |= (1<<ADSC);
  // then wait for completion
  while (ADCSRA & (1 << ADSC));
  //return 10-bit result
  return ADC;
} 

personally I don't see much merit in (0 << n) to document the bits you aren't setting - does the maintainer reading this really care? (for me it just obfuscates what you ARE setting).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree the function that reads any of 16 a/d channels has a couple more lines than the function that just reads channels 0-7. There always seems to be a desire to make thinks smaller/faster once we get them to do anything sensible. I was just pointing out that the compiler will generate an instruction that will read both bytes from the a/d data register, thus saving 2 bytes of flash and maybe one or two clock cycles if a 16th of a usec each. Smaller! Faster!

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I was just pointing out that the compiler will generate an instruction that will read both bytes from the a/d data register, thus saving 2 bytes of flash and maybe one or two clock cycles if a 16th of a usec each.

Wrong, Bob, wrong. No flash or time savings.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Shucks. Then does it save one line on the printout every time it gets printed? Might save one cluster on the hard disk when the c source gets saved? So not much of a savings at all then huh?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you everyone (specially bobgardner, clawson and theusch) for the explanation.
Appreciate it...