PiSupply / PiJuice

Resources for PiJuice HAT for Raspberry Pi - use your Pi Anywhere
https://uk.pi-supply.com/collections/pijuice/products/pijuice-portable-power-raspberry-pi
GNU General Public License v3.0
438 stars 104 forks source link

OCV and R values for battery ext profile #366

Open StudentSA opened 5 years ago

StudentSA commented 5 years ago

Good Day,

I am trying to use an alternative battery and have figured out most of the profile settings. however I do not know what values to use for OCV10, OCV50, OCV90 and R10, R50, R90

  1. What are these values referring to?
  2. How can I get these values?

Thanks

tvoverbeek commented 5 years ago

OCVxx is the Open Circuit Voltage at xx% charge. Rxx is the corresponding internal battery resistance. For an explanation see the test program which can be used to determine these values: https://github.com/PiSupply/PiJuice/blob/master/Software/Test/pijuice-soc-test.py These values are used when you specify charge level measured directly by MCU. You could start with the values of one of the included profiles, closest to your alternative battery. The included profiles are: 'BP6X_1400', 'BP7X_1820', 'SNN5843_2300', 'PJLIPO_12000', 'PJLIPO_5000', 'PJBP7X_1600', 'PJSNN5843_1300', 'PJZERO_1200', 'PJZERO_1000', 'PJLIPO_600', 'PJLIPO500' The number after the '' is the capacity in mah Note, the pijuice only supports single cell batteries.

StudentSA commented 5 years ago

Thanks for pointing me in the right direction @tvoverbeek. however I believe The version in Hardware->Batteries folder is more recent: https://github.com/PiSupply/PiJuice/blob/master/Hardware/Batteries/pijuice-soc-test.py

I've tried the process, but I cannot say I've had much luck. I have been trying to understand the tests (pijuice-soc-test.py) methodology and could share what I have found.

  1. The script isolates the PiJuice from the RPi by switching off the 5V GPIO output. i.e. the PiJuice is no longer supplying the Pi at all. Instead the PI is being run from the Pi's 5V supply. This is done to prevent charging the battery. The variable in the script for "Igpio" is misleading and should rather be removed. This variable might have been there when the "load" was the RPi itself instead of the Load Resistor on VSYS. However it is no longer relevant now as GPIO supply is being removed.

  2. The script will discharge the battery by switching a load on to VSYS pins which in essence is connecting a load directly to the battery. The smaller the load the faster the battery discharges. however we need to be cognisant of the fact the battery performance is also affected by faster/slower discharge. It would therefore be advisable to discharge at your expected use case rate. *NOTE! A 2(Two) Ohm Resistor is the absolute minimum that should be used as VSYS only supplies up to 2.1A. A 10(Ten) Ohm 2W+ precision resistor is ideal.

  3. As the battery is discharged, we take measurements of the battery voltage, however I found erratic readings when using the original script. this may indicate a firmware limitation where more time is required to "settle" the battery voltage, or it could indicate a capacitor across VSYS that has causes the value to change slower than we sample.

  4. The Resistor used as a load needs be be a high precision one. I would advise that you measure your resistor with a good multimeter to get an accurate value. Remember that Battery internal resistance is small (i.e. range 0.1 ohm -> 0.3 ohm) and if you are off on specifying your RLoad correctly this error will be influence your Rbat readings.

I have done my own take of the script without consideration for Pi Current Draw (as per point 1). I used the standard PiJuice API and taken multiple samples on vbat to achieve more reliable test results.

For anyone interested:

#!/usr/bin/python3
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# Code is based on Milan Neskovic original work
# Adapted to Python3 and mostly using the PiJuice Python API
# Voltage reading done using sampling as once off was giving me erratic results
# Current only based on Load Resistor and small estimated 0.06 Ohm (System resistance)

# Usage:
# Use to measure battery open circuit voltage and resistance by discharging battery using resistor load attached at PiJuice Vsys connection.
# Power supply should be connected to Raspberry Pi only. Program will output to screen and text file measured battery resistance-Rbat and open circuit voltage-OCV.
# For getting ocv10,ocv50,ocv90,r10,r50,r90 profile configuration parameters, Test should be started at battery full and finished when battery discharges to zero.
# Approximate battery resistance for purpose of profile calibration can be evaulated after few measurement points in short program run from arbitrary battery level.
# Output file name: battery_soc_test.txt
# Battery capacity depends on conditions like load current and temperature, so nominal capacity may not correspond to real test capacity. To get better test accuracy
# it is needed to normalize final dSOC after full discharge to 100%, real_soc=100+dSOC*100/dSOCfinal.
# Program prints charge level estimated by pijuice as pj charge, and can be used for accuracy test.

# Parameters:
c0=13200 #[mAh] battery capacity
Rload = 11 #[Ohm] Resistance of test load resistor connected to Vsys. 10Ohm/2W resistor is good example. Set to 0 if load resistor is not connected.
onTime = 5.0 # Length of time interval resistor load is on
offTime = 2.5 # Length of time interval resistor load is off
endVolt = 3250 #[mV], end test when battery voltage drops below this level

from pijuice import PiJuice # Import pijuice module
import sys, time, datetime, calendar
from time import sleep
from array import array
import signal

pijuice = PiJuice(1, 0x14) # Instantiate PiJuice interface object

def get_battery_voltage():
    battery_voltage=-1
    charge_info = pijuice.status.GetBatteryVoltage()
    if (charge_info["error"] == "NO_ERROR"):
        #print ("Voltage Info Valid")
        battery_voltage = charge_info["data"]
    return int(battery_voltage)

def set_vsys(onoff):
    if onoff:
        #set vsys on with max current 2100mA
        pijuice.power.SetSystemPowerSwitch(2100)
    else:
        pijuice.power.SetSystemPowerSwitch(0)

def get_avg_voltage():
    voltage=0
    #if enough reading were taken then avg after dropping off min and max (possible error)
    if len(voltage_arr) > 4:
        #print ("Voltage based on ", len(voltage_arr), " readings. Dropping: ", max(voltage_arr)," and ", min(voltage_arr))
        voltage_arr.pop(0)  #remove first two readings
        voltage_arr.pop(0)
        voltage_arr.remove(max(voltage_arr))
        voltage_arr.remove(min(voltage_arr))
    return sum(voltage_arr)/len(voltage_arr)
    #return max(set(voltage_arr), key=voltage_arr.count)

def get_highres_charge_level():
    charge_lvl = -1.0
    soc_info=pijuice.interface.ReadData(0x42, 2)
    if soc_info['error'] == 'NO_ERROR':
        d=soc_info['data']
        charge_lvl = ((d[1] << 8) | d[0])/10.0
    return charge_lvl

def signal_handler(sig, frame):
    #Ensure VSYS is OFF and not driving load till burnout :)
    print("Switching OFF VSYS")
    set_vsys(0)
    print("Switch On GPIO 5V")
    set_GPIO_input(1)
    print("Exiting...")
    sys.exit(0)

def set_GPIO_input(onoff):
    pwr_in_cfg=pijuice.interface.ReadData(0x5E, 1)
    if pwr_in_cfg['error'] == 'NO_ERROR':
        d=pwr_in_cfg['data']
        w = (d[0] | 0x02) if (onoff) else (d[0] & 0xFD)
        pijuice.interface.WriteDataVerify(0x5E, [w])

signal.signal(signal.SIGINT, signal_handler)

volt_on=0
volt_off=0
chargeLvl=0
initLvl=10000
onOff = 0
dSOC=0
dSocDiff=0
start_time = time.time()
adv_time = 0
voltage_arr = []

#Power OFF GPIO 5V to prevent battery charging
set_GPIO_input(0)

while 1:
    #print("STATE IS:",onOff)
    #Read the battery voltage
    voltage_arr.append(get_battery_voltage())
    elapsed_time = time.time() - start_time

    if elapsed_time > adv_time:
        if onOff:
            volt_on=get_avg_voltage()
            voltage_arr.clear()
        else:
            volt_off=get_avg_voltage()
            voltage_arr.clear()
        if onOff:
            Ibat=volt_on/(Rload+0.06) #Vsys load current. Addition of sys switch resistance.
            #print("Ibat = ", str(Ibat), ". With volt_on = ", volt_on, " and volt_off = ", volt_off)
            dSOC=dSOC-(Ibat*onTime)/3600 # state of charge drop counted from program start
            Rbat=(volt_off-volt_on)/(Ibat+0.000000001)
            dSOCp=dSOC/c0*100
            sleep(0.002)
            chargeLvl=get_highres_charge_level()
            if initLvl==10000:
                initLvl=chargeLvl
            else:
                dSocDiff=initLvl-chargeLvl+dSOCp
            out="%08.2f"%elapsed_time+'sec, Vbon:%04d'%volt_on+ 'mV, OCV:%04d'%volt_off+ 'mV, Ibat:%06.1f'%Ibat+'mA, Rbat:%07.5f'%Rbat+ 'Ohm, dSOC:%07.2f'%dSOCp+'%,'+' pj charge:%05.1f'%chargeLvl+'%,'+' diff:%05.2f'%dSocDiff
            print(out)
            output_file = open('battery_soc_test.txt', 'a')
            output_file.write(out+'\n')
            output_file.close()
        if (volt_off < endVolt) and (volt_off > 0):
            print('end voltage reached, terminating')
            print('dSOC90: %07.2f'%(dSOCp*0.1)+' %') # Read ocv90 and r90 from line where dSOC matches dSOC90 value
            print('dSOC50: %07.2f'%(dSOCp*0.5)+' %') # Read ocv50 and r50 from line where dSOC matches dSOC50 value
            print('dSOC10: %07.2f'%(dSOCp*0.9)+' %') # Read ocv10 and r10 from line where dSOC matches dSOC10 value

            print("Switching OFF VSYS and Enabling Charging")
            set_vsys(0)
            set_GPIO_input(1)
            break

        onOff = not onOff
        set_vsys(onOff)
        adv_time = adv_time + (onTime if onOff else offTime)

    sleep(0.1)