/* bq769x0.cpp - Battery management system based on bq769x0 for Arduino Copyright (C) 2015 Martin J ger (m.jaeger@posteo.de) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ /* TODO: - Balancing algorithm - SOC calculation + coulomb counting - Autobalancing (what is this?) */ #include // log for thermistor calculation #include "bq769x0.h" #include "bq769x0_reg.h" #include "i2c.h" #include "delay.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Define port and pin for bq769x0 boot */ #define BQ769X0_PWR_H_PORT (PortD) #define BQ769X0_PWR_H_PIN (Pin03) #define BQ769X0_PWR_M_PORT (PortD) #define BQ769X0_PWR_M_PIN (Pin04) #define BQ769X0_PWR_L_PORT (PortD) #define BQ769X0_PWR_L_PIN (Pin05) #define BQ769X0_BOOT_PORT (PortE) #define BQ769X0_BOOT_PIN (Pin03) #define BQ769X0_ALERT_PORT (PortD) #define BQ769X0_ALERT_PIN (Pin02) /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ const uint8_t bqType = bq76940; const uint8_t bqI2CAddress = 0x08; // Variables bool crcEnabled; int I2CAddress; uint8_t type; // indicates if a new current reading or an error is available from BMS IC bool alertInterruptFlag = true; // init with true to check and clear errors at start-up int numberOfCells; int cellVoltages[MAX_NUMBER_OF_CELLS]; // mV uint8_t idCellMaxVoltage; uint8_t idCellMinVoltage; uint32_t batVoltage; // mV uint32_t batSumVoltage; int temperatures[MAX_NUMBER_OF_THERMISTORS]; // C/10 uint8_t balStatus[3]; // Current limits (mA) long maxChargeCurrent; long maxDischargeCurrent; int idleCurrentThreshold = 30; // mA // Temperature limits (deg C / 10) int minCellTempCharge; int minCellTempDischarge; int maxCellTempCharge; int maxCellTempDischarge; // Cell voltage limits (mV) int maxCellVoltage; int minCellVoltage; int balancingMinCellVoltage_mV; uint8_t balancingMaxVoltageDifference_mV; int32_t adcGain = 0; // uV/LSB int32_t adcOffset = 0; // mV int32_t adcGain2 = 1000; // 1.000 uint8_t errorStatus = 0; uint8_t sys_ctrl1 = 0; uint8_t sys_ctrl2 = 0; uint8_t protect[3] = {0, }; uint8_t uv_param = 0; uint8_t ov_param = 0; bool balancingActive = false; int balancingMinIdleTime_s = 1800; // default: 30 minutes unsigned long idleTimestamp = 0; unsigned int secSinceErrorCounter = 0; unsigned long interruptTimestamp = 0; /* Private function prototypes -----------------------------------------------*/ void updateCellBalStatus(void); /* Private functions ---------------------------------------------------------*/ // CRC calculation taken from LibreSolar mbed firmware 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; } //---------------------------------------------------------------------------- void bq769x0_pwr_pin_init(void) { stc_port_init_t stcPortInit; /* configuration structure initialization */ MEM_ZERO_STRUCT(stcPortInit); stcPortInit.enExInt = Disable; stcPortInit.enPullUp = Disable; stcPortInit.enPinMode = Pin_Mode_Out; /* Port/Pin initialization */ PORT_Init(BQ769X0_PWR_H_PORT, BQ769X0_PWR_H_PIN, &stcPortInit); PORT_Init(BQ769X0_PWR_M_PORT, BQ769X0_PWR_M_PIN, &stcPortInit); PORT_Init(BQ769X0_PWR_L_PORT, BQ769X0_PWR_L_PIN, &stcPortInit); PORT_ResetBits(BQ769X0_PWR_L_PORT, BQ769X0_PWR_L_PIN); PORT_ResetBits(BQ769X0_PWR_M_PORT, BQ769X0_PWR_M_PIN); PORT_ResetBits(BQ769X0_PWR_H_PORT, BQ769X0_PWR_H_PIN); } //---------------------------------------------------------------------------- void bq769x0_boot_pin_init(uint8_t io) { stc_port_init_t stcPortInit; /* configuration structure initialization */ MEM_ZERO_STRUCT(stcPortInit); stcPortInit.enExInt = Disable; stcPortInit.enPullUp = Disable; if (io) stcPortInit.enPinMode = Pin_Mode_Out; else stcPortInit.enPinMode = Pin_Mode_In; /* LED Port/Pin initialization */ PORT_Init(BQ769X0_BOOT_PORT, BQ769X0_BOOT_PIN, &stcPortInit); } //---------------------------------------------------------------------------- void bq769x0_alert_pin_init(uint8_t io) { stc_port_init_t stcPortInit; /* configuration structure initialization */ MEM_ZERO_STRUCT(stcPortInit); stcPortInit.enExInt = Disable; stcPortInit.enPullUp = Disable; if (io) stcPortInit.enPinMode = Pin_Mode_Out; else stcPortInit.enPinMode = Pin_Mode_In; /* LED Port/Pin initialization */ PORT_Init(BQ769X0_ALERT_PORT, BQ769X0_ALERT_PIN, &stcPortInit); } //---------------------------------------------------------------------------- uint8_t bq769x0_getAlertStatus(void) { return PORT_GetBit(BQ769X0_ALERT_PORT, BQ769X0_ALERT_PIN); } void bq769x0_ResetAlert(void) { bq769x0_alert_pin_init(1); PORT_SetBits(BQ769X0_ALERT_PORT, BQ769X0_ALERT_PIN); delay_os_ms(100); PORT_ResetBits(BQ769X0_ALERT_PORT, BQ769X0_ALERT_PIN); bq769x0_alert_pin_init(0); } //---------------------------------------------------------------------------- void bq769x0_construct(void) { type = bqType; I2CAddress = bqI2CAddress; if (type == bq76920) { numberOfCells = 5; } else if (type == bq76930) { numberOfCells = 10; } else { numberOfCells = 15; } // prevent errors if someone reduced MAX_NUMBER_OF_CELLS accidentally if (numberOfCells > MAX_NUMBER_OF_CELLS) { numberOfCells = MAX_NUMBER_OF_CELLS; } bq769x0_pwr_pin_init(); } //----------------------------------------------------------------------------- int bq769x0_begin(uint8_t alertPin, uint8_t bootPin) { I2c_Config(); // Init I2C bus // initialize variables for (uint8_t i = 0; i < numberOfCells; i++) { cellVoltages[i] = 0; } // Boot IC if pin is defined (else: manual boot via push button has to be done before calling this method) bq769x0_alert_pin_init(false); bq769x0_boot_pin_init(true); PORT_ResetBits(BQ769X0_BOOT_PORT, BQ769X0_BOOT_PIN); delay_os_ms(5); bq769x0_boot_pin_init(false); delay_os_ms(10); if (determineAddressAndCrc()) { // initial settings for bq769x0 writeRegister(SYS_CTRL1, 0x10); // 00010000b); // switch internal die temperature (TEMP_SEL) and ADC on (ADC_EN) writeRegister(SYS_CTRL2, 0x00); // 01000000b); // switch CC_EN off sys_ctrl1 = readRegister(SYS_CTRL1); sys_ctrl2 = readRegister(SYS_CTRL2); // get ADC offset and gain adcOffset = (signed int) readRegister(ADCOFFSET); // convert from 2's complement // adcGain = 365 + (((readRegister(ADCGAIN1) & B00001100) << 1) | ((readRegister(ADCGAIN2) & B11100000) >> 5)); // uV/LSB adcGain = 365 + (((readRegister(ADCGAIN1) & 0xC0) << 1) | ((readRegister(ADCGAIN2) & 0xE0) >> 5)); // uV/LSB setCellOvervoltageProtection(4000, 2); setCellUndervoltageProtection(2000, 2); protect[0] = readRegister(PROTECT1); protect[1] = readRegister(PROTECT2); protect[2] = readRegister(PROTECT3); ov_param = readRegister(OV_TRIP); uv_param = readRegister(UV_TRIP); // Cell balancing Test for (int i = 0; i < numberOfCells; i++) { bq769x0_updateBalancingSwitches(1 << i, (i % 2)); delay_os_ms(300); } bq769x0_updateBalancingSwitches(0x7FFF, 0); delay_os_ms(500); bq769x0_updateBalancingSwitches(0x7FFF, 1); delay_os_ms(500); return 0; } else { #ifdef CONSOLE_DEBUG printf("BMS communication error\r\n"); #endif /* CONSOLE_DEBUG */ return 1; } } //---------------------------------------------------------------------------- // automatically find out address and CRC setting bool determineAddressAndCrc(void) { // printf("Determining i2c address and whether CRC is enabled\r\n"); // check for each address and CRC combination while also set CC_CFG to 0x19 as per datasheet 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; 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; return false; } //---------------------------------------------------------------------------- // Fast function to check whether BMS has an error // (returns 0 if everything is OK) int bq769x0_checkStatus(void) { // printf("checkStatus: %d\r\n", errorStatus); if (alertInterruptFlag == false && errorStatus == 0) { return 0; } else { regSYS_STAT_t sys_stat; sys_stat.regByte = readRegister(SYS_STAT); // if (sys_stat.bits.CC_READY == 1) { // //LOG_PRINTLN("Interrupt: CC ready"); // updateCurrent(true); // automatically clears CC ready flag // } // Serious error occured if (sys_stat.regByte & 0x3F) // B00111111) { if (alertInterruptFlag == true) { secSinceErrorCounter = 0; } errorStatus = sys_stat.regByte; // int secSinceInterrupt = (millis() - interruptTimestamp) / 1000; // // // check for overrun of millis() or very slow running program // if (abs(secSinceInterrupt - secSinceErrorCounter) > 2) { // secSinceErrorCounter = secSinceInterrupt; // } // // // called only once per second // if (secSinceInterrupt >= secSinceErrorCounter) // { if (sys_stat.regByte & 0x20) { //B00100000) { // XR error // datasheet recommendation: try to clear after waiting a few seconds if (secSinceErrorCounter % 3 == 0) { // printf("Clearing XR error\r\n"); writeRegister(SYS_STAT, 0x20); //B00100000); } } if (sys_stat.regByte & 0x10) { // B00010000) { // Alert error if (secSinceErrorCounter % 10 == 0) { // printf("Clearing Alert error\r\n"); writeRegister(SYS_STAT, 0x10); // B00010000); } } if (sys_stat.regByte & 0x08) { // B00001000) { // UV error // updateVoltages(); // if (cellVoltages[idCellMinVoltage] > minCellVoltage) { // printf("Clearing UV error\r\n"); writeRegister(SYS_STAT, 0x08); // B00001000); // } } if (sys_stat.regByte & 0x04) { // B00000100) { // OV error // updateVoltages(); // if (cellVoltages[idCellMaxVoltage] < maxCellVoltage) { // printf("Clearing OV error\r\n"); writeRegister(SYS_STAT, 0x04); // B00000100); // } } if (sys_stat.regByte & 0x02) { // B00000010) { // SCD if (secSinceErrorCounter % 60 == 0) { // printf("Clearing SCD error\r\n"); writeRegister(SYS_STAT, 0x02); // B00000010); } } if (sys_stat.regByte & 0x01) { // B00000001) { // OCD if (secSinceErrorCounter % 60 == 0) { // printf("Clearing OCD error\r\n"); writeRegister(SYS_STAT, 0x01); // B00000001); } } secSinceErrorCounter++; // } } else { errorStatus = 0; } return errorStatus; } } //---------------------------------------------------------------------------- // should be called at least once every 250 ms to get correct coulomb counting void bq769x0_updateData(void) { bq769x0_checkStatus(); // printf("update\r\n"); // updateCurrent(true); // will only read new current value if alert was triggered updateVoltages(); // updateTemperatures(); updateCellBalStatus(); // updateBalancingSwitches(); } //---------------------------------------------------------------------------- // puts BMS IC into SHIP mode (i.e. switched off) void bq769x0_shutdown(void) { writeRegister(SYS_CTRL1, 0x0); writeRegister(SYS_CTRL1, 0x1); writeRegister(SYS_CTRL1, 0x2); } //---------------------------------------------------------------------------- // sets balancing registers if balancing is allowed // (sufficient idle time + voltage) uint8_t bq769x0_updateBalancingSwitches(uint16_t sBal, uint8_t mode) { uint8_t numberOfSections = numberOfCells / 5; uint32_t nBal = 0; uint8_t setSectionBal[3]; // check if balancing allowed if (bq769x0_checkStatus() == 0) { balancingActive = true; if (mode == 0) nBal = sBal & 0x00555555; // 0101 0101 0101 0101 else nBal = sBal & 0x00AAAAAA; // 1010 1010 1010 1010 // printf("sbal = %04X, nbal = %04X\r\n", sBal, nBal); setSectionBal[0] = (nBal >> 0) & 0x1F; setSectionBal[1] = (nBal >> 5) & 0x1F; setSectionBal[2] = (nBal >> 10) & 0x1F; for (int section = 0; section < numberOfSections; section++) { // set balancing register for this section writeRegister(CELLBAL1 + section, setSectionBal[section]); } } else if (balancingActive == true) { // clear all CELLBAL registers for (int section = 0; section < numberOfSections; section++) { // printf("Clearing Register CELLBAL %d\r\n", section+1); writeRegister(CELLBAL1 + section, 0x0); } balancingActive = false; } return balancingActive; } //---------------------------------------------------------------------------- int setCellUndervoltageProtection(int voltage_mV, int delay_s) { // LOG_PRINTLN("setUVP"); regPROTECT3_t protect3; uint8_t 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; // LOG_PRINT("UV_DELAY: "); // LOG_PRINTLN(i); break; } } writeRegister(PROTECT3, protect3.regByte); // returns the actual current threshold value return ((long)1 << 12 | uv_trip << 4) * adcGain / 1000 + adcOffset; } //---------------------------------------------------------------------------- int setCellOvervoltageProtection(int voltage_mV, int delay_s) { // LOG_PRINTLN("setOVP"); regPROTECT3_t protect3; uint8_t 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; // LOG_PRINT("OV_DELAY: "); // LOG_PRINTLN(i); break; } } writeRegister(PROTECT3, protect3.regByte); // returns the actual current threshold value return ((long)1 << 13 | ov_trip << 4) * adcGain / 1000 + adcOffset; } //---------------------------------------------------------------------------- uint16_t getNumberOfCells(void) { return numberOfCells; } //---------------------------------------------------------------------------- uint32_t getBatteryVoltage(void) { return batVoltage; } //---------------------------------------------------------------------------- uint32_t getBatterySumVoltage(void) { return batSumVoltage; } //---------------------------------------------------------------------------- int getMaxCellVoltage(void) { return cellVoltages[idCellMaxVoltage]; } //---------------------------------------------------------------------------- uint16_t getCellVoltage(uint16_t idCell) { return cellVoltages[idCell - 1]; } //---------------------------------------------------------------------------- float getTemperatureDegC(uint8_t channel) { if (channel >= 1 && channel <= 3) { return (float)temperatures[channel-1] / 10.0; } else return -273.15; // Error: Return absolute minimum temperature } //---------------------------------------------------------------------------- float getTemperatureDegF(uint8_t channel) { return getTemperatureDegC(channel) * 1.8 + 32; } //---------------------------------------------------------------------------- uint16_t getCellBalStatus(void) { return (uint16_t)(((balStatus[0] & 0x1F) << 0) | ((balStatus[1] & 0x1F) << 5) | ((balStatus[2] & 0x1F) << 10) ); } //---------------------------------------------------------------------------- uint8_t getAfeStatus(void) { return errorStatus; } //---------------------------------------------------------------------------- uint8_t getAfeCtrl1(void) { return sys_ctrl1; } uint8_t getAfeCtrl2(void) { return sys_ctrl2; } uint8_t getAfeProtect(uint8_t id) { return protect[id]; } uint8_t getAfeUvTrip(void) { return uv_param; } uint8_t getAfeOvTrip(void) { return ov_param; } //---------------------------------------------------------------------------- void updateTemperatures(void) { float tmp = 0; int adcVal = 0; int vtsx = 0; unsigned long rts = 0; uint8_t reg_data[2]; if (I2C_Reg_Read(I2CAddress, 0x2C, reg_data, 2, 1000) == Ok) { // calculate R_thermistor according to bq769x0 datasheet // adcVal = ((reg_data[0] & B00111111) << 8) | Wire.read(); adcVal = ((reg_data[0] & 0x3F) << 8) | reg_data[1]; 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 deg C reference temperature for Beta equation assumed tmp = 1.0 / (1.0 / (273.15+25) + 1.0 / 3 * log(rts/10000.0)); // K temperatures[0] = (tmp - 273.15) * 10.0; // printf("temperatures = %5.1f [%02X%02X]\r\4n", (float)temperatures[0] / 10.0, reg_data[0], reg_data[1]); } // Wire.beginTransmission(I2CAddress); // Wire.write(0x2C); // Wire.endTransmission(); // // if (Wire.requestFrom(I2CAddress, 2) == 2) // { // // calculate R_thermistor according to bq769x0 datasheet // adcVal = ((Wire.read() & B00111111) << 8) | Wire.read(); // 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 deg 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; // } } //---------------------------------------------------------------------------- // reads all cell voltages and updates batVoltage // now supports CRC, taken from mbed version of LibreSolar firmware (thanks to mikethezipper) uint8_t voltBuf[15 * 2] = {0, }; void updateVoltages(void) { uint32_t adcVal = 0; // uint8_t crc; // crc = 0; if (crcEnabled == true) { // if (I2C_Reg_Read(I2CAddress, buf[0], voltBuf, 15 * 2, 1000) == Ok) // { // // check if CRC matches data bytes // // CRC of first uint8_t 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]){ // // LOG_PRINTLN("VCxHI CRC doesn't match"); // // return; // to exit without saving corrupted value // // } // // // CRC of subsequent bytes contain only data // crc = _crc8_ccitt_update(0, buf[2]); // // if (crc != buf[3]) { // // LOG_PRINTLN("VCxLO CRC doesn't match"); // // return; // to exit without saving corrupted value // // } // } } else { if (I2C_Reg_Read(I2CAddress, VC1_HI_BYTE, voltBuf, numberOfCells * 2, 1000) == Ok) { uint32_t vSum = 0; for (int i = 0; i < numberOfCells; i++) { // combine VCx_HI and VCx_LO bits and calculate cell voltage // adcVal = (buf[0] & 0b00111111) << 8 | buf[2]; // read VCx_HI bits and drop the first two bits, shift left then append VCx_LO bits adcVal = (voltBuf[i * 2] & 0x3F) << 8 | voltBuf[(i * 2) + 1]; // read VCx_HI bits and drop the first two bits, shift left then append VCx_LO bits cellVoltages[i] = adcVal * adcGain / 1000 + adcOffset; // calculate real voltage in mV vSum += cellVoltages[i]; } batSumVoltage = vSum; } uint32_t adcValPack = ((readRegister(BAT_HI_BYTE) << 8) | readRegister(BAT_LO_BYTE)) & 0x0000FFFF; // 0b1111 1111 1111 1111; // batVoltage = 4 * adcGain * adcValPack / 1000 + (connectedCells * adcOffset); // in original LibreSolar, connectedCells is converted to uint8_t, maybe to reduce bit size batVoltage = 4 * adcGain * adcValPack / 1000 + (numberOfCells * adcOffset); // in original LibreSolar, connectedCells is converted to uint8_t, maybe to reduce bit size // printf("batVoltage = %ld\r\n", batVoltage); } // Wire.beginTransmission(I2CAddress); // Wire.write(buf[0]); // tell slave that this is the address it is interested in // Wire.endTransmission(); // end transmission so that read can begin // /****************************************************\ // Note that each cell voltage is 14 bits stored across two 8 bit register locations in the BQ769x0 chip. // This means that first we need to read register HI (in first instance this is VC1_HI_BYTE), // however this 8 bit piece of data has two worthless first digits - garbage. // To remove the first two bits, the bitwise & is used. By saying A & 00111111, only the last 6 bits of A are used. // Meanwhile all of the 8 bits on the low side are used. So the overall reading is the last 6 bits of high in front of the 8 bits from low. // To add the hi and lo readings together, the << is used to shift the hi value over by 8 bits, then adding it to the 8 bits. // This is done by using the OR operator |. So the total thing looks like: adcVal = (VC_HI_BYTE & 0b00111111) << 8 | VC_LO_BYTE; // \****************************************************/ // // will run once for each cell up to the total num of cells // for (int i = 0; i < numberOfCells; i++) { // if (crcEnabled == true) { // // refer to datasheet at 10.3.1.4 "Communications Subsystem" // Wire.requestFrom(I2CAddress, 4); // request 4 bytes: 1) VCx_HI - 2) VCx_HI CRC - 3) VCx_LO - 4) VCx_LO CRC // buf[0] = Wire.read(); // VCx_HI - note that only bottom 6 bits are good // buf[1] = Wire.read(); // VCx_HI CRC - done on address and data // buf[2] = Wire.read(); // VCx_LO - all 8 bits are used // buf[3] = Wire.read(); // VCx_LO CRC - done on just the data uint8_t // // // check if CRC matches data bytes // // CRC of first uint8_t 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]){ // // LOG_PRINTLN("VCxHI CRC doesn't match"); // // return; // to exit without saving corrupted value // // } // // // CRC of subsequent bytes contain only data // crc = _crc8_ccitt_update(0, buf[2]); // // if (crc != buf[3]) { // // LOG_PRINTLN("VCxLO CRC doesn't match"); // // return; // to exit without saving corrupted value // // } // } // // if CRC is disabled only read 2 bytes and call it a day :) // else { // Wire.requestFrom(I2CAddress, 2); // buf[0] = Wire.read(); // VCx_HI - note that only bottom 6 bits are good // buf[2] = Wire.read(); // VCx_LO - all 8 bits are used // } // // combine VCx_HI and VCx_LO bits and calculate cell voltage // adcVal = (buf[0] & 0b00111111) << 8 | buf[2]; // read VCx_HI bits and drop the first two bits, shift left then append VCx_LO bits // cellVoltages[i] = adcVal * adcGain / 1000 + adcOffset; // calculate real voltage in mV // // // filter out voltage readings from unconnected cell(s) // if (cellVoltages[i] > 500) { // connectedCells++; // add one to the temporary cell counter var - only readings above 500mV are counted towards real cell count // } // if (cellVoltages[i] > cellVoltages[idCellMaxVoltage]) { // idCellMaxVoltage = i; // } // if (cellVoltages[i] < cellVoltages[idCellMinVoltage] && cellVoltages[i] > 500) { // idCellMinVoltage = i; // } // } // // long adcValPack = ((readRegister(BAT_HI_BYTE) << 8) | readRegister(BAT_LO_BYTE)) & 0b1111111111111111; // batVoltage = 4 * adcGain * adcValPack / 1000 + (connectedCells * adcOffset); // in original LibreSolar, connectedCells is converted to uint8_t, maybe to reduce bit size } void updateCellBalStatus(void) { uint8_t tmp[4]; if (I2C_Reg_Read(I2CAddress, CELLBAL1, tmp, 3, 1000) == Ok) { balStatus[0] = tmp[0]; balStatus[1] = tmp[1]; balStatus[2] = tmp[2]; } } void updateAdcGain2(int32_t Gain2) { adcGain2 = Gain2; } //---------------------------------------------------------------------------- // now supports CRC, taken from mbed version of LibreSolar firmware (thanks to mikethezipper) void writeRegister(uint8_t address, int data) { // printf("write: 0x%02X ", address); // printf(" --> 0x%02X\r\n", data); uint8_t crc = 0; uint8_t 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_Reg_Write(I2CAddress, buf[0], &buf[1], 2, 1000); } else { I2C_Reg_Write(I2CAddress, buf[0], &buf[1], 1, 1000); } // // note that writes to the bq769x0 IC are: 1) start - 2) address - 3) address - 4) data - 5) CRC8 - 6) stop bit // Wire.beginTransmission(I2CAddress); // writes start bit - the first step // Wire.write(buf[0]); // writes register address // Wire.write(buf[1]); // writes data - the fourth step // // 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; // Wire.write(buf[2]); // writes CRC // LOG_PRINT(" CRC:"); // LOG_PRINT(byte2char(buf[2])); // } // Wire.endTransmission(); // LOG_PRINTLN(); } //---------------------------------------------------------------------------- uint8_t readRegister(uint8_t address) { uint8_t ret = 0; I2C_Reg_Read(I2CAddress, address, &ret, 1, 1000); return ret; // Wire.beginTransmission(I2CAddress); // Wire.write(address); // Wire.endTransmission(); // Wire.requestFrom(I2CAddress, 1); // return Wire.read(); } //---------------------------------------------------------------------------- // supports CRC, taken from mbed version of LibreSolar firmware // however often freeze momentarily at "while (crc != buf[1]);" // int bq769x0::readRegister(uint8_t address) // { // LOG_PRINT("read: "); // LOG_PRINTLN(byte2char(address)); // uint8_t crc = 0; // char buf[2]; // buf[0] = (char) address; // Wire.beginTransmission(I2CAddress); // Wire.write(buf[0]); // Wire.endTransmission(); // if (crcEnabled == true) { // do { // Wire.requestFrom(I2CAddress, 2); // buf[0] = Wire.read(); // buf[1] = Wire.read(); // // 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 { // Wire.requestFrom(I2CAddress, 1); // return Wire.read(); // } // } //---------------------------------------------------------------------------- // the actual ISR, called by static function alertISR() void setAlertInterruptFlag(void) { // interruptTimestamp = millis(); alertInterruptFlag = true; } //---------------------------------------------------------------------------- // for debug purposes #if BQ769X0_DEBUG void bq769x0::printRegisters() { LOG_PRINT(F("0x00 SYS_STAT: ")); LOG_PRINTLN(byte2char(readRegister(SYS_STAT))); LOG_PRINT(F("0x01 CELLBAL1: ")); LOG_PRINTLN(byte2char(readRegister(CELLBAL1))); LOG_PRINT(F("0x02 CELLBAL2: ")); LOG_PRINTLN(byte2char(readRegister(CELLBAL2))); LOG_PRINT(F("0x03 CELLBAL3: ")); LOG_PRINTLN(byte2char(readRegister(CELLBAL3))); LOG_PRINT(F("0x04 SYS_CTRL1: ")); LOG_PRINTLN(byte2char(readRegister(SYS_CTRL1))); LOG_PRINT(F("0x05 SYS_CTRL2: ")); LOG_PRINTLN(byte2char(readRegister(SYS_CTRL2))); LOG_PRINT(F("0x06 PROTECT1: ")); LOG_PRINTLN(byte2char(readRegister(PROTECT1))); LOG_PRINT(F("0x07 PROTECT2: ")); LOG_PRINTLN(byte2char(readRegister(PROTECT2))); LOG_PRINT(F("0x08 PROTECT3 ")); LOG_PRINTLN(byte2char(readRegister(PROTECT3))); LOG_PRINT(F("0x09 OV_TRIP: ")); LOG_PRINTLN(byte2char(readRegister(OV_TRIP))); LOG_PRINT(F("0x0A UV_TRIP: ")); LOG_PRINTLN(byte2char(readRegister(UV_TRIP))); LOG_PRINT(F("0x0B CC_CFG: ")); LOG_PRINTLN(byte2char(readRegister(CC_CFG))); LOG_PRINT(F("0x2A BAT_HI: ")); LOG_PRINTLN(byte2char(readRegister(BAT_HI_BYTE))); LOG_PRINT(F("0x2B BAT_LO: ")); LOG_PRINTLN(byte2char(readRegister(BAT_LO_BYTE))); LOG_PRINT(F("0x32 CC_HI: ")); LOG_PRINTLN(byte2char(readRegister(CC_HI_BYTE))); LOG_PRINT(F("0x33 CC_LO: ")); LOG_PRINTLN(byte2char(readRegister(CC_LO_BYTE))); // LOG_PRINT(F("0x50 ADCGAIN1: ")); // LOG_PRINTLN(byte2char(readRegister(ADCGAIN1))); // LOG_PRINT(F("0x51 ADCOFFSET: ")); // LOG_PRINTLN(byte2char(readRegister(ADCOFFSET))); // LOG_PRINT(F("0x59 ADCGAIN2: ")); // LOG_PRINTLN(byte2char(readRegister(ADCGAIN2))); } #endif