1008 lines
30 KiB
C
1008 lines
30 KiB
C
/*
|
|
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
|
|
<http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
TODO:
|
|
- Balancing algorithm
|
|
- SOC calculation + coulomb counting
|
|
- Autobalancing (what is this?)
|
|
*/
|
|
|
|
#include <math.h> // 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
|