Bug with analogReference

I'm not sure if this is a known issue or not but there appears to be a bug with analogReference when switching between INTERNAL and DEFAULT and vice versa.

Simply issuing the analogReference(xxx) does not in fact switch the ADC reference voltage after completion of the code. It doesn't switch the voltage until you issue an analogRead().

I've been doing a lot of testing with the ADC prescaler, measuring the time it takes when switching back and forth between INTERNAL and DEFAULT. I've found that when going from DEFAULT(5v) to INTERNAL(1.1v) it takes approximately 5600 microseconds before the reference voltage reaches 1.1 volts. Conversely switching from INTERNAL to DEFAULT only takes about 28 microseconds.

With this information I tried throwing a delay of 7 milliseconds between analog reads when switching from DEFAULT to INTERNAL with the below code excerpt expecting to see a nice step output with the below loop:

for (int i=0;int < 1000;i++){
    sample[i] = analogRead(0);
    if (i == 10){
         analogReference(INTERNAL);
         delay(7);
    }
}

unfortunately the graphed out data still shows the adc count sloping up as if without the delay ever being added (with the prescaler set to 4Mhz it's about a 719 samples long ramp up).

If I simply add an analogRead() immediately after the changing ANREF like so:

for (int i=0;int < 1000;i++){
    sample[i] = analogRead(0);
    if (i == 10){
         analogReference(INTERNAL);
         analogRead(0); //added to switch reference
         delay(7);
    }
}

then I see the expected "step graph" on the output. I can post graphs if you like.

The same holds true when switching from INTERNAL to DEFAULT.

So I guess my question is is this a bug or normal operation for the ADC????

I believe this is normal behavior for the ADC. There are some delays and possibly incorrect readings when you switch between different references. But you're right, the Arduino code doesn't actually change the reference until you do an analogRead().

Known 'feature' of the AVR chip. From the datasheet in section 23:

"The first ADC conversion result after switching reference voltage source may
be inaccurate, and the user is advised to discard this result."

There is lots of related technical information on the ADC section of the data sheet, so you might want to browse that section.

Lefty

Known 'feature' of the AVR chip. From the datasheet in section 23:

Actually it looks like a function of the IDE. I found this inside inside the wiring_analog.c

uint8_t analog_reference = DEFAULT;

void analogReference(uint8_t mode)
{
// can't actually set the register here because the default setting
// will connect AVCC and the AREF pin, which would cause a short if
// there's something connected to AREF.
analog_reference = mode;
}

IF I'm reading this correctly setting analogReference does nothing by itself. It looks like it was purposely written this way as a safety to prevent a short if someone had an external source connected to the AREF pin. My guess is they had to do this or else you would have to set the analogReference in each sketch in the setup section. This way they were able to set the power on default to DEFAULT so you wouldn't have to use analogreference by default.

Ofcourse this assumes I'm reading this correctly (I'm still new to microcontrollers).

Again the Atmel AVR datasheet is ALWAYS the bible for such issues. The Arduino IDE and board can only do what the chip is designed to do and abide by any restrictions and cautions.

From the AVR datasheet:

If the user has a fixed voltage source connected to the AREF pin, the user may not use the other
reference voltage options in the application, as they will be shorted to the external voltage. If no
external voltage is applied to the AREF pin, the user may switch between AVCC and 1.1V as reference
selection.

From the Arduino reference section ( analogReference() - Arduino Reference ) there is a work around for those wishing a work around for the above:

Warning
If you're using an external reference voltage (applied to the AREF pin), you must set the analog reference to EXTERNAL before calling analogRead(). Otherwise, you will short together the active reference voltage (internally generated) and the AREF pin, possibly damaging the microcontroller on your Arduino board.

Alternatively, you can connect the external reference voltage to the AREF pin through a 5K resistor, allowing you to switch between external and internal reference voltages. Note that the resistor will alter the voltage that gets used as the reference because there is an internal 32K resistor on the AREF pin. The two act as a voltage divider, so, for example, 2.5V applied through the resistor will yield 2.5 * 32 / (32 + 5) = ~2.2V at the AREF pin.

Lefty

My guess is they had to do this or else you would have to set the analogReference in each sketch in the setup section. This way they were able to set the power on default to DEFAULT so you wouldn't have to use analogreference by default.

I think this is the exact reason why the deferred change of reference was chosen.

You could argue however that when analogReference is actually called, the function should change the reference before returning. If you think this is a better approach, you could post a change request accordingly.

You could argue however that when analogReference is actually called, the function should change the reference before returning. If you think this is a better approach, you could post a change request accordingly.

That is the point I was trying to make, however I don't think there is a safe workaround for this and I believe the original programmers realized this. So I guess it's not really a bug but a little know issue?

I also wanted to point out to anyone reading this thread that "throwing away" one or two Areads after switching the reference only works in the scenario when switching from INTERNAL to DEFAULT. This practice doesn't work when switching the other way since the stabilization time can be anywhere from 50 - 200 times longer depending on the prescalar used. Using the default PS setting you would actually have to "throw away" approx 51 reads before getting valid data. I actually put together a little chart showing the number of throw away analogread's for different arrangements below if anyone is interested.

SWITCHING FROM DEFAULT TO INTERNAL
prescalar ADC clk Settling Time Discard Reads
128(D) 125kHz 5720us 51
64 250kHz 5704us 95
32 500kHz 5672us 177
16 1MHz 5628us 312
8 2MHz 5616us 509
4 4MHz 5560us 721
2 8MHz DOES NOT WORK

SWITCHING FROM INTERNAL TO DEFAULT
prescalar ADC clk Settling Time Discard Reads
128(D) 125kHz 120us 0-1
64 250kHz 128us 1-2
32 500kHz 68us 1-2
16 1MHz 40us 1-2
8 2MHz 28us 1-2
4 4MHz 28us 2-3
2 8MHz DOES NOT WORK

As a side note, if you connect a 100nF cap across ARef like the datasheet recommends for noise immunity then all of the settling times and throw away reads effectively DOUBLE! I haven't tried it using the LC network yet since I don't have a coil handy but if I get one I'll post some data if anyone's interested. This has definitely been a learning experience :o, thank you everyone for your input.

As a side note, if you connect a 100nF cap across ARef

I do and trust others as well - so these figures are relevant. Thanks for sharing!

... however I don't think there is a safe workaround for this

I don't see any issues with this. Keep the function/logic as is, but add code to change the reference in addition to saving it to a global variable. It is then still ok for sketches not using ADC and reference will still default to 5V internal for those who need it.

Have you tried to disable/enable ADC (disable, change reference, enable) to see if this has an impact on reference settling time?

Have you tried to keep the reference constant, but use multiple channels (e.g. alternating between high and low voltage inputs)?

Edit:
Another option may be to ground an anlog input pin and do a sample off this pin just after changing reference. This woud discharge the ADC sampling capacitor, but I'm not sure how it would influence reference settling time.

As for ADC clock speeds above 250kHz, you're probably on uncharted territory as this is outside Atmel recomendations. A test like yours may however reveal to what extent higher sampling frequencies may be useful.

Have you tried to disable/enable ADC (disable, change reference, enable) to see if this has an impact on reference settling time?

Actually I believe this approach would double the conversion time going from I to D. If I'm reading the datasheet correctly the first conversion after enabling the ADC is something like 25 ADC clock cycles and each subsequent measurement is only 13, it would then take twice as long to to convert the voltage after reenabling it. I think the voltage settling time is more of an internal electrical (capacitance) characteristic of the IC because the time is relatively consistent across different prescalar values. If someone has more intimate knowledge of the ADC circuitry they would be better at answering this question.

So I thought I'd try some thing else, I decided to put varying resistors across the Aref and GND since the internal Vref is connected to Aref. Using the prescalar at max speed (4MHz) and varying resistance from 10Meg ohm to 820 ohms I was able to get the settling time down from 5560us @ 721 discards to 136us @ 17 discards. See chart below:

R ohms Settling Time Discard Reads
10 Meg 5516us 715
1 Meg 5340us 692
100k 4064us 527
10k 1184us 153
1k 160us 20
820 136us 17
<820 Started changing the Vref voltage

I don't know if this is safe to do or not, but if anyone has any experience with loading the Aref pin down with a resistor please feel free to chime in. If not I guess I just have to leave it running this way over night and see if it releases the magic smoke or not :)....

If someone has more intimate knowledge of the ADC circuitry they would be better at answering this question.

I thought you were keen on finding this out yourself through measurements?

As for your observations, I'm not entirely convinced that you measure the actual settling time of the voltage reference. Rather you may be measuring the charge/discharge rate of the ADC sampling capacitor through your analog input.

If you feed your analog input with a voltage divider, the recomended input impedance is 10k. More than this and you will see a reduced frequency response which obviously will worsen with higher samplig frequencies.

For faster response after switching reference, you either need to lower the input impedance (which may not always be possible or desired) or you can add a discharge step by connecting the ADC input to ground for one sample just before or after switching reference. If this is unacceptable in terms of sampling frequency, there is not much we can do other than interface an additional external ADC.

So this is the last thing I did. This time I hooked my scope up to the Aref pin (since this pin shows the Vref voltage) and triggered the analogReference change in both directions and captured the signal. I tied A0 to GND and repeated the same test with it tied to 5v. One direction showed the same discharge curve and measured about 5500us and the other direction was actually a little faster than what the sketch reported, it was roughly 7.5us.

I redid your last test on a standard Arduino and this seems to confirm your measurements.

Above is from switching AREF from internal Vcc to the internal 1V1 bandgap reference and settling time is just above 5ms. This is significantly more than one ADC sample as the AtMega328 datasheet suggests.

From AtMega328 datasheet:

The first ADC conversion result after switching reference voltage source may
be inaccurate, and the user is advised to discard this result.

Max analog sampling frequency, when using the Arduino core (analogRead), is just above 8kHz - so we would in fact need to discard up towards 50 samples to be safe. As such, the Atmel quote above seems somewhat of an understatement.

A related thread on the "AVR freaks" site suggests that the internal bandgap reference has issues with sinking current. In the context above this would be relevant as the 100n Arduino Aref capacitor is at 5V prior to switching.

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=80458&start=all&postdays=0&postorder=asc

This significant settling delay would severly limit the usefullness of doing ADC sampling in a round-robin fashion with alternating internal AREF's.

Redoing the test, but switching from Aref at Gnd to 1V1 internal, shows the following:

Settling delay here is approximately 75us. So apparently sourcing current is less of an issue with the bandgap reference.

I also measured the time it takes to discharge the 100n Aref capacitor.

AREF drops from 5V to 1V1 in about 5us when shorted to ground.

As a workaround for the slow settling issue, I added the following logic to my ADC sampling application:

(starting at 5V internal reference)

  • change to external reference
  • short AREF to Gnd (I used digital pin 3 for this)
  • wait 5us
  • disconnect AREF from Gnd
  • change to internal 1V1 reference

With above steps, it was possible to reduce settling time from in excess of 5000us to around 5us. So, there may be hope for round-robin sampling with alternating AREF's after all. However at the added cost of an IO pin and some additional code.

Thank you for confirming I'm not crazy!! Ofcourse the only other variable could be the oscope since we are both apparantly using the same Chinese knock off scope...LOL.

Just curious but how do you guys post pictures? I've been trying to find a way to post pictures but am unsure how most people do it.

Thanks again for the help...it's been very enlightening to say the least.

I've been trying to find a way to post pictures but am unsure how most people do it.

Use the option in your browser to look at the page source. You'll see stuff like:

If you post your pictures on some photo sharing site, like flickr, and use the Insert Image button, 3rd one on the top row, you can insert a link to your picture.

I'm been thinking about adding a lower-level API for the ADC (and timers, etc.), which would provide direct control over the registers (i.e. non-deferred reference switching along with other things). See: Sensor Library for Arduino (David A. Mellis) for an initial idea of what the implementation might look like. Suggestions welcome here or on the developers mailing list.

I believe I've found what's causing the large delays. It's the 100nF cap that they put across the AREF pin. I didn't even realize it was on the pin because at first look I didn't see it on the schematic because it's tucked away in the top corner.

I breadboarded the ATMEGA and ran the tests both ways with and without the capacitor. With the cap I saw the standard 3.5ms switching time (D-I) and without the cap it dropped all the way down to 12.7us . There was a large improvement in the other switch time (I-D) from 3.75us down to 15ns without the cap.

I tried the same test with a 10nF cap and the two switch times were at 359us(D-I) and 435ns (I-D). Using the input impedance of the AREF circuitry (32k ohms) these times make sense if you calculate the RC time constant.

See: Sensor Library for Arduino (David A. Mellis) for an initial idea of what the implementation might look like. Suggestions welcome here or on the developers mailing list.

Those are some good ideas Dave. Another suggestion would be an "averaging" analogRead (ie analogReadAVG(pin,samples_to_average)). I'm guessing a lot of people take sensor readings by embedding the analogRead function in a for loop and average the samples, might be nice to have that all in one function.

  • short AREF to Gnd (I used digital pin 3 for this)
    - wait 5us
  • disconnect AREF from Gnd

How does one wait 5 micro-seconds with an arduino?? I didn't know you could do that.

Another suggestion would be an "averaging" analogRead (ie analogReadAVG(pin,samples_to_average)).

I second this so much! Now I know it's not such a big deal to copypaste your usual bits and pieces, but I find myself doing it a lot with smoothing. Very annoying. Please add this functionality.

How does one wait 5 micro-seconds with an arduino??

http://arduino.cc/en/Reference/DelayMicroseconds