ADC fluctuation - Battery level reading unstable

Hi everyone,

In Arduino Mega, I have implemented a voltage meter for a 3.7v Li-Ion battery which is unstable because the ADC fluctuates between 150-154. How can I avoid this fluctuation? Just averaging the readings?

Tried using 0.1uF capacitors between analog port A6 and GND, and between the 5V pin and GND, but no change observed.

Thank you!

// Define analog input
#define ANALOG_IN_PIN A6
 
// Floats for ADC voltage & Input voltage
float adc_voltage = 0.0;
float in_voltage = 0.0;
 
// Floats for resistor values in divider (in ohms)
float R1 = 30000.0;
float R2 = 7500.0; 
 
// Float for Reference Voltage
float ref_voltage = 5.0;
 
// Integer for ADC value
int adc_value = 0;
 
void setup()
{
   // Setup Serial Monitor
   Serial.begin(9600);
   Serial.println("DC Voltage Test");
}
 
void loop(){
   // Read the Analog Input
   adc_value = analogRead(ANALOG_IN_PIN);
   
   // Determine voltage at ADC input
   adc_voltage  = (adc_value * ref_voltage) / 1024.0; 
   
   // Calculate voltage at divider input
   in_voltage = adc_voltage / (R2/(R1+R2)) ; 
   
   // Print results to Serial Monitor to 2 decimal places
  Serial.print("Input Voltage = ");
  Serial.println(in_voltage, 2);
  Serial.println(adc_value);
  
  delay(1000);
}

Wiring

**Serial port readings (Voltage read and ADC)

0.01uF or 0.1uF? Your wiring diagram shows the former, your text says the latter.

Also, from the "Analog Input Circuitry" section of the ATmega2560 datasheet:

The ADC is optimized for analog signals with an output impedance of approximately 10K or less. If such a source is used, the sampling time will be negligible. If a source with higher impedance is used, the sampling time will depend on how long time the source needs to charge the S/H capacitor, which can vary widely. The user is recommended to only use low impedant sources with slowly varying signals, since this minimizes the required charge transfer to the S/H capacitor.

Given that, you might want to try reducing your resistors to 10K and 2.5K.

Also, rather than using the default 5V reference, a more stable voltage reference (either one of the internal references or a well bypassed external voltage on the AREF pin) can help.

I’d be thinking an electrolytic of 10uF or greater would help flatten out the readings.

I think the resistors are low enough, you don’t want parasitic drain through the divider.

Is the battery connected to anything else other than the voltage divider?
How long are the wires to the battery?

Are you sure you need a voltage divider if the battery voltage is < to the reference voltage ?

Thank you for your reply!

  1. I made a mistake with the value of the capacitor in the initial diagram, I've already corrected it.
  2. I changed the resistor values and used the 1.1V reference voltage. Now, the ADC fluctuation is minimal, ranging between 723 and 721 initially, and then stabilizing to 722-723. :grinning:

Captura de pantalla 2024-05-20 102431

Thank you! I replaced the 0.1uF with 22uF (I don't have a 10uF on hand) and there was no major difference after changing the internal voltage from 5V to 1.1V, as well as the resistors.

Thank you @jim-p .

No, the only components I have connected for this test are the Arduino Mega, the voltage divider, the battery, and a 0.1uF capacitor as indicated in my diagram. The length of the cables is 10cm.
I'm going to see what happens now that I integrate this into the main circuit. :skull_and_crossbones:

Hi! At least now that I'm using a 1.1V reference, I should use the voltage divider.

There's ALWAYS a possibility of being on the hairy edge between two counts so you can't expect rock solid readings.

I conducted additional tests by adding an OLED screen and changing internal voltage, resistor values and power supply to the Mega.

I expect: Power up the system with the battery whose voltage level is being read and displayed on an OLED screen.

Problem: Voltage level fluctuating and inaccuracy in the reading.

I will:

  1. Try using an external source for the reference voltage.
  2. Test other values of R1 and R2 according to R2 / (R1+R2)**

Please, any feedback or correction, much appreciated. Thanks!

Test results (In yellow the ideal configuration: Battery 1 as power supply and being read):

Schem - Power Supply: USB Port / Reading voltage from: Battery 1

Schem - Power Supply: Battery External / Reading voltage from: Battery 1

Schem - Power Supply: Battery 1 / Reading voltage from: Battery 1 (IDEAL)

CODE:

#include <SPI.h>
#include <Wire.h>
#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
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
 
// Define analog input
#define ANALOG_IN_PIN A7
 
// Floats for ADC voltage & Input voltage
float adc_voltage = 0.0;
float in_voltage = 0.0;
 
// Floats for resistor values in divider (in ohms)
float R1 = 18000.0;
float R2 = 5000.0; 
 
// Float for Reference Voltage
float ref_voltage = 1.1; 
 
// Integer for ADC value
int adc_value = 0;
 
void setup()
{
  // Setup Serial Monitor
  Serial.begin(9600);
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }
  analogReference(INTERNAL1V1);
  display.clearDisplay();
}
 
void loop() {
   // Read the Analog Input
   adc_value = analogRead(ANALOG_IN_PIN);
   Serial.print("ADC = ");
   Serial.println(adc_value);
   
   // Determine voltage at ADC input
   adc_voltage  = (adc_value * ref_voltage) / 1024.0; 
   
   // Calculate voltage at divider input
   in_voltage = adc_voltage / (R2/(R1+R2)) ;
    
  // Print results to Serial Monitor to 2 decimal places
  Serial.print("Input Voltage = ");
  Serial.println(in_voltage, 2);
 
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(SCREEN_WIDTH -50, 0); 
  display.print("RC:");
  display.print(in_voltage, 2);
  display.println("v");+
  display.display();
  delay(1000);
 
}

You had no cases where it only varied by one count, such as going between 722 and 723 ?

About the accuracy of the ADC: the datasheet specifies an absolute accuracy of 3 LSB. That means that a reading of 151 really means: 151 +/- 3, so readings from 148 to 154 are within spec.

If you’d connect the battery directly to the input pin, as suggested in post#5, the reading might be around 750 (meaning 750 +/- 3), improving the accuracy of the total system 5 fold. However, this introduces a problem if the battery is connected and the supply voltage is switched off.

Another problem is the reference voltage. The supply from your PC is used not only to power your board, but also as reference voltage for the ADC. From my PC I’ve seen voltages as high as 5.1V and as low as 4.5V. That means that calibration is necessary.

The schematics in post#1 suggest using the internal reference of 1.1V. The datasheet specifies this as 1.0V to 1.2V. Here calibration is necessary too.

One possibility is connecting the battery with a 10k resistor in series to your input A6. This resistor is high enough to protect the input pin, but will not affect the measurement; there will be drain on the battery with the board supply off, not with supply on. I suggest to use the 3V3 output for calibration, that regulator is specified as 1%. The 3V3 can be connected to A7directly, in which case the formula is:
voltageBattery = 3.3 * adcResultA6 / adcResultA7 .

You may also use a voltage reference such as the TL431, that is available with 0.5%, 1% or 2% accuracy. The formula becomes:
2.5 * adcResultA6 / adcResultA7 .

If you’re using the battery that is measured for powering your board, bear in mind that the board alone may consume as much as 50mA.

Thank you so much for such detailed feedback! I did it just as you commented. I connected the battery to port A6 through a 10K resistor, and to A7, the 3.3v for calibration. Additionally, I set the internal reference voltage to 5v, and it worked wonderfully for me.

PD: Sorry for my delayed reply, I had an authorization issue with my account that Arduino has already resolved.