Post Image
By Matt GaidicaApril 26, 2023In Uncategorized

Modifying Current Ranger to Estimate Battery Life

I came upon Current Ranger from LowPowerLab and have been amazed at how feature-full it is, not to mention the ability to modify the Arduino firmware.

Recently, I developed a wireless interface for the INA226 current sensor but it lacks some precision and requires a bit of calibration that I never came to fully trust. It has been great for viewing current profiles over time, but I wanted a rock-solid device to characterize the current draw.

The INA226 vs. Current Ranger

I attempted to tap into the Current Ranger’s serial port and expose current data via Bluetooth Low Energy (BLE), which is problematic for a few reasons. Namely, there is no way to guarantee a sampling rate so you are either dropping packets because a BLE buffer is being overloaded or you are throttling how often BLE data is sent.

There were similar problems using the INA226, however, it has a unique sampling regime in which sample conversion time and averaging were done on the chip, and with some playing of the settings, you could write “all” the data to BLE, albeit you might be sending the average value of ~5ms. The high sampling bandwidth of the INA226 did provide some confidence that even if the resulting data were averaged, it was catching most of the fast current transients critical for something like a BLE device that has rapid transitions during advertising or scanning.

Current Ranger does not have quite the same bandwidth on sampling as the INA226 because of its architecture. The Current Ranger’s bandwidth on the actual raw voltage output is high, but it is only internally sampled via an analog-to-digital converter in a loop that is servicing multiple things (e.g., refreshing the screen). However, it’s still pretty fast and my estimate was that you can almost get 1-millisecond resolution while updating the screen. Therefore, in theory, by accumulating the calculated current over time you could accurately estimate the average current and then battery life.

Upgrading Current Ranger

I want to view how long a battery would last given the current draw on Current Ranger. Let’s begin with all the defines and variables placed at the top of the file:

// Matt
#define AVG_CURRENT_DURATION 120        // s, window to calculate avg current in µA
#define AVG_CURRENT_CALC_INTERVAL 2000  // ms, interval to refresh calculation
#define SIMULATE_BATTERY 40             // mAh
uint8_t AVG_CURRENT_ENABLED = true;
float sumCurrent[AVG_CURRENT_DURATION] =   ;     // one element per second
int sumCurrentCount[AVG_CURRENT_DURATION] =   ;  // one element per second
int avgCurrentInterval = 0;                         // millis() tracker
float avgCurrent = 0;                               // value to refresh every AVG_CURRENT_CALC_INTERVAL
int lastCurrentIdx = 0;                             // used to reset stale data

This is going to calculate the average current over 120 seconds and update it every 2000 milliseconds. It will then use that calculation to display the lifetime (in days) of a 40mAh battery. My approach was to sample the current draw in µA every loop iteration (i.e. as fast as possible) and store the sum of those samples in the sumCurrent array where each element represents a 1-second bin based on the modulo of time using the millis() function in Arduino. This will essentially create a ring buffer. Of course, to get the average current for that bin, the number of samples summed needs to be tracked, in sumCurrentCount. Finally, every AVG_CURRENT_CALC_INTERVAL, the current for every bin will be averaged and the grand average is saved in avgCurrent for display.

This code is placed somewhere after uint8_t VOUTCalculated = false; in the main loop:

// Matt
  if (!AUTORANGE) readVOUT();
  VOUT = readDiff * ldoOptimized * (BIAS ? 1 : OUTPUT_CALIB_FACTOR);
  VOUTCalculated = true;

  float uA = VOUT * (RANGE_NA ? 0.001 : RANGE_UA ? 1
                                                 : 1000);     // make µA
  int currentIdx = (millis() / 1000) % AVG_CURRENT_DURATION;  // zero indexed
  // accumulate sumCurrent as 1-second bins, reset stale data
  if (currentIdx != lastCurrentIdx) {
    sumCurrent[currentIdx] = 0;
    sumCurrentCount[currentIdx] = 0;
    lastCurrentIdx = currentIdx;
  sumCurrent[currentIdx] += uA;   // accumulate
  sumCurrentCount[currentIdx]++;  // track as divisor
  if (millis() - avgCurrentInterval > AVG_CURRENT_CALC_INTERVAL) {
    avgCurrentInterval = millis();
    avgCurrent = 0;
    int validIdx = 0;
    for (int i = 0; i < AVG_CURRENT_DURATION; i++) {
      if (sumCurrentCount[i] > 0) {  // avoid divide by zero
        avgCurrent += sumCurrent[i] / (float)sumCurrentCount[i];
    if (validIdx > 0) {  // avoid divide by zero
      avgCurrent = avgCurrent / (float)validIdx;

Finally, to display the average current I modified this block to maintain most of the original functionality. The first line will display the average current and the next line will calculate the battery life in days.

// Matt
u8g2.setFont(u8g2_font_6x12_tf);  //7us
  u8g2.drawStr(0, 12, analog_ref_half ? "AUTO\xb7\xbd" : "AUTO");
  u8g2.setCursor(42, 12);
    u8g2.print(avgCurrent, 0);
    // next line
    u8g2.setCursor(0, 24);
    float batteryLifeDays = (SIMULATE_BATTERY * 1000 / avgCurrent) / 24;
    if (batteryLifeDays <= 10000) {
      u8g2.print(batteryLifeDays, 2);
    } else 
    u8g2.print(" days");
  } else {
    u8g2.print(readDiff, 0);  // default
} else {
  if (analog_ref_half) u8g2.drawStr(0, 12, "\xbd");
  u8g2.setCursor(12, 12);
  u8g2.print(readDiff, 0);


Maybe in the future, some sequence of touch buttons could iterate over several battery sizes or modify the sampling duration but there are already a lot of touch sequences native to Current Ranger that I don’t want to override. I hope this helps you [low-power geeks] in estimating the longevity of a battery based on current consumption.

Useful links:

svgA Real-time Current/Power Meter "Watto" for Profiling Low Power Devices
svgNext Post