analogRead problem with Atmega328

Thread Starter

sumeryamaner

Joined May 29, 2017
117
I am designing a control module for RC aircraft and I have to monitor battery voltage in addition to other tasks.
I have truncated the code which is too long to post here. I have a function called "checkvolt()" which performs two succesive analogRead() and calculates the battery voltage. Then I print the result on an OLED display.
First let's have a look at the code:
Code:
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"

#define I2C_ADDRESS 0x3C
#define RST_PIN -1
#define batteryTestpin 14

SSD1306AsciiWire oled;

int volt;
float voltage;


void setup ()
{
  Wire.begin();
  Wire.setClock(400000L);
  analogReference(INTERNAL);
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
  oled.setFont(Callibri15);
  oled.setLetterSpacing(2);
  oled.clear();
  selftest();
}

void loop()
{
  for(int n = 0; n < 8; n = n + 2) // Continuously read and print the voltage values
  {
  checkvolt();
  oled.setCursor(0, n);
  oled.print("              ");
  oled.setCursor(0, n);
  oled.print(volt);
  oled.setCursor(64, n);
  oled.print(voltage);
  }
}


void selftest()
{
  oled.clear();
  oled.println("Testing...");
  checkvolt();
  oled.print("Battery: ");
  oled.println(voltage);
  delay(3000);
  oled.clear();
}


void checkvolt()
{
  volt = analogRead(batteryTestpin);
  volt = analogRead(batteryTestpin); // Two reads for better analogRead function
  voltage = volt * 11.8 / 1000.0; // voltage in Volts
}
As you can see, this is a test code but the voltage check is similar to the main code.
There are two voltage checks. One at the beginning, in the function "selftest()" and second in the "loop()".
They all use the function "checkvolt()" to check and calculate the voltage.
The variables (int)volt and (float)voltage are globally defined.
The battery is a 2S LiPo (nominal voltage 7.40 V).
In the circuit, there is a voltage divider from the battery to the "batterytestpin" of the Atmega328. It consists of a 100k and a 10k resistor. That means the battery voltage is being divided by a factor of 11.
For the voltage measurement I am using the internal 1.1V of the Atmega328.
I havea 100nF capacitor connected between the Aref pin and ground.

The problem:
* When I read the voltage from the function selftest() I get a result of 3.20 V.
* When I read the voltage from the loop() I get a result of 8.10V (The LiPo is almost fully charged) and this is the correct reading.
* Both functions call "checkvolt()" for the voltage measurement!

I cannot find any explanation for this behaviour.
Any ideas and suggestions are welcome...
 

danadak

Joined Mar 10, 2018
4,057
There is a limit of 10K SPS on analogRead(), I am suspicious that two consecutive
analogReads in checkvolt() should have a small delay between them so that s/h input
for A/D has settled. Try 1 mS.

Also your equation has / 1000, should be / 1024 as its a 10 bit converter.

lastly do this in checkvolt -

  1. volt = analogRead(batteryTestpin);
  2. volt = ( volt + analogRead( batteryTestpin ) ) / 2; // Two reads for better analogRead function
Will act as a noise filter by averaging.

Regards, Dana.
 
Last edited:

Thread Starter

sumeryamaner

Joined May 29, 2017
117

Thread Starter

sumeryamaner

Joined May 29, 2017
117
There is a limit of 10K SPS on analogRead(), I am suspicious that two consecutive
analogReads in checkvolt() should have a small delay between them so that s/h input
for A/D has settled. Try 1 mS.

Regards, Dana.
Thank you for the answer.
The code of the function checkvolt() is the same whether it has been called by loop() or by selftest(). I havetried to call checkvolt() acouple of times in the setup() as a kind of initialisation but there was no change.
 

Thread Starter

sumeryamaner

Joined May 29, 2017
117
checkvolt() does not have a return X. You need return X to return result X to the function that called checkvolt(). Here is reference to explain it: https://www.arduino.cc/en/Reference/FunctionDeclaration

Since it is void checkvolt(), nothing is returned to the function that called called checkvolt().
I have changed the code and I used a new Atmega328 but the result is the same.

Code:
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"

#define I2C_ADDRESS 0x3C
#define RST_PIN -1
#define batteryTestpin 14

SSD1306AsciiWire oled;

int volt;
float voltage;


void setup ()
{
  Wire.begin();
  Wire.setClock(400000L);
  analogReference(INTERNAL);
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
  oled.setFont(Callibri15);
  oled.setLetterSpacing(2);
  oled.clear();
  selftest();
}

void loop()
{
  for(int n = 0; n < 8; n = n + 2)
  {
  voltage = checkvolt();
  oled.setCursor(0, n);
  oled.print("              ");
  oled.setCursor(0, n);
  oled.print(volt);
  oled.setCursor(64, n);
  oled.print(voltage);
  }
}


void selftest()
{
  oled.clear();
  oled.println("Testing...");
  voltage = checkvolt();
  oled.print("Battery: ");
  oled.println(voltage);
  delay(3000);
  oled.clear();
}


float checkvolt()
{
  int v = analogRead(batteryTestpin);
  v = analogRead(batteryTestpin);
  float x = v * 11.8 / 1000.0;
  return x;
}
 

Raymond Genovese

Joined Mar 5, 2016
1,653
To use the 1.1 internal for Vref, you have to select it in ADMUX - right? Have you done that? The default value is AREF, Internal Vref turned off.

Edit: I mean I do see your line analogReference(INTERNAL); but is that doing it?

Edited again:

You can use the internal 1.1 to measure your Vcc whether from battery or USB.

This code, from https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/ works and should verify your battery voltage. It can be helpful to see how it is being done versus how you are doing it and reading the ADC.

C:
// from https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/

void setup()
{
  Serial.begin(9600);
}

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
}

void loop()
{
  Serial.println(readVcc());
  delay(1000);
}
hope it helps
 
Last edited:

djsfantasi

Joined Apr 11, 2010
9,237
Your original code should have worked. You were right that by manipulating the global variables, you don’t need to use “return” statements.

The only thing I see is that you may have an issue with interim calculations with a mix of int and long data types.

Try this:

Float x = (float(v) * 11.8) / 1000.0;
 

danadak

Joined Mar 10, 2018
4,057
I tried the double analogRead() and got no significant change one reading to next, in a
tight loop. So thats a dead end. Although the A/D does have a sample and hold, and
example projects do use a couple of mS of delay between readings. But I was reading
a singular value, on same pin, so S/H is just refreshed with same value over and over.
If there were a significant difference where S/H had to slew to a new value......maybe
could be a problem. If I have time I will try that.


Regards, Dana.
 
I looked at it further. Specifically, I set up the voltage divider and the cap as the TS said. Instead of a battery, I am simply using the 5V line on the Arduino.

Simplified code
C:
#define RST_PIN -1
#define batteryTestpin A0

int volt;
float voltage;
void setup ()
{
  Serial.begin(9600);
  analogReference(INTERNAL);
}
void loop()
{
  voltage = checkvolt();
  delay(1000);
}

  float checkvolt()
{
  int v = analogRead(batteryTestpin);
  v = analogRead(batteryTestpin);
  //float x = v * 11.8 / 1000.0;
  float x= (v*1.1)/1024;
  Serial.print("V at analog input=");
  Serial.println(x);
  Serial.print("V at 'battery'=");
  x*=11.0;
  Serial.println(x);
  return x;
}

I am seeing:
V at analog input=0.46
V at 'battery'=5.05
V at analog input=0.46
V at 'battery'=5.01
V at analog input=0.45
V at 'battery'=5.00
V at analog input=0.45
V at 'battery'=5.00
V at analog input=0.45
V at 'battery'=4.95
V at analog input=0.45
V at 'battery'=4.93
V at analog input=0.45
V at 'battery'=4.93
V at analog input=0.45
V at 'battery'=4.93

Which is all reasonable. So, I think he is using the 1.1 internal reference and I don't see an error. But with a 1.1v reference, each analog count = ~.001 v

Also, I don't get the 11.8 or the /1000 and you can see that I am using:
float x= (v*1.1)/1024; for the voltage at the divider and
x*=11.0; for the voltage of the "battery" or in this case the Arduino 5v pin.
 

djsfantasi

Joined Apr 11, 2010
9,237
I looked at it further. Specifically, I set up the voltage divider and the cap as the TS said. Instead of a battery, I am simply using the 5V line on the Arduino.

Simplified code
C:
#define RST_PIN -1
#define batteryTestpin A0

int volt;
float voltage;
void setup ()
{
  Serial.begin(9600);
  analogReference(INTERNAL);
}
void loop()
{
  voltage = checkvolt();
  delay(1000);
}

  float checkvolt()
{
  int v = analogRead(batteryTestpin);
  v = analogRead(batteryTestpin);
  //float x = v * 11.8 / 1000.0;
  float x= (v*1.1)/1024;
  Serial.print("V at analog input=");
  Serial.println(x);
  Serial.print("V at 'battery'=");
  x*=11.0;
  Serial.println(x);
  return x;
}

I am seeing:
V at analog input=0.46
V at 'battery'=5.05
V at analog input=0.46
V at 'battery'=5.01
V at analog input=0.45
V at 'battery'=5.00
V at analog input=0.45
V at 'battery'=5.00
V at analog input=0.45
V at 'battery'=4.95
V at analog input=0.45
V at 'battery'=4.93
V at analog input=0.45
V at 'battery'=4.93
V at analog input=0.45
V at 'battery'=4.93

Which is all reasonable. So, I think he is using the 1.1 internal reference and I don't see an error. But with a 1.1v reference, each analog count = ~.001 v

Also, I don't get the 11.8 or the /1000 and you can see that I am using:
float x= (v*1.1)/1024; for the voltage at the divider and
x*=11.0; for the voltage of the "battery" or in this case the Arduino 5v pin.
Missed where both the 11.8 and 1000 values were from. I should have caught that 1000 should have been 1024. And I misunderstood the 11.8 value.

Great catch, @Raymond Genovese
 
Missed where both the 11.8 and 1000 values were from. I should have caught that 1000 should have been 1024. And I misunderstood the 11.8 value.

Great catch, @Raymond Genovese
I guess a point is that if you are going to measure a battery voltage that is maybe 7-8v, why use the internal 1.1v reference, because you lose some much resolution with having to use a divider like that - why not use the 5V? Am I missing something there?
 

Thread Starter

sumeryamaner

Joined May 29, 2017
117
Thank you all for the great advices.

I am using the 1.1V internal reference to achieve better accuracy because if I use the Vcc as reference, fluctuations in the supply voltage would lead to unreliable results. (Btw I am planning to use aprecision external reference).

The statement "analogReference(INTERNAL);" should select the internal 1.1V reference according to the documentation.

About the 11.8 and 1000...
volt = analogRead(pin);
I have to multiply with 11 for the voltage divider.
Then I have to multiply with 1100 (mV) and divide by 1024.
Now...
11 * 1100 / 1024 = 11.8...
volt * 11.8 is voltage in mV.
Divided by 1000 gives V value.

Double read is especially useful when changing analog inputs but as you pointed out, there is no benefit if I am using only one analog input. But this cannot be the reason of my problem.

There is no explanation and fix for the two different results of the same piece of code (checkvolt()) under the same conditions.

What do you get if you replace the 'loop' code with simply 'selftest();' ?
I will try this...
 
Thank you all for the great advices.

I am using the 1.1V internal reference to achieve better accuracy because if I use the Vcc as reference, fluctuations in the supply voltage would lead to unreliable results. (Btw I am planning to use aprecision external reference).

The statement "analogReference(INTERNAL);" should select the internal 1.1V reference according to the documentation.

About the 11.8 and 1000...
volt = analogRead(pin);
I have to multiply with 11 for the voltage divider.
Then I have to multiply with 1100 (mV) and divide by 1024.
Now...
11 * 1100 / 1024 = 11.8...
volt * 11.8 is voltage in mV.
Divided by 1000 gives V value.

Double read is especially useful when changing analog inputs but as you pointed out, there is no benefit if I am using only one analog input. But this cannot be the reason of my problem.

There is no explanation and fix for the two different results of the same piece of code (checkvolt()) under the same conditions.


I will try this...

OK, I see the 11.8 and /1000 now that you explained it and it looks fine to me. But, I can't duplicate your error.

When I run this with the 5V supply into the divider as described...
C:
#define RST_PIN -1
#define batteryTestpin A0

int volt;
float voltage;
void setup ()
{
  Serial.begin(9600);
  analogReference(INTERNAL);
  int v = analogRead(batteryTestpin);
  v = analogRead(batteryTestpin);
  float x = v * 11.8 / 1000.0;
  Serial.print("Battery: ");
  Serial.println(x);
  delay(3000);
}
void loop()
{
  voltage = checkvolt();
  delay(1000);
}

  float checkvolt()
{
  int v = analogRead(batteryTestpin);
  v = analogRead(batteryTestpin);
  float x = v * 11.8 / 1000.0;
  Serial.print("V at 'battery'=");
  Serial.println(x);
  return x;
}
I get this output:
Battery: 5.03
V at 'battery'=5.14
V at 'battery'=5.05
V at 'battery'=5.09
V at 'battery'=5.10
V at 'battery'=5.01
V at 'battery'=5.09
V at 'battery'=5.11
V at 'battery'=5.01
V at 'battery'=5.11
V at 'battery'=5.07
V at 'battery'=5.05
V at 'battery'=5.10
V at 'battery'=5.11
V at 'battery'=5.11
V at 'battery'=5.07

So, yes, there is some fluctuation, but nothing like the error you reported. I even put two reads (one or two makes no difference) in setup() thinking that maybe a delay was needed after setting analogReference(INTERNAL);

I don't know.
 
I think my first step should be using the Vcc as reference and not the internal 1.1V.
Are you reliably getting that initial errant read?

The problem:
* When I read the voltage from the function selftest() I get a result of 3.20 V.
That is what I am stuck on. While trying to reproduce your error, I used both the internal 1.1v reference as well as the default 5v and it made no difference.

Is there anything funky about your SSD1306 library? I ask simply because I am just using plain old Serial. Maybe just reaching there, but noting some differences. Is there something about the battery that we are missing?

Please do let us know what happens.
 
Top