Problem using ADMUX to switch Vrefs on the fly

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

I'm having trouble using ADMUX on ATMega644 to change the ADC reference voltage on the fly in my code. I have read the documentation many, many times... and think I am doing everything right, but apparently not.

I do not have any external voltage hooked up to AREF, only two capacitors in parallel. I would like to use AVCC for a couple conversions, but then the internal 2.56V reference for another. As long as I do not try switching on the fly, I can use either reference successfully with my code that sets ADMUX appropriately... but whenever I try switching all I get is the AVCC as the VREF, and it's as if the line where I set ADMUX for the 2.56V ref is completely ignored. I tried following all of the directions in the datasheet, such as taking one conversion and throwing it out after switching voltage references, have tried writing to ADMUX to set the 2.56V ref multiple times in a row, tried making several A/D conversions after that (instead of just one) and they all give a result as if the AREF is still AVCC. It's only when I comment out the line where I set ADMUX for AVCC up above that I can get the 2.56V ref to work, however then my conversions which need the AVCC as the ref are not working properly.

If someone could look over these code segments and see if you can spot anything I'm doing wrong, I would greatly appreciate it.....

Code Segments

#define SetBit(x,bit)			((x) |= (1<<bit))
#define ClrBit(x,bit)			((x) &= ~(1<<bit))

// ADC Channel Select
void adc_chsel(uint8_t channel)
{
	//   (admux & 11100000) | (channel & 00000111)
	ADMUX = (ADMUX & 0xe0) | (channel & 0x07);
}


// Start Conversion & Read Specified A/D Channel N Times Averaging Result
float ADCReadChannelNTimes(uint8_t channel, char n)
{
	float sum=0;
	
	// Select Channel
	adc_chsel(channel);

	// Start A/D Conversion <~ 1st one is discarded
	ADCSRA |= _BV(ADSC);
	
	// Wait for Conversion Complete
	while (bit_is_set(ADCSRA,ADSC));  

	// Sample 'n' Times and Return the Average
	for (int i = 0; i < n; i++)
	{
		// Start A/D Conversion
		ADCSRA |= _BV(ADSC);
		
		// Wait for Conversion Complete
		while (bit_is_set(ADCSRA,ADSC));  
		
		// Add ADC Result to Sum
		sum += ADC;
	}
	
	// Return the Result
	return (sum/(float)n);
}


...

void main(void)
{
   while (1)
   {   
        ...
   
	// Set AVCC as Ref (Seems to override the 2.56V request further down) 
	ClrBit(ADMUX,REFS1);
	SetBit(ADMUX,REFS0);

	// Sample Gyro Rates
	Gyros.YawRateCounts = ADCReadChannelNTimes(0,3);
	Gyros.YawRefVoltageCnts = ADCReadChannelNTimes(1,3);

	// Set 2.56V as ref (only works if above AVCC lines commented out)
	SetBit(ADMUX,REFS1);
	SetBit(ADMUX,REFS0);	
	
	// Sample Battery Voltage
	BatteryVoltage = ADCReadChannelNTimes(4,3) * (2.56 / 1024.0);

      ...

   }
}

Thanks,
James

Last Edited: Sat. Jul 14, 2007 - 12:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I made some edits on my original post trying to clarify the problem I'm having.... And just so you don't have to look up the Mega644 documentation, here are the lines related to setting the ADMUX register to change the ref voltage:

[REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0] - ADMUX

-----------------------------------------------
REFS1 REFS0 Voltage Reference Selection
0 0 AREF, Internal Vref turned off
0 1 AVCC with external capacitor at AREF pin
1 0 Internal 1.1V Voltage Reference with external capacitor at AREF pin
1 1 Internal 2.56V Voltage Reference with external capacitor at AREF pin
----------------------------------------------

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

What value capacitor do you have on the AREF pin? You have to allow enough time for this capacitor to charge/discharge to the new reference voltage.

If you want faster performance and less code size, don't use floating point! From what I can see in your code. integer calcs are perfectly adequate.

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

One is a 0.1 uF, and the other is 20 pF. I'm sampling with the A/D's in a 50 hz loop.

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

I don't imediately see your problem, but consider...

void main(void) {

   ...

   SetBit(ADMUX,REFS0); 

   ...

   while (1) {

        ... 
    
        // Set AVCC as Ref (Seems to override the 2.56V request further down) 
        ClrBit(ADMUX,REFS1); 

        // Sample Gyro Rates 
        Gyros.YawRateCounts = ADCReadChannelNTimes(0,3); 
        Gyros.YawRefVoltageCnts = ADCReadChannelNTimes(1,3); 

        // Set 2.56V as ref (only works if above AVCC lines commented out) 
        SetBit(ADMUX,REFS1); 
    
        // Sample Battery Voltage 
        BatteryVoltage = ADCReadChannelNTimes(4,3) * (2.56 / 1024.0); 

        ... 

   } 
}

You might want to show us your ADCReadChannelNTimes(X,Y) function. I suspect their might be a possible issue there.

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

Carl... its right there with the for loop.... James... its a stumper isnt it? Try changing the 2 setbits to select the ref to just one write to the mux like ADMUX = 3; Fire for effect.

Imagecraft compiler user

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

Bob, I just don't see it!

I just got up. The sleep schedule is all messed up - working 6:30PM to 6:30AM for the next 4 weeks, then I swith for 4 weeks. It alternates every 4 weeks. It's hell on the body and mind...

I'm thinking the OP isn't guarding (masking) ADMUX properly within the ADCReadChannelNTimes(X,Y) function. I had this problem last week, while writing a 4 bit HD44780 text based LCD initialization function.

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

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

Bob, I had tried that also.... using a line like:

ADMUX |= 0b11000000;

in place of:

	SetBit(ADMUX,REFS1);
	SetBit(ADMUX,REFS0);	

I'm starting to think maybe it's what Kartman said... the capacitors at AREF are trying to stabilize the voltage at 5V when the 2.56V internal ref is momentarily hooked up instead. And then on the next 50hz cycle, the 5V is connected again, so the capacitors are probably filtering the two switching voltages into one average voltage? Because I get the 2.56V ref just fine if I do not try using the 5V, and I get the 5V ref just fine... well, regardless of whether I try using the 2.56V or not.

I guess everything is a compromise! You need capacitors on AREF to stabilize your voltage reference, but then you lose the ability to rapidly switch between two different voltage references on the fly...?

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

So the theory is 10 bits at 1.1V is like 11 bits at 2.56V is like 12 bits at 5V... so (disregarding signal to noise for a moment), one should get best voltage resolution at 1.1V ref... about 1mv..... compared to 5mv at 5V... cool trick. I suppose you can read all channels just fine if you leave one ref selected and just switch the mux channel, but you are trying to get the extra res on the lo ref?

Imagecraft compiler user

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

I have sensors that output voltages in the 0-5V range that I needed the AVCC for. I was only wanting to use the 2.56V ref to accurately measure battery voltage, i.e. bypassing my 5V regulator and using a voltage divider to drop the battery voltage down into the 0-2.56V range. Since the output of the 5V regulator (and hence AVCC) starts to droop when the battery supply voltage approaches 5V or lower... my calculation of battery voltage would be inaccurate which assumes AVCC is a constant 5V. I was experimenting with this before and noticed that you need to use the 2.56V ref for such a purpose, since it's the only voltage you can count on staying constant as battery voltage starts dropping. Using AVCC would work fine for battery voltages > 5.2V or so... but as the battery voltage drops and your AVCC becomes 4.9V, the calculated battery voltage is too high and you never discover that your voltage is really dropping. Anyway, that was the motivation... I'm at the point of giving up at this point though. I was first thinking... well, I'll just use AVCC to measure battery voltage then, but what's the point if the accuracy decreases proportionally with the battery voltage drop... giving higher & higher readings making you think the voltage is not really dropping once the output of the 5V reg goes below 5? Any ideas?

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

There was a thread on this a while back, with some numbers on how long it took to change reference and have the value at the pin reflect the new value. With your two capacitors it may take quite some time (i.e., milliseconds) to get those all chaged up. Maybe set up a test program that switches every (say) 1 seconds, and watch the pin on a 'scope. I'd think there might be two situations: the charge time when going higher, and the discharge time when going lower.

Lee

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

Okay... I just thought of an alternate solution.

I have a 3.3V regulator on my board which I can sample with another A/D channel and use the A/D result as my "volts per count" conversion factor. So instead of just hard-coding the conversion factor with "5/1024" for instance, I'll have "3.3/X" where X is the sampled result from the output of the 3.3V regulator. I just want an accurate measure of what the battery voltage is even when the 5V regulator can no longer sustain a 5V level... since that's the time you'd most care about knowing anyway! And this way, I can just leave AREF set at AVCC for all time.

Thanks for the comments Bob, Lee, Carl, & Kartman...

Sincerely,
James

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

I've always (which means twice) measured the AVR supply voltage by selecting VCC as AREF and setting the ADMUX to sample the 2.56V reference.

So basically, adcresult = 1024*2.56/VCC (5V in my case), so flipping that around makes VCC = 1024*2.56/adcresult, and I think I used uint32_t vcc=1024*2.56*1000/adcresult, so I got the result in millivolts.

This has the drawback that the reference is not always exactly 2.56 volts, but varies a bit between different AVRs, so if you are going to use that by masses, the constant needs to be calibrated according to the internal reference voltage.

- Jani

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

Here is my post on the subject from about a year ago.with some measured timing info.....

https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=40698&highlight=

Hoyt

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

Quote:
by selecting VCC as AREF and setting the ADMUX to sample the 2.56V reference.

How do you sample the 2.56V reference? Do you have to physically wire a connection from some pin into an ADC channel or is this all done in code somehow? I hadn't read about such a capability anywhere in the datasheet so I'd love to know if this is really possible.

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

644 is a newer avr with more stuff.... a/ds grow more features as the avr model number gets bigger.. I noticed some stuff in the 128 isnt in the 16

Imagecraft compiler user

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

jdowns wrote:

Quote:
How do you sample the 2.56V reference?
Not much to it. Select the reference in the ADC-register, and measure the voltage on the Vref-pin. So it is available at a pin, but if you wish to use in some external circuitry, you need to buffer Vref, usually with an Opamp.

Nard

A GIF is worth a thousend words   They are called Rosa, Sylvia, Tricia, and Ulyana. You can find them https://www.linuxmint.com/

Dragon broken ? http://aplomb.nl/TechStuff/Dragon/Dragon.html for how-to-fix tips

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

Quote:
Select the reference in the ADC-register, and measure the voltage on the Vref-pin.
If you set the reference to 2.56V and then attempted to measure the voltage on the Vref pin, wouldn't you just get 1023 by definition? To get anything other than a saturated measurement, I'd assume you'd have to set the reference to AVCC... but then where is the 2.56V pin to measure? I don't get it...

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

I presume previous talk about measuring 2.56V reference was not with the ADC, but with a multimeter or a scope.

But if one selects AVCC as reference, and uses the mux to select 2.56V as the ADC input, then it can be measured with the ADC. It seems that not all AVRs can do this, but at least on mega8 this is possible.

- Jani

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

Quote:

How do you sample the 2.56V reference? Do you have to physically wire a connection from some pin into an ADC channel or is this all done in code somehow?

No need to wire anything. It's all code.

You can sample the 1.1V reference. It's at the bottom of the channel selection table on page 249 of the mega644 datasheet. Use AVCC as reference and calculate it based on conversion result.

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

Thank you!!!!

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

This is an issue that appears to affect all ATmegas....

As pointed out to me by Andreas of avr@atmel.com:

The latest '128 page 236 data sheet says:

"If no external voltage is applied to the AREF pin, the user may switch between AVCC and 2.56 V as reference selection. The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result."

He also added, "This is without a capacitor though, so it may be inaccurate a bit longer when adding the capacitor."

Since the data sheet recommends adding the external capacitor, it should probably elaborate on how long it will take after switching AREF to get valid results from ADC.

We have a 100nF cap on AREF, and it appears that when we switch from 2.56V to AVCC (3.2V) the first sample is correct, however when we switch the other direction it is not. Furthermore, throwing away one sample doesn't appear to be sufficient.

The current fix we have implemented (which appears to work) is to put a fixed delay between the time we enable the ADC and the time we start conversion. I believe this is equivalent to throwing away the first sample(s). The delay was empirically determined using a scope to watch AREF, and by observing the ADC results with a fixed input while varying the delay.

An alternative, I suppose, would be to change the reference, select the 1.23VBG as the input channel, and sample it until the ADC result settled down around the expected value for the reference selected @ ~1.23VBG.

Since this issue affects the entire AVR family, I believe an applications note would be very helpful.

David Howard

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

I don't think any of that is very new. IIRC "discard the first sample after AREF change" has been there since the first AREF selections were available (Mega8? Mega163/323?).

After that it pretty much makes sense. Since the internal signal has some "drive" then it is going to charge your 100nF fairly quickly. Since it doesn't use much it takes a while to draw it down.

It certainly does get more complicated as the newer generations come out, and it is tempting to do clever things with different reference voltages.

Try this (and tell me whether it works and I can patent it ;) ): When you are going to change the reference "down" from 3.2V to 2.5V, set ADMUX to use "0 0 AREF, Internal Vref turned off". This has about 30k input impedance and should draw down the small cap pretty quickly. Try with and without doing a conversion--my guess from recent work verifying that the AREF input impedance is indeed about 30k :( is that you won't even need to do a conversion.

I'll wager a cold one that the time needed to draw down and settle with the above will be less than waiting it out. But just a guess. Let us know.

[Hmmm--maybe not. Time constant for 32k and 100nF is about 3ms, right? That IS a long time.]

Lee

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

Well i had same problem some time ago and i calculated that for ATMega168 the VREF pin consumes ca 140uA and i have 100nF VREF capacitor. It takes about 4-5mS to discharge it.
If you apply 1.1V before AVcc is fallen you get overcurrent in chip and something bad can happen.
The robust way to overcome this is next:

//Turn vref off
ADMUX &= ~(1 << REFS0);
//wait ca 4-5mS - i have 8MHz clock, so at least 32k instructions
for(rTemp = 0; rTemp <= 200; rTemp++)
{
for(rTemp2 = 0; rTemp2 <= 200; rTemp2++)
{
asm("nop");
}
}

//Internal 1.1V Voltage Reference with external capacitor at AREF pin
ADMUX |= (1 << REFS0) | (1 << REFS1);

//And 1st dummy conversion to stabilize Vref
ADMUX &= ~ 0x0F; //channel 0
ADCSRA |= (1 << ADSC);
loop_until_bit_is_clear(ADCSRA,ADSC);

..And go on and do whatever you want! :P