My quest for analogRead accuracy failing miserably

I would like to use a one of these: https://www.sparkfun.com/products/9028 in a Power supply read the voltage and current sensors with an arduino mega and display the voltages on a 3.2 tft touch display. I was getting inconsistent results so I googled and eventually came across these two pages:

http://hacking.majenko.co.uk/making-accurate-adc-readings-on-arduino
https://code.google.com/p/tinkerit/wiki/SecretVoltmeter

Then realized that neither pertain to the arduino mega…

I’m powering the mega using a server power supply. Would it be worthwhile to run the 5v line into the AREF pin to try and stabilize the readings?

Any other input or suggestions are welcome as well.

Thanks,

Loren

You need to define your accuracy. Is it 3% accuracy you need or 0.01%? The higher the accuracy the more complicated/expensive your design will become.

Got it....

I guess for my purposes of this project being accurate to a tenth of a volt would be good.

I added analogReference(INTERNAL1V1); this morning but I don’t think that helped a whole lot. The voltage is still varying by 2 or 3 volts.

Here is the code that I’ve been using:

#include<UTFT.h>
#include<UTouch.h>
#include "Menus.h"


extern uint8_t SmallFont[];
//UTFT myGLCD(ITDB32S, 38,39,40,41);   // Remember to change the model parameter to suit your display module!
//UTouch myTouch(6,5,4,3,2);


//Sketch Variables

int mnu;
int x,y;
int voltagepin = 1;
int involt = 0;
int currpin = 0;
int inamp = 0;

Menus menus;

void setup(){

  Serial.begin(9600);
  analogReference(INTERNAL1V1);

  menus.initScrn();
  menus.homeScrn();
 
}


void loop(){
  menus.touched();


  //if(mnu==0){

    //involt = analogRead(voltagepin);
    //delay(10);
    involt = analogRead(voltagepin);
    Serial.println(involt);
    //delay(10);
    //inamp = analogRead(currpin); 
    //delay(10);
    inamp = analogRead(currpin); 
    
    
  
    //int temp2 = analogRead(voltagepin);
    String rawVal = "Read voltage on A0:  ";
    rawVal += String(involt,DEC);
    myGLCD.print(rawVal, LEFT, 28);

 

    float calcV = (involt/69.35);
     String decimal = dtostrf(calcV,5,2,"stuff");
    String voltdisp = "Calculated Voltage:  ";
    voltdisp += decimal;
    myGLCD.print(voltdisp, LEFT, 42); 
   
  //}


}

Would achieving accuracy to .1v be achieved by hardware, code, or both?

Loren

The voltage is still varying by 2 or 3 volts.

That is a very long way off and suggests that something is seriously wrong.

The odd thing is that if you use the internal reference the maximum voltage you can measure is 1.1V so how can that vary by 2 or 3 volts?

Can you post a schematic?

I added analogReference(INTERNAL1V1); this morning but I don’t think that helped a whole lot. The voltage is still varying by 2 or 3 volts.

This cannot possibly work. If you use the 1,1V reference, you can only measure between 0 and 1.1V

int voltagepin = 1;

This looks like trouble. The analog input pins use the defined code names A0, A1, A2.....

Pin 1 isn't even a good pin to use for digital I/O, because on most arduinos it is the serial connection pin.

int involt = 0;
int currpin = 0;
int inamp = 0;

Same for this nonsense.

Your readings are not just "inaccurate", they are complete garbage.

I suggest you look at the basic examples and tutorials for analog input.

I think I’ve got it…

Yesterday I found this code segment:

long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  
 
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
 
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
 
  long result = (high<<8) | low;
 
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

It does the same thing as the examples in the OP but pertains to a mega. Then I decided to take a step back and think for a minute. There were too many unknowns and I was guessing too much. So I simplified my sketch to show exactly what voltages were coming off of the sensor. When I was able to verify with a meter that the voltages the display were stable and matched I figured out the offset that I needed to calculate the voltage being read by the sensor. So for now I think that I have what I need.

Here is what my sketch looks like now:

#include<UTFT.h>
#include<UTouch.h>
#include "Menus.h"


extern uint8_t SmallFont[];
//UTFT myGLCD(ITDB32S, 38,39,40,41);   // Remember to change the model parameter to suit your display module!
//UTouch myTouch(6,5,4,3,2);


//Sketch Variables

int mnu;
int x,y;
int voltagepin = 0;
int involt = 0;
int currpin = 1;
int inamp = 0;

Menus menus;

void setup(){

  Serial.begin(9600);
  analogReference(INTERNAL1V1);

  menus.initScrn();
  menus.homeScrn();
 
}


void loop(){
  menus.touched();


  //if(mnu==0){

    //involt = analogRead(voltagepin);
    //delay(10);
    involt = analogRead(voltagepin);
    Serial.println(involt);
    //delay(10);
    //inamp = analogRead(currpin); 
    //delay(10);
    inamp = analogRead(currpin); 
    
    
  
    //int temp2 = analogRead(voltagepin);
    String rawVal = "Read voltage on A0:  ";
    rawVal += String(involt,DEC);
    myGLCD.print(rawVal, LEFT, 28);

 

    float calcV = (involt/69.35);
     String decimal = dtostrf(calcV,5,2,"stuff");
    String voltdisp = "Calculated Voltage:  ";
    voltdisp += decimal;
    myGLCD.print(voltdisp, LEFT, 42); 
    
    
    float Vcc = readVcc()/1000.0;
    float Voltage = ((involt / 1023.0) * Vcc)/.0584;
    
    String refString = "Reference voltage is:  ";
    String decifloat = dtostrf(Vcc,5,2,"stuff");
    refString += decifloat;
    myGLCD.print(refString, LEFT, 56); 
    
    String calcedIn ="Calculated A0 in is:  ";
    String temp3 = dtostrf(Voltage,5,4,"stuff");
    calcedIn += temp3;
    myGLCD.print(calcedIn, LEFT, 70); 
    
  //}


}

long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  
 
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
 
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
 
  long result = (high<<8) | low;
 
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

michinyon: int voltagepin = 1;

This looks like trouble. The analog input pins use the defined code names A0, A1, A2.....

No.

They can also be referred to as simply 0, 1, 2 and so on like:-

val = analogRead(1);
// or
int apin = 1;
val = analogRead(apin);

The OP seems to be using a special version of google which directs him or her to the most complicated version of their problem in preference to straightforward explanations.

Anyway, you now have code that will work on any kind of processor.

michinyon:

int voltagepin = 1;

This looks like trouble. The analog input pins use the defined code names A0, A1, A2…

Pin 1 isn’t even a good pin to use for digital I/O, because on most arduinos it is the serial connection pin.

No. For legacy reasons analog input pins can be addressed using three different methods, however the third method is not portable across different arduino boards (example Uno Vs Mega boards).

For example a Uno, these three result in the same analog input pins being addressed:

  1. A0 to A5
  2. 0 to 5 // not the same as digital pin addresses 0 to 5, the analogRead() function ‘just knows’ it means the analog input pins not digital pins
  3. 14 to 19

A0…A5 is the most recent and best method of using analog pins, even if using them as digital pins.

michinyon: The OP seems to be using a special version of google which directs him or her to the most complicated version of their problem in preference to straightforward explanations.

Anyway, you now have code that will work on any kind of processor.

Does anyone have a simpler way that would be just as accurate?