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 (AVG_CURRENT_ENABLED) { 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]; validIdx++; } } 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 if (AUTORANGE) { u8g2.drawStr(0, 12, analog_ref_half ? "AUTO\xb7\xbd" : "AUTO"); u8g2.setCursor(42, 12); if (AVG_CURRENT_ENABLED) { u8g2.print(AVG_CURRENT_DURATION); u8g2.print("s:"); u8g2.print(avgCurrent, 0); u8g2.print(char('µ')); u8g2.print("A"); // next line u8g2.setCursor(0, 24); u8g2.print(SIMULATE_BATTERY); u8g2.print("mAh:"); 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); }
Conclusion
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:
Recent Comments
Archives
- April 2023
- January 2023
- November 2022
- May 2022
- March 2022
- January 2022
- December 2021
- April 2021
- December 2020
- October 2020
- August 2020
- July 2020
- March 2020
- February 2020
- January 2020
- December 2019
- November 2019
- October 2019
- January 2019
- December 2018
- November 2018
- August 2018
- July 2018
- April 2018
- March 2018
- November 2017
- October 2017
- February 2017
- October 2016
- August 2016
- July 2016
- November 2015
- October 2013
- February 2013
- January 2013
- August 2012
- July 2012
- June 2012
- May 2012
- April 2012
- February 2012
- December 2011