Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
bq769x0.cpp
- Committer:
- MPPT51
- Date:
- 2021-07-31
- Revision:
- 0:d92f936cf10d
File content as of revision 0:d92f936cf10d:
/* Battery management system based on bq769x0 for ARM mbed
* Copyright (c) 2015-2018 Martin Jäger (www.libre.solar)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <math.h> // log for thermistor calculation
#include "bq769x0.h"
#include "registers.h"
#include "mbed.h"
DigitalOut bms1_on(p8);
DigitalOut bms2_on(p12);
const char *byte2char(int x)
{
static char b[9];
b[0] = '\0';
int z;
for (z = 128; z > 0; z >>= 1)
{
strcat(b, ((x & z) == z) ? "1" : "0");
}
return b;
}
uint8_t _crc8_ccitt_update (uint8_t inCrc, uint8_t inData)
{
uint8_t i;
uint8_t data;
data = inCrc ^ inData;
for ( i = 0; i < 8; i++ )
{
if (( data & 0x80 ) != 0 )
{
data <<= 1;
data ^= 0x07;
}
else
{
data <<= 1;
}
}
return data;
}
//----------------------------------------------------------------------------
//bq769x0::bms1_output(PinName bms1_on) : _pin(LED1) {
// _Pin = 1;
//}
bq769x0::bq769x0(I2C& bqI2C, PinName alertPin, int bqType, int bqI2CAddress, bool crc):
_i2c(bqI2C), _alertInterrupt(alertPin)
{
_timer.start();
_alertInterrupt.rise(callback(this, &bq769x0::setAlertInterruptFlag));
// set some safe default values
autoBalancingEnabled = false;
balancingMinIdleTime_s = 1800; // default: 30 minutes
idleCurrentThreshold = 30; // mA
thermistorBetaValue = 3435; // typical value for Semitec 103AT-5 thermistor
alertInterruptFlag = true; // init with true to check and clear errors at start-up
type = bqType;
if (type == bq76920) {
numberOfCells = 5;
} else if (type == bq76930) {
numberOfCells = 10;
} else {
numberOfCells = 15;
}
// initialize variables
for (int i = 0; i < numberOfCells - 1; i++) {
cellVoltages[i] = 0;
}
//init1();
//init2();
//crcEnabled = crc;
//I2CAddress = bqI2CAddress;
/*
if (determineAddressAndCrc())
{
// initial settings for bq769x0
writeRegister(SYS_CTRL1, 0b00011000); // switch external thermistor and ADC on
writeRegister(SYS_CTRL2, 0b01000000); // switch CC_EN on
// get ADC offset and gain
adcOffset = (signed int) readRegister(ADCOFFSET); // convert from 2's complement
adcGain = 365 + (((readRegister(ADCGAIN1) & 0b00001100) << 1) |
((readRegister(ADCGAIN2) & 0b11100000) >> 5)); // uV/LSB
printf("BMS OK\n");
}
else {
// TODO: do something else... e.g. set error flag
#if BQ769X0_DEBUG
printf("BMS communication error\n");
#endif
}
*/
}
void bq769x0::init1(void){
bms1_on = 1;
wait(1);
printf("BMS1 init\n");
_timer.reset();
if (determineAddressAndCrc())
{
printf("BMS1 init11\n");
// initial settings for bq769x0
writeRegister(SYS_CTRL1, 0b00011000); // switch external thermistor and ADC on
printf("BMS1 init111\n");
writeRegister(SYS_CTRL2, 0b01000000); // switch CC_EN on
printf("BMS1 init111\n");
// get ADC offset and gain
adcOffset1 = (signed int) readRegister(ADCOFFSET); // convert from 2's complement
adcGain1 = 365 + (((readRegister(ADCGAIN1) & 0b00001100) << 1) |
((readRegister(ADCGAIN2) & 0b11100000) >> 5)); // uV/LSB
printf("BMS1 OK\n");
}
else {
// TODO: do something else... e.g. set error flag
#if BQ769X0_DEBUG
printf("BMS1 communication error\n");
#endif
}
bms1_on = 0;
}
void bq769x0::init2(void){
bms2_on = 1;
wait(0.5);
printf("BMS2 init\n");
_timer.reset();
if (determineAddressAndCrc())
{
// initial settings for bq769x0
writeRegister(SYS_CTRL1, 0b00011000); // switch external thermistor and ADC on
writeRegister(SYS_CTRL2, 0b01000000); // switch CC_EN on
printf("BMS init 1\n");
// get ADC offset and gain
adcOffset2 = (signed int) readRegister(ADCOFFSET); // convert from 2's complement
adcGain2 = 365 + (((readRegister(ADCGAIN1) & 0b00001100) << 1) |
((readRegister(ADCGAIN2) & 0b11100000) >> 5)); // uV/LSB
printf("BMS2 OK\n");
}
else {
// TODO: do something else... e.g. set error flag
#if BQ769X0_DEBUG
printf("BMS1 communication error\n");
#endif
}
bms2_on = 0;
}
//----------------------------------------------------------------------------
// automatically find out address and CRC setting
bool bq769x0::determineAddressAndCrc(void)
{
I2CAddress = 0x08;
crcEnabled = true;
writeRegister(CC_CFG, 0x19);
if (readRegister(CC_CFG) == 0x19) {
return true;
}
I2CAddress = 0x18;
crcEnabled = true;
writeRegister(CC_CFG, 0x19);
if (readRegister(CC_CFG) == 0x19) {
return true;
}
I2CAddress = 0x08;
crcEnabled = false;
writeRegister(CC_CFG, 0x19);
if (readRegister(CC_CFG) == 0x19) {
return true;
}
I2CAddress = 0x18;
crcEnabled = false;
writeRegister(CC_CFG, 0x19);
if (readRegister(CC_CFG) == 0x19) {
return true;
}
return false;
}
//----------------------------------------------------------------------------
// Boot IC by pulling the boot pin TS1 high for some ms
void bq769x0::boot(PinName bootPin)
{
DigitalInOut boot(bootPin);
boot = 1;
wait_ms(5); // wait 5 ms for device to receive boot signal (datasheet: max. 2 ms)
boot.input(); // don't disturb temperature measurement
wait_ms(10); // wait for device to boot up completely (datasheet: max. 10 ms)
}
//----------------------------------------------------------------------------
// Fast function to check whether BMS has an error
// (returns 0 if everything is OK)
int bq769x0::checkStatus()
{
// printf("errorStatus: ");
// printf(errorStatus);
if (alertInterruptFlag == false && errorStatus == 0) {
return 0;
} else {
regSYS_STAT_t sys_stat;
sys_stat.regByte = readRegister(SYS_STAT);
// first check, if only a new CC reading is available
if (sys_stat.bits.CC_READY == 1) {
//printf("Interrupt: CC ready");
updateCurrent(); // automatically clears CC ready flag
}
// Serious error occured
if (sys_stat.regByte & 0b00111111)
{
if (alertInterruptFlag == true) {
secSinceErrorCounter = 0;
}
errorStatus = sys_stat.regByte;
unsigned int secSinceInterrupt = (_timer.read_ms() - interruptTimestamp) / 1000;
// check for overrun of _timer.read_ms() or very slow running program
if (abs((long)(secSinceInterrupt - secSinceErrorCounter)) > 2) {
secSinceErrorCounter = secSinceInterrupt;
}
// called only once per second
if (secSinceInterrupt >= secSinceErrorCounter)
{
if (sys_stat.regByte & 0b00100000) { // XR error
// datasheet recommendation: try to clear after waiting a few seconds
if (secSinceErrorCounter % 3 == 0) {
#if BQ769X0_DEBUG
printf("Attempting to clear XR error");
#endif
writeRegister(SYS_STAT, 0b00100000);
enableCharging();
enableDischarging();
}
}
if (sys_stat.regByte & 0b00010000) { // Alert error
if (secSinceErrorCounter % 10 == 0) {
#if BQ769X0_DEBUG
printf("Attempting to clear Alert error");
#endif
writeRegister(SYS_STAT, 0b00010000);
enableCharging();
enableDischarging();
}
}
if (sys_stat.regByte & 0b00001000) { // UV error
updateVoltages();
if (cellVoltages[idCellMinVoltage] > minCellVoltage) {
#if BQ769X0_DEBUG
printf("Attempting to clear UV error");
#endif
writeRegister(SYS_STAT, 0b00001000);
enableDischarging();
}
}
if (sys_stat.regByte & 0b00000100) { // OV error
updateVoltages();
if (cellVoltages[idCellMaxVoltage] < maxCellVoltage) {
#if BQ769X0_DEBUG
printf("Attempting to clear OV error");
#endif
writeRegister(SYS_STAT, 0b00000100);
enableCharging();
}
}
if (sys_stat.regByte & 0b00000010) { // SCD
if (secSinceErrorCounter % 60 == 0) {
#if BQ769X0_DEBUG
printf("Attempting to clear SCD error");
#endif
writeRegister(SYS_STAT, 0b00000010);
enableDischarging();
}
}
if (sys_stat.regByte & 0b00000001) { // OCD
if (secSinceErrorCounter % 60 == 0) {
#if BQ769X0_DEBUG
printf("Attempting to clear OCD error");
#endif
writeRegister(SYS_STAT, 0b00000001);
enableDischarging();
}
}
secSinceErrorCounter++;
}
}
else {
errorStatus = 0;
}
return errorStatus;
}
}
//----------------------------------------------------------------------------
// checks if temperatures are within the limits, otherwise disables CHG/DSG FET
void bq769x0::checkCellTemp()
{
int numberOfThermistors = numberOfCells/5;
bool cellTempChargeError = 0;
bool cellTempDischargeError = 0;
for (int thermistor = 0; thermistor < numberOfThermistors; thermistor++) {
cellTempChargeError |=
temperatures[thermistor] > maxCellTempCharge - cellTempChargeErrorFlag ? cellTempHysteresis : 0 ||
temperatures[thermistor] < minCellTempCharge + cellTempChargeErrorFlag ? cellTempHysteresis : 0;
cellTempDischargeError |=
temperatures[thermistor] > maxCellTempDischarge - cellTempDischargeErrorFlag ? cellTempHysteresis : 0 ||
temperatures[thermistor] < minCellTempDischarge + cellTempDischargeErrorFlag ? cellTempHysteresis : 0;
}
if (cellTempChargeErrorFlag != cellTempChargeError) {
cellTempChargeErrorFlag = cellTempChargeError;
if (cellTempChargeError) {
disableCharging();
#if BQ769X0_DEBUG
printf("Temperature error (CHG)");
#endif
}
else {
enableCharging();
#if BQ769X0_DEBUG
printf("Clearing temperature error (CHG)");
#endif
}
}
if (cellTempDischargeErrorFlag != cellTempDischargeError) {
cellTempDischargeErrorFlag = cellTempDischargeError;
if (cellTempDischargeError) {
disableDischarging();
#if BQ769X0_DEBUG
printf("Temperature error (DSG)");
#endif
}
else {
enableDischarging();
#if BQ769X0_DEBUG
printf("Clearing temperature error (DSG)");
#endif
}
}
}
//----------------------------------------------------------------------------
// should be called at least once every 250 ms to get correct coulomb counting
void bq769x0::update()
{
//updateCurrent(); // will only read new current value if alert was triggered
updateVoltages();
//updateTemperatures();
//updateBalancingSwitches();
//checkCellTemp();
}
void bq769x0::update1(){
updateVoltages1();
}
void bq769x0::update2(){
updateVoltages2();
}
//----------------------------------------------------------------------------
// puts BMS IC into SHIP mode (i.e. switched off)
void bq769x0::shutdown()
{
writeRegister(SYS_CTRL1, 0x0);
writeRegister(SYS_CTRL1, 0x1);
writeRegister(SYS_CTRL1, 0x2);
}
//----------------------------------------------------------------------------
bool bq769x0::enableCharging()
{
#if BQ769X0_DEBUG
printf("checkStatus() = %d\n", checkStatus());
printf("Umax = %d\n", cellVoltages[idCellMaxVoltage]);
printf("temperatures[0] = %d\n", temperatures[0]);
#endif
int numberOfThermistors = numberOfCells/5;
bool cellTempChargeError = 0;
for (int thermistor = 0; thermistor < numberOfThermistors; thermistor++) {
cellTempChargeError |=
temperatures[thermistor] > maxCellTempCharge ||
temperatures[thermistor] < minCellTempCharge;
}
if (checkStatus() == 0 &&
cellVoltages[idCellMaxVoltage] < maxCellVoltage &&
cellTempChargeError == 0)
{
int sys_ctrl2;
sys_ctrl2 = readRegister(SYS_CTRL2);
writeRegister(SYS_CTRL2, sys_ctrl2 | 0b00000001); // switch CHG on
#if BQ769X0_DEBUG
printf("Enabling CHG FET\n");
#endif
return true;
}
else {
return false;
}
}
//----------------------------------------------------------------------------
void bq769x0::disableCharging()
{
int sys_ctrl2;
sys_ctrl2 = readRegister(SYS_CTRL2);
writeRegister(SYS_CTRL2, sys_ctrl2 & ~0b00000001); // switch CHG off
#if BQ769X0_DEBUG
printf("Disabling CHG FET\n");
#endif
}
//----------------------------------------------------------------------------
bool bq769x0::enableDischarging()
{
#if BQ769X0_DEBUG
printf("checkStatus() = %d\n", checkStatus());
printf("Umin = %d\n", cellVoltages[idCellMinVoltage]);
printf("temperatures[0] = %d\n", temperatures[0]);
#endif
int numberOfThermistors = numberOfCells/5;
bool cellTempDischargeError = 0;
for (int thermistor = 0; thermistor < numberOfThermistors; thermistor++) {
cellTempDischargeError |=
temperatures[thermistor] > maxCellTempDischarge ||
temperatures[thermistor] < minCellTempDischarge;
}
if (checkStatus() == 0 &&
cellVoltages[idCellMinVoltage] > minCellVoltage &&
cellTempDischargeError == 0)
{
int sys_ctrl2;
sys_ctrl2 = readRegister(SYS_CTRL2);
writeRegister(SYS_CTRL2, sys_ctrl2 | 0b00000010); // switch DSG on
return true;
}
else {
return false;
}
}
//----------------------------------------------------------------------------
void bq769x0::disableDischarging()
{
int sys_ctrl2;
sys_ctrl2 = readRegister(SYS_CTRL2);
writeRegister(SYS_CTRL2, sys_ctrl2 & ~0b00000010); // switch DSG off
#if BQ769X0_DEBUG
printf("Disabling DISCHG FET\n");
#endif
}
//----------------------------------------------------------------------------
void bq769x0::enableAutoBalancing(void)
{
autoBalancingEnabled = true;
}
//----------------------------------------------------------------------------
void bq769x0::setBalancingThresholds(int idleTime_min, int absVoltage_mV, int voltageDifference_mV)
{
balancingMinIdleTime_s = idleTime_min * 60;
balancingMinCellVoltage_mV = absVoltage_mV;
balancingMaxVoltageDifference_mV = voltageDifference_mV;
}
//----------------------------------------------------------------------------
// sets balancing registers if balancing is allowed
// (sufficient idle time + voltage)
void bq769x0::updateBalancingSwitches(void)
{
long idleSeconds = (_timer.read_ms() - idleTimestamp) / 1000;
int numberOfSections = numberOfCells/5;
// check for _timer.read_ms() overflow
if (idleSeconds < 0) {
idleTimestamp = 0;
idleSeconds = _timer.read_ms() / 1000;
}
// check if balancing allowed
if (checkStatus() == 0 &&
idleSeconds >= balancingMinIdleTime_s &&
cellVoltages[idCellMaxVoltage] > balancingMinCellVoltage_mV &&
(cellVoltages[idCellMaxVoltage] - cellVoltages[idCellMinVoltage]) > balancingMaxVoltageDifference_mV)
{
//printf("Balancing enabled!");
balancingStatus = 0; // current status will be set in following loop
//regCELLBAL_t cellbal;
int balancingFlags;
int balancingFlagsTarget;
for (int section = 0; section < numberOfSections; section++)
{
// find cells which should be balanced and sort them by voltage descending
int cellList[5];
int cellCounter = 0;
for (int i = 0; i < 5; i++)
{
if ((cellVoltages[section*5 + i] - cellVoltages[idCellMinVoltage]) > balancingMaxVoltageDifference_mV) {
int j = cellCounter;
while (j > 0 && cellVoltages[section*5 + cellList[j - 1]] < cellVoltages[section*5 + i])
{
cellList[j] = cellList[j - 1];
j--;
}
cellList[j] = i;
cellCounter++;
}
}
balancingFlags = 0;
for (int i = 0; i < cellCounter; i++)
{
// try to enable balancing of current cell
balancingFlagsTarget = balancingFlags | (1 << cellList[i]);
// check if attempting to balance adjacent cells
bool adjacentCellCollision =
((balancingFlagsTarget << 1) & balancingFlags) ||
((balancingFlags << 1) & balancingFlagsTarget);
if (adjacentCellCollision == false) {
balancingFlags = balancingFlagsTarget;
}
}
#if BQ769X0_DEBUG
//printf("Setting CELLBAL%d register to: %s\n", section+1, byte2char(balancingFlags));
#endif
balancingStatus |= balancingFlags << section*5;
// set balancing register for this section
writeRegister(CELLBAL1+section, balancingFlags);
} // section loop
}
else if (balancingStatus > 0)
{
// clear all CELLBAL registers
for (int section = 0; section < numberOfSections; section++)
{
#if BQ769X0_DEBUG
printf("Clearing Register CELLBAL%d\n", section+1);
#endif
writeRegister(CELLBAL1+section, 0x0);
}
balancingStatus = 0;
}
}
//----------------------------------------------------------------------------
int bq769x0::getBalancingStatus()
{
return balancingStatus;
}
//----------------------------------------------------------------------------
void bq769x0::setShuntResistorValue(float res_mOhm)
{
shuntResistorValue_mOhm = res_mOhm;
}
//----------------------------------------------------------------------------
void bq769x0::setThermistorBetaValue(int beta_K)
{
thermistorBetaValue = beta_K;
}
//----------------------------------------------------------------------------
void bq769x0::setBatteryCapacity(long capacity_mAh)
{
nominalCapacity = capacity_mAh * 3600;
}
//----------------------------------------------------------------------------
void bq769x0::setOCV(int voltageVsSOC[NUM_OCV_POINTS])
{
OCV = voltageVsSOC;
}
//----------------------------------------------------------------------------
float bq769x0::getSOC(void)
{
return (double) coulombCounter / nominalCapacity * 100;
}
//----------------------------------------------------------------------------
// SOC calculation based on average cell open circuit voltage
void bq769x0::resetSOC(int percent)
{
if (percent <= 100 && percent >= 0)
{
coulombCounter = nominalCapacity * (percent / 100.0);
}
else // reset based on OCV
{
printf("NumCells: %d, voltage: %d V\n", getNumberOfConnectedCells(), getBatteryVoltage());
int voltage = getBatteryVoltage() / getNumberOfConnectedCells();
coulombCounter = 0; // initialize with totally depleted battery (0% SOC)
for (int i = 0; i < NUM_OCV_POINTS; i++)
{
if (OCV[i] <= voltage) {
if (i == 0) {
coulombCounter = nominalCapacity; // 100% full
}
else {
// interpolate between OCV[i] and OCV[i-1]
coulombCounter = (double) nominalCapacity / (NUM_OCV_POINTS - 1.0) *
(NUM_OCV_POINTS - 1.0 - i + ((float)voltage - OCV[i])/(OCV[i-1] - OCV[i]));
}
return;
}
}
}
}
//----------------------------------------------------------------------------
void bq769x0::setTemperatureLimits(int minDischarge_degC, int maxDischarge_degC,
int minCharge_degC, int maxCharge_degC, int hysteresis_degC)
{
// Temperature limits (°C/10)
minCellTempDischarge = minDischarge_degC * 10;
maxCellTempDischarge = maxDischarge_degC * 10;
minCellTempCharge = minCharge_degC * 10;
maxCellTempCharge = maxCharge_degC * 10;
cellTempHysteresis = hysteresis_degC * 10;
}
//----------------------------------------------------------------------------
void bq769x0::setIdleCurrentThreshold(int current_mA)
{
idleCurrentThreshold = current_mA;
}
//----------------------------------------------------------------------------
long bq769x0::setShortCircuitProtection(long current_mA, int delay_us)
{
regPROTECT1_t protect1;
// only RSNS = 1 considered
protect1.bits.RSNS = 1;
protect1.bits.SCD_THRESH = 0;
for (int i = sizeof(SCD_threshold_setting)-1; i > 0; i--) {
if (current_mA * shuntResistorValue_mOhm / 1000 >= SCD_threshold_setting[i]) {
protect1.bits.SCD_THRESH = i;
break;
}
}
protect1.bits.SCD_DELAY = 0;
for (int i = sizeof(SCD_delay_setting)-1; i > 0; i--) {
if (delay_us >= SCD_delay_setting[i]) {
protect1.bits.SCD_DELAY = i;
break;
}
}
writeRegister(PROTECT1, protect1.regByte);
// returns the actual current threshold value
return (long)SCD_threshold_setting[protect1.bits.SCD_THRESH] * 1000 /
shuntResistorValue_mOhm;
}
//----------------------------------------------------------------------------
long bq769x0::setOvercurrentChargeProtection(long current_mA, int delay_ms)
{
// ToDo: Software protection for charge overcurrent
return 0;
}
//----------------------------------------------------------------------------
long bq769x0::setOvercurrentDischargeProtection(long current_mA, int delay_ms)
{
regPROTECT2_t protect2;
// Remark: RSNS must be set to 1 in PROTECT1 register
protect2.bits.OCD_THRESH = 0;
for (int i = sizeof(OCD_threshold_setting)-1; i > 0; i--) {
if (current_mA * shuntResistorValue_mOhm / 1000 >= OCD_threshold_setting[i]) {
protect2.bits.OCD_THRESH = i;
break;
}
}
protect2.bits.OCD_DELAY = 0;
for (int i = sizeof(OCD_delay_setting)-1; i > 0; i--) {
if (delay_ms >= OCD_delay_setting[i]) {
protect2.bits.OCD_DELAY = i;
break;
}
}
writeRegister(PROTECT2, protect2.regByte);
// returns the actual current threshold value
return (long)OCD_threshold_setting[protect2.bits.OCD_THRESH] * 1000 /
shuntResistorValue_mOhm;
}
//----------------------------------------------------------------------------
int bq769x0::setCellUndervoltageProtection(int voltage_mV, int delay_s)
{
regPROTECT3_t protect3;
int uv_trip = 0;
minCellVoltage = voltage_mV;
protect3.regByte = readRegister(PROTECT3);
uv_trip = ((((long)voltage_mV - adcOffset) * 1000 / adcGain) >> 4) & 0x00FF;
uv_trip += 1; // always round up for lower cell voltage
writeRegister(UV_TRIP, uv_trip);
protect3.bits.UV_DELAY = 0;
for (int i = sizeof(UV_delay_setting)-1; i > 0; i--) {
if (delay_s >= UV_delay_setting[i]) {
protect3.bits.UV_DELAY = i;
break;
}
}
writeRegister(PROTECT3, protect3.regByte);
// returns the actual current threshold value
return ((long)1 << 12 | uv_trip << 4) * adcGain / 1000 + adcOffset;
}
//----------------------------------------------------------------------------
int bq769x0::setCellOvervoltageProtection(int voltage_mV, int delay_s)
{
regPROTECT3_t protect3;
int ov_trip = 0;
maxCellVoltage = voltage_mV;
protect3.regByte = readRegister(PROTECT3);
ov_trip = ((((long)voltage_mV - adcOffset) * 1000 / adcGain) >> 4) & 0x00FF;
writeRegister(OV_TRIP, ov_trip);
protect3.bits.OV_DELAY = 0;
for (int i = sizeof(OV_delay_setting)-1; i > 0; i--) {
if (delay_s >= OV_delay_setting[i]) {
protect3.bits.OV_DELAY = i;
break;
}
}
writeRegister(PROTECT3, protect3.regByte);
// returns the actual current threshold value
return ((long)1 << 13 | ov_trip << 4) * adcGain / 1000 + adcOffset;
}
//----------------------------------------------------------------------------
int bq769x0::getBatteryCurrent()
{
return batCurrent;
}
//----------------------------------------------------------------------------
int bq769x0::getBatteryVoltage()
{
return batVoltage;
}
//----------------------------------------------------------------------------
int bq769x0::getMaxCellVoltage()
{
return cellVoltages[idCellMaxVoltage];
}
//----------------------------------------------------------------------------
int bq769x0::getMinCellVoltage()
{
return cellVoltages[idCellMinVoltage];
}
//----------------------------------------------------------------------------
int bq769x0::getCellVoltage(int idCell)
{
return cellVoltages[idCell-1];
}
//----------------------------------------------------------------------------
int bq769x0::getNumberOfCells(void)
{
return numberOfCells;
}
//----------------------------------------------------------------------------
int bq769x0::getNumberOfConnectedCells(void)
{
return connectedCells;
}
//----------------------------------------------------------------------------
float bq769x0::getTemperatureDegC(int channel)
{
if (channel >= 1 && channel <= 3) {
return (float)temperatures[channel-1] / 10.0;
}
else {
return -273.15; // Error: Return absolute minimum temperature
}
}
//----------------------------------------------------------------------------
float bq769x0::getTemperatureDegF(int channel)
{
return getTemperatureDegC(channel) * 1.8 + 32;
}
//----------------------------------------------------------------------------
void bq769x0::updateTemperatures()
{
float tmp = 0;
int adcVal = 0;
int vtsx = 0;
unsigned long rts = 0;
// calculate R_thermistor according to bq769x0 datasheet
adcVal = (readRegister(TS1_HI_BYTE) & 0b00111111) << 8 | readRegister(TS1_LO_BYTE);
vtsx = adcVal * 0.382; // mV
rts = 10000.0 * vtsx / (3300.0 - vtsx); // Ohm
// Temperature calculation using Beta equation
// - According to bq769x0 datasheet, only 10k thermistors should be used
// - 25°C reference temperature for Beta equation assumed
tmp = 1.0/(1.0/(273.15+25) + 1.0/thermistorBetaValue*log(rts/10000.0)); // K
temperatures[0] = (tmp - 273.15) * 10.0;
if (type == bq76930 || type == bq76940) {
adcVal = (readRegister(TS2_HI_BYTE) & 0b00111111) << 8 | readRegister(TS2_LO_BYTE);
vtsx = adcVal * 0.382; // mV
rts = 10000.0 * vtsx / (3300.0 - vtsx); // Ohm
tmp = 1.0/(1.0/(273.15+25) + 1.0/thermistorBetaValue*log(rts/10000.0)); // K
temperatures[1] = (tmp - 273.15) * 10.0;
}
if (type == bq76940) {
adcVal = (readRegister(TS3_HI_BYTE) & 0b00111111) << 8 | readRegister(TS3_LO_BYTE);
vtsx = adcVal * 0.382; // mV
rts = 10000.0 * vtsx / (3300.0 - vtsx); // Ohm
tmp = 1.0/(1.0/(273.15+25) + 1.0/thermistorBetaValue*log(rts/10000.0)); // K
temperatures[2] = (tmp - 273.15) * 10.0;
}
}
//----------------------------------------------------------------------------
void bq769x0::updateCurrent()
{
int adcVal = 0;
regSYS_STAT_t sys_stat;
sys_stat.regByte = readRegister(SYS_STAT);
// check if new current reading available
if (sys_stat.bits.CC_READY == 1)
{
//printf("reading CC register...\n");
adcVal = (readRegister(CC_HI_BYTE) << 8) | readRegister(CC_LO_BYTE);
batCurrent = (int16_t) adcVal * 8.44 / shuntResistorValue_mOhm; // mA
coulombCounter += batCurrent / 4; // is read every 250 ms
// reduce resolution for actual current value
if (batCurrent > -10 && batCurrent < 10) {
batCurrent = 0;
}
// reset idleTimestamp
if (abs(batCurrent) > idleCurrentThreshold) {
idleTimestamp = _timer.read_ms();
}
// no error occured which caused alert
if (!(sys_stat.regByte & 0b00111111)) {
alertInterruptFlag = false;
}
writeRegister(SYS_STAT, 0b10000000); // Clear CC ready flag
}
}
//----------------------------------------------------------------------------
// reads all cell voltages to array cellVoltages[NUM_CELLS] and updates batVoltage
void bq769x0::updateVoltages()
{
long adcVal = 0;
char buf[4];
int connectedCellsTemp = 0;
uint8_t crc;
// read cell voltages
buf[0] = (char) VC1_HI_BYTE;
_i2c.write(I2CAddress << 1, buf, 1);
printf("Tx:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
idCellMaxVoltage = 0;
idCellMinVoltage = 0;
for (int i = 0; i < numberOfCells; i++)
{
if (crcEnabled == true) {
_i2c.read(I2CAddress << 1, buf, 4);
adcVal = (buf[0] & 0b00111111) << 8 | buf[2];
// CRC of first bytes includes slave address (including R/W bit) and data
crc = _crc8_ccitt_update(0, (I2CAddress << 1) | 1);
crc = _crc8_ccitt_update(crc, buf[0]);
if (crc != buf[1]) return; // don't save corrupted value
// CRC of subsequent bytes contain only data
crc = _crc8_ccitt_update(0, buf[2]);
if (crc != buf[3]) return; // don't save corrupted value
}
else {
//printf("XX:%d\n\r", i);
_i2c.read(I2CAddress << 1, buf, 2);
adcVal = (buf[0] & 0b00111111) << 8 | buf[1];
printf("Rx:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
//printf("%d, %d, %d, %d\n\r", cellVoltages[i], adcVal, adcGain, adcOffset);
}
cellVoltages[i] = adcVal * adcGain / 1000 + adcOffset;
printf("%d, %d, %d, %d\n\r", cellVoltages[i], adcVal, adcGain, adcOffset);
if (cellVoltages[i] > 500) {
connectedCellsTemp++;
}
if (cellVoltages[i] > cellVoltages[idCellMaxVoltage]) {
idCellMaxVoltage = i;
}
if (cellVoltages[i] < cellVoltages[idCellMinVoltage] && cellVoltages[i] > 500) {
idCellMinVoltage = i;
}
}
connectedCells = connectedCellsTemp;
// read battery pack voltage
adcVal = (readRegister(BAT_HI_BYTE) << 8) | readRegister(BAT_LO_BYTE);
batVoltage = 4.0 * adcGain * adcVal / 1000.0 + connectedCells * adcOffset;
}
void bq769x0::updateVoltages1()
{
bms1_on = 1;
wait(0.3);
long adcVal = 0;
char buf[4];
int connectedCellsTemp = 0;
// read cell voltages
buf[0] = (char) VC1_HI_BYTE;
_i2c.write(I2CAddress << 1, buf, 1);
//printf("Tx1:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
idCellMaxVoltage = 0;
idCellMinVoltage = 0;
for (int i = 0; i < numberOfCells; i++)
{
_i2c.read(I2CAddress << 1, buf, 2);
adcVal = (buf[0] & 0b00111111) << 8 | buf[1];
//printf("Rx1:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
cellVoltages[i] = adcVal * adcGain1 / 1000 + adcOffset1;
//printf("1:%d, %d, %d, %d\n\r", cellVoltages[i], adcVal, adcGain1, adcOffset1);
if (cellVoltages[i] > 500) {
connectedCellsTemp++;
}
if (cellVoltages[i] > cellVoltages[idCellMaxVoltage]) {
idCellMaxVoltage = i;
}
if (cellVoltages[i] < cellVoltages[idCellMinVoltage] && cellVoltages[i] > 500) {
idCellMinVoltage = i;
}
}
connectedCells = connectedCellsTemp;
// read battery pack voltage
adcVal = (readRegister(BAT_HI_BYTE) << 8) | readRegister(BAT_LO_BYTE);
batVoltage = 4.0 * adcGain * adcVal / 1000.0 + connectedCells * adcOffset;
bms1_on = 0;
}
void bq769x0::updateVoltages2()
{
bms2_on = 1;
wait(0.3);
long adcVal = 0;
char buf[4];
int connectedCellsTemp = 0;
// read cell voltages
buf[0] = (char) VC1_HI_BYTE;
_i2c.write(I2CAddress << 1, buf, 1);
//printf("Tx2:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
idCellMaxVoltage = 0;
idCellMinVoltage = 0;
for (int i = 0; i < numberOfCells; i++)
{
_i2c.read(I2CAddress << 1, buf, 2);
adcVal = (buf[0] & 0b00111111) << 8 | buf[1];
//printf("Rx2:%x, %x, %d, %d\n\r", buf[0], buf[1], buf[0], buf[1]);
cellVoltages[i] = adcVal * adcGain2 / 1000 + adcOffset2;
//printf("2:%d, %d, %d, %d\n\r", cellVoltages[i], adcVal, adcGain2, adcOffset2);
if (cellVoltages[i] > 500) {
connectedCellsTemp++;
}
if (cellVoltages[i] > cellVoltages[idCellMaxVoltage]) {
idCellMaxVoltage = i;
}
if (cellVoltages[i] < cellVoltages[idCellMinVoltage] && cellVoltages[i] > 500) {
idCellMinVoltage = i;
}
}
connectedCells = connectedCellsTemp;
// read battery pack voltage
adcVal = (readRegister(BAT_HI_BYTE) << 8) | readRegister(BAT_LO_BYTE);
batVoltage = 4.0 * adcGain * adcVal / 1000.0 + connectedCells * adcOffset;
bms2_on = 0;
}
//----------------------------------------------------------------------------
void bq769x0::writeRegister(int address, int data)
{
uint8_t crc = 0;
char buf[3];
buf[0] = (char) address;
buf[1] = data;
if (crcEnabled == true) {
// CRC is calculated over the slave address (including R/W bit), register address, and data.
crc = _crc8_ccitt_update(crc, (I2CAddress << 1) | 0);
crc = _crc8_ccitt_update(crc, buf[0]);
crc = _crc8_ccitt_update(crc, buf[1]);
buf[2] = crc;
_i2c.write(I2CAddress << 1, buf, 3);
}
else {
_i2c.write(I2CAddress << 1, buf, 2);
}
}
//----------------------------------------------------------------------------
int bq769x0::readRegister(int address)
{
uint8_t crc = 0;
char buf[2];
#if BQ769X0_DEBUG
//printf("Read register: 0x%x \n", address);
#endif
buf[0] = (char)address;
_i2c.write(I2CAddress << 1, buf, 1);;
if (crcEnabled == true) {
do {
_i2c.read(I2CAddress << 1, buf, 2);
// CRC is calculated over the slave address (including R/W bit) and data.
crc = _crc8_ccitt_update(crc, (I2CAddress << 1) | 1);
crc = _crc8_ccitt_update(crc, buf[0]);
} while (crc != buf[1]);
return buf[0];
}
else {
_i2c.read(I2CAddress << 1, buf, 1);
return buf[0];
}
}
//----------------------------------------------------------------------------
// The bq769x0 drives the ALERT pin high if the SYS_STAT register contains
// a new value (either new CC reading or an error)
void bq769x0::setAlertInterruptFlag()
{
interruptTimestamp = _timer.read_ms();
alertInterruptFlag = true;
}
#if BQ769X0_DEBUG
//----------------------------------------------------------------------------
// for debug purposes
void bq769x0::printRegisters()
{
printf("0x00 SYS_STAT: %s\n", byte2char(readRegister(SYS_STAT)));
printf("0x01 CELLBAL1: %s\n", byte2char(readRegister(CELLBAL1)));
printf("0x04 SYS_CTRL1: %s\n", byte2char(readRegister(SYS_CTRL1)));
printf("0x05 SYS_CTRL2: %s\n", byte2char(readRegister(SYS_CTRL2)));
printf("0x06 PROTECT1: %s\n", byte2char(readRegister(PROTECT1)));
printf("0x07 PROTECT2: %s\n", byte2char(readRegister(PROTECT2)));
printf("0x08 PROTECT3: %s\n", byte2char(readRegister(PROTECT3)));
printf("0x09 OV_TRIP: %s\n", byte2char(readRegister(OV_TRIP)));
printf("0x0A UV_TRIP: %s\n", byte2char(readRegister(UV_TRIP)));
printf("0x0B CC_CFG: %s\n", byte2char(readRegister(CC_CFG)));
printf("0x32 CC_HI: %s\n", byte2char(readRegister(CC_HI_BYTE)));
printf("0x33 CC_LO: %s\n", byte2char(readRegister(CC_LO_BYTE)));
/*
printf("0x50 ADCGAIN1: %s\n", byte2char(readRegister(ADCGAIN1)));
printf("0x51 ADCOFFSET: %s\n", byte2char(readRegister(ADCOFFSET)));
printf("0x59 ADCGAIN2: %s\n", byte2char(readRegister(ADCGAIN2)));
*/
}
#endif