Kia Niro BMS
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Kia Niro BMS
Posting some pictures of the BMS used in the Kia Niro PHEV.
Battery is 96S.
12pc of slave modules connected each to 8 cells or more specific two modules of 4 cells each.
Slaves are proably connected in daisy-chain fashion. Using MAX17823BG chips to measure cells.
Slave board, front. Slave board, back. The master is a very odd one. This unit is used as stand alone in the Niro HEV and as a master in the PHEV.
It uses a Infineon SAK-XC2387A as main controller and when used in HEV 6pc of LTC6803-1 for measurement.
To my suprise these LTC6803 chips are still mounted but not used in the PHEV? These chips cost around 15€ if I bought them at low quantity. A bit of a waste to mount them and not use them...
EDIT: I have looked into this and found out that the master I have is from a Kia Optima and NOT the Niro.
Master board, front. Master board, back.
Battery is 96S.
12pc of slave modules connected each to 8 cells or more specific two modules of 4 cells each.
Slaves are proably connected in daisy-chain fashion. Using MAX17823BG chips to measure cells.
Slave board, front. Slave board, back. The master is a very odd one. This unit is used as stand alone in the Niro HEV and as a master in the PHEV.
It uses a Infineon SAK-XC2387A as main controller and when used in HEV 6pc of LTC6803-1 for measurement.
To my suprise these LTC6803 chips are still mounted but not used in the PHEV? These chips cost around 15€ if I bought them at low quantity. A bit of a waste to mount them and not use them...
EDIT: I have looked into this and found out that the master I have is from a Kia Optima and NOT the Niro.
Master board, front. Master board, back.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Forgot the slave connector pinouts.
Cell connector when seen from the connector side. Communication connector when seen from the connector side. The pins with lines inbetween switches place every next module, hence my guess, daisy chain.
Cell connector when seen from the connector side. Communication connector when seen from the connector side. The pins with lines inbetween switches place every next module, hence my guess, daisy chain.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
I have looked into this and found out that the master I have is from a Kia Optima and not the Niro. First post updated.
The Optima uses a centralized bms consisting of two "master" modules where one is master and the other is slave.
The master module for the Niro is another thing, part nr 375A0-G2610. The guy I bought the battery from don't have this one so I will have to manage without it or source a separate one. Not shure if it can be used anyway but would be good to look at for design ideas.
My plan is to use the Niro cell-modules together with a home built master. The MAX17823BG have a datasheet and is recommended to be used with a MAX17841B to translate from Maxim's Daisy-chain differential UART protocol to SPI.
The Optima uses a centralized bms consisting of two "master" modules where one is master and the other is slave.
The master module for the Niro is another thing, part nr 375A0-G2610. The guy I bought the battery from don't have this one so I will have to manage without it or source a separate one. Not shure if it can be used anyway but would be good to look at for design ideas.
My plan is to use the Niro cell-modules together with a home built master. The MAX17823BG have a datasheet and is recommended to be used with a MAX17841B to translate from Maxim's Daisy-chain differential UART protocol to SPI.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Today I did some more investigation.
Complete slave module pinouts. Note that the pins are not connected directly to the slave controller IC, there are components in between but that is how they end up. At this point I can't find out whats exacly connected to the pins to the right in above pinout. It is a chain with all slaves connected in parallel when all slaves are connected.
What I have found so far including my guesses on Q1 and U2. U2 guess is based on pinout and possible usage of for example TL431 shunt reference. Might be a voltage monitor or similar as well. I need to investigate this further.
Anyone have any guesses for Q1 and U2?
Complete slave module pinouts. Note that the pins are not connected directly to the slave controller IC, there are components in between but that is how they end up. At this point I can't find out whats exacly connected to the pins to the right in above pinout. It is a chain with all slaves connected in parallel when all slaves are connected.
What I have found so far including my guesses on Q1 and U2. U2 guess is based on pinout and possible usage of for example TL431 shunt reference. Might be a voltage monitor or similar as well. I need to investigate this further.
Anyone have any guesses for Q1 and U2?
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
I've been able to comunicate with the cell modules using a MAX17841 between the Arduino Pro Mini and the daisy-chained slaves.
Connected as per schematic. As of now I have only set it up and been able to read out the cell voltages.
Next is to see if I can get cell balancing to work.
There is also a lot of diagnostics available but needs more work with the code so this will have to wait.
Connected as per schematic. As of now I have only set it up and been able to read out the cell voltages.
Next is to see if I can get cell balancing to work.
There is also a lot of diagnostics available but needs more work with the code so this will have to wait.
Re: Kia Niro BMS
That's fantastic.
I recently bought a water damaged Kia Nero pack and am interested in using the OEM BMS units if they still work. I think the BMS units are the same as the ones you have but I'll be able to check properly later on. I had abandoned the idea of using the OEM modules because of the lack of any sort of hacking I could find on the internet but this has proved me wrong.
I have the rear pack of 12 battery modules and 7 of them are still good which is enough for a 100V 24.7Ah pack for use on a small motorbike, sadly the rest have discharged to 0.0V. I have all 8 BMS modules and although some show signs of corrosion I am hoping enough work for the good cells I have.
Would you be willing to share the code, even if it's a bit shonky, so I can give feedback it works (or not) on another battery pack.?
I recently bought a water damaged Kia Nero pack and am interested in using the OEM BMS units if they still work. I think the BMS units are the same as the ones you have but I'll be able to check properly later on. I had abandoned the idea of using the OEM modules because of the lack of any sort of hacking I could find on the internet but this has proved me wrong.
I have the rear pack of 12 battery modules and 7 of them are still good which is enough for a 100V 24.7Ah pack for use on a small motorbike, sadly the rest have discharged to 0.0V. I have all 8 BMS modules and although some show signs of corrosion I am hoping enough work for the good cells I have.
Would you be willing to share the code, even if it's a bit shonky, so I can give feedback it works (or not) on another battery pack.?
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
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.
Please let me know when you find any improvments!
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.
Please let me know when you find any improvments!
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.
}
-
- Posts: 210
- Joined: Fri Dec 06, 2019 8:59 pm
- Location: Dublin & Kilkenny Ireland
- Has thanked: 2 times
- Been thanked: 13 times
- Contact:
Re: Kia Niro BMS
Both Kia Niro and hyundai kona , I assume have same BMS as Hyundai / Kia is stamped on the parts. The Hyundai global service portal has circuit diagram and some info. I dont know if I have any saved but you can get access for 15e for 24hrs
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Some more information.
If not using the extra check link, described here viewtopic.php?p=20335#p20335 it is a good idea to remove R1 and R4 to avoid an unbalanced current draw from cell1. The pack I bought that had been in storage for a while, the no:1 cells where all lower than the rest. I will remove the resistors the next time I have the pack out of the car.
Also note that when balancing using this MAX17823 circuit you must NOT activate adjacent balancing switches as this causes large currents and die overtemperature and forced shutdown of the slaves...
If not using the extra check link, described here viewtopic.php?p=20335#p20335 it is a good idea to remove R1 and R4 to avoid an unbalanced current draw from cell1. The pack I bought that had been in storage for a while, the no:1 cells where all lower than the rest. I will remove the resistors the next time I have the pack out of the car.
Also note that when balancing using this MAX17823 circuit you must NOT activate adjacent balancing switches as this causes large currents and die overtemperature and forced shutdown of the slaves...
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Attaching my latest BMS SW, with improved error handling.
My battery consists of 12x8s where cell 1-4 is on position 1-4 and then cell 5-8 is on position 6-9 on the slave modules. For other cases of connections at least function "storeCellVoltage()", "setupSlaves()" and "balanceCells()" will need to be adjusted.Re: Kia Niro BMS
You did great job with this BMS. I have the same battery pack (half of them, in a fact, under seats only), so I'm very interested in your idea. I can see the code is more complicated now and there are changes to the schema. Could you tell us more about the current PCB version?
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
My main BMS board consists of one micro controller board (ArduinCAN) on top of the BMS_5 board.
Schematic: Pictures: The 328P can be used with a Arduino Pro Mini bootloader. I uplod via ISP directly which gives quicker boot of the uC but needs some "FUSE" adjustment to not overwrite EEPROM when uploading this way.
Schematic: Pictures: The 328P can be used with a Arduino Pro Mini bootloader. I uplod via ISP directly which gives quicker boot of the uC but needs some "FUSE" adjustment to not overwrite EEPROM when uploading this way.
Re: Kia Niro BMS
Thanks a lot, it's very helpful. Probably I'll have a few questions, so be patient, please
Now I must check availability of some electronic components in Poland, and then track PCB in THT, because I have't experience in SMD. In programming too, so maybe it's good opportunity to learn.

- EV_Builder
- Posts: 1205
- Joined: Tue Apr 28, 2020 3:50 pm
- Location: The Netherlands
- Has thanked: 18 times
- Been thanked: 37 times
- Contact:
Re: Kia Niro BMS
Good job! I will be joining the party soon!
Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
see http://www.wdrautomatisering.nl for bespoke BMS modules.
- EV_Builder
- Posts: 1205
- Joined: Tue Apr 28, 2020 3:50 pm
- Location: The Netherlands
- Has thanked: 18 times
- Been thanked: 37 times
- Contact:
Re: Kia Niro BMS
I have a Kona Slave here on the bench. How did you find the comm pins? Does the board need external supply or does it use the cell voltages directly?bexander wrote: ↑Mon Dec 07, 2020 3:00 pm Today I did some more investigation.
Complete slave module pinouts. Note that the pins are not connected directly to the slave controller IC, there are components in between but that is how they end up.
IMG_1541_scale.JPG
At this point I can't find out whats exacly connected to the pins to the right in above pinout. It is a chain with all slaves connected in parallel when all slaves are connected.
What I have found so far including my guesses on Q1 and U2.
Krets.JPG
IMG_1539_scale.JPG
U2 guess is based on pinout and possible usage of for example TL431 shunt reference.
Referens.JPG
Might be a voltage monitor or similar as well. I need to investigate this further.
Anyone have any guesses for Q1 and U2?
The Kona slave board has 2 slave chips on one board.
But its very similar in components (which is obvious but still good to mention).
Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
see http://www.wdrautomatisering.nl for bespoke BMS modules.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Both looking at the cable harness that was used to connect the slaves together and also looking at the MA17823 pinout in the datasheet and following those on the pcb to the connector.EV_Builder wrote: ↑Fri Sep 16, 2022 11:45 am I have a Kona Slave here on the bench. How did you find the comm pins? Does the board need external supply or does it use the cell voltages directly?
Two slave chips? Both MAX17823 or something else? Please, post a picture if possible.EV_Builder wrote: ↑Fri Sep 16, 2022 11:45 am The Kona slave board has 2 slave chips on one board.
But its very similar in components (which is obvious but still good to mention).
I have examined the slaves from a Kia E-Niro (EV) and I think they use ASIC:s so not possible to find and datasheet or similar.
EDIT:
I have examined the slaves from a Kia E-Niro (EV) and they use the MAX17845 circuit.
Re: Kia Niro BMS
Hello @bexander. Could You share the code for ATtiny85? And another question - what type of current sensor (for A6 and A7) do You use?
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
The ATtiny85 is used for HV voltage measurement. Built with parts I had at home so not required to use the specific parts. Anyway, here is the code: Very advanced code...

You might need to adjust osccal to get correct UART timing when using internal RC-based oscillator as clock.
The current sensor is from the Kia Niro PHEV battery pack. It is a LEM DHAB S/318.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Info on the library used:
https://www.arduino.cc/reference/en/lib ... serialout/
EDIT:
And the osccal code: Measure output with a oscilloscope and adjust osccal to match set times.
https://www.arduino.cc/reference/en/lib ... serialout/
EDIT:
And the osccal code: Measure output with a oscilloscope and adjust osccal to match set times.
Re: Kia Niro BMS
Thank You. Unfortunately I must look for this current sensor or similar, because in my pack ("underseats" only) there wasn't any.
- EV_Builder
- Posts: 1205
- Joined: Tue Apr 28, 2020 3:50 pm
- Location: The Netherlands
- Has thanked: 18 times
- Been thanked: 37 times
- Contact:
Re: Kia Niro BMS
These are marked: MAX17845
Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
see http://www.wdrautomatisering.nl for bespoke BMS modules.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
Very interesting. Could not find the datasheet for this 17845 but found one for the 17843. Seem to communicate using the same interface as the 17823.
If it is the same as for 17845 I would try to locate pin 6, 7, 12, 13, 20, 21, 24 and 25. Those are the communications pins. See if you can trace them to the connector?
- EV_Builder
- Posts: 1205
- Joined: Tue Apr 28, 2020 3:50 pm
- Location: The Netherlands
- Has thanked: 18 times
- Been thanked: 37 times
- Contact:
Re: Kia Niro BMS
That's awesome! i was pulling some strings too but this is even bettter

Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
see http://www.wdrautomatisering.nl for bespoke BMS modules.
- bexander
- Posts: 866
- Joined: Tue Jun 16, 2020 6:00 pm
- Location: Gothenburg, Sweden
- Has thanked: 71 times
- Been thanked: 97 times
Re: Kia Niro BMS
I just looked quickly into the datasheet and I belive it to be close to a direct replacement for the MAX17823 regarding communication to and from the chip.EV_Builder wrote: ↑Tue Sep 20, 2022 9:48 am That's awesome! i was pulling some strings too but this is even bettter![]()