/*
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