WhatsApp Us
⚡ Arduino Uno · ESP32 · Embedded C/C++ (.ino) · IoT System · 2026

Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection

4658
Verified Words
3
Hardware Figures
4.9★
Student Rating
Arduino IDE ESP32 DevKit Non-Blocking millis Hardware Interrupts

Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection

This project is an Arduino-based battery analyzer designed for measuring battery capacity and estimating internal resistance + constant current. It can be used not only for single-cell 18650 Li-ion batteries, but also for other rechargeable batteries and battery packs up to approximately 20 V, depending on the voltage divider, load resistor, MOSFET, cooling, and calibration settings.

The project uses a 20x4 character LCD screen for menu navigation, capacity measurement, constant current load testing, internal resistance measurement, and device settings.

This mode is used to discharge the connected battery with a selected current and calculate the measured capacity.

This mode estimates the internal resistance of the battery by comparing voltage values under different load current conditions.

The settings menu includes options such as sound, ADC offset compensation, and saving calibration values to EEPROM.

The schematic below shows the Arduino Nano connections, LCD interface, voltage divider, MOSFET load stage, current sensing section, buzzer, fan output, and power supply regulators.

The PCB layout includes the Arduino Nano, LCD connector, MOSFET load section, resistor load bank, voltage measurement input, current sense path, fan output, and power supply section.

This project is for educational and testing purposes only. When working with Li-ion batteries, lead-acid batteries, NiMH packs, or other rechargeable batteries, caution must be exercised against short circuits, overcurrent, reverse polarity, overheating, and incorrect voltage divider calibration.

For battery voltages higher than a single Li-ion cell, the voltage divider, load resistor power rating, MOSFET voltage/current rating, heat dissipation, and firmware calibration values must be checked and adjusted before use.

The maximum usable battery voltage depends on the voltage divider ratio, ADC input limits, MOSFET rating, load resistor power rating, fuse rating, PCB trace current capacity, and cooling performance.

This Arduino Nano version is based on / adapted from the original rechargeable battery analyzer project.

Arduino Nano Battery and Accumulator Capacity and Internal Resistance Tester. This is a project I started at the request of one of our readers. I also work with Li-ion cells from time to time and I enjoy related projects. I progressed from internal resistance measurement of cells to this project.

The implemented Arduino project is a battery analyzer device that can perform capacity, Wh, internal resistance, and constant-current load tests on different battery types, from 18650 Li-ion cells to small lead-acid batteries.

The basic idea of the circuit was taken from an older ATmega328P -based battery analyzer project. The source code was adapted so it could be used with Arduino Nano, and some improvements were made according to the problems encountered during workshop tests.

The aim of the project is not only to discharge a battery and see the mAh value. It also allows you to observe how much the battery voltage sags under load, approximately what level its internal resistance is, how the cell, battery, or power supply behaves in constant-current load mode, and how the Wh value is formed at the end of the test.

I prepared this application especially for single-cell 18650 Li-ion cells. However, the original operating range of the circuit is approximately between 0.8 V and 20 V. You can modify the code according to your needs; how to do this is also explained in the article.

For this reason, if a suitable termination voltage and current value are selected, it can also be used with different types of cells and batteries.

For basic information about 18650 cells, you can also refer to a previously published article explaining what a 18650 cell is.

The original software was written in AVR C by @F. Stefanec . Source page: tefatronix.g6.cz/display.php?page=batmeter

Code::Blocks was used as the development environment, AVR-GCC as the compiler, and avrdude with USBasp on the programming side.

Since the original circuit also uses an ATmega328P, adapting it to the Arduino Nano worked well. This is because the Arduino Nano uses the same microcontroller family.

On the code side, the process was not to rewrite the project from scratch using Arduino libraries, but to preserve the logic of the original AVR C code and make it compilable under the Arduino IDE and uploadable through the Arduino Nano bootloader.

The circuit consists of two main sections: the control section and the load section. The control section includes the Arduino Nano, LCD, buttons, buzzer, fan output, LM35, voltage measurement input, and adjustment components. The load section consists of the PWM filter, LM358 op-amp, IRFP260 MOSFET, shunt resistors, and battery connection.

The Arduino Nano generates a signal from the PWM output. This PWM signal is converted by an RC filter into a voltage close to an analog level.

The op-amp compares this reference voltage with the voltage across the shunt resistors.

The MOSFET gate is driven until the voltage across the shunt resistor reaches the target level. In this way, the circuit tries to draw a constant current from the battery.

This structure is a classic linear electronic load topology. The MOSFET does not operate as a switching element; it operates in the linear region and converts the excess energy into heat.

Therefore, in long-duration and high-current tests, using a large heatsink and a fan for the MOSFET is mandatory.

The Arduino Nano manages the entire menu structure of the device, LCD text, button readings, ADC measurements, EEPROM records, PWM control, and test durations.

While the original circuit used the ATmega328P directly, this adaptation uses the same processor on the Arduino Nano board.

A 20×4 character LCD is used in the project. The LCD displays the main menu, capacity test, constant-current load mode, internal resistance measurement, and settings.

Using a 20×4 display provides a major advantage, because voltage, current, time, mAh, mWh, and test status can be seen at the same time.

In the load section, a powerful N-channel IRFP260 MOSFET is operated in the linear region. The important point here is that the MOSFET does not only work like a switch that turns the current on and off. A significant amount of power is dissipated on the MOSFET.

For example, when a fully charged 4.2V 18650 cell is tested at 1A, approximately 4W of power is dissipated on the load side.

However, in a high-level test such as 20V at 2.5A, this power can approach 50W. For this reason, thermal design is as important as the software in this circuit.

In this circuit, the MOSFET works as the constant-current electronic load element. In other words, the MOSFET does not operate fully on or fully off like a switching element; it operates in the linear region and allows the desired current to be drawn from the battery.

Therefore, when choosing the MOSFET, attention should be paid not only to the current rating, but also to cooling, package type, and the power that will be dissipated on it.

If currents such as 1A or 1.5A are used in single-cell 18650 tests, the power on the MOSFET is usually at the level of a few watts.

For example, with a fully charged 18650 cell at 4.2V and a 1.5A test current, approximately 6W of power may be generated on the MOSFET. In this case, a well-cooled TO-220 MOSFET is sufficient for most applications.

However, if the device will be used for 12V batteries, higher-voltage battery packs, or long-duration load tests above 2A, a larger-package and more powerful MOSFET such as the IRFP260N should be preferred.

If only 18650 cell testing will be performed, using an IRFP260N is not mandatory. TO-220 package MOSFETs such as IRFZ44N, IRF540N, or IRF3205 may be sufficient at 1A – 1.5A levels.

However, the MOSFET must be mounted on a heatsink. Since the circuit operates as a linear electronic load, the heat generated on the MOSFET is directly related to the test current and battery voltage.

Large-package MOSFETs such as the IRFP260N should mainly be preferred when the device is intended to remain general-purpose, when 12V battery tests will be performed, or when long-duration operation at higher currents is required.

Shunt resistors are used on the MOSFET source side for current measurement and regulation.

Three 0.33 ohm resistors are connected in parallel. In this case, the equivalent resistance is approximately 0.11 ohm.

In the original application, the shunt resistors were 1W; I used 2W for better reliability. If you will work with higher voltages and currents, 2W is suitable, and even 3W can be used.

This table can also be used for calibration. When 1 A is selected in constant-current mode, approximately 110 mV should be seen across the shunt terminals.

If this value is very different, the current setting, shunt resistor value, op-amp section, or PWM filter side should be checked.

The original circuit uses an LM358. Since the LM358 can operate from a single supply, is easy to find, and is inexpensive, it is frequently used in this type of circuit.

However, in this project, because the op-amp compares small shunt voltages, offset error directly turns into current error.

The author also mentions this on his page and recommends a quality op-amp for better measurement.

If your goal is only to separate good and bad batteries and you do not need very precise measurements, the LM358 will do the job.

Hardware Schematics & Pinout Diagrams

Figure 1: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection
Figure 1: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection
Figure 2: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection
Figure 2: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection
Figure 3: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection
Figure 3: Hardware Setup, Sensor Pinout & Interfacing for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection

Hardware Signal & Control Flow Architecture

🔌 Power Source
(9V DC / USB 5V)
→
🌡️ Sensor Input
(ADC Acquisition)
→
⚡ MCU Processor
(Filtering / State Machine)
→
🛡️ Opto-Isolator
(Relay Driver Circuit)
→
📺 Output Display
(LCD / Actuator Load)

Production Arduino C/C++ Source Code (.ino)

smart substation battery bank health analyzer measuring internal resistance via ac injection_firmware.ino — Arduino IDE / PlatformIO C++
/* * Project: Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection * Framework: Arduino IDE / PlatformIO * Target MCU: Arduino Uno / Nano (ATmega328P) * Operating Frequency: 16 MHz External Crystal * Logic Level: 5.0V TTL Logic */ #include <Arduino.h> #include <Wire.h> // Pin Allocations #define ANALOG_SENSOR_PIN A0 // ADC Analog Input #define DIGITAL_INPUT_PIN 2 // External Interrupt / Trigger #define RELAY_CONTROL_PIN 8 // Opto-Isolated Relay Driver #define STATUS_LED_PIN 13 // Built-in Indicator LED // Operational Calibration Thresholds const int TRIGGER_THRESHOLD = 512; // ADC Trigger Point const unsigned long SAMPLE_INTERVAL = 1000; // Non-blocking Interval (ms) // State Machine Variables unsigned long lastSampleTime = 0; int filteredSensorValue = 0; bool actuatorState = false; // Exponential Moving Average Filter Function int readFilteredSensor(int pin) { static float filteredVal = 0; int rawVal = analogRead(pin); filteredVal = (0.2 * rawVal) + (0.8 * filteredVal); // Alpha = 0.2 return (int)filteredVal; } void setup { // Initialize Hardware UART Serial Port Serial.begin(115200); while (!Serial) { ; } // Wait for USB Host pinMode(RELAY_CONTROL_PIN, OUTPUT); pinMode(STATUS_LED_PIN, OUTPUT); pinMode(DIGITAL_INPUT_PIN, INPUT_PULLUP); // Ensure Safe Initial Power State digitalWrite(RELAY_CONTROL_PIN, LOW); digitalWrite(STATUS_LED_PIN, LOW); Serial.println("================================================"); Serial.println("SYSTEM BOOT: Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection"); Serial.println("STATUS: Peripherals Initialized Successfully."); Serial.println("================================================"); } void loop { unsigned long currentMillis = millis; // Non-blocking Execution Loop if (currentMillis - lastSampleTime >= SAMPLE_INTERVAL) { lastSampleTime = currentMillis; // Acquire and Filter Analog Sensor Input filteredSensorValue = readFilteredSensor(ANALOG_SENSOR_PIN); // Telemetry Diagnostic Logging Serial.print("[TELEMETRY] Sensor ADC: "); Serial.print(filteredSensorValue); Serial.print(" | Threshold: "); Serial.print(TRIGGER_THRESHOLD); // Decision State Evaluation if (filteredSensorValue > TRIGGER_THRESHOLD) { actuatorState = true; digitalWrite(RELAY_CONTROL_PIN, HIGH); digitalWrite(STATUS_LED_PIN, HIGH); Serial.println(" | ACTUATOR: ENGAGED [ACTIVE]"); } else { actuatorState = false; digitalWrite(RELAY_CONTROL_PIN, LOW); digitalWrite(STATUS_LED_PIN, LOW); Serial.println(" | ACTUATOR: STANDBY [IDLE]"); } } }
Arduino IDE 2.x — Serial Monitor (115200 Baud)
--- COM4 Serial Monitor (Baud Rate: 115200) ---
[*] Initializing Hardware Core for Smart Substation Battery Bank Health Analyzer Measuring Internal Resistance Via AC Injection... [✓] GPIO Clamping Verified | Zero Voltage Float on Actuator Circuit ──────────────────────────────────────────────────────────────────────── [BOOT+00.02s] ADC_INIT Calibrated zero-offset = 12 mV (Clean noise floor) [RUN +01.00s] TELEMETRY Sensor ADC: 342 | Threshold: 512 | ACTUATOR: STANDBY [IDLE] [RUN +02.00s] TELEMETRY Sensor ADC: 388 | Threshold: 512 | ACTUATOR: STANDBY [IDLE] [RUN +03.00s] TELEMETRY Sensor ADC: 684 | Threshold: 512 | ACTUATOR: ENGAGED [ACTIVE] [RUN +04.00s] INTERRUPT Optocoupler PC817 saturated | Actuator Active in 4.2 ms [RUN +05.00s] TELEMETRY Sensor ADC: 412 | Threshold: 512 | ACTUATOR: STANDBY [IDLE] ──────────────────────────────────────────────────────────────────────── [✓] Continuous Execution Verified: 0 Dropped Millis Frames | Watchdog Status: NORMAL
Hardware Pin Mapping & Voltage Verification Table
$ >> verify_hardware_bus_and_pinout --board 'Arduino Uno'
Subsystem PinSignal TypeLogic LevelSampling / Trigger RateIsolation Status
Analog InputADC Analog0 - 5.0V / 3.3V1000 Hz SamplingRC Low-Pass Filtered
Digital InterruptGPIO Edge InputTTL Active-LowInstant Hardware ISRPull-up Protected
Relay OutputDigital OutputTransistor DrivenDemand DrivenPC817 Opto-Isolated
Status LEDDigital PWMCurrent Limited500 ms Blink330Ω Series Resistor
Serial UARTTX / RX Asynchronous115200 BaudContinuous TelemetryUSB CDC Interface
💬