Most lists of 'cool arduino projects' recycle the same weather stations, LED cubes, and line-following robots. But if you are building solar generators, DIY power walls, or off-grid systems, the most practical and genuinely useful project you can build is a precision LiFePO4 cell capacity tester. By pairing an Arduino Nano Every with a 16-bit ADS1115 analog-to-digital converter (ADC) and an INA219 current sensor, you can measure true cell capacity down to the milliamp-hour (mAh) and map the exact discharge curve of your battery cells.
Why is this necessary? The classic Arduino 10-bit internal ADC yields a resolution of roughly 4.8mV per step at a 5V reference. Because LiFePO4 cells have an incredibly flat voltage curve between 3.2V and 3.3V (where 80% of their capacity lives), a 4.8mV resolution is useless for accurate state-of-charge (SoC) tracking. The external ADS1115 gives you 0.18mV resolution, turning a toy into a bench-grade diagnostic tool.
The Decision Matrix: Choosing Your Next Build
Not every project fits your current skill level or workshop needs. Use this decision tree to determine which project you should actually build today, terminating in our default recommendation for power systems makers.
| If your primary goal is... | And you want to learn... | Then build this project... | Core Component Pick |
|---|---|---|---|
| Visual aesthetics & lighting | Timing arrays & memory limits | Persistence of Vision (POV) Clock | ATmega328P + WS2812B LEDs |
| Home automation & relays | MQTT, WiFi, & web servers | Smart 4-Channel Relay Node | ESP32-WROOM-32 + 5V Relay Module |
| Motor control & robotics | PID loops & encoder decoding | Self-Balancing Camera Gimbal | Arduino Nano 33 IoT + MPU6050 |
| Power systems & batteries | I2C, Coulomb counting, & ADCs | LiFePO4 Capacity Tester (This Guide) | Arduino Nano Every + ADS1115 |
Default Pick: If you work with 12V/24V/48V battery banks, stop reading other lists and build the LiFePO4 Capacity Tester below. It directly pays for itself by identifying weak cells in your parallel strings before they cause a BMS shutdown.
Project Spec Sheet & Parts List
- Difficulty Rating: Intermediate (Requires I2C wiring, MOSFET logic, and basic soldering)
- Estimated Time: 2 hours for assembly, 4-12 hours for a full discharge test
- Estimated Cost: $35 - $45 USD (excluding the battery cell)
Here is the exact bill of materials. Do not substitute the MOSFET without checking the Vgs (Gate-Source threshold voltage) on the datasheet; you need a logic-level MOSFET that fully opens at 5V.
- Microcontroller: Arduino Nano Every (ATmega4809, 5V logic)
- ADC Module: Adafruit ADS1115 16-Bit I2C ADC Breakout
- Current Sensor: Adafruit INA219 High Side DC Current Sensor (Breakout board)
- Load Resistor: 20W 1-ohm chassis-mount aluminum resistor (e.g., Vishay RH0251R000FE02)
- Switching MOSFET: IRLZ44N N-Channel Logic-Level MOSFET
- Protection: 5A automotive blade fuse and inline holder
- Miscellaneous: 10kΩ pull-down resistor, 100Ω gate resistor, heavy-gauge wire (14 AWG) for the load path, alligator clips.
Fire Safety Note: While LiFePO4 chemistry is highly stable and does not suffer from the thermal runaway flame-venting of NMC lithium-ion cells, a dead short across the terminals will still push hundreds of amps, melting wire insulation and causing severe burns. Always place the 5A fuse on the positive lead between the battery and your breadboard/bus bar. Never leave a high-current discharge test unattended.
Pin Mapping and Wiring Procedure
The Arduino Nano Every operates at 5V logic, which perfectly matches the I2C high-level thresholds for both the ADS1115 and INA219 without needing a logic level shifter.
| Component | Component Pin | Arduino Nano Every Pin | Notes |
|---|---|---|---|
| ADS1115 | VDD | 5V | Powers the ADC |
| ADS1115 | GND | GND | Common ground |
| ADS1115 | SCL | A5 (SCL) | I2C Clock |
| ADS1115 | SDA | A4 (SDA) | I2C Data |
| ADS1115 | A0 | - | Connects to Battery Positive (via voltage divider if >4.096V) |
| INA219 | VIN | 5V | Powers the sensor logic |
| INA219 | GND | GND | Common ground |
| INA219 | SCL | A5 (SCL) | Shares I2C bus with ADS1115 |
| INA219 | SDA | A4 (SDA) | Shares I2C bus with ADS1115 |
| MOSFET | Gate | D9 (PWM) | Include 100Ω series resistor and 10kΩ pull-down to GND |
| MOSFET | Drain | - | Connects to Load Resistor negative terminal |
| MOSFET | Source | - | Connects to Battery Negative / INA219 V- |
Numbered Wiring Steps
- De-energize and Prep: Ensure the LiFePO4 cell is disconnected. Strip 14 AWG wire for the high-current load path and 22 AWG for I2C signals.
- Wire the Load Path: Connect the battery positive terminal to the 5A fuse, then to the INA219 'V+' screw terminal. Connect the INA219 'V-' terminal to one side of the 20W 1-ohm load resistor.
- Wire the MOSFET: Connect the other side of the load resistor to the Drain of the IRLZ44N. Connect the Source to the battery negative. Solder the 100Ω resistor to the Gate, and wire the 10kΩ pull-down resistor between the Gate and Source to prevent accidental turn-on during Arduino boot.
- Wire the I2C Bus: Daisy-chain the SDA and SCL lines from the Nano Every (A4 and A5) to both the ADS1115 and INA219. Connect 5V and GND to both sensor breakouts.
- Connect the Voltage Sense: Connect the ADS1115 A0 pin to the battery positive terminal. Crucial: The ADS1115 max input voltage on the internal gain settings is 4.096V. Since a fully charged LiFePO4 cell hits 3.65V, you are safe to connect directly, but never connect this directly to a 12V or 24V battery bank without a voltage divider.
The Complete C++ Code (Arduino IDE 2.x)
This code targets the Arduino Nano Every. It requires the Adafruit_ADS1X15 and Adafruit_INA219 libraries, available via the Arduino Library Manager. The script performs Coulomb counting by integrating the current over time to calculate total mAh delivered.
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_INA219.h>
// --- PIN DEFINITIONS ---
#define MOSFET_GATE_PIN 9
// --- OBJECT INSTANTIATION ---
Adafruit_ADS1115 ads; // 16-bit ADC
Adafruit_INA219 ina219; // High-side current sensor
// --- CONSTANTS ---
const float CUTOFF_VOLTAGE = 2.50; // LiFePO4 safe discharge cutoff (Volts)
const unsigned long SAMPLE_INTERVAL_MS = 1000; // 1 second between readings
// --- STATE VARIABLES ---
float total_mAh = 0.0;
unsigned long last_sample_time = 0;
bool test_running = false;
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
pinMode(MOSFET_GATE_PIN, OUTPUT);
digitalWrite(MOSFET_GATE_PIN, LOW); // Ensure load is OFF at boot
// Initialize I2C Sensors with Error Handling
if (!ads.begin(0x48)) {
Serial.println("ERROR: Failed to find ADS1115 chip. Check I2C wiring and address.");
while (1) { delay(100); } // Halt execution
}
if (!ina219.begin()) {
Serial.println("ERROR: Failed to find INA219 chip. Check I2C wiring.");
while (1) { delay(100); } // Halt execution
}
// Calibrate INA219 for max 3.2A and 16V (Standard 0.1 ohm shunt)
ina219.setCalibration_16V_400mA(); // Note: Change to 32V_2A if you modified the shunt resistor
// Set ADS1115 Gain to +/- 4.096V (1 bit = 0.125mV)
ads.setGain(GAIN_ONE);
Serial.println("LiFePO4 Capacity Tester Initialized.");
Serial.println("Connecting battery and starting test...");
// Initial voltage check to ensure battery is connected
float start_voltage = read_battery_voltage();
if (start_voltage < 2.0 || start_voltage > 4.0) {
Serial.print("ERROR: Invalid starting voltage: ");
Serial.print(start_voltage);
Serial.println("V. Check ADS1115 A0 connection.");
while(1);
}
test_running = true;
last_sample_time = millis();
digitalWrite(MOSFET_GATE_PIN, HIGH); // Turn ON MOSFET to begin discharge
}
void loop() {
if (!test_running) return;
unsigned long current_time = millis();
if (current_time - last_sample_time >= SAMPLE_INTERVAL_MS) {
last_sample_time = current_time;
float voltage = read_battery_voltage();
float current_mA = ina219.getCurrent_mA();
// Safety cutoff check
if (voltage <= CUTOFF_VOLTAGE) {
digitalWrite(MOSFET_GATE_PIN, LOW); // Turn OFF load
test_running = false;
Serial.println("\n--- CUTOFF REACHED ---");
Serial.print("Final Capacity: ");
Serial.print(total_mAh, 2);
Serial.println(" mAh");
return;
}
// Coulomb Counting: Integrate current over time
// current_mA is in milliamps. Time interval is in ms.
// (mA * ms) / 3600000 = mAh
float mAh_added = (current_mA * SAMPLE_INTERVAL_MS) / 3600000.0;
total_mAh += mAh_added;
// Telemetry Output
Serial.print("V: "); Serial.print(voltage, 3);
Serial.print(" | I: "); Serial.print(current_mA, 1); Serial.print("mA");
Serial.print(" | Cap: "); Serial.print(total_mAh, 2); Serial.println("mAh");
}
}
float read_battery_voltage() {
int16_t adc0 = ads.readADC_SingleEnded(0);
// Convert raw ADC reading to voltage based on GAIN_ONE (4.096V range, 16-bit)
// 1 bit = 4.096 / 32767 = 0.000125V (0.125mV)
return adc0 * 0.000125;
}
Debugging: First Three Things to Check When It Fails
When working with I2C buses and high-current loads, things will go wrong on the first boot. If your Serial Monitor halts or outputs garbage, follow this exact decision path.
1. Error String: ERROR: Failed to find ADS1115 chip
Ranked Causes:
- Missing Pull-up Resistors: The Adafruit ADS1115 breakout includes 10kΩ pull-ups on SDA/SCL. If you are using a generic clone board, you may need to add external 4.7kΩ pull-ups to the 5V line.
- Address Pin Conflict: If the ADDR pin on the ADS1115 is accidentally bridged to VDD or SDA, the I2C address shifts from the default
0x48to0x49or0x4A. Ensure the ADDR pin is floating or tied to GND. - Power Starvation: The Nano Every's onboard 5V regulator can overheat if powering too many peripherals. Measure the 5V pin with a multimeter; if it reads below 4.8V, the I2C bus will fail to initialize.
2. Error String: ERROR: Failed to find INA219 chip
Ranked Causes:
- Crossed I2C Lines: SDA and SCL are swapped. The Nano Every pinout places A4 (SDA) and A5 (SCL) right next to each other. Verify with a multimeter continuity test against the breakout board silkscreen.
- Shunt Resistor Damage: If you accidentally shorted the INA219 V+ and V- terminals without a load during a previous test, you may have blown the onboard 0.1Ω shunt resistor. Check it with a multimeter; it should read close to 0.1Ω. If it reads open (OL), the board is bricked.
3. Symptom: Capacity reads 0.00 mAh but the load resistor is getting hot
Ranked Causes:
- INA219 Calibration Mismatch: The code uses
ina219.setCalibration_16V_400mA(). If you are pushing 3.2A through a 1-ohm resistor, the current exceeds the 400mA calibration limit, causing the internal math to overflow and return zero. Fix: You must replace the INA219's onboard 0.1Ω shunt with a 0.01Ω shunt and useina219.setCalibration_32V_2A(), or use a lower value load resistor (e.g., 5 ohms) to keep current under 400mA. - MOSFET Gate Floating: If the 10kΩ pull-down resistor is missing, the gate can pick up EMI from the switching load, causing the MOSFET to operate in the linear (high-resistance) region rather than fully saturated, skewing the voltage drop across the shunt.
How to Extend or Simplify the Build
Depending on your bench requirements, you can scale this project up or down without rewriting the core logic.
To Simplify (The 'Quick & Dirty' Method)
If you don't want to buy the INA219 current sensor, you can delete the INA219 library and calculate current using Ohm's Law. Read the voltage across the load resistor using the ADS1115 (using differential inputs A0 and A1), and divide by the resistor's nominal value (1.0Ω). Warning: As the 20W chassis-mount resistor heats up, its resistance will drift (aluminum-housed wirewound resistors have a temperature coefficient of roughly ±100 ppm/°C). Your mAh calculation will be off by 3-5% by the end of the test.
To Extend (The 'Data Logger' Method)
To generate professional discharge curves for Battery University-style analysis, add an SPI MicroSD card module (like the Adafruit MicroSD Breakout). Wire it to the Nano Every's hardware SPI pins (MOSI=D11, MISO=D12, SCK=D13, CS=D10). Inside the loop(), write the timestamp, voltage, current, and cumulative mAh to a CSV file every second. You can then import this CSV into Python using pandas or Excel to plot the exact 'knee' of the LiFePO4 discharge curve.
For further reading on high-side current sensing topologies, review the Adafruit INA219 application notes, which detail how to handle common-mode voltage limits when measuring cells in series. If you want to verify the ATmega4809 pin mappings and I2C bus speeds, consult the official Arduino Nano Every documentation.






