I have a 3.3V standalone Arduino with lots of things:
OneWire sensors (it disables the interrupts now and then), I2C interrupts, LDR sensor, reading own voltage with resistor divider (33k and 22k), reading own voltage internally against internal reference, sleep mode (SLEEP_MODE_IDLE).
When something is wrong, a red led is turned on.
This happens once in a few weeks, and it turned out that reading its own voltage with a resistor divider returnes a voltage lower than 1.8V (that is one of the checks in the sketch). But such a voltage is not possible.
int raw;
float voltage;
analogReference(INTERNAL);
// function for sleep IDLE until 100ms is reached.
GoIntoSleep(100);
raw = analogRead(pinVolt);
voltage = (float) raw / 1024.0 * 2.56 * 2.5;
if(voltage < 1.8)
{
digitalWrite(pinLed, HIGH);
}
There is no way to force this problem to happen, so it is almost impossible to find it.
I suspect reading the own VCC against the internal reference could cause it. I read that it could cause a problem on the next analogRead.
My workaround is to try up to 10 times, until the voltage is above 1.8V. However, I still would like to know what caused it.
EDIT: This standalone runs at 3.3V, I wrote 5V by mistake.
This is what the ATmega328P manual says about this:
23.5.2 ADC Voltage Reference
The reference voltage for the ADC (VREF) indicates the conversion range for the ADC. Single
ended channels that exceed VREF will result in codes close to 0x3FF. VREF can be selected as
either AVCC, internal 1.1V reference, or external AREF pin.
AVCC is connected to the ADC through a passive switch. The internal 1.1V reference is gener-
ated from the internal bandgap reference (VBG) through an internal amplifier. In either case, the
external AREF pin is directly connected to the ADC, and the reference voltage can be made
more immune to noise by connecting a capacitor between the AREF pin and ground. VREF can
also be measured at the AREF pin with a high impedance voltmeter. Note that VREF is a high
impedance source, and only a capacitive load should be connected in a system.
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 ref-
erence selection. The first ADC conversion result after switching reference voltage source may
be inaccurate, and the user is advised to discard this result.
This is what the analogRead() function does:
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;
}
int analogRead(uint8_t pin)
{
uint8_t low, high;
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#elif defined(analogPinToChannel) && (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__))
pin = analogPinToChannel(pin);
#else
if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif
#if defined(__AVR_ATmega32U4__)
pin = analogPinToChannel(pin);
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#elif defined(ADCSRB) && defined(MUX5)
// the MUX5 bit of ADCSRB selects whether we're reading from channels
// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
#if defined(ADMUX)
ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif
// without a delay, we seem to read from the wrong channel
//delay(1);
#if defined(ADCSRA) && defined(ADCL)
// start the conversion
sbi(ADCSRA, ADSC);
// ADSC is cleared when the conversion finishes
while (bit_is_set(ADCSRA, ADSC));
// we have to read ADCL first; doing so locks both ADCL
// and ADCH until ADCH is read. reading ADCL second would
// cause the results of each conversion to be discarded,
// as ADCL and ADCH would be locked when it completed.
low = ADCL;
high = ADCH;
#else
// we dont have an ADC, return 0
low = 0;
high = 0;
#endif
// combine the two bytes
return (high << 8) | low;
}
As you can see it uses the reference value and sets the ADMUX and does not discard if a reference change was done. Your work-around, i.e. read additional values, is just fine.
And to answer your question: Yes the ADC can return a faulty value due to time required to stabilize reference voltage and noise.
@rainwulf, I don't know ! There is no way to make this error to happen. The red led is off at the moment.
This standalone runs on 3.3V, and it is a good and very stable 3.3V with a few 100nF and a 10uF.
@kowalski, Thank you ! I do not completely understand it, but the first ADC after switching ref voltage could be bad. I have to check all my sketches now
Also you are sending the MCU to sleep, then waking it up and then instantly reading an analog value, never a good thing to do.
You should put in a Delay(20); to give the MCU time to wake up the analog section and the ADC.
@Nick Gammon, I added lots of test to my sketch for situations that should not happen because sometimes a value was wrong, but that turned out to be a bug in my I2C communication. After that I just kept those test, and to my surprise the red led turned on.
voltage = (float) raw / 1024.0 * 2.56 * 2.5;
1024 = bits
2.56 = internal reference
2.5 = multiplier for 33k + 22k resistor divider (22k to ground, 33k to Vcc).
@rainwulf, The SLEEP_MODE_IDLE and SLEEP_MODE_ADC can be used with an active ADC. But perhaps the situation is different when changing the referece or with the code in analogRead(). Thanks, I will use normal delays.
@MarkT, I have it on my atmega8 and also my analog values on my mega 2560 board fluctuate. I use measuring it's own Vcc by testing it against the reference voltage on both, so changing the mux might be the problem in both.
They also both happen to have an internal reference of 2.56V.
Running first tests: The fluctuation on my mega 2560 is gone, it is now 40mV in 27V range (resistor divider). That is 9 bits accurate. I assume that the problem on the atmega8 is completely gone.
@holmes4, yes, error detection is correct. The measured voltage should not be below 1.8V.