KIT Solar Car Project / Mbed 2 deprecated BMS_ver1

Dependencies:   mbed INA226gfg

Revision:
0:d92f936cf10d
diff -r 000000000000 -r d92f936cf10d bq769x0.cpp
--- /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
+