My wife and I purchased a set of 3 used Cuda 400 DPVs which included 6 NiMH battery packs of various age and abuse. Prior to purchase the battery packs were float charged on the shelf for half a year without a full discharge cycle. First test outing we had serious issues with voltage droop and run time. Reaching out to DiveX, they suggested just running the batteries through a few cycles to remove any ‘memory’ that the packs acquired during storage.

So, bench testing it is! Amazon purchase list:

All up, $125 freedom bucks. Be forewarned, this is all ‘bench racing’ and not a controlled lab situation. It is still Good Enough.

The packs in question are 400 watt hour, D cell NiMH in 34s1p configuration. Default ESC cutoff is 35v, slightly above 1.0v per cell. DiveX provides detailed performance charts, which gives a ballpark of 2 hours at 150 fpm cruse speed. with a 400 watt hour pack, that’s a load of 200w, 5 amp. A good amount of juice to sink.

Using two 8ohm brake resisters in series gives a slightly more friendly 2.5 amp / 100w load and C/4 discharge rate. They still settle down at 140f after half an hour, so care still must be taken to avoid burns. The Gavin Burn Tester has a similar 100w power disappation.

The DMM’s leads are crimped and terminated to the smaller PowerPlug connectors. These are then attached to the charging harness on the battery pack. The resisters are wired in series, and attached to the power harness of the pack via the larger PowerPlug connectors.

Initially voltage was monitored manually with my trusty Fluke 117 DMM every 15 minutes. this required constant baby sitting, and is not really workable with a fleet of packs.

The Tekpower uses a serial over USB protocol, which is fairly simple. Twice a second, the DMM emits the display and meter state, terminated by \r\n. First 5 bytes are the display read out in ascii (sort of..), and the remainder consists of bit flags. The QtDMM Project and Sigrok Project have parsers that are close, but do not match on the feature flags. No matter, we only care about instantaneous voltage readings, and can make lots of assumptions about how the DMM is configured.

Given a known resistive load and voltage, you can calculate amperage and wattage; integrated over time you’ve got watt-hours. yay.

So to glue it all together, a straightforward python script that emits CSV of time, voltage, and watt hours:

#!/usr/bin/python3 -u

load = 16.1
discharge = 35.0

from serial import Serial
from time import time
from sys import stderr

peak = 0
start = None
last = []
lastprint = None
lastcount = 60
twh = 0

with Serial('/dev/ttyUSB0',2400,timeout=2) as ser:
    while True:
        l = ser.readline()
        if not len(l):

        # overload
        if chr(l[3]) == ':':
            print('overload?', file=stderr)

        f = [int("{:02x}".format(c),16) for c in l[5:]]
        # 4570 20 3211 0000 802d 0d0a
        if f[0] != 32:
            print('incorrect mode?',f[:6],file=stderr)

        # convert first 5 bytes into float for voltage
        v = int(l[:5].decode('ascii'))/100

        # quick and dirty range check.  confirms DVM is at least in the ballpark
        if v < 30 or v > 50:

        # reset peak current, should be a one shot
        if v > peak:
            peak = v

        tick = time()

        if not start:
            # start the clock once we drop half a volt from peak
            if (peak - v) > .5 or v < 42:
                print('on load?',file=stderr)
                print('"time (m)","v","wh"')
                lastprint = start = tick
                print('off load?',file=stderr)

        runtime = tick - start

        # quick and dirty noise filtering
        if v not in last:
            # calc time delta
            dt = tick - lastprint
            lastprint = tick

            # calc watt/hour used over time delta
            a = v/load # this is current 'lower' voltage, err conservative
            w = v * a
            dwh = w*dt/3600
            twh = twh + dwh

        last = last[:lastcount]

        if start and v < discharge:
            print('discharge reached: {:.1f} watt hours consumed'.format(twh))

This results in a CSV file, one record per 1/100th volt drop, as well as a estimated watt-hour capacity.

battery/ $ sudo ./ | tee 2020-08-08/a.csv ; mpc pause
off load?
off load?
on load?
"time (m)","v","wh"
189.43,35.01, 339.8
189.45,35.00, 339.8
189.59,34.99, 340.0
discharge reached: 340.0 watt hours consumed

Once the script terminates at the cutoff voltage, the mpc client pauses music playback, which is a nice signal to go unplug the pack. Would be nice to actuate a relay to automatically cut off the load, but that’s beyond this stage of prototyping.

A second script parses the csv files and generates a graph via matplotlib:


import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
from glob import glob

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.set_title('discharge curve')

max_time = 210




for f in sorted(glob('*.csv')):
    a = pd.read_csv(f, dtype = { 'time (m)': np.float64 }, engine='python',skipfooter=1).drop(columns=['wh'])
    ax.plot(a['time (m)'],a['v'],label=f)


which results in:

Discharge Curve

Quite surprising to see such multiple voltage drops at the end of charge. After seeing the actual discharge curves, with these aging packs, a more sane setting might closer to 38 or 39v for the ESC cutoff.

While all the packs are most definitely not new, they are still usable for a few more weekends at the quarry.

  • Pack A: 340 Wh non linear at 38v
  • Pack B: 264 Wh non linear at 39v, low capacity.
  • Pack C: 343 Wh fully linear to 35v
  • Pack D: 343 Wh non linear at 36v
  • Pack E: 244 Wh fully linear, low capacity.
  • Pack F: 307 Wh fully linear, but noticeable voltage drop under load.