Nano ADC (Vbg Ref) reads low Vbg value and high GND value

My Nano (running at about 4.5 V) ADC with Vbg ref (REFS[1:0] = 00) gives:

  • low Vbg values (MUX[3:0] = 1110) of 1016 to 1017; I expected closer to1023, and
  • high GND values (MUX[3:0] = 1111) of 1 to 3; I expected closer to 0.
    Is this fixable? Have I made a mistake? A workaround suggestion?
    Sample code (hacked from larger program) follows
    (BTW Where's 'insert code? Where's post preview? Let's try BlockQuote; umm no, maybe preformat? Any also attached as upload)
const int kLoopsPerSecond = 10; // was 50 in main prog
long nV = 0;
bool bLogThisLoop = false;
int ePin[2] = {14, 15};
double V[2] = {0.0, 0.0};
double minV[2] = {9e9, 9e9};
double maxV[2] = {-9e9, -9e9};
double sumV[2] = {0.0, 0.0};
double sumV2[2] = {0.0, 0.0};

// the setup function runs once when you press reset or power the board
void setup() {
  //Serial.begin(57600); // ESP8266 default of 74880 not supported on Linux
  Serial.begin(115200); // ESP8266 default of 74880 not supported on Linux
  while(!Serial); // for the Arduino Leonardo/Micro only
  Serial.println();
  Serial.println(F(__DATE__ " @ " __TIME__));
  Serial.println(F(__FILE__));
  Serial.println();
  pinMode(LED_BUILTIN, OUTPUT);

  InitADCbg_ec_a();
  Serial.print(F("ADMUX=0x"));Serial.println(ADMUX, HEX);
  Serial.print(F("ADCSRA=0x"));Serial.println(ADCSRA, HEX);
  Serial.println();
}

void PrintStats(){
  static double vCC = 4.5;
  String res;
  double vRef;
  for(int ix = 0; ix < 2; ++ix){
    res = F("vbg:");
    vRef = 1.1;
    int iP = ePin[ix];
    res += String(iP);
    res += F("=");
    if(iP == 0xE){ res += F("V1.1"); }
    if(iP == 0xF){ res += F("GND"); }

    double v = V[ix];
    res += F("\t=");
    res += String(v);
    res += F("   \tavg = ");
    double avg = sumV[ix] / nV;
    res += String(avg);
    res += F(" \t std = ");
    double sd = sqrt(((sumV2[ix] / nV) - (avg * avg)));
    res += String(sd);
    res += F(" \t min = ");
    res += String(minV[ix]);
    res += F(" \t max = ");
    res += String(maxV[ix]);
    res += F(" \t V = ");
    res += String(vRef * ((avg + 0.5) / 1024.0), 4);
    Serial.println(res);
  }
}

long GetLoopPeriod_lps(int loopsPerSecond){
  return millis() * loopsPerSecond / 1000;
}

bool IsSignificantNumber(int n, int maxInDecade){
  if(maxInDecade > 9) { maxInDecade = 9; }
  if(maxInDecade < 1) { maxInDecade = 1; }
  String str, strMaxInDecade;
  str = n;
  strMaxInDecade = maxInDecade;
  str.replace("0", "");
  str.replace("-", "");
  if(str.length() == 0) return true; // 0 is significant
  if(str.length() >= 2) return false; // NN cannot be significant
  return str <= strMaxInDecade; // N only significat if in range 1 - maxInDecade
}

// the loop function runs over and over again forever
void loop() {
  ++nV;
  digitalWrite(LED_BUILTIN, nV % 2 ? HIGH : LOW);

  bLogThisLoop = IsSignificantNumber(nV, 5);

  long period = GetLoopPeriod_lps(kLoopsPerSecond);
  if(bLogThisLoop){
    Serial.print(F("Loop Start nV="));Serial.println(nV, DEC);
  }

  char ch = 0;
  while(Serial.available() > 0){
    int sr = Serial.read();
    Serial.print(F("Serial.read()=0x"));Serial.println(sr, HEX);
    ch = sr;
  }

  for(int ix = 0; ix < 2; ++ix){
    uint16_t v;
    ReadAnalog_ec_a(0); // just get one out the way

    int iP = ePin[ix];
    v = ReadAnalog_ec_a(iP);
    if(v < minV[ix]) { minV[ix] = v; }
    if(v > maxV[ix]) { maxV[ix] = v; }
    V[ix] = v;
    sumV[ix] += v;
    sumV2[ix] += (double)v * v;
  }
  if(bLogThisLoop) {
    PrintStats();
    Serial.println();
  }
  while(period == GetLoopPeriod_lps(kLoopsPerSecond)){} // pause whilst same time period
}

void InitADCbg_ec_a()
{
  ADMUX = 0; // do full reset
  ADMUX |= (1<<REFS1) | (1<<REFS0); // 11 = 1.1V // Select Vref=Vbg

  ADCSRA = 0; // do full reset
  // prescaler range is 2 to 128; ADPS[2:0] 00x = /2, 010 = /4, 011 = /8, 100 = /16, 101 = /32, 110 = /64, 111 = /128
  //set prescaller to 128 (ADPS2+1+0) and enable ADC
  ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(1<<ADEN);     

  ReadAnalog_ec_a(0); // just get one out the way
}

uint16_t ReadAnalog_ec_a(uint8_t ADCchannel)
{
  // https://embedds.com/adc-on-atmega328-part-1/
  //select ADC channel with safety mask
  ADMUX = (ADMUX & 0xF0) | (ADCchannel & 0x0F); 
  uint16_t res;  
  res = physical_ReadAnalog_ec_a();
  res = physical_ReadAnalog_ec_a();
  res = physical_ReadAnalog_ec_a();
  res = physical_ReadAnalog_ec_a();
  res = physical_ReadAnalog_ec_a();
  return res;
}
uint16_t physical_ReadAnalog_ec_a()
{
  //single conversion mode
  ADCSRA |= (1<<ADSC);
  // wait until ADC conversion is complete
  while( ADCSRA & (1<<ADSC) ){}
  return ADC;
}

NanoBoardAdcInternalRefMinimal.ino (4.0 KB)

Hi, your introduction is a little rough.
I think you have a classic Arduino Nano with a ATmega328P.
Then you should have added a link to the datasheet and mention on what page the ADC mux is, and maybe put a image of that table into your post.
A sketch that shows the problem could be 10% of what you have now. I'm too lazy to check your whole sketch.

When changing the mux, I use a delay of 10ms and then I throw away the first ADC value.
That is enough for the ATmega328P, for the ATmega2560 it can be a little harder.

Selecting a different analog input is often no problem. In other situations, such are measuring VCC, that delay and throwing away the first value might be needed.

ADC tutorial: https://www.gammon.com.au/adc

[ADDED] I was confused that you read ADC. It seems to be the same as ADCW which I use.

My numbers with a Arduino Uno which has nothing connected to it, only the USB cable:

ADMUX=0xC0
ADCSRA=0x97

Loop Start nV=1
vbg:14=V1.1	=1023.00   	avg = 1023.00 	 std = 0.00 	 min = 1023.00 	 max = 1023.00 	 V = 1.0995
vbg:15=GND	=0.00   	avg = 0.00 	 std = 0.00 	 min = 0.00 	 max = 0.00 	 V = 0.0005

Loop Start nV=2
vbg:14=V1.1	=1023.00   	avg = 1023.00 	 std = 0.00 	 min = 1023.00 	 max = 1023.00 	 V = 1.0995
vbg:15=GND	=0.00   	avg = 0.00 	 std = 0.00 	 min = 0.00 	 max = 0.00 	 V = 0.0005

Loop Start nV=3
vbg:14=V1.1	=1023.00   	avg = 1023.00 	 std = 0.00 	 min = 1023.00 	 max = 1023.00 	 V = 1.0995
vbg:15=GND	=0.00   	avg = 0.00 	 std = 0.00 	 min = 0.00 	 max = 0.00 	 V = 0.0005

Loop Start nV=4
vbg:14=V1.1	=1023.00   	avg = 1023.00 	 std = 0.00 	 min = 1023.00 	 max = 1023.00 	 V = 1.0995
vbg:15=GND	=0.00   	avg = 0.00 	 std = 0.00 	 min = 0.00 	 max = 0.00 	 V = 0.0005

With a simulation I don't get those numbers. Are you using a real ATmega328P ? If so, what have you done with it ?

1 Like

Thank you for your response; it was very helpful - especially your run output. I guess it means that my code is good, and my Nano ... not so good.
I got half a dozen lot of cheap no-name Nano clones from a Chinese supplier; maybe now I know why they were cheap ... I'll fish out another one and see if it improves things.

P.S. I have now soldered 100nF ceramics across 5V to GND, and across REF to GND**, and removed the Nano from the breadboard, with perhaps a tiny improvement in figures.
The chip is marked "ATMEL, MEGA328P, U-PH, 35473D, 19250LQ".
With my test being all internal to the chip itself, I am presuming that they are, if not rejects, then at least barely within tolerance.
** This is actually to the mini-USB case which is earthed; I'd rather a short lead to minimise catching it.

I was intrigued by your problem and decided to investigate. I wrote a simple program that does nothing but do ADC conversions using the 1.1V reference and a selected 1.1V or GND multiplexer settings. Indeed, with my nano I see a code of 1017 for the band gap reference, but I do get 0 for the ground reference. However digging into what there is in the datasheet,

  • There is a 2 LSB absolute error, so you could be reading 4 counts low (1019) and the part would still be in spec. Atmel never claimed these were precision devices. Also those specs assume a 4V VREF and 4V VCC. No specs for using the 1.1V reference and one would naturally think both from an EE point of view and one of specmanship it would be worse with the lower voltage reference.

  • Another strong possibility is the internal connections in the part. The path from the BG generator to the reference voltage is much different than that to the multiplexer based on the block diagram for the ADC and there is a possibility that there are even two different buffer amplifiers from the BG to supply the separate reference voltages. Also the spec for the 1.1V reference is ±0.1v, or about 9%, so there is also that if you are looking for accuracy.

So I suspect the old adage, "you get what you pay for" is true here.

Thank you, @almytom. Note that, even if the Vbg is way out (say, 1.5V), when checking Vbg against a reference of Vbg, is should still be 1023; it's all relative to itself. The same with GND (although your GND seems to be good). I might try the test with a Uno Mega & see how I go.

The operative word is "should". The circuit is not symmetrical. The reference is always present while the measured value goes through a sample&hold circuit. If there is leakage, the sampled voltage will drift. I also tried the program on an Arduino Leonardo (ATmega32U4) and it was different being incredibly noisy with values fluctuating from 1015 to 1023. It presumably has the same ADC circuit.

1 Like