Kia Niro BMS

User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Kia Niro BMS

Post by bexander »

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.
IMG_1482.JPG
Slave board, back.
IMG_1483.JPG
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.
IMG_1484.JPG
Master board, back.
IMG_1485.JPG
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

Forgot the slave connector pinouts.
Cell connector when seen from the connector side.
Cell_connector.JPG
Communication connector when seen from the connector side. The pins with lines inbetween switches place every next module, hence my guess, daisy chain.
Com_connector..JPG
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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.
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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?
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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.
BMS.pdf
(34.85 KiB) Downloaded 723 times
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.
User avatar
Mouse
Posts: 138
Joined: Wed Sep 25, 2019 8:17 am
Location: Wales
Been thanked: 7 times
Contact:

Re: Kia Niro BMS

Post by Mouse »

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.?
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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!

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.
}
MAX17841B.pdf
(1.3 MiB) Downloaded 327 times
MAX17823B.pdf
(1.61 MiB) Downloaded 321 times
MAX17852.pdf
(2.22 MiB) Downloaded 257 times
paaa
Posts: 209
Joined: Fri Dec 06, 2019 8:59 pm
Location: Dublin & Kilkenny Ireland
Has thanked: 2 times
Been thanked: 11 times
Contact:

Re: Kia Niro BMS

Post by paaa »

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
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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...
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

Attaching my latest BMS SW, with improved error handling.
BMS2_7_3.ino
(47.38 KiB) Downloaded 266 times
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.
marucha79
Posts: 18
Joined: Wed Jul 20, 2022 6:28 am
Been thanked: 1 time

Re: Kia Niro BMS

Post by marucha79 »

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?
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

My main BMS board consists of one micro controller board (ArduinCAN) on top of the BMS_5 board.
Schematic:
ArduinoCAN.pdf
(55.16 KiB) Downloaded 244 times
BMS_5.pdf
(73.95 KiB) Downloaded 263 times
Pictures:
ArduinoCAN.png
BMS_5.png
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.
marucha79
Posts: 18
Joined: Wed Jul 20, 2022 6:28 am
Been thanked: 1 time

Re: Kia Niro BMS

Post by marucha79 »

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.
User avatar
EV_Builder
Posts: 1199
Joined: Tue Apr 28, 2020 3:50 pm
Location: The Netherlands
Has thanked: 16 times
Been thanked: 33 times
Contact:

Re: Kia Niro BMS

Post by EV_Builder »

Good job! I will be joining the party soon!
Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
User avatar
EV_Builder
Posts: 1199
Joined: Tue Apr 28, 2020 3:50 pm
Location: The Netherlands
Has thanked: 16 times
Been thanked: 33 times
Contact:

Re: Kia Niro BMS

Post by EV_Builder »

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?
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?

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.
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

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?
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 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).
Two slave chips? Both MAX17823 or something else? Please, post a picture if possible.
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.
marucha79
Posts: 18
Joined: Wed Jul 20, 2022 6:28 am
Been thanked: 1 time

Re: Kia Niro BMS

Post by marucha79 »

Hello @bexander. Could You share the code for ATtiny85? And another question - what type of current sensor (for A6 and A7) do You use?
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

marucha79 wrote: Sun Sep 18, 2022 3:06 pm Hello @bexander. Could You share the code for ATtiny85? And another question - what type of current sensor (for A6 and A7) do You use?
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:
BMS2_VM_2_0.ino
(487 Bytes) Downloaded 142 times
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.
Datasheet DHAB S 318.pdf
(934.74 KiB) Downloaded 227 times
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

Info on the library used:
https://www.arduino.cc/reference/en/lib ... serialout/

EDIT:
And the osccal code:
OSCCAL.ino
(436 Bytes) Downloaded 137 times
Measure output with a oscilloscope and adjust osccal to match set times.
marucha79
Posts: 18
Joined: Wed Jul 20, 2022 6:28 am
Been thanked: 1 time

Re: Kia Niro BMS

Post by marucha79 »

Thank You. Unfortunately I must look for this current sensor or similar, because in my pack ("underseats" only) there wasn't any.
User avatar
EV_Builder
Posts: 1199
Joined: Tue Apr 28, 2020 3:50 pm
Location: The Netherlands
Has thanked: 16 times
Been thanked: 33 times
Contact:

Re: Kia Niro BMS

Post by EV_Builder »

bexander wrote: Fri Sep 16, 2022 4:45 pm Two slave chips? Both MAX17823 or something else? Please, post a picture if possible.
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.
WhatsApp Image 2022-09-19 at 15.38.38.jpeg
WhatsApp Image 2022-09-19 at 15.38.34.jpeg
These are marked: MAX17845
Converting an Porsche Panamera
see http://www.wdrautomatisering.nl for bespoke BMS modules.
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

EV_Builder wrote: Mon Sep 19, 2022 1:44 pm These are marked: MAX17845
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?
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

I contacted Maxim Integrated and they sent me a datasheet:
MAX17845.pdf
MAX17845 datasheet
(1.5 MiB) Downloaded 238 times
User avatar
EV_Builder
Posts: 1199
Joined: Tue Apr 28, 2020 3:50 pm
Location: The Netherlands
Has thanked: 16 times
Been thanked: 33 times
Contact:

Re: Kia Niro BMS

Post by EV_Builder »

bexander wrote: Tue Sep 20, 2022 9:40 am I contacted Maxim Integrated and they sent me a datasheet:
MAX17845.pdf
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.
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Re: Kia Niro BMS

Post by bexander »

EV_Builder wrote: Tue Sep 20, 2022 9:48 am That's awesome! i was pulling some strings too but this is even bettter :)
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.
Post Reply