Sure, here is my entire BMS code, maybe you can figure out what is MAX17841 and what not? Otherwise I can strip it down a bit.
NOTE: This is work in progress and it contains no diagnostics of the slaves at all.
More info can be found in the datasheets for the MAX17841 and MAX17823.
There is also some extra info in the MAX17852 datasheet. This is a similar IC to MAX17823.
Code: Select all
// Change log
/*
1_4
* Changed how balance cells are turned off
* Send "FORCEPOR" to slaves before turn off
1_3
* Changed pinout for SPIsupplyPIN
* Changed SPIsupplyPIN to inverted as when output is LOW the supply is ON
* Changed alive-counter check to 0x01(0x0C) for readAddressedSlave and writeAddressedSlave
* Added reset of setupDone and dataReady in functionSTART()
1_2
* Added Serial.print for cellBalancing()
* Added check in functionON() to not allow gears when charging
1_1
* Changed currentSensorSupplyPIN to inverted as when output is LOW the supply is ON
* Changed where cellBalancing is turned of to main loop()
1_0
* Initial version based on BMS_14_8 and SPI_MAX17841_1_15
*/
// Include libraries
#include "mcp_can.h"
#include "SPI.h"
#include "LowPower.h"
enum State_enum
{
SLEEP, SAVEPOWER, STOP, START, ON, INIT
};
// Stated declaration
State_enum state;
// Constant declaration
// status MASK
const uint8_t statusCapacityReset = 0x01;
const uint8_t statusLowVoltage = 0x02;
const uint8_t statusVeryLowVoltage = 0x04;
const uint8_t statusAllowCharging = 0x08;
const uint8_t statusFalseData = 0x10;
const uint8_t statusChargeRequest = 0x20;
// errorByte MASK
const uint8_t initError = 0x01;
const uint8_t mismatchBefore = 0x02;
const uint8_t mismatchAfter = 0x04;
const uint8_t receiveBuffer = 0x08;
// Battery constants
const uint8_t numberOfCells = 96;
//const uint32_t fullCapacity = 2854170000; // 24.8 * 1023 * 2 * 1000 * 3600 / 64 = (24.8 * 115087500)
const uint32_t fullCapacity = 2859924375; // 24.85 * 1023 * 2 * 1000 * 3600 / 64 = (24.85 * 115087500)
const int16_t highVoltageCutOff = 4200; // Define high voltage cut off, in mV
const int16_t lowVoltageWarning = 3600; // Define low voltage warning, in mV
const int16_t lowVoltageCutOff = 3400; // Define low voltage cut off, in mV
const int16_t voltageAllowance = 9; // Define the accuracy the balancing algorithm can balance it to, in mV
const int16_t chargerStartVoltage = 4000; // Define charger restart voltage, in mV
const int16_t startShuntVoltage = 4050; // Define start shunting voltage, in mV
// Other constants
const uint8_t chargeCompensationFactor = 253; // Charge current compensation (253 / 256 = 0.988)
// Variable declaration
uint16_t cellVoltage[96];
int8_t cellTemperature[12];
int16_t dieTemperature[12];
uint32_t totalCapacity;
boolean setupDone;
uint8_t dataReady;
uint8_t status;
// I/O-PINS
//const uint8_t MAX_INT_PIN = 2; // INT0, D2, Interupt PIN for MAX17841
const uint8_t SPI_MAX_CS_PIN = 14; // A0, ChipSelect PIN for MAX17841
const uint8_t SPIsupplyPIN = 5; // D5, Supply to MAX17841 and MCP2515-module
const uint8_t currentSensorSupplyPIN = 9; // D9
const uint8_t wakeUpPIN = 3; // INT1, D3
const uint8_t ACAvailable = 4; // D4
//const uint8_t chargerRelayPIN = 8; // D8
const uint8_t SPI_MCP_CS_PIN = 10; // D10, CS PIN for MCP2515 CAN module
// SPI-bus
// CLK, SCK - PIN 13
// MISO, SDO - PIN 12
// MOSI, SDI - PIN 11
// SPI setup
SPISettings MAX17841(4000000, MSBFIRST, SPI_MODE0);
// CAN setup
MCP_CAN CAN(SPI_MCP_CS_PIN); // Set CS pin
/********
* SETUP *
********/
void setup()
{
pinMode(wakeUpPIN, INPUT_PULLUP);
pinMode(ACAvailable, INPUT_PULLUP);
pinMode(currentSensorSupplyPIN, OUTPUT);
digitalWrite(currentSensorSupplyPIN, HIGH); // Supply OFF
pinMode(SPIsupplyPIN, OUTPUT);
digitalWrite(SPIsupplyPIN, HIGH); // Supply OFF
//pinMode(chargerRelayPIN, OUTPUT);
//digitalWrite(chargerRelayPIN, LOW);
resetCapacity(); // Set initial capacity
state = INIT;
delay(100); // to wait for pullup inputs to settle
}
/*******
* LOOP *
*******/
void loop()
{
static uint32_t sleepTimer; // For timing
uint8_t stateInput = digitalRead(wakeUpPIN);
switch (state)
{
case INIT:
if (stateInput == HIGH)
{
state = SLEEP;
}
else
{
state = START;
}
break;
case START:
functionSTART();
state = ON;
break;
case ON:
functionON();
if (stateInput == HIGH)
{
state = STOP;
}
break;
case STOP:
functionSTOP();
sleepTimer = millis();
state = SAVEPOWER;
break;
case SAVEPOWER:
functionSAVEPOWER();
if (stateInput == LOW)
{
state = START;
}
else if ((millis() - sleepTimer) >= 900000) // check if 15min has passed
{
state = SLEEP;
//balanceCells(highVoltageCutOff); // Turn off cell balancing
//writeAllSlaves(0x1A, 0x0000); // Turn off cell balancing
//setupDone = false;
writeDataAll(0x10, 0x0080); // Set DEVCFG1, FORCEPOR
Serial.println("Sleep");
}
break;
case SLEEP:
functionSLEEP();
state = START;
break;
default:
// Error
break;
}
}
/**************************************************************************************************************************************************************
* Starts BMS from power input or from sleep. Turns on power to cell boards, current sensor and contactor, sets PIN as OUTPUT, setup CAN and setup cell boards *
**************************************************************************************************************************************************************/
void functionSTART() // state START function
{
//digitalWrite(chargerRelayPIN, LOW);
digitalWrite(currentSensorSupplyPIN, LOW); // Supply ON
digitalWrite(SPIsupplyPIN, LOW); // Supply ON
pinMode(SPI_MAX_CS_PIN, OUTPUT);
digitalWrite(SPI_MAX_CS_PIN, HIGH); // No transaction
pinMode(13, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(SPI_MCP_CS_PIN, OUTPUT);
delay(10); // To let OUTPUT:s settle before CAN.begin
Serial.begin(115200);
while (CAN_OK != CAN.begin(MCP_STDEXT, CAN_250KBPS, MCP_8MHZ)) // init can bus : baudrate = 250k
{ delay(100); }
CAN.init_Mask(0,0,0x07FF0000); // Init first mask
CAN.init_Filt(0,0,0x01700000); // Init first filter
CAN.init_Filt(1,0,0x01000000); // Init second filter
CAN.init_Mask(1,0,0x07FF0000); // Init second mask
CAN.init_Filt(2,0,0x01700000); // Init third filter
CAN.init_Filt(3,0,0x01700000); // Init fouth filter
CAN.init_Filt(4,0,0x01000000); // Init fifth filter
CAN.init_Filt(5,0,0x01000000); // Init sixth filter
CAN.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
setupDone = false; // Reset setup
dataReady = 0x02; // Reset data ready
status = 0x00; // Reset status
Serial.println("Daisy chain init");
daisyChainInit();
Serial.println("Set all slaves");
setupSlaves();
}
/**************************************
* BMS main loop, runs BMS in state ON *
**************************************/
void functionON()
{
static uint32_t lastMillis = millis(); // Initial start time
static uint32_t chargerRequestTimer = millis(); // For timing
uint8_t gearRequest = readCAN();
if(!(digitalRead(ACAvailable)) && (millis() - chargerRequestTimer < 1000)) // Set charge request if AC is available within 1s of startup
{
status |= statusChargeRequest;
}
if(status & statusChargeRequest) // No gears allowed when charging
{
gearRequest = 0x00;
}
if (millis() - lastMillis >= 250) // Test if the 250ms period has elapsed
{
uint16_t timeSinceLast;
int32_t instantCurrent;
uint8_t highCell;
uint8_t lowCell;
int32_t voltage;
timeSinceLast = millis() - lastMillis;
lastMillis = millis(); // IMPORTANT to save the start time
instantCurrent = readCurrent();
changeCapacity(instantCurrent, timeSinceLast);
measureCellData();
if(dataReady == 0x02)
{
highCell = highestCell();
lowCell = lowestCell();
voltage = totalVoltage();
checkCells(highCell, lowCell);
balanceCells(lowCell);
sendDataCAN(instantCurrent, voltage, highCell, lowCell, gearRequest);
}
else // Error
{
//digitalWrite(chargerRelayPIN, LOW); // Shut off charger
status |= statusFalseData;
sendDataCAN(0, 0, 0, 0, gearRequest);
//balanceCells(highVoltageCutOff); // Turn off cell balancing
writeAllSlaves(0x1A, 0x0000); // Turn off cell balancing
}
}
}
/********************************************************************************
* Stops external +5V supply (current sensor) and prohibits charging, state STOP *
********************************************************************************/
void functionSTOP()
{
digitalWrite(currentSensorSupplyPIN, HIGH); // Supply OFF
//digitalWrite(chargerRelayPIN, LOW);
status &= ~statusChargeRequest; // Stop charging
sendDataCAN(0, 0, 0, 0, 0);
}
/********************************************
* Maintains cell balancing, state SAVEPOWER *
********************************************/
void functionSAVEPOWER()
{
static uint32_t lastMillis = millis(); // Initial start time
if(millis() - lastMillis >= 250) // Test if the 250ms period has elapsed
{
uint8_t lowCell;
lastMillis = millis(); // IMPORTANT to save the start time
measureCellData();
if(dataReady == 0x02)
{
lowCell = lowestCell();
balanceCells(lowCell);
}
else // Error
{
//balanceCells(highVoltageCutOff); // Turn off cell balancing
writeAllSlaves(0x1A, 0x0000); // Turn off cell balancing
}
}
}
/***********************************************************************************************
* Stops SPI, sets PIN:s as INPUT, turns of power to cell boards and goes to sleep, state SLEEP *
***********************************************************************************************/
void functionSLEEP()
{
SPI.end();
pinMode(13, INPUT);
pinMode(11, INPUT);
pinMode(12, INPUT);
pinMode(SPI_MCP_CS_PIN, INPUT);
pinMode(SPI_MAX_CS_PIN, INPUT);
digitalWrite(SPIsupplyPIN, HIGH); // Supply OFF
Serial.end();
attachInterrupt(digitalPinToInterrupt(wakeUpPIN), wakeUp, LOW);
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
detachInterrupt(digitalPinToInterrupt(wakeUpPIN));
}
/********************************************************
* Calculates total battery voltage, returns value in mV *
********************************************************/
int32_t totalVoltage()
{
int32_t totV = 0L;
for (int i=0; i<numberOfCells;i++)
{
totV += cellVoltage[i];
}
return totV;
}
/***********************************************************************
* Calculates the highest cell of the battery pack, returns value in mV *
***********************************************************************/
uint8_t highestCell()
{
uint16_t voltageHighest = 0;
uint8_t cellNumber;
for (int i=0; i<numberOfCells; i++)
{
if (cellVoltage[i] > voltageHighest)
{
voltageHighest = cellVoltage[i];
cellNumber = i;
}
}
return cellNumber;
}
/**********************************************************************
* Calculates the lowest cell of the battery pack, returns value in mV *
**********************************************************************/
uint8_t lowestCell()
{
uint16_t voltageLowest = 5000;
uint8_t cellNumber;
for(int i=0; i<numberOfCells; i++)
{
if (cellVoltage [i] < voltageLowest)
{
voltageLowest = cellVoltage[i];
cellNumber = i;
}
}
return cellNumber;
}
/********************************************
* Check status of cells and allows charging *
********************************************/
void checkCells(uint8_t highCell, uint8_t lowCell)
{
if (cellVoltage[highCell] >= highVoltageCutOff) // Check if highest cell has reached max allowed voltage
{
//digitalWrite(chargerRelayPIN, LOW); // Shut off charger
//resetCapacity();
status &= ~statusAllowCharging;
}
if (cellVoltage[highCell] < chargerStartVoltage) // Check if ok to allow charging
{
//digitalWrite(chargerRelayPIN, HIGH); // Allowing charging
status |= statusAllowCharging;
}
/*
if (digitalRead(chargerRelayPIN) == HIGH) // Check charger relay PIN status
{
status |= statusAllowCharging;
}
*/
if (cellVoltage[lowCell] < lowVoltageWarning) // Check if lowest cell has reached min prefered voltage
{
// Warn the driver
status |= statusLowVoltage;
}
else
{
status &= ~statusLowVoltage;
}
if (cellVoltage[lowCell] < lowVoltageCutOff) // Check if lowest cell has reached min allowed voltage
{
// Stop car
status |= statusVeryLowVoltage;
}
else
{
status &= ~statusVeryLowVoltage;
}
}
/*********************************************************************************
* Reads current sensor, returns value in A x 1023 x 2 ( x 2046) to keep accuracy *
*********************************************************************************/
int32_t readCurrent()
{
int32_t current;
uint16_t currentLow;
uint16_t currentHigh;
currentLow = analogRead(A6) + 1; // +1 for calibration, to read zero at zero current
currentHigh = analogRead(A7) + 1; // +1 for calibration, to read zero at zero current
Serial.print(currentLow);
Serial.print("\t");
Serial.println(currentHigh);
if (currentLow <= 102 || currentLow >= 921) // Check to use high or low current range sensor
{
current = 1750 * (uint32_t)currentHigh - 895125 - 0; // Offset added to read correct current (xA x 2046)
}
else
{
current = 150 * (uint32_t)currentLow - 76725 - 0; // Offset added to read correct current at 0A (xA x 2046)
}
return current;
}
/******************************************************
* Resets the capacity to default, fully charged value *
******************************************************/
void resetCapacity()
{
totalCapacity = fullCapacity;
}
/***********************************
* Calculates the new totalCapacity *
***********************************/
void changeCapacity(int32_t current, uint16_t lapsedTime)
{
uint32_t capacityChange;
uint32_t absCurrent;
if (current < 0) // Discharging
{
if (current > -409) // If negative current is less than 0.2A, set absCurrent to 0A, i.e. no capacity change
{
absCurrent = 0;
}
else
{
absCurrent = -(current);
}
capacityChange = ((uint32_t)absCurrent * lapsedTime) >> 6;
totalCapacity -= capacityChange; // Stored battery capacity (Ah x 1023 x 2 x 1000 x 3600 / 64)
}
else // Charging
{
if (current < 409) // If positive current is less than 0.2A, set absCurrent to 0A, i.e. no capacity change
{
absCurrent = 0;
}
else
{
absCurrent = current;
}
capacityChange = ((((uint32_t)absCurrent * lapsedTime) >> 8) * chargeCompensationFactor) >> 6; // Division by 256 and then multiply by factor
totalCapacity += capacityChange; // Stored battery capacity (Ah x 1023 x 2 x 1000 x 3600 / 64)
}
}
/********************************
* Sends out battery data on CAN *
********************************/
void sendDataCAN(int32_t current, int32_t voltage, uint8_t highCell, uint8_t lowCell, uint8_t gearRequest)
{
static uint32_t lastMillisStart = millis();
int16_t currentLCD = current / 204.6; // Current in dA
uint16_t voltageLCD = voltage / 100; // Voltage in dV
uint16_t capacityLCD = totalCapacity / 11508750; // Capacity in dAh
uint16_t highVoltageLCD = cellVoltage[highCell]; // Highest cell voltage in mV
uint16_t lowVoltageLCD = cellVoltage[lowCell]; // Lowest cell voltage in mV
uint8_t mainBMS[8] = {highByte(currentLCD), lowByte(currentLCD), highByte(voltageLCD), lowByte(voltageLCD), highByte(capacityLCD), lowByte(capacityLCD), status, 0x00};
uint8_t highLowBMS[8] = {(highCell + 1), highByte(highVoltageLCD), lowByte(highVoltageLCD), (lowCell + 1), highByte(lowVoltageLCD), lowByte(lowVoltageLCD), 0x00, 0x00};
uint8_t tempBMS[8] = {cellTemperature[1], cellTemperature[2], cellTemperature[3], cellTemperature[5], cellTemperature[6], cellTemperature[9], cellTemperature[10], cellTemperature[4]};
if(((millis() - lastMillisStart) > 200) && ((millis() - lastMillisStart) < 2000)) // 200ms - 2s
{
gearRequest |= 0x02; // Add start bit
}
CAN.sendMsgBuf(0x101, 0, 1, gearRequest);
CAN.sendMsgBuf(0x180, 0, 8, mainBMS);
CAN.sendMsgBuf(0x181, 0, 8, highLowBMS);
CAN.sendMsgBuf(0x182, 0, 8, tempBMS);
Serial.print(voltageLCD/10.0);
Serial.print("\t");
Serial.print(currentLCD/10.0);
Serial.print("\t");
Serial.print(capacityLCD/10.0);
Serial.print("\t");
Serial.println(status, HEX);
Serial.print(highCell+1);
Serial.print(" ");
Serial.print(highVoltageLCD);
Serial.print("\t");
Serial.print(lowCell+1);
Serial.print(" ");
Serial.println(lowVoltageLCD);
Serial.print(cellTemperature[1]);
Serial.print(" ");
Serial.print(cellTemperature[2]);
Serial.print(" ");
Serial.print(cellTemperature[3]);
Serial.print(" ");
Serial.print(cellTemperature[5]);
Serial.print(" ");
Serial.print(cellTemperature[6]);
Serial.print(" ");
Serial.print(cellTemperature[9]);
Serial.print(" ");
Serial.print(cellTemperature[10]);
Serial.print(" ");
Serial.println(cellTemperature[4]);
}
/**********************************************************************************************
* Checks CAN-bus buffer and if new data is available. Resets capacity if data byte is correct *
**********************************************************************************************/
uint8_t readCAN()
{
uint32_t rxId;
uint8_t len;
uint8_t rxBuf[8];
static uint32_t lastMillisReset;
uint8_t gearRequest = 0x00;
if(CAN_MSGAVAIL == CAN.checkReceive()) // Check if data is coming
{
CAN.readMsgBuf(&rxId, &len, rxBuf); // read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
if(rxId == 0x170) // Reset capacity request
{
if(rxBuf[0] == 'Z')
{
resetCapacity();
status |= statusCapacityReset;
lastMillisReset = millis(); // Start timer
}
}
if(rxId == 0x100) // Gear request from HMI
{
gearRequest = rxBuf[0];
}
}
if((status & statusCapacityReset) && (millis() - lastMillisReset >= 5000)) // 5s
{
lastMillisReset = 0; // Stop timer
status &= ~statusCapacityReset; // Remove status capacity reset
}
return gearRequest;
}
/******************************************************
* Measures cell voltages and temperatures from slaves *
******************************************************/
void measureCellData()
{
uint32_t watchdogTimer = millis();
dataReady = 0x00;
writeDataAll(0x13, 0x0001); // Set SCANCTRL, SCAN
while(!(dataReady == 0x02)) // Wait for data stored
{
if(dataReady == 0x00)
{
readAllSlaves(0x13); // Read SCANCTRL to check when data is ready to be read
}
if(dataReady == 0x01)
{
readData(); // Read and store data
dataReady = 0x02;
}
if((millis() - watchdogTimer) > 10) // Watchdog timer of 10ms
{
break;
}
}
}
/*********************
* Balancing of cells *
*********************/
void balanceCells(uint8_t lowCell)
{
writeDataAll(0x18, 0x1500); // Set WATCHDOG timer to 5s
for (int i=0; i < 12; i++) // Go through modules to set cells for balancing
{
uint16_t cellToBalance = 0x0000;
for (int j=0; j < 8; j++) // Go through cells to set cells for balancing
{
uint8_t cellNumber = j+i*8;
if (cellVoltage[cellNumber] > startShuntVoltage && (cellVoltage[cellNumber] - cellVoltage[lowCell]) > voltageAllowance) // Ok to balance
{
if (j < 4)
{
cellToBalance |= (0x01 << j);
}
else if (j >= 4 && j <8)
{
cellToBalance |= (0x01 << (j+1));
}
else //Error
{
cellToBalance = 0x0000;
}
}
else // Turn off balance discharge
{
if (j < 4)
{
cellToBalance &= ~(0x01 << j);
}
else if (j >= 4 && j <8)
{
cellToBalance &= ~(0x01 << (j+1));
}
else //Error
{
cellToBalance = 0x0000;
}
}
}
writeAddressedSlave(0x1A, cellToBalance, i); // Send request to set cell balance switches
Serial.print(i);
Serial.print(" ");
Serial.println(cellToBalance, BIN);
}
}
/************************************
* Reads data from all slave devices *
************************************/
void readAllSlaves(uint8_t dataRegister) // Read all slaves
{
uint8_t command = 0x03; // READALL
uint8_t byteList[3] = {command, dataRegister, 0x00};
uint8_t PEC = calculatePEC(byteList, 3);
uint8_t readRegisterData[29];
uint8_t errorByte = 0x00;
SPI.beginTransaction(MAX17841);
// Load the READALL command sequence into the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC0); // WR_LD_Q SPI command byte (write the load queue)
SPI.transfer(0x1D); // Message length (5 + 2 x n = 29)
SPI.transfer(command); // READALL command byte
SPI.transfer(dataRegister); // Register address
SPI.transfer(0x00); // Data-check byte (seed value = 0x00)
SPI.transfer(PEC); // PEC byte
SPI.transfer(0x00); // Alive-counter byte (seed value = 0x00)
digitalWrite(SPI_MAX_CS_PIN, HIGH);
transmitQueue();
// Read the receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x93); // RD_NXT_MSG SPI command byte
for(int i=0; i<29; i++)
{
readRegisterData[i] = SPI.transfer(0x93);
}
errorByte |= receiveBufferError();
SPI.endTransaction();
// Verify that the device register data is received correctly during the READALL sequence
if(!((readRegisterData[0] == command) && (readRegisterData[1] == dataRegister)))
{
errorByte |= mismatchBefore;
}
if(setupDone)
{
uint8_t checkPEC = calculatePEC(readRegisterData, 27);
// Check check-byte, PEC and alive-counter
if(!((readRegisterData[26] == 0x00) && (readRegisterData[27] == checkPEC) && (readRegisterData[28] == 0x0C)))
{
errorByte |= mismatchAfter;
}
}
// Print data received
for(int i=0; i<29; i++)
{
Serial.print(readRegisterData[i], HEX);
Serial.print(" ");
}
Serial.println();
if(errorByte) // Error
{
Serial.println(errorByte, HEX);
errorByte &= 0x00; // Clear errors
Serial.println("errorByte cleared");
readAllSlaves(dataRegister); // Resend READALL
}
else // Store data received
{
if((dataRegister >= 0x20) && (dataRegister <= 0x2B)) // Cell voltage measurements
{
storeCellVoltage(dataRegister, readRegisterData);
}
if(dataRegister == 0x2D) // Aux voltage measurements (external temperature sensors)
{
storeCellTemperature(dataRegister, readRegisterData);
}
if(dataRegister == 0x50) // Die temperature measurements
{
storeDieTemperature(dataRegister, readRegisterData);
}
if(dataRegister == 0x13) // Read SCANCTRL to check if data is ready to be read
{
checkDataReady(dataRegister, readRegisterData);
}
}
}
/*****************************************
* Reads data from addressed slave device *
*****************************************/
void readAddressedSlave(uint8_t dataRegister, uint8_t address) // Read addressed slave
{
uint8_t command = 0x05; // READDEVICE
command |= (address << 3);
uint8_t byteList[3] = {command, dataRegister, 0x00};
uint8_t PEC = calculatePEC(byteList, 3);
uint8_t readRegisterData[7];
uint8_t errorByte = 0x00;
SPI.beginTransaction(MAX17841);
// 1, Load the READDEVICE command sequence into the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC0); // WR_LD_Q SPI command byte (write the load queue)
SPI.transfer(0x07); // Message length (5 + 2 x n = 29)
SPI.transfer(command); // READALL command byte
SPI.transfer(dataRegister); // Register address
SPI.transfer(0x00); // Data-check byte (seed value = 0x00)
SPI.transfer(PEC); // PEC byte
SPI.transfer(0x00); // Alive-counter byte (seed value = 0x00)
digitalWrite(SPI_MAX_CS_PIN, HIGH);
transmitQueue();
// 4, Read the receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x93); // RD_NXT_MSG SPI command byte
for(int i=0; i<7; i++)
{
readRegisterData[i] = SPI.transfer(0x93);
}
errorByte |= receiveBufferError();
SPI.endTransaction();
// Verify that the device register data is received correctly during the READDEVICE sequence
if(!((readRegisterData[0] == command) && (readRegisterData[1] == dataRegister)))
{
errorByte |= mismatchBefore;
}
if(setupDone)
{
uint8_t checkPEC = calculatePEC(readRegisterData, 5);
// Check check-byte, PEC and alive-counter
if(!((readRegisterData[4] == 0x00) && (readRegisterData[5] == checkPEC) && (readRegisterData[6] == 0x01)))
{
errorByte |= mismatchAfter;
}
}
// Print data received
for(int i=0; i<7; i++)
{
Serial.print(readRegisterData[i], HEX);
Serial.print(" ");
}
Serial.println();
if(errorByte) // Error
{
Serial.println(errorByte, HEX);
errorByte &= 0x00; // Clear errors
Serial.println("errorByte cleared");
readAddressedSlave(dataRegister, address); // Resend READDEVICE
}
else // Store data received
{
}
}
/***********************************
* Writes data to all slave devices *
***********************************/
void writeDataAll(uint8_t dataRegister, uint16_t data)
{
uint8_t command = 0x02; // WRITEALL
uint8_t byteList[4] = {command, dataRegister, lowByte(data), highByte(data)};
uint8_t PEC = calculatePEC(byteList, 4);
uint8_t readRegisterData[6];
uint8_t errorByte = 0x00;
SPI.beginTransaction(MAX17841);
// 1, Load the WRITEALL command sequence into the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC0); // WR_LD_Q SPI command byte (write the load queue)
SPI.transfer(0x06); // Message length
SPI.transfer(command); // WRITEALL command byte
SPI.transfer(dataRegister); // Register address
SPI.transfer(lowByte(data)); // LS byte of register data to be written
SPI.transfer(highByte(data)); // MS byte of register data to be written
SPI.transfer(PEC); // PEC byte
SPI.transfer(0x00); // Alive-counter byte (seed value = 0x00)
digitalWrite(SPI_MAX_CS_PIN, HIGH);
transmitQueue();
// 4, Read the receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x93); // RD_NXT_MSG SPI command byte
for(int i=0; i<6; i++)
{
readRegisterData[i] = SPI.transfer(0x93);
}
digitalWrite(SPI_MAX_CS_PIN, HIGH);
errorByte |= receiveBufferError();
SPI.endTransaction();
// Verify that the device register data is what was written during the WRITEALL sequence
if(!((readRegisterData[0] == command) && (readRegisterData[1] == dataRegister) && (readRegisterData[2] == lowByte(data)) && (readRegisterData[3] == highByte(data)) && (readRegisterData[4] == PEC)))
{
errorByte |= mismatchBefore;
}
if(setupDone)
{
// Check alive-counter
if(!(readRegisterData[5] == 0x0C))
{
errorByte |= mismatchAfter;
}
}
// Print data received
for(int i=0; i<6; i++)
{
Serial.print(readRegisterData[i], HEX);
Serial.print(" ");
}
Serial.println();
if(errorByte) // Error
{
Serial.println(errorByte, HEX);
errorByte &= 0x00; // Clear errors
Serial.println("errorByte cleared");
writeDataAll(dataRegister, data); // Resend WRITEALL
}
}
/****************************************
* Writes data to addressed slave device *
****************************************/
void writeAddressedSlave(uint8_t dataRegister, uint16_t data, uint8_t address) // Write addressed slave
{
uint8_t command = 0x04; // WRITEDEVICE
command |= (address << 3);
uint8_t byteList[4] = {command, dataRegister, lowByte(data), highByte(data)};
uint8_t PEC = calculatePEC(byteList, 4);
uint8_t readRegisterData[6];
uint8_t errorByte = 0x00;
SPI.beginTransaction(MAX17841);
// 1, Load the WRITEDEVICE command sequence into the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC0); // WR_LD_Q SPI command byte (write the load queue)
SPI.transfer(0x06); // Message length
SPI.transfer(command); // WRITEDEVICE command byte
SPI.transfer(dataRegister); // Register address
SPI.transfer(lowByte(data)); // LS byte of register data to be written
SPI.transfer(highByte(data)); // MS byte of register data to be written
SPI.transfer(PEC); // PEC byte
SPI.transfer(0x00); // Alive-counter byte (seed value = 0x00)
digitalWrite(SPI_MAX_CS_PIN, HIGH);
transmitQueue();
// 4, Read the receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x93); // RD_NXT_MSG SPI command byte
for(int i=0; i<6; i++)
{
readRegisterData[i] = SPI.transfer(0x93);
}
digitalWrite(SPI_MAX_CS_PIN, HIGH);
errorByte |= receiveBufferError();
SPI.endTransaction();
// Verify that the device register data is what was written during the WRITEDEVICE sequence
if(!((readRegisterData[0] == command) && (readRegisterData[1] == dataRegister) && (readRegisterData[2] == lowByte(data)) && (readRegisterData[3] == highByte(data)) && (readRegisterData[4] == PEC)))
{
errorByte |= mismatchBefore;
}
if(setupDone)
{
// Check alive-counter
if(!(readRegisterData[5] == 0x01))
{
errorByte |= mismatchAfter;
}
}
// Print data received
for(int i=0; i<6; i++)
{
Serial.print(readRegisterData[i], HEX);
Serial.print(" ");
}
Serial.println();
if(errorByte) // Error
{
Serial.println(errorByte, HEX);
errorByte &= 0x00; // Clear errors
Serial.println("errorByte cleared");
writeAddressedSlave(dataRegister, data, address); // Resend WRITEDEVICE
}
}
/**********************************************
* Reads data from slaves and stores in arrays *
**********************************************/
void readData()
{
for(int i=0; i<4; i++)
{
readAllSlaves(0x20 + i); // Read CELL 1-4 of all slaves
}
for(int i=0; i<4; i++)
{
readAllSlaves(0x25 + i); // Read CELL 5-8 of all slaves
}
readAllSlaves(0x2D); // Read AIN1 of all slaves (Cell temperature)
readAllSlaves(0x50); // Read DIAG (Die temperature) of all slaves
for(int j=0; j<12; j++)
{
Serial.print(j+1);
Serial.print(": ");
for(int i=0; i<8; i++)
{
Serial.print(cellVoltage[i+j*8]);
Serial.print(" ");
}
Serial.print(cellTemperature[j]);
Serial.print(" ");
Serial.print(dieTemperature[j]);
Serial.println();
}
}
/*********************************************************
* Stores measured cell voltage data in cellVoltage array *
*********************************************************/
void storeCellVoltage(uint8_t dataRegister, uint8_t readRegisterData[29])
{
if((dataRegister >= 0x20) && (dataRegister <= 0x23)) // Cell voltage registers 1-4
{
uint8_t cellNumberOffset = dataRegister - 0x20;
for(int i=0; i<12; i++)
{
uint16_t measVoltage = ((readRegisterData[25-i*2] << 8) + readRegisterData[24-i*2]);
measVoltage = (measVoltage >> 2) * (uint32_t)5000 / 0x3FFF;
cellVoltage[i*8 + cellNumberOffset] = measVoltage;
}
}
if((dataRegister >= 0x25) && (dataRegister <= 0x28)) // Cell voltage registers 5-8
{
uint8_t cellNumberOffset = dataRegister - 0x20 - 1;
for(int i=0; i<12; i++)
{
uint16_t measVoltage = ((readRegisterData[25-i*2] << 8) + readRegisterData[24-i*2]);
measVoltage = (measVoltage >> 2) * (uint32_t)5000 / 0x3FFF;
cellVoltage[i*8 + cellNumberOffset] = measVoltage;
}
}
}
/***************************************************************************
* Stores measured temperature sensor voltage data in cellTemperature array *
***************************************************************************/
void storeCellTemperature(uint8_t dataRegister, uint8_t readRegisterData[29])
{
for(int i=0; i<12; i++)
{
if((i == 0) || (i == 7) || (i == 8) || (i == 11)) // No temp sensor connected
{
cellTemperature[i] = 0;
}
else
{
uint16_t beta = 1300;
uint16_t measTemperature = ((readRegisterData[25-i*2] << 8) + readRegisterData[24-i*2]);
measTemperature = (measTemperature >> 4);
int8_t temperature = beta / (log((float)4095 / (4095 - measTemperature)) + beta / 298.15) - 273;
cellTemperature[i] = temperature;
}
}
}
/***************************************************************
* Stores measured die temperature data in dieTemperature array *
***************************************************************/
void storeDieTemperature(uint8_t dataRegister, uint8_t readRegisterData[29])
{
for(int i=0; i<12; i++)
{
uint16_t measTemperature = ((readRegisterData[25-i*2] << 8) + readRegisterData[24-i*2]);
measTemperature = (measTemperature >> 2) * (uint32_t)230700 / 5029581;
dieTemperature[i] = measTemperature - (int16_t)273;
}
}
/****************************************************
* Check if data is ready to be read from all slaves *
****************************************************/
void checkDataReady(uint8_t dataRegister, uint8_t readRegisterData[29])
{
static uint8_t counter = 0;
dataReady = 0x01;
for(int i=0; i<12; i++)
{
if(!(readRegisterData[25-i*2] & 0x80))
dataReady = 0x00;
}
if(dataReady == 0x00) // Data not ready
{
counter++; // Increase counter
}
else // Data ready
{
counter = 0; // Reset counter
}
if(counter >= 10) // Time out, resend SCAN and reset counter
{
writeDataAll(0x13, 0x0001); // Set SCANCTRL, SCAN
counter = 0;
}
}
/***********************************************************
* PEC Calculation, CRC-8, from MAX17823 datasheet, page 96 *
***********************************************************/
uint8_t calculatePEC(uint8_t byteList[29], uint8_t numberOfBytes)
{
uint8_t CRCByte = 0;
uint8_t POLY = 0xB2;
for (int byteCounter = 0; byteCounter < numberOfBytes; byteCounter++)
{
CRCByte ^= byteList[byteCounter];
for (int bitCounter = 0; bitCounter < 8; bitCounter++)
{
CRCByte = (CRCByte & 0x01) ? ((CRCByte >> 1) ^ POLY) : (CRCByte >> 1);
}
}
return CRCByte;
}
/********************
* Slave board setup *
********************/
void setupSlaves()
{
// Setup
readAllSlaves(0x01); // Read ADDRESS of all slaves
readAllSlaves(0x02); // Read STATUS of all slaves
writeDataAll(0x02, 0x0000); // Clear STATUS register
writeDataAll(0x10, 0x0040); // Set DEVCFG1, ALIVECNTEN
setupDone = true;
// Enable measurement
writeDataAll(0x12, 0x11EF); // Set MEASUREEN, Enable cell voltages 1-4, 6-9 and AUX1
writeDataAll(0x1E, 0x0009); // Set TOPCELL, Top cell for measurement is 9
writeDataAll(0x51, 0x0006); // Set DIAGCFG, Enable Die temperature measurement
//writeDataAll(0x18, 0x1500); // Set WATCHDOG, Set watchdg for cell balancing to 5s
//writeDataAll(0x13, 0x0001); // Set SCANCTRL, SCAN
}
/********************************************************
* Start transmitting the queue and check receive buffer *
********************************************************/
void transmitQueue()
{
uint8_t check = 0;
// Start transmitting the loaded sequence from the transmit queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xB0); // WR_NXT_LD_Q SPI command byte (write the next load queue)
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// Check if a message has been received into the receive buffer
while(!(check &= 0x12)) // If RX_Status[1] is true, continue. If false, then repeat transaction until true
{
// Poll RX_Stop_Status bit
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x01); // Read RX_Status register
check = SPI.transfer(0x01); // Read RX_Status register
digitalWrite(SPI_MAX_CS_PIN, HIGH);
}
delay(1); // Needed to work, unknown why?
}
/*********************************
* Check for receive buffer error *
*********************************/
uint8_t receiveBufferError()
{
uint8_t check = 0x00;
uint8_t errorByte = 0x00;
// Check for receive buffer errors
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x09); // Read RX_Interrupt_Flags register
check = SPI.transfer(0x09);
digitalWrite(SPI_MAX_CS_PIN, HIGH);
if(!(check == 0x00)) // Error
{
errorByte |= receiveBuffer; // Set status byte
// Clear INT flag register
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x08); // Write RX_Interrupt_Flags register
SPI.transfer(0x00); // Clear flags
digitalWrite(SPI_MAX_CS_PIN, HIGH);
}
return errorByte;
}
/*******************************************
* UART Daisy-Chain Initialization Sequence *
*******************************************/
void daisyChainInit()
{
uint8_t check;
uint8_t data[4];
uint8_t errorByte = 0x00;
SPI.beginTransaction(MAX17841);
// 1, Enable Keep-Alive mode
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x10); // Write Configuration 3 register
SPI.transfer(0x05); // Set keep-alive period to 160μs
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 2, Enable Rx Interrupt flags for RX_Error and RX_Overflow
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x04); // Write RX_Interrupt_Enable register
SPI.transfer(0x88); // Set the RX_Error_INT_Enable and RX_Overflow_INT_Enable bits
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 3, Clear receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xE0); // Clear receive buffer
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 4, Wake-up UART slave devices
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x0E); // Write Configuration 2 register
SPI.transfer(0x30); // Enable Transmit Preambles mode
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 5, Wait for all UART slave devices to wake up
check = 0;
while(check != 0x21) // If RX_Status = 21h, continue. Otherwise, repeat transaction until true or timeout
{
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x01); // Read RX_Status register
check = SPI.transfer(0x01); // Read RX_Status register
digitalWrite(SPI_MAX_CS_PIN, HIGH);
}
// 6, End of UART slave device wake-up period
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x0E); // Write Configuration 2 register
SPI.transfer(0x10); // Disable Transmit Preambles mode
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 7, Wait for null message to be received
check = 0;
while(!(check & 0x10)) // If RX_Status[4] is true, continue. If false, then repeat transaction until true.
{
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x01); // Read RX_Status register
check = SPI.transfer(0x01); // Read RX_Status register
digitalWrite(SPI_MAX_CS_PIN, HIGH);
}
// 8, Clear transmit buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x20); // Clear transmit buffer
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 9, Clear receive buffer
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xE0); // Clear receive buffer
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 10, Load the HELLOALL command sequence into the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC0); // WR_LD_Q SPI command byte (write the load queue)
SPI.transfer(0x03); // Message length
SPI.transfer(0x57); // HELLOALL command byte
SPI.transfer(0x00); // Register address (0x00)
SPI.transfer(0x00); // Initialization address of HELLOALL
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 11, Verify contents of the load queue
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0xC1); // RD_LD_Q SPI command byte
for(int i=0; i<4; i++)
{
data[i] = SPI.transfer(0xC1);
}
if(!((data[0] == 0x03) && (data[1] == 0x57) && (data[2] == 0x00) && (data[3] == 0x00)))
{
errorByte |= initError;
}
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 12, Transmit HELLOALL sequence
// 13, Poll RX_Stop_Status bit
transmitQueue();
// 14, Read the HELLOALL message that propagated through the daisy-chain and was returned back to the ASCI
digitalWrite(SPI_MAX_CS_PIN, LOW);
SPI.transfer(0x93); // RD_NXT_MSG SPI transaction
for(int i=0; i<3; i++)
{
data[i] = SPI.transfer(0x93);
}
if(!((data[0] == 0x57) && (data[1] == 0x00) && (data[2] == 0x0C)))
{
errorByte |= initError;
}
digitalWrite(SPI_MAX_CS_PIN, HIGH);
// 15, Check for receive buffer errors
errorByte |= receiveBufferError();
SPI.endTransaction();
if(errorByte) // Error
{
Serial.println(errorByte, HEX);
errorByte &= 0x00; // Clear errors
Serial.println("errorByte cleared");
daisyChainInit(); // Redo Daisy chain init
}
}
/***************************************
* Handler for the wakeUp PIN interrupt *
***************************************/
void wakeUp()
{
// Just a handler for the pin interrupt.
}