Step 1: Get yourself an HP 6632B power supply. These are 100W two-quadrant (charge + discharge) units.
Step 2: Connect to its GPIB or RS232 (untested) port.
Step 3: Download, configure, and run the attached software.
Use gnuplot if you want to see pretty plots of the logged battery behavior.
import visa import time charge_voltage = 14.5 charge_current = 2.0 charge_current_stop = 0.1 discharge_voltage = 11.0 discharge_current = 2.0 discharge_current_stop = -0.5 poll_seconds = 10 rest_seconds = 300 iterations = 3 resurrect = 0 class HP6632B: """A class for controlling an HP 6632B power supply""" def __init__(self, GPIB_address = 21): resource_manager = visa.ResourceManager() self.__instrument = resource_manager.open_resource('GPIB::{0}::INSTR'.format(GPIB_address)) self.set_output(False) def set_output(self, on = False): self.__instrument.write('OUTP {0}'.format('1' if on else '0')) def set_current(self, amps = 0): self.__instrument.write('CURR {0} MA'.format(amps * 1000)) def set_voltage(self, volts = 0): self.__instrument.write('VOLT {0} MV'.format(volts * 1000)) def measure_current(self): return float(self.__instrument.query('MEAS:CURR?')) def measure_voltage(self): return float(self.__instrument.query('MEAS:VOLT?')) psu = HP6632B(21) for iteration in range(0, iterations): if resurrect: log = open('charge-resurrection.csv', 'w', 0) psu.set_current(charge_current) psu.set_voltage(charge_voltage) psu.set_output(True) resurrected = 0 while True: now = time.time() current = psu.measure_current() voltage = psu.measure_voltage() print('Charge resurrection {0}: {1}V @ {2}A'.format(now, voltage, current)) log.write("{0},{1},{2}\n".format(now, voltage, current)) if current > charge_current_stop: resurrected += 1 if current <= charge_current_stop and resurrected >= 10: break time.sleep(poll_seconds) psu.set_output(False) print('Charge complete. Resting for {0} seconds.'.format(rest_seconds)) log.close() time.sleep(rest_seconds) log = open('discharge-{0}.csv'.format(iteration), 'w', 0) psu.set_current(discharge_current) psu.set_voltage(discharge_voltage) psu.set_output(True) while True: now = time.time() current = psu.measure_current() voltage = psu.measure_voltage() print('Discharge #{0} {1}: {2}V @ {3}A'.format(iteration, now, voltage, current)) log.write("{0},{1},{2}\n".format(now, voltage, current)) if current >= discharge_current_stop: break time.sleep(poll_seconds) psu.set_output(False) print('Discharge complete. Resting for {0} seconds.'.format(rest_seconds)) log.close() time.sleep(rest_seconds) log = open('charge-{0}.csv'.format(iteration), 'w', 0) psu.set_current(charge_current) psu.set_voltage(charge_voltage) psu.set_output(True) while True: now = time.time() current = psu.measure_current() voltage = psu.measure_voltage() print('Charge #{0} {1}: {2}V @ {3}A'.format(iteration, now, voltage, current)) log.write("{0},{1},{2}\n".format(now, voltage, current)) if current <= charge_current_stop: break time.sleep(poll_seconds) psu.set_output(False) print('Charge complete. Resting for {0} seconds.'.format(rest_seconds)) log.close() time.sleep(rest_seconds) print('{0} iterations complete.'.format(iterations))