Time for analogReference(INTERNAL) to come up

After a day or two of very frustrating troubleshooting, I think I've discovered something unexpected. I used a 3.3V Pro-Mini board and some other analog hardware to build an accurate MilliOhm meter with an autozero function. It's powered by two AAA Lithium batteries, powering Vcc directly.
During Setup, I use the analogReference(INTERNAL); statement to select the 1.10V reference. The design of the code and circuitry is such that it measures the resistance across a pair of terminals by reading the voltage drop with a 100mA constant current source applied. The reading at startup (during Setup) is done with the test leads shorted, so as to measure and establish the "offset" resistance of the test leads, so that it can later be subtracted from the actual reading of the unknown resistance.
The auto-zero feature wasn't working properly, so I resorted to looking at the reference pin of the atmega328 with a scope, while also monitoring the digital output that enables the current source. What I found was that, even though the analogReference(INTERNAL); statement occurs immediately after entering Setup, the reference voltage didn't drop from Vcc to 1.10V until a few seconds later when the first analogRead() occurred. This meant that the reference voltage was dropping just as I was reading the A/D, which of course caused huge errors. (No, I didn't add any caps to the Ref pin)
I verified this (and fixed the problem) by adding a "dummy" analogRead() statement several seconds before I actually needed it.
I was wondering if others had discovered this, and if this is documented somewhere. Did I make some mistake somewhere? My code (which is working fine now) is below.

// This is a Milli-Ohm meter. It can measure resistance from 1 mOhm to 11.00 Ohms, using a Pro-Mini board (3.3V),
// a 100mA constant current source (LT1635) and a diff amp with a gain of ten (TLV9151) to gain-up the voltage drop across the load.
// The current source is turned on for a short time, and the voltage drop across the terminals is measured and gained-up by 10.
// The resistance is calculated and displayed on an OLED display.
// There is an auto-zero phase at startup that allows the lead resistance (and any internal offsets) to be zero'd out.
// Modified the auto-zero code and added an auto-range feature to measure resistances up to 11.00 Ohms.
// Had to add a "dummy" analog read a few seconds before it was need to "wake-up" the internal reference!
// Rev 2.0, 1/20/2024, D. Salerno

#include <Wire.h>  // Library for I2C communication
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

float rev = 2.0;     // This is the revision of the code
float vref = 1.110;  // This is the actual internal reference voltage
float gain = 10.05;  // This is the diff-amp gain
float ICC = 0.101;   // This is the constant current pulse, in amps
int enable = 10;     // This is D10 (pin 13), used as an output to enable the current source
int t1 = 2;          // This is the current pulse duration in msec
int t2 = 1000;       // This is the measurement period in msec
int count = 0;       // This is the A/D count when reading the isense input
int bigcount = 0;    // This is the A/D count when reading the bigsense input
float offset = 0;    // This is the measured offset, at Startup, which is subtracted from the measured resistance
int isense = A0;     // This is the A/D input pin that is used
int bigsense = A1;   // This A/D input is used when the resistance is > 1.1 Ohms (from before the gain of 10 amp)

float resistance;    // This is the calculated resistance in Ohms
int Vc = 9;          // This is the digital input (pin 12) for monitoring the collector voltage of the current sink, to check for a load

// This measures the battery voltage at Startup, without needing a resistor divider
long readVcc() {  // This measures the Vcc voltage, using the internal reference to compare it to
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(20);  // Allow time for the reference to settle
  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA, ADSC));
  long result = ADCL;
  result |= ADCH << 8;
  result = 1135530L / result;  // Back-calculate AVcc in mV (1.1V * 1023 * 1000)
  return result;
}

void setup() {
  analogReference(INTERNAL);    // Use the internal 1.10V reference for the A/D
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  Serial.begin(9600);
  pinMode(enable, OUTPUT);  // Declare the digital output that will enable the current source
  pinMode(Vc, INPUT);       // Declare the digital input that monitors the collector voltage
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.clearDisplay();
  float Vcc = readVcc();        // gives the Vcc voltage in mV
  display.setCursor(0, 0);      // (X pos, Y pos) Start the text in the upper left-hand corner (indented in a bit)
  display.print(Vcc / 1000, 2); // Display the voltage in volts, with two decimal places
  display.println(" V");
  display.print("Rev ");
  display.println(rev, 1);
  display.println("1/20/2024");
  display.display();
  delay(2000); // 
  display.clearDisplay();
  display.setCursor(0, 0);      // Start the text in the upper left-hand corner (indented in a bit)
  display.println("Begin");     // Begin the Autozero routine
  display.println("Auto-zero"); // Short the test leads together
  display.display();
  count = analogRead(isense);   // This is a "dummy" Read to wake up the reference a few seconds before the real Read!
  delay(2000);
  digitalWrite(enable, HIGH);   // Turn on the current source
  display.clearDisplay();
  display.setCursor(0, 18);     // Start the text in the upper left-hand corner (indented in a bit)
  display.println("Short the"); // Begin the Autozero routine
  display.println("Leads");     // Short the test leads together
  display.display();
  while(digitalRead(Vc) == HIGH) {  // Wait for the test leads to be shorted, which will make Vc Low
    delay(100);
   }
  delay(2000);                  // Allow time to short the leads and "settle" before taking the reading
  count = analogRead(isense);   // Read the analog input pin that monitors the gained-up voltage drop
  delay(10);                    // Probably don't need a delay here
  offset = (count * vref / (1023  * gain * ICC));  // Calculate the offset resistance
  Serial.print("Offset = ");
  Serial.print(offset, 3);
  Serial.println(" Ohms");
  digitalWrite(enable, LOW);    // Turn off the current
  delay(1000);
  display.clearDisplay();
  display.setCursor(0, 0);      // Start the text in the upper left-hand corner (indented in a bit)
  display.println("Remove");
  display.println("Short");
  display.display();
  delay(1500);
  display.clearDisplay();
  display.setCursor(0, 0);      // Start the text in the upper left-hand corner (indented in a bit)
  display.println("Offset = "); // Display the measured offset resistance
  display.println(offset, 3);   // Display the measured offset, with fixed decimal places
  display.println("Ohms");
  display.display();
  delay(2000);
}

void loop() {
  digitalWrite(enable, HIGH);      // Turn on the current source
  delay(t1/2);                     // Wait (t1/2) milliseconds, so that you sample in the middle of the pulse
  count = analogRead(isense);      // read the analog input pin that monitors the gained-up voltage drop
  delay(1);                        // Allow time between A/D readings
  bigcount = analogRead(bigsense); // Read the voltage before the gain of 10 amp
  delay(t1/2);                     // Probably don't need a delay here
  digitalWrite(enable, LOW);       // Turn off the current source
  display.clearDisplay();
  display.setCursor(0, 0);         // Start the text in the upper left-hand corner (indented in a bit)
  if (count < 1022) {              // For resistances less than 1.1 Ohms
    resistance = (count * vref / (1023 * gain * ICC)) - offset;
    display.println("Resistance");   //
    display.println(resistance, 3);  // Display the measured resistance, with fixed decimal places
    display.println("Ohms");
    display.display();
  }
  else if (count >= 1022 && bigcount < 1022) {
    resistance = (bigcount * vref / (1023 * ICC)) - (offset);
    display.println("Resistance");   //
    display.println(resistance, 2);  // Display the measured resistance, with fixed decimal places
    display.println("Ohms");
  }
  else {
    display.println("Resistance");
    display.println("> 11 Ohms");
  }
  display.display();
  delay(t2);  // Wait before taking another reading
}

If those two are in series, you have to connect it to the RAW pin. Not the VCC, unless you want to blow up the processor.

When changing the analogreference, it's recommended to discard the first two ADC readings, to give the changed reference time to settle.

Please be careful with your code tags. Thanks for using them at all, but... your code goes inside the tags, your question goes outside the tags. Please fix that.

Two series AA or AAA Lithium (primary) cells have a total voltage between ~3.0V and 3.4V, just fine for connecting to the Vcc pin of the Pro-Mini. These are NOT 3.6V Lithium-Ion batteries - which do not come in AA or AAA packages.

I literally gave the reference SECONDS to settle. My point was that it was clear on the 'scope that the reference was sitting at Vcc for seconds until I did the first analogRead, at which point it dropped to the proper 1.10V.

Sorry - I'll get right on that.
I've never posted any code here before, and when I tried to, I got a warning telling me what to do (using the "code" key at the top of the message menu). I thought I followed those instructions, but apparently wasn't supposed to include my text before the code.
Having said that, I don't really see why it matters, since it's all readable and it's pretty clear where the code begins, but thanks for the correction.

How does one go about fixing that?

Never heard of those 1.8V AAA lithium cells, but now I have and the ones I saw are not cheap.

analogreference is only telling analogread what reference it should use, but the actual switching takes place at time of analogread.

Yes, the Lithium batteries are pricey for sure, so I use them judiciously. They're good for apps that have to work at low temp, so I put them in my outdoor remote temp sensors (which last for a year or more), and they're good for high current drain apps. Although the average current drain of this app is only about 5mA, due to the low duty cycle of the pulsed current, I wanted to minimize the Vcc droop during the 150mA pulse, which is too long to rely on just decoupling caps.

As far as the reference goes, my scope photos indicate that the reference switches to 1.10V with the FIRST analogRead. After that it stays at 1.10V until a reset/power cycle. I just wasn't aware that it would wait until you needed to use it. Seems like a bad idea, as borne out in my test results. As you stated earlier, you want the reference to settle to its steady-state value before you actually do an A/D read. The "dummy" read statement I added takes care of this. I had assumed that the switch to the internal reference would occur after the analogReference(INTERNAL); statement, but apparently not.

Edit the post (pencil icon under it).

You will see a line with 3x back-tick symbols at the top. Move those to just before the code begins. The back-ticks must be on a line by themselves.

@huskaroo
The analog reference is not actually set till you do the first read
So after you set the reference with analogReference (INTERNAL) do a dummy analogRead

1 Like

Got it, thanks.

1 Like

jim-p, thanks so much. I finally figured it out the hard way, by trying a dummy read as an experiment, but it's good to get confirmation. Seems like a strange requirement, not very "elegant".

I've used the internal reference a few times in the past without a dummy read, because I didn't notice an issue. If it's a matter of reference settling time on the first read, it may be that my previous designs were not as sensitive to an error as this one, where a mV or two matters.

Not really sure why they did it that way but both the reference and channel are selected when you do an analogRead.

Not to belabor this, but I ran a simple test where I measured a fixed input voltage (0.306V) that was applied to the A0 input of a 3.3V Pro_Mini board. (What the heck, I'm retired, haha.) I did three analog reads, 1msec apart and printed the resulting A/D values to the serial printer. See the code below.
First I manually measured the voltage at the Vref pin of the processor, and it was 1.090V, so the A/D reading ("count") should ideally be 287.

The first A/D reading (after applying power or doing a reset) was 268. After 1msec, all the subsequent readings were either 286 or 287. (I also ran a test where I took many readings). So the first A/D reading had an error of 7%. After 1msec the readings were all within 1 LSB of the correct value.

You'll see in the code that I also turn a digital output on and off and on again. This provides me with a trigger for the 'scope. I took a scope photo of the voltage at the Vref pin of the micro, and after a Reset, it showed the voltage rising linearly from 0V, with some overshoot, to ~1.1V. The rise time was 200usec. However, after that it took a few hundred usec more for it to really stabilize and smooth out.
Based on this test of one, I would always wait at least 1msec between the first (dummy) analog read and any subsequent reads that you care about.

I just thought some people might find the results interesting. I'd attach the scope photo for reference, but I don't see how that's done (being a Newbie).

// This is a test to see when the Internal Reference is actually enabled
// and how long it takes to rise and reach regulation
// Scope photos show that it doesn't begin to change until the first analog read
// It takes 200usec to rise and about another 400usec to stabilize
// I would do a dummy analog read and then wait at least 1msec before doing another read that you care about
// Rev 1.0, 1/21/2024, D. Salerno

int count = 0;
int enable = 10;     
int isense = A0;     // This is the A/D input pin that is used

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL);   // Use the internal 1.10V reference for the A/D
  pinMode(enable, OUTPUT);  
  digitalWrite(enable, HIGH);  // Start with enable High
  delay(2);  // Wait a few msec
  digitalWrite(enable, LOW);   // Command enable Low - use this as a time reference and trigger source
  count = analogRead(isense);  // Do the first analog read
  digitalWrite(enable, HIGH);  // Set enable high - use this as a time reference
  Serial.println(count);
  delay(1); // wait 1msec
  count = analogRead(isense);  // read the A/D again
  Serial.println(count);       // Compare readings
  delay(1); // wait 1msec
  count = analogRead(isense);  // read the A/D again
  Serial.println(count);       // Compare readings
   
}

void loop() {
  delay(1000);
}

I posted a link to the scope photo here:


The top trace is the reference, the bottom trace is the digital output "enable".

The analog read takes place immediately after the digital write command to set enable low.

That's why we do the dummy read and throw that value away.

I used a 3.3V Pro-Mini board and some other analog hardware to build an accurate MilliOhm meter with an autozero function.

Can you provide a schematic so we can see how you have everything connected?

Schematic for pro mini

you will see the external reference is decoupled to ground on the board via a 0.1uF cap.

From the 328 data sheet this shows the reference is selected by the admux register.

When reference - internal is selected the reference supply connects to the DAC AND the external reference pin.

That is why Reference-EXTERNAL MUST be selected FIRST if you apply a voltage to that pin.

image

Then the AREF charges the 0.1UF from the source impedance of the internal reference.
Hence the delay.

I understand why there's a delay for the Vref pin to rise, I just thought it would have occurred after the analogReference(INTERNAL) command, and not wait for an analog read.

Exactly. Understood.

Here's my schematic.