component
Modular BMS
Public Made by Adomby adom
Modular Battery Management System — a composable BMS built from Adom molecules. TI BQ76952 monitor/protector with MSP430 host firmware, supporting 18650 cylindrical and EVE LF105 prismatic cell carrie
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216
#include "msp430f5529.h"
#define ACTIVE
#ifdef ACTIVE
/* --COPYRIGHT--,BSD_EX
* Copyright (c) 2021, Texas Instruments Incorporated
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Texas Instruments Incorporated nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* --/COPYRIGHT--*/
//******************************************************************************
// BQ76952EVM demo code for MSP430FR2x55 + BQ769x2
//
// Description: MSP430FR2x55 functions as the I2C host that communicates
// with the BQ769x2 sending and receiving different types of commands.
// ACLK = 32.768kHz REFO or external XT1, MCLK = SMCLK = 1MHz DCO
//
// /|\ /|\
// MSP430FR2x55 10k |
// ----------------- | 10k
// /|\ | P4.5|---+---|-- I2C Data (UCB1SDA)
// | | | |
// ---|RST P4.4|-------+-- I2C Clock (UCB1SCL)
// | |
// | P2.3|---> SHUT
// | |
// | P4.2|---> UART RX
// | |
// LEDs <---|P8.0 -8.2 P4.3|<--- UART TX
// | |
//
//
// Andrew Han and Matt Sunna
// Texas Instruments Inc.
// August 2021
// Built with Code Composer Studio (CCS) v10.1.1
//******************************************************************************
//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------
#include <assert.h>
#include <msp430.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "BQ769x2Header.h"
//------------------------------------------------------------------------------
// Defines / macros
//------------------------------------------------------------------------------
// USER CONFIG HERE
#define NUM_CELLS 3
#define NUM_CELLS_BITS 0x0007 // bit 0 for cell 0, bit 3 for cell 3, etc. Write 1 to enable, write 0 to disable
// other
#define DEV_ADDR 0x08 // BQ769x2 7-bit I2C address
#define CRC_Mode 0 // 0 = disabled, 1 = enabled
#define MAX_BUFFER_SIZE 10
#define R 0 // Read
#define W 1 // Write
#define W2 2 // Write data with two bytes
// Porting macros for F5529: match FR-style USCI flags
#define USCI_I2C_UCRXIFG0 USCI_I2C_UCRXIFG
#define USCI_I2C_UCTXIFG0 USCI_I2C_UCTXIFG
// These extra FIFO levels don't exist on F5529; they will never be hit
#define USCI_I2C_UCRXIFG1 0x12
#define USCI_I2C_UCRXIFG2 0x14
#define USCI_I2C_UCRXIFG3 0x16
#define USCI_I2C_UCTXIFG1 0x18
#define USCI_I2C_UCTXIFG2 0x1A
#define USCI_I2C_UCTXIFG3 0x1C
// Upper bound for __even_in_range
#define USCI_I2C_UCBIT9IFG USCI_I2C_UCTXIFG
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef enum I2C_ModeEnum
{
IDLE_MODE,
NACK_MODE,
TX_REG_ADDRESS_MODE,
RX_REG_ADDRESS_MODE,
TX_DATA_MODE,
RX_DATA_MODE,
SWITCH_TO_RX_MODE,
SWITHC_TO_TX_MODE,
TIMEOUT_MODE
} I2C_Mode;
typedef enum
{
OUTPUT_HUMAN = 0,
OUTPUT_JSON = 1
} OutputMode;
//------------------------------------------------------------------------------
// Globals
//------------------------------------------------------------------------------
// UART output mode
static OutputMode output_mode = OUTPUT_HUMAN; // default to human-readable output
// I2C state machine
I2C_Mode MasterMode = IDLE_MODE;
uint8_t TransmitRegAddr = 0;
uint8_t ReceiveBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t RXByteCtr = 0;
uint8_t ReceiveIndex = 0;
uint8_t TransmitBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t TXByteCtr = 0;
uint8_t TransmitIndex = 0;
// RX / measurement buffers
uint8_t RX_data[2] = {0x00, 0x00};
uint8_t RX_32Byte[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint16_t CellVoltage[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float Temperature[3] = {0, 0, 0};
uint16_t Stack_Voltage = 0x00;
uint16_t Pack_Voltage = 0x00;
uint16_t LD_Voltage = 0x00;
uint16_t Pack_Current = 0x00;
uint16_t AlarmBits = 0x00;
uint8_t value_SafetyStatusA;
uint8_t value_SafetyStatusB;
uint8_t value_SafetyStatusC;
uint8_t value_PFStatusA;
uint8_t value_PFStatusB;
uint8_t value_PFStatusC;
uint8_t FET_Status;
uint16_t CB_ActiveCells;
uint8_t UV_Fault = 0;
uint8_t OV_Fault = 0;
uint8_t SCD_Fault = 0;
uint8_t OCD_Fault = 0;
uint8_t ProtectionsTriggered = 0;
uint8_t LD_ON = 0;
uint8_t DSG = 0;
uint8_t CHG = 0;
uint8_t PCHG = 0;
uint8_t PDSG = 0;
//------------------------------------------------------------------------------
// Function prototypes
//------------------------------------------------------------------------------
// Board / low-level
void GPIO_initPins(void);
void GPIO_configPins(void);
void LED_On(int32_t ledPin);
void LED_Off(int32_t ledPin);
void UART_Init(void);
void I2C_initModule(void);
void map_I2C_and_UART(void);
// Utility
void delayUS(uint16_t us);
int putchar(int c);
void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count);
unsigned char Checksum(unsigned char *ptr, unsigned char len);
// I2C access
I2C_Mode I2C_ReadReg(uint8_t reg_addr, uint8_t *reg_data, uint8_t count);
I2C_Mode I2C_WriteReg(uint8_t reg_addr, uint8_t *reg_data, uint8_t count);
// BQ769x2 API
void BQ769x2_SetRegister(uint16_t reg_addr, uint32_t reg_data, uint8_t datalen);
void CommandSubcommands(uint16_t command);
void Subcommands(uint16_t command, uint16_t data, uint8_t type);
void DirectCommands(uint8_t command, uint16_t data, uint8_t type);
void BQ769x2_Init(void);
void BQ769x2_ReadFETStatus(void);
void BQ769x2_WriteFETStatus(bool DSG, bool CHG);
void update_fets_from_buttons(void);
void BQ769x2_ShutdownPin(void);
void BQ769x2_ReleaseShutdownPin(void);
uint16_t BQ769x2_ReadAlarmStatus(void);
void BQ769x2_ReadSafetyStatus(void);
void BQ769x2_ReadPFStatus(void);
uint16_t BQ769x2_ReadVoltage(uint8_t command);
void BQ769x2_ReadAllVoltages(void);
uint16_t BQ769x2_ReadCurrent(void);
float BQ769x2_ReadTemperature(uint8_t command);
// JSON/Hydrogen packets
void send_json_notification(const char *method,
const char *id,
const char *data_fmt, ...);
// --- 1. BMS output messages ---------------------------------------------------
void output_bms_config(const char *battery_type, int cell_count);
void output_bms_voltages(uint32_t v_mV, const uint16_t *CellVoltage, int cell_count);
void output_fet_status(bool chg, bool dsg);
void output_current_value(int16_t current_mA);
// --- output mode selection --------------------------------------------------
void set_output_mode(OutputMode mode)
{
output_mode = mode; // set global output mode
}
//------------------------------------------------------------------------------
// main
//------------------------------------------------------------------------------
int main(void)
{
// Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
GPIO_initPins();
map_I2C_and_UART();
UART_Init();
I2C_initModule();
LED_On(0); // Popwer LED
set_output_mode(OUTPUT_JSON); // or OUTPUT_JSON
CommandSubcommands(BQ769x2_RESET); // Reset the BQ769x2 registers
delayUS(60000);
BQ769x2_Init(); // Configure all BQ769x2 RAM settings
delayUS(10000);
BQ769x2_WriteFETStatus(0,1);
CommandSubcommands(FET_ENABLE); // Enable CHG and DSG FETs
delayUS(10000);
CommandSubcommands(SLEEP_DISABLE); // Disable Sleep for full-speed measurements
// Wait for FETs to close and measurements to settle
delayUS(60000);
delayUS(60000);
delayUS(60000);
delayUS(60000);
delayUS(20000);
delayUS(20000);
delayUS(20000);
LED_On(1);
printf("hello world\n\r");
output_bms_config("Li-ion", 3);
while (1)
{
static int i = 0; // Loop counter for periodic output
// Read alarm status and check for new measurements
AlarmBits = BQ769x2_ReadAlarmStatus();
if (AlarmBits & 0x80)
{
// FULLSCAN complete: new measurements available
BQ769x2_ReadAllVoltages();
Pack_Current = BQ769x2_ReadCurrent();
Temperature[0] = BQ769x2_ReadTemperature(TS1Temperature);
Temperature[1] = BQ769x2_ReadTemperature(TS3Temperature);
// Clear the FULLSCAN bit
DirectCommands(AlarmStatus, 0x0080, W);
}
if (AlarmBits & 0xC000)
{
// Safety Status bits present in AlarmStatus
BQ769x2_ReadSafetyStatus();
if (ProtectionsTriggered & 1)
{
P1OUT |= BIT0; // Turn on LED to indicate protection
}
// Clear Safety Status Alarm bits
DirectCommands(AlarmStatus, 0xF800, W);
}
else
{
if (ProtectionsTriggered & 1)
{
BQ769x2_ReadSafetyStatus();
if (!(ProtectionsTriggered & 1))
{
P1OUT &= ~BIT0; // Turn off LED when protection clears
printf("PROTECT");
}
}
}
// Output for every 20 cycles
if (i % 20 == 0){
printf("Running\n\r");
output_bms_voltages(Stack_Voltage, CellVoltage, NUM_CELLS);
update_fets_from_buttons();
BQ769x2_ReadFETStatus();
output_fet_status(CHG, DSG);
output_current_value((int16_t)BQ769x2_ReadCurrent());
}
// Output for every 100 cycles
if (i > 100){
output_bms_config("Li-ion", 3);
BQ769x2_ReadSafetyStatus();
if (UV_Fault || OV_Fault || SCD_Fault || OCD_Fault){
printf("Fault detected\n\r");
printf("UV: %d", UV_Fault);
printf("OV: %d", OV_Fault);
printf("SCD: %d", SCD_Fault);
printf("OCD: %d", OCD_Fault);
}
i = 0;
}
i++;
delayUS(20000); // repeat loop every ~20 ms
}
}
//------------------------------------------------------------------------------
// UART Output helper functions
//------------------------------------------------------------------------------
// Low level formatting function
static void send_json_notification_data(const char *method, const char *id, const char *data_str)
{
uint32_t timestamp = 5005;
printf("{\"method\":\"%s\","
"\"data\":{%s},"
"\"timestamp\":%lu,"
"\"source\":\"robot\","
"\"type\":\"notification\","
"\"id\":\"%s\","
"\"agent\":\"robot\"}\n",
method, data_str, (unsigned long)timestamp, id);
}
// For simple, fixed data payloads
static void send_json_notification_fmt(const char *method,
const char *id,
const char *data_fmt, ...)
{
char data_buf[256];
va_list args;
va_start(args, data_fmt);
vsnprintf(data_buf, sizeof(data_buf), data_fmt, args); // format into data_buf
va_end(args);
send_json_notification_data(method, id, data_buf);
}
// -----------------------------------------------------------------------------
// Public “user” functions (no JSON/quote escaping here)
// -----------------------------------------------------------------------------
// 1. BMS/config --------------------------------------------------------------
void output_bms_config(const char *battery_type, int cell_count)
{
const char* type = "battery_type";
const char* count = "cell_count";
if (output_mode == OUTPUT_HUMAN)
{
printf("BMS config:\n\r");
printf(" %s: %s\n\r", type, battery_type);
printf(" %s: %d\n\r", count, cell_count);
}
else
{
// all escaping handled inside send_json_notification_fmt()
send_json_notification_fmt(
"/BMS/config",
"001",
"\"%s\":\"%s\",\"%s\":%d",
type, battery_type, count, cell_count);
}
}
// 2. Pack + cell voltages ----------------------------------------------------
// v_mV: pack voltage in millivolts
// CellVoltage[i]: per-cell voltage in millivolts
void output_bms_voltages(uint32_t v_mV, const uint16_t *CellVoltage, int cell_count)
{
if (output_mode == OUTPUT_HUMAN)
{
printf("Pack voltage: %u.%03u V\n\r",
v_mV / 1000, v_mV % 1000);
for (int i = 0; i < cell_count; i++)
{
printf(" Battery %d voltage: %u.%03u V\n\r",
i + 1,
CellVoltage[i] / 1000,
CellVoltage[i] % 1000);
}
}
else
{
// build dynamic JSON data body, then wrap with send_json_notification_data()
char data_buf[256];
int len = 0;
// pack voltage
len += snprintf(data_buf + len, sizeof(data_buf) - len,
"\"pack_voltage_V\":%u.%03u",
v_mV / 1000, v_mV % 1000);
// cell voltages
for (int i = 0; i < cell_count && len < (int)sizeof(data_buf); i++)
{
len += snprintf(data_buf + len, sizeof(data_buf) - len,
",\"cell%d_voltage_V\":%u.%03u",
i + 1,
CellVoltage[i] / 1000,
CellVoltage[i] % 1000);
}
send_json_notification_data("/BMS/voltages",
"002",
data_buf);
}
}
// Example function here
void output_fet_status(bool chg, bool dsg)
{
const char* chgStatus = "CHG_Status";
const char* dsgStatus = "DSG_Status";
if (output_mode == OUTPUT_HUMAN)
{
printf("%s: %d %s: %d \n\r", chgStatus, chg, dsgStatus, dsg);
}
else
{
// all escaping handled inside send_json_notification_fmt()
send_json_notification_fmt(
"/BMS/fet_status",
"003",
"\"%s\":%d,\"%s\":%d",
chgStatus, chg, dsgStatus, dsg);
}
}
void output_error_notifications(bool UV, bool OV, bool SCD, bool OCD)
{
const char* UV_s = "Under_Voltage_Status";
const char* OV_s = "Over_Voltage_Status";
const char* SCD_s = "SCD_Status";
const char* OCD_s = "OCD_Status";
if (output_mode == OUTPUT_HUMAN)
{
printf("%s: %d %s: %d %s: %d %s: %d \n\r", UV_s, UV, OV_s, OV, SCD_s, SCD, OCD_s, OCD);
}
else
{
// all escaping handled inside send_json_notification_fmt()
send_json_notification_fmt(
"/BMS/error_notifications",
"004",
"\"%s\":%d,\"%s\":%d,\"%s\":%d,\"%s\":%d",
UV_s, UV, OV_s, OV, SCD_s, SCD, OCD_s, OCD);
}
}
void output_current_value(int16_t current_mA)
{
const char* current_s = "Current_mA";
if (output_mode == OUTPUT_HUMAN)
{
printf("%s: %d \n\r", current_s, current_mA);
}
else
{
// all escaping handled inside send_json_notification_fmt()
send_json_notification_fmt(
"/BMS/current_measurement",
"005",
"\"%s\":%d",
current_s, current_mA);
}
}
//------------------------------------------------------------------------------
// High-level BQ769x2 helpers
//------------------------------------------------------------------------------
void BQ769x2_Init(void)
{
// Enter CONFIGUPDATE mode (Subcommand 0x0090)
CommandSubcommands(SET_CFGUPDATE);
// Power configuration
BQ769x2_SetRegister(PowerConfig, 0x2D80, 2);
// Enable REG0 pre-regulator
BQ769x2_SetRegister(REG0Config, 0x01, 1);
// Enable REG1 with 3.3 V output
BQ769x2_SetRegister(REG12Config, 0x0D, 1);
// DFETOFF pin controls both CHG and DSG FET
BQ769x2_SetRegister(DFETOFFPinConfig, 0x42, 1);
// ALERT pin configuration
BQ769x2_SetRegister(ALERTPinConfig, 0x2A, 1);
// TS1 measures cell temperature
BQ769x2_SetRegister(TS1Config, 0x07, 1);
// TS3 measures FET temperature
BQ769x2_SetRegister(TS3Config, 0x0F, 1);
// HDQ pin: no thermistor on EVM; set to 0
BQ769x2_SetRegister(HDQPinConfig, 0x00, 1);
// VCell Mode: enable 16 cells
BQ769x2_SetRegister(VCellMode, NUM_CELLS_BITS, 2);
// Enabled Protections A: SCD, OCD1, OCC, COV, CUV
BQ769x2_SetRegister(EnabledProtectionsA, 0xBC, 1);
// Enabled Protections B: all enabled
BQ769x2_SetRegister(EnabledProtectionsB, 0xF7, 1);
// Default Alarm Mask
BQ769x2_SetRegister(DefaultAlarmMask, 0xF882, 2);
// Cell balancing configuration
BQ769x2_SetRegister(BalancingConfiguration, 0x03, 1);
// CUV Threshold
BQ769x2_SetRegister(CUVThreshold, 0x31, 1);
// COV Threshold
BQ769x2_SetRegister(COVThreshold, 0x55, 1);
// OCC Threshold
BQ769x2_SetRegister(OCCThreshold, 0x05, 1);
// OCD1 Threshold
BQ769x2_SetRegister(OCD1Threshold, 0x0A, 1);
// SCD Threshold
BQ769x2_SetRegister(SCDThreshold, 0x05, 1);
// SCD Delay
BQ769x2_SetRegister(SCDDelay, 0x03, 1);
// SCDL latch limit
BQ769x2_SetRegister(SCDLLatchLimit, 0x01, 1);
// Exit CONFIGUPDATE mode
CommandSubcommands(EXIT_CFGUPDATE);
}
uint16_t BQ769x2_ReadAlarmStatus(void)
{
DirectCommands(AlarmStatus, 0x00, R);
return (RX_data[1] * 256 + RX_data[0]);
}
void BQ769x2_ReadSafetyStatus(void)
{
// Safety Status A
DirectCommands(SafetyStatusA, 0x00, R);
value_SafetyStatusA = (RX_data[1] * 256 + RX_data[0]);
UV_Fault = ((0x4 & RX_data[0]) >> 2);
OV_Fault = ((0x8 & RX_data[0]) >> 3);
SCD_Fault = ((0x8 & RX_data[1]) >> 3);
OCD_Fault = ((0x2 & RX_data[1]) >> 1);
// Safety Status B
DirectCommands(SafetyStatusB, 0x00, R);
value_SafetyStatusB = (RX_data[1] * 256 + RX_data[0]);
// Safety Status C
DirectCommands(SafetyStatusC, 0x00, R);
value_SafetyStatusC = (RX_data[1] * 256 + RX_data[0]);
if ((value_SafetyStatusA + value_SafetyStatusB + value_SafetyStatusC) > 1)
{
ProtectionsTriggered = 1;
}
else
{
ProtectionsTriggered = 0;
}
}
void BQ769x2_ReadPFStatus(void)
{
DirectCommands(PFStatusA, 0x00, R);
value_PFStatusA = (RX_data[1] * 256 + RX_data[0]);
DirectCommands(PFStatusB, 0x00, R);
value_PFStatusB = (RX_data[1] * 256 + RX_data[0]);
DirectCommands(PFStatusC, 0x00, R);
value_PFStatusC = (RX_data[1] * 256 + RX_data[0]);
}
void BQ769x2_ReadFETStatus(void)
{
DirectCommands(FETStatus, 0x00, R);
FET_Status = (RX_data[1] * 256 + RX_data[0]);
DSG = ((0x4 & RX_data[0]) >> 2);
CHG = (0x1 & RX_data[0]);
PCHG = ((0x2 & RX_data[0]) >> 1);
PDSG = ((0x8 & RX_data[0]) >> 3);
}
void BQ769x2_WriteFETStatus(bool DSG, bool CHG){
uint8_t State = 0;
// DSG control
if (!DSG){
State |= (1 << 0); // set DSG_OFF bit = force off
}
// CHG control
if (!CHG){
State |= (1 << 2); // set CHG_OFF bit = force off
}
Subcommands(FET_CONTROL, State, 1); // or whatever your write call requires
}
void update_fets_from_buttons(void)
{
bool dsg_enable = false;
bool chg_enable = false;
if (P3IN & BIT3) {
dsg_enable = true; // p3.3 high -> enable discharge
}
if (P3IN & BIT4) {
chg_enable = true; // p3.4 high -> enable charge
}
BQ769x2_WriteFETStatus(dsg_enable, chg_enable); // apply fet control
}
void BQ769x2_ShutdownPin(void)
{
P2OUT |= BIT3;
}
void BQ769x2_ReleaseShutdownPin(void)
{
P2OUT &= ~BIT3;
}
uint16_t BQ769x2_ReadVoltage(uint8_t command)
{
DirectCommands(command, 0x00, R);
if (command >= Cell1Voltage && command <= Cell16Voltage)
{
// Cell 1–16: mV
return (RX_data[1] * 256 + RX_data[0]);
}
else
{
// Stack / Pack / LD: 0.01 V units
return 10 * (RX_data[1] * 256 + RX_data[0]);
}
}
void BQ769x2_ReadAllVoltages(void)
{
unsigned char x;
int cellvoltageholder = Cell1Voltage;
for (x = 0; x < 16; x++)
{
CellVoltage[x] = BQ769x2_ReadVoltage(cellvoltageholder);
cellvoltageholder += 2;
}
Stack_Voltage = BQ769x2_ReadVoltage(StackVoltage);
Pack_Voltage = BQ769x2_ReadVoltage(PACKPinVoltage);
LD_Voltage = BQ769x2_ReadVoltage(LDPinVoltage);
}
uint16_t BQ769x2_ReadCurrent(void)
{
DirectCommands(CC2Current, 0x00, R);
return (RX_data[1] * 256 + RX_data[0]); // mA
}
float BQ769x2_ReadTemperature(uint8_t command)
{
DirectCommands(command, 0x00, R);
return (0.1f * (float)(RX_data[1] * 256 + RX_data[0])) - 273.15f;
}
//------------------------------------------------------------------------------
// Low-level BQ769x2 register / command helpers
//------------------------------------------------------------------------------
void BQ769x2_SetRegister(uint16_t reg_addr, uint32_t reg_data, uint8_t datalen)
{
uint8_t TX_Buffer[2] = {0x00, 0x00};
uint8_t TX_RegData[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
//TX_RegData in little endian format
TX_RegData[0] = reg_addr & 0xff;
TX_RegData[1] = (reg_addr >> 8) & 0xff;
TX_RegData[2] = reg_data & 0xff; //1st byte of data
switch(datalen)
{
case 1: //1 byte datalength
I2C_WriteReg(0x3E, TX_RegData, 3);
delayUS(2000);
TX_Buffer[0] = Checksum(TX_RegData, 3);
TX_Buffer[1] = 0x05; //combined length of register address and data
I2C_WriteReg(0x60, TX_Buffer, 2); // Write the checksum and length
delayUS(2000);
break;
case 2: //2 byte datalength
TX_RegData[3] = (reg_data >> 8) & 0xff;
I2C_WriteReg(0x3E, TX_RegData, 4);
delayUS(2000);
TX_Buffer[0] = Checksum(TX_RegData, 4);
TX_Buffer[1] = 0x06; //combined length of register address and data
I2C_WriteReg(0x60, TX_Buffer, 2); // Write the checksum and length
delayUS(2000);
break;
case 4: //4 byte datalength, Only used for CCGain and Capacity Gain
TX_RegData[3] = (reg_data >> 8) & 0xff;
TX_RegData[4] = (reg_data >> 16) & 0xff;
TX_RegData[5] = (reg_data >> 24) & 0xff;
I2C_WriteReg(0x3E, TX_RegData, 6);
delayUS(2000);
TX_Buffer[0] = Checksum(TX_RegData, 6);
TX_Buffer[1] = 0x08; //combined length of register address and data
I2C_WriteReg(0x60, TX_Buffer, 2); // Write the checksum and length
delayUS(2000);
break;
}
}
void CommandSubcommands(uint16_t command) //For Command only Subcommands
// See the TRM or the BQ76952 header file for a full list of Command-only subcommands
{ //For DEEPSLEEP/SHUTDOWN subcommand you will need to call this function twice consecutively
uint8_t TX_Reg[2] = {0x00, 0x00};
//TX_Reg in little endian format
TX_Reg[0] = command & 0xff;
TX_Reg[1] = (command >> 8) & 0xff;
I2C_WriteReg(0x3E,TX_Reg,2);
delayUS(2000);
}
void Subcommands(uint16_t command, uint16_t data, uint8_t type)
// See the TRM or the BQ76952 header file for a full list of Subcommands
{
//security keys and Manu_data writes dont work with this function (reading these commands works)
//max readback size is 32 bytes i.e. DASTATUS, CUV/COV snapshot
uint8_t TX_Reg[4] = {0x00, 0x00, 0x00, 0x00};
uint8_t TX_Buffer[2] = {0x00, 0x00};
//TX_Reg in little endian format
TX_Reg[0] = command & 0xff;
TX_Reg[1] = (command >> 8) & 0xff;
if (type == R) {//read
I2C_WriteReg(0x3E,TX_Reg,2);
delayUS(2000);
I2C_ReadReg(0x40, RX_32Byte, 32); //RX_32Byte is a global variable
}
else if (type == W) {
//FET_Control, REG12_Control
TX_Reg[2] = data & 0xff;
I2C_WriteReg(0x3E,TX_Reg,3);
delayUS(1000);
TX_Buffer[0] = Checksum(TX_Reg, 3);
TX_Buffer[1] = 0x05; //combined length of registers address and data
I2C_WriteReg(0x60, TX_Buffer, 2);
delayUS(1000);
}
else if (type == W2){ //write data with 2 bytes
//CB_Active_Cells, CB_SET_LVL
TX_Reg[2] = data & 0xff;
TX_Reg[3] = (data >> 8) & 0xff;
I2C_WriteReg(0x3E,TX_Reg,4);
delayUS(1000);
TX_Buffer[0] = Checksum(TX_Reg, 4);
TX_Buffer[1] = 0x06; //combined length of registers address and data
I2C_WriteReg(0x60, TX_Buffer, 2);
delayUS(1000);
}
}
void DirectCommands(uint8_t command, uint16_t data, uint8_t type)
// See the TRM or the BQ76952 header file for a full list of Direct Commands
{ //type: R = read, W = write
uint8_t TX_data[2] = {0x00, 0x00};
//little endian format
TX_data[0] = data & 0xff;
TX_data[1] = (data >> 8) & 0xff;
if (type == R) {//Read
I2C_ReadReg(command, RX_data, 2); //RX_data is a global variable
delayUS(2000);
}
if (type == W) {//write
//Control_status, alarm_status, alarm_enable all 2 bytes long
I2C_WriteReg(command,TX_data,2);
delayUS(2000);
}
}
//------------------------------------------------------------------------------
// Utility functions
//------------------------------------------------------------------------------
void delayUS(uint16_t us)
{
uint16_t ms = us / 1000;
for (uint16_t i = 0; i < ms; i++)
{
__delay_cycles(1000);
}
}
int putchar(int c)
{
while (!(UCA1IFG & UCTXIFG))
;
UCA1TXBUF = (unsigned char)c;
return c;
}
void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count)
{
for (uint8_t i = 0; i < count; i++)
{
dest[i] = source[i];
}
}
unsigned char Checksum(unsigned char *ptr, unsigned char len)
{
unsigned char checksum = 0;
for (unsigned char i = 0; i < len; i++)
{
checksum += ptr[i];
}
checksum = 0xff & ~checksum;
return checksum;
}
//------------------------------------------------------------------------------
// I2C register access
//------------------------------------------------------------------------------
I2C_Mode I2C_ReadReg(uint8_t reg_addr, uint8_t *reg_data, uint8_t count)
{
/* Initialize state machine */
MasterMode = TX_REG_ADDRESS_MODE;
TransmitRegAddr = reg_addr;
RXByteCtr = count;
TXByteCtr = 0;
ReceiveIndex = 0;
TransmitIndex = 0;
/* Initialize device address and interrupts */
UCB1I2CSA = DEV_ADDR;
UCB1IFG &= ~(UCTXIFG + UCRXIFG); // Clear any pending interrupts
UCB1IE &= ~UCRXIE; // Disable RX interrupt
UCB1IE |= UCTXIE; // Enable TX interrupt
UCB1CTLW0 |= UCTR + UCTXSTT; // I2C TX, start condition
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupts
//For debugger
__no_operation();
/* Copy over received data */
CopyArray(ReceiveBuffer, reg_data, count);
return MasterMode;
}
I2C_Mode I2C_WriteReg(uint8_t reg_addr, uint8_t *reg_data, uint8_t count)
{
/* Initialize state machine */
MasterMode = TX_REG_ADDRESS_MODE;
TransmitRegAddr = reg_addr;
/* Copy register data to TransmitBuffer */
CopyArray(reg_data, TransmitBuffer, count);
TXByteCtr = count;
RXByteCtr = 0;
ReceiveIndex = 0;
TransmitIndex = 0;
/* Initialize device address and interrupts */
UCB1I2CSA = DEV_ADDR;
UCB1IFG &= ~(UCTXIFG + UCRXIFG); // Clear any pending interrupts
UCB1IE &= ~UCRXIE; // Disable RX interrupt
UCB1IE |= UCTXIE; // Enable TX interrupt
UCB1CTLW0 |= UCTR + UCTXSTT; // I2C TX, start condition
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupts
//For debugger
__no_operation();
return MasterMode;
}
//------------------------------------------------------------------------------
// Board support: GPIO, I2C, UART, port mapping
//------------------------------------------------------------------------------
void LED_On(int32_t ledPin)
{
assert(ledPin >= 0 && ledPin <= 2);
P8OUT &= ~(1 << ledPin);
}
void LED_Off(int32_t ledPin)
{
assert(ledPin >= 0 && ledPin <= 2);
P8OUT |= (1 << ledPin);
}
void GPIO_initPins(void)
{
P1OUT = 0x00;
P2OUT = 0x00;
P3OUT = 0x00;
P4OUT = 0x0F;
P5OUT = 0x00;
P6OUT = 0x00;
P8OUT = 0x00;
P1DIR = 0xFF;
P2DIR = 0xFF;
P3DIR = 0xFF;
P4DIR = 0x00;
P5DIR = 0xFF;
P6DIR = 0xFF;
P8DIR = 0xFF;
GPIO_configPins();
}
void GPIO_configPins(void)
{
// P8.0 (LED1)
P8OUT &= ~BIT0; // Set P8.0 to low
P8DIR |= BIT0; // Set P8.0 to output direction
// P8.1 (LED2)
P8OUT |= BIT1; // Set P8.1 to high
P8DIR |= BIT1; // Set P8.1 to output direction
// P8.2 (LED3)
P8OUT |= BIT2; // Set P8.2 to high
P8DIR |= BIT2; // Set P8.2 to output direction
P3DIR &= ~(BIT3 | BIT4); // Set P4.3 and P4.4 to input
P3REN |= (BIT3 | BIT4); // Enable pull up/down resistor
P3OUT &= ~(BIT3 | BIT4); // Output low (pull down)
}
void map_I2C_and_UART(void)
{
PMAPKEYID = PMAPKEY; // Unlock port mapping
PMAPCTL |= PMAPRECFG;
P4MAP4 = PM_UCB1SCL; // Set P4.4 as SCL
P4MAP5 = PM_UCB1SDA; // Set P4.5 as SCL
P4MAP2 = PM_UCA1RXD; // Set 4.2 as RXD
P4MAP3 = PM_UCA1TXD; // Set 4.3 as TXD
PMAPKEYID = 0;
}
void I2C_initModule(void)
{
// --- Configure I2C pins for UCB1 on MSP430F5529 ---
// P4.4 = UCB1SDA, P4.5 = UCB1SCL
P4SEL |= BIT4 | BIT5; // select peripheral function for P4.4/P4.5
//P4SEL |= BIT6 | BIT7; // select peripheral function for P4.6/P4.7
// --- Configure USCI_B1 in I2C master mode ---
UCB1CTL1 |= UCSWRST; // hold USCI in reset
// CTL0: sync, I2C, master
UCB1CTL0 = UCMST | // master mode
UCMODE_3 | // I2C mode
UCSYNC; // synchronous mode
// CTL1: SMCLK as source while still in reset
UCB1CTL1 = UCSWRST | UCSSEL_2; // UCSSEL_2 = SMCLK
// Baud rate: BRCLK / (UCB1BR0+256*BR1)
// For example: SMCLK = 16 MHz, BR=40 -> 400 kHz I2C
// We are running now without FLL (1Mhz)
UCB1BR0 = 10;
UCB1BR1 = 0;
// 7-bit slave address of BQ769x2 (0x08) – goes into UCB1I2CSA
UCB1I2CSA = DEV_ADDR;
// Release from reset and start operation
UCB1CTL1 &= ~UCSWRST;
// Enable NACK interrupt (optional – your ISR already handles NACK)
UCB1IE |= UCNACKIE;
}
void UART_Init(void)
{
P4SEL |= BIT2 | BIT3;
P4DIR |= BIT3;
P4DIR &= ~BIT2;
UCA1CTL1 |= UCSWRST;
UCA1CTL1 &= ~(UCSSEL_3);
UCA1CTL0 = UCMODE_0;
UCA1CTL1 |= UCSSEL_1; // ACLK
UCA1BR0 = 0x03;
UCA1BR1 = 0x00;
UCA1MCTL = UCBRS_3 + UCBRF_0;
UCA1CTL1 &= ~UCSWRST;
}
//------------------------------------------------------------------------------
// I2C ISR
//------------------------------------------------------------------------------
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCI_B1_VECTOR
__interrupt void USCI_B1_ISR(void)
#elif defined(__GNUC__)
void __attribute__((interrupt(USCI_B1_VECTOR))) USCI_B1_ISR(void)
#else
#error Compiler not supported!
#endif
{
uint8_t rx_val = 0;
switch (UCB1IV)
{
case USCI_NONE:
break;
case USCI_I2C_UCALIFG:
break;
case USCI_I2C_UCNACKIFG:
break;
case USCI_I2C_UCSTTIFG:
break;
case USCI_I2C_UCSTPIFG:
break;
case USCI_I2C_UCRXIFG3:
case USCI_I2C_UCTXIFG3:
case USCI_I2C_UCRXIFG2:
case USCI_I2C_UCTXIFG2:
case USCI_I2C_UCRXIFG1:
case USCI_I2C_UCTXIFG1:
break;
case USCI_I2C_UCRXIFG0:
rx_val = UCB1RXBUF;
if (RXByteCtr)
{
ReceiveBuffer[ReceiveIndex++] = rx_val;
RXByteCtr--;
}
if (RXByteCtr == 1)
{
UCB1CTLW0 |= UCTXSTP;
}
else if (RXByteCtr == 0)
{
UCB1IE &= ~UCRXIE;
MasterMode = IDLE_MODE;
__bic_SR_register_on_exit(CPUOFF);
}
break;
case USCI_I2C_UCTXIFG0:
switch (MasterMode)
{
case TX_REG_ADDRESS_MODE:
UCB1TXBUF = TransmitRegAddr;
if (RXByteCtr)
{
MasterMode = SWITCH_TO_RX_MODE;
}
else
{
MasterMode = TX_DATA_MODE;
}
break;
case SWITCH_TO_RX_MODE:
UCB1IE |= UCRXIE;
UCB1IE &= ~UCTXIE;
UCB1CTLW0 &= ~UCTR;
MasterMode = RX_DATA_MODE;
UCB1CTLW0 |= UCTXSTT;
if (RXByteCtr == 1)
{
while (UCB1CTLW0 & UCTXSTT)
;
UCB1CTLW0 |= UCTXSTP;
}
break;
case TX_DATA_MODE:
if (TXByteCtr)
{
UCB1TXBUF = TransmitBuffer[TransmitIndex++];
TXByteCtr--;
}
else
{
UCB1CTLW0 |= UCTXSTP;
MasterMode = IDLE_MODE;
UCB1IE &= ~UCTXIE;
__bic_SR_register_on_exit(CPUOFF);
}
break;
default:
__no_operation();
break;
}
break;
default:
break;
}
}
#endif