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.
Diff: bq769x0.cpp
- Revision:
- 0:d92f936cf10d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bq769x0.cpp Sat Jul 31 09:12:21 2021 +0000 @@ -0,0 +1,1227 @@ +/* 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 +