There are a lot of fuel gauges and battery chargers available for 3.7V lithium batteries, but when it comes to lower 1.5-1.2V batteries, there are very limited circuits and dedicated ICs. I came across using alkaline batteries somehow which is a small battery-powered project. And I wonder about the battery a lot; this may be clear to you if you see my page. I want to estimate the battery life, but how is it possible with these non-rechargeable batteries? Is there any technique to measure the State of Charge (SoC)? Unlike Li-ion batteries, alkaline batteries have a pretty flat voltage curve that depends on the load. Cell-to-cell variations are also a major drawback in these small batteries. The best possible and simplest one is estimation through voltage. And certainly, we're gonna use this approach, but in combination with a load. I will let you know the details. I’ll walk through a practical SoC estimation method that works well on low-cost microcontrollers without using a dedicated fuel-gauge IC.

The two-step SOC measurement approach:

● Voltage-based SoC (for long-term reference)

● ΔI (current difference) under two loads (for accuracy)

This approach is very low-cost and easy to implement. And suitable for all kinds of 1.5V alkaline batteries. But as we know, the cell-to-cell chemistry is not in our hands, so there may be an accuracy of 90-92% only.

Typical Error in Measurement with differnt methods:

The two-step voltage SOC verification is required because fresh alkaline cells can read anywhere from 1.55–1.65 V. Mid-life cells still sit around 1.3–1.4 V. Under load, voltage droops heavily and recovers slowly. Temperature and internal resistance dominate the reading. With only voltage measurement, we can reach up to an accuracy of ±70–80%. So, we need more information than measuring voltage alone.

mini_IMG_0033.jpg

That is where a ΔI measurement comes into the picture; instead of directly computing internal resistance, we can observe how current collapses under different loads. I also tried the internal resistance method, but it needs very huge setup, which most hobbyists do not want to purchase. And with DC measurement, I certainly reach a saturation level where the internal resistance stops responding to measurements.

mini_IMG_0032.jpg

The idea is to use two known load resistors, Load 1 with 22 Ω and Load 2 with 9.7 Ω (2*4.7 Ω + offset. The values are not random, but at full swing, change should produce ΔI = I{9.7} - I{22} greater than 80 mA for better resolution over the range. What happens at the cell level is:

● Fresh battery → high current difference

● Weak battery → currents converge → ΔI collapses

Problem with this setup + Scope of improvement:

Because in the last step we are mapping values to a real datasheet, there might be some changes due to slope errors, which you can correct with proper calibration. The second error chance in the ΔI measurement is because, as the cell voltage decreases, the current through both resistors changes, and that change should be compensated or linearized based on the chemistry of the battery. That’s how I have implemented the indirect internal resistance in the code.

A better approach can be used with the use of a constant current load, but it comes with additional BOM and cost. However, it will make the measurements too easy. I will share one example in the end. This will drive the MOSFET into the saturation region, so when two constant currents flow, it will compute the voltage drop.

Components Required:

mini_IMG_0025.jpg

  • P-channel MOSFET
  • Arduino Nano
  • 2* 1.5V battery (one discharged to 50%)
  • 9.7 Ω and 22 Ω resistors
  • Power source

Circuit Diagram:

Screenshot_2026_01_25-15.png

Here is the circuit diagram. Just with 4 components, you can build this circuit. Now all the computations are done with the help of the MCU. Q1 is turned on in the linear region for a small duration, and the measurements are performed in the same way as Q2. Download all the material from here.

Measurement Flow

First, the open-circuit voltage of the circuit is measured by the Arduino's 10-bit ADC. Then MOSFET 1 with a 22 Ω load is turned on for a short period, and the voltage under the load is measured; similarly with a 9.7 Ω load. After the measurement, the computation is done on ΔI. Then, based on the readings, the values are mapped, and SOC is estimated. Here are the proper steps:

  • Measure open-circuit voltage (Voc)
  • Apply 22 Ω load → measure voltage → compute current
  • Apply 9.7 Ω load → measure voltage → compute current
  • Compute: ΔI

Arduino Code:

SOC measurement rules:

/**************************************************
 * Alkaline Battery SoC Estimation
 * Method 1: ΔI between 9.7Ω & 22Ω
 * Method 2: Voltage-SoC LUT (with calibration)
 **************************************************/

#define ADC_PIN        A0
#define MOSFET_LOW     6     // 9.7Ω load
#define MOSFET_HIGH    7     // 22Ω load

#define R_LOW   9.7
#define R_HIGH  22.0

#define ADC_REF       4.83
#define ADC_RES       1023.0
#define DIVIDER_RATIO 1.0

// ---------- USER CALIBRATION ----------
#define VOLTAGE_GAIN   1.000   // slope calibration
#define VOLTAGE_OFFSET 0.000   // offset calibration (Volts)

// ---------- ADC SETTINGS ----------
#define ADC_SAMPLES   12

// ---------- TIMING (ms) ----------
#define LOAD_PULSE_MS 20
#define RECOVERY_MS   300

// ---------- ΔI THRESHOLDS ----------
#define DI_MAX  81.0   // mA → 100%
#define DI_MIN  61.0   // mA → 0%

// ---------- VOLTAGE–SOC LUT ----------
#define VSOC_POINTS 11

const float voltLUT[VSOC_POINTS] = {
  1.60, 1.55, 1.50, 1.45, 1.40,
  1.35, 1.30, 1.25, 1.20, 1.10, 1.00
};

const float socLUT[VSOC_POINTS] = {
  100, 90, 80, 70, 60,
  50, 40, 30, 20, 10, 0
};

// -------------------------------------------------
// ADC voltage read with averaging
// -------------------------------------------------
float readBatteryVoltage()
{
  long sum = 0;
  for (int i = 0; i < ADC_SAMPLES; i++) {
    sum += analogRead(ADC_PIN);
    delay(2);
  }
  float adc = sum / (float)ADC_SAMPLES;
  return (adc * ADC_REF / ADC_RES) * DIVIDER_RATIO;
}

// -------------------------------------------------
// Voltage → SoC using LUT + interpolation
// -------------------------------------------------
float voltageToSoC(float V)
{
  if (V >= voltLUT[0]) return 100.0;
  if (V <= voltLUT[VSOC_POINTS - 1]) return 0.0;

  for (int i = 0; i < VSOC_POINTS - 1; i++) {
    if (V <= voltLUT[i] && V >= voltLUT[i + 1]) {
      float t = (V - voltLUT[i + 1]) /
                (voltLUT[i] - voltLUT[i + 1]);
      return socLUT[i + 1] +
             t * (socLUT[i] - socLUT[i + 1]);
    }
  }
  return 0.0;
}

// -------------------------------------------------
// ΔI → SoC conversion
// -------------------------------------------------
float deltaIToSoC(float deltaI_mA)
{
  if (deltaI_mA >= DI_MAX) return 100.0;
  if (deltaI_mA <= DI_MIN) return 0.0;

  return 100.0 * (deltaI_mA - DI_MIN) / (DI_MAX - DI_MIN);
}

// -------------------------------------------------
// Full battery measurement
// -------------------------------------------------
void measureBattery()
{
  float Voc_raw, Voc_cal;
  float V_low, V_high;
  float I_low, I_high;
  float deltaI_mA;
  float soc_deltaI, soc_voltage;

  // ---- Open-circuit voltage ----
  Voc_raw = readBatteryVoltage();
  Voc_cal = (Voc_raw * VOLTAGE_GAIN) + VOLTAGE_OFFSET;

  soc_voltage = voltageToSoC(Voc_cal);

  // ---- 22Ω load ----
  digitalWrite(MOSFET_HIGH, HIGH);
  delay(LOAD_PULSE_MS);
  V_high = readBatteryVoltage();
  digitalWrite(MOSFET_HIGH, LOW);

  delay(RECOVERY_MS);

  // ---- 9.7Ω load ----
  digitalWrite(MOSFET_LOW, HIGH);
  delay(LOAD_PULSE_MS);
  V_low = readBatteryVoltage();
  digitalWrite(MOSFET_LOW, LOW);

  // ---- Currents ----
  I_high = V_high / R_HIGH;
  I_low  = V_low  / R_LOW;

  deltaI_mA = (I_low - I_high) * 1000.0;
  soc_deltaI = deltaIToSoC(deltaI_mA);

  // ---- Print ----
  Serial.println("===== Battery Measurement =====");

  Serial.print("Voc (raw): ");
  Serial.print(Voc_raw, 4);
  Serial.println(" V");

  Serial.print("Voc (cal): ");
  Serial.print(Voc_cal, 4);
  Serial.println(" V");

  Serial.print("SoC (Voltage): ");
  Serial.print(soc_voltage, 1);
  Serial.println(" %");

  Serial.print("V(22Ω): ");
  Serial.print(V_high, 4);
  Serial.println(" V");

  Serial.print("I(22Ω): ");
  Serial.print(I_high * 1000.0, 1);
  Serial.println(" mA");

  Serial.print("V(9.7Ω): ");
  Serial.print(V_low, 4);
  Serial.println(" V");

  Serial.print("I(9.7Ω): ");
  Serial.print(I_low * 1000.0, 1);
  Serial.println(" mA");

  Serial.print("ΔI: ");
  Serial.print(deltaI_mA, 1);
  Serial.println(" mA");

  Serial.print("SoC (ΔI): ");
  Serial.print(soc_deltaI, 1);
  Serial.println(" %");

  Serial.println("================================");
}

// -------------------------------------------------
// Setup & loop
// -------------------------------------------------
void setup()
{
  Serial.begin(9600);

  pinMode(MOSFET_LOW, OUTPUT);
  pinMode(MOSFET_HIGH, OUTPUT);

  digitalWrite(MOSFET_LOW, LOW);
  digitalWrite(MOSFET_HIGH, LOW);

  Serial.println("Alkaline Battery SoC Meter (Voltage + ΔI)");
}

void loop()
{
  measureBattery();
  delay(5000);
}

● Works best at room temperature

● Thresholds depend on resistor values and pulse width

● Same chemistry and similar cell size recommended

● Allow recovery time between pulses

Calibrate the Setup:

The calibration of the ΔI profile is linearized over the range of 20 to 80%. And the voltage range is over 10 to 90%. However, the slope can be adjusted based on the battery used, because in most highly reliable devices, we either have Duracell as standard or some other brand. By analyzing the trend, we got:

Screenshot_2026_01_25-14.png

Calibrating ΔI:

// ΔI → SoC conversion
// -------------------------------------------------
float deltaIToSoC(float deltaI_mA)
{  if (deltaI_mA >= DI_MAX) return 100.0;  if (deltaI_mA <= DI_MIN) return 0.0;
  return 100.0 * (deltaI_mA - DI_MIN) / (DI_MAX - DI_MIN);
}


Calibrating Voltage:

#define VOLTAGE_GAIN   1.000   // slope calibration
#define VOLTAGE_OFFSET 0.000   // offset calibration (Volts)

This works because ΔI implicitly encodes internal resistance, without explicitly calculating it. But if you test the other batteries rather than these standard ones, this method gives accuracy up to at least 85%.

Taking the Project to the Next Level - With JUSTWAY

Screenshot_2025_12_14-13.png

Electronics without proper housing and the audio circuit, they simply would not work. Yes! Because to keep the system available to us a proper 3D casing should be there. JUSTWAY assists you in turning your do it yourself project into a high-quality prototype that feels and looks like a genuine product that is ready for the market. What they do:

Screenshot_2025_12_14-14.png

  • Rapid Prototyping
  • CNC Machining (Aluminum 6061 / Stainless Steel 304)
  • Sheet Metal Fabrication
  • Injection Molding
  • Urethane Casting
  • 3D Printing (SLA & HPA-PA12)

Upload your CAD files at JUSTWAY.com, Select the material & finish then preview your model in 3D and place your order.

Results:

Test1: Discharged battery

op1.jpg

Test2: Fully charged one

op2.jpg

Final Thoughts:

mini_IMG_0029.jpg

This project shows that you don’t need a fuel-gauge IC to estimate alkaline battery SoC accurately. By combining intelligent load switching, current measurement, and a little empirical modeling, we can build a low-cost alkaline battery SOC measurement tool. Now this method can be integrated into the firmware of any device. But to do so, we should have proper calibration values. Still, if you need a proper SOC with 95-99% accuracy, then go for a fuel gauge IC solution. Test the services offered by JUSTWAY.com and turn your ideas into actual products