Communicate and control Eltek Flatpack 2 HE

Development and discussion of fast charging systems eg Chademo , CCS etc
User avatar
bexander
Posts: 834
Joined: Tue Jun 16, 2020 6:00 pm
Location: Gothenburg, Sweden
Has thanked: 63 times
Been thanked: 89 times

Communicate and control Eltek Flatpack 2 HE

Post by bexander »

I was asked if I can share any details of how you can control Eltek Flatpack 2 HE over CAN so here goes.

My knowledge of this is based on the forum thread below and my own experimenting and use of these power supplys as EV charger.
https://endless-sphere.com/forums/viewt ... ce854514ff

I have used 3pcs of 48V Flatpack 2 HE connected in series on the DC-side and y-connected (1 supply on each phase to neutral) on the AC-side to run 9kW of charging into my 42S LiFePO4 batterypack.

NOTE: The CAN-bus on the 48V Flatpacks is related to DC-side minus connector i.e. not isolated so when connected in series the CAN-bus needs to be isolated from the controlling microprocessor. I did this by using ISO1050 CAN-trancievers.
EDIT: johu proves not needed, my misstake
EDIT2: In order to get reliable communication the CAN-bus on the 48V Flatpacks needs to be related to the DC-side negative connector, resulting in that the CAN-bus need to be isolated from the controlling microprocessor.

CAN speed is 125kbit/s.
To send requests to the FP (Flatpack) you first need to send a "login" message consisting of the FP serial number which is printed on the lable of the FP followed by two empty bytes (0x00). Send the serial number (ex 141471110820) in HEX and use the following ID: 0x05004804.
Arduino code using mcp_can library and MCP2515 CAN controller.

Code: Select all

unsigned char stmp1[8] = {0x14, 0x14, 0x71, 0x11, 0x08, 0x20, 0x00, 0x00};    //this is the serial number of the flatpack followed by two 00 bytes 
CAN.sendMsgBuf(0x05004804, 1, 8, stmp1);                                      //send message to log in
Within 5s of the "login" you then send your "request" with ID: 0x05FF4004.
Current in dA (Ampere times 10) in HEX with low byte first. For 16.0A translate 160 to HEX => 0x00A0, separate into 2 bytes and place as byte 0 & 1.
Voltage in cV (Voltage times 100) in HEX with low byte first. For 57.6V translate 5760 to HEX => 0x1680, separate into 2 bytes and place as byte 2 & 3 and also byte 4 & 5.
An over voltage protection level also needs to be set, above the requested voltage. Done in same manner as Voltage so for 59.5V translate 5950 to HEX => 0x173E, separate into 2 bytes and place as byte 6 & 7.

Code: Select all

unsigned char stmp2[8] = {0xA0, 0x00, 0x80, 0x16, 0x80, 0x16, 0x3E, 0x17};    //set rectifier to 16.0 amps (00 A0) 57.60 (16 80) and OVP to 59.5V (17 3E)
CAN.sendMsgBuf(0x05FF4004, 1, 8, stmp2); 
So one login followed by repeated requests with less then 5s interval is enough. For robustness I send both login and request every time.
If communication is lost the FP reverts back to the default voltage.
This default voltage can be set by sending first login and then a message with ID: 0x05009C00.
Byte 0 = 0x29, Byte 1 = 0x15, Byte 2= 0x00.
For 43.7V translate 4370 to HEX => 0x1112, separate into 2 bytes and place as byte 3 & 4.

Code: Select all

byte stmp2[5] = {0x29, 0x15, 0x00, 0x12, 0x11};                   //set rectifier permanently to 43.70 (11 12)
CAN.sendMsgBuf(0x05009C00, 1, 5, stmp2); 
Login followedby above message only needs to be sent once and then comes into effect after communication time-out (5s) or after power cycle. This will be remembered after power off. I use 43.7V as I found this the lowest stable voltage.

Voltage can be set between 43.5V and 57.5V. Current can be set between 0 and max 62.5A for a 3000W FP.
NOTE: When using current limiting the FP will only maintain set current if it can do so with a voltage above 47.0V. If voltage needs to reduce further the current output will be max current, 62.5A so be careful when designing the charging algorithm.
User avatar
johu
Site Admin
Posts: 5684
Joined: Thu Nov 08, 2018 10:52 pm
Location: Kassel/Germany
Has thanked: 153 times
Been thanked: 960 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by johu »

Great info, I also use these in a test setup 32S LFP.
bexander wrote: Fri Jan 15, 2021 4:28 pm NOTE: The CAN-bus on the 48V Flatpacks is related to DC-side minus connector i.e. not isolated so when connected in series the CAN-bus needs to be isolated from the controlling microprocessor. I did this by using ISO1050 CAN-trancievers.
Are you really sure? I also use them in series but just connected the CAN buses straight through.

Also I never send the login. Maybe a different message, will check. I set the default voltage once (I think with login) and so low that it doesn't charge. It seems it has stored that setting to eeprom.
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

The FP sends out a status message which contains useful information.
It uses the following ID depending on situation.
0501400C + status data : walkin and below 43 volts (error) and during walk-out (input voltage low)
05014010 + status data : walkin busy
05014004 + status data : normal voltage reached
05014008 + status data : current-limiting active

The status message received contains 8 bytes as follows:
Byte 0, temperature 1 in Celsius
Byte 1 (LSB) and Byte 2 (MSB), current output in dA. Divide by 10 to get current in A.
Byte 3 (LSB) and Byte 4 (MSB), voltage output in cV. Divide by 100 to get voltage in V.
Byte 5, input voltage in V.
Byte 6, unkown
Byte 7, temperature 2 in Celsius

Walkin is the time from startup to the default voltage. Will be 5s if ID: 0x05FF4004 is used when sending requests as per above post. Will be 60s if ID: 0x05FF4005 is used instead. This takes effect after a power cycle.

The FP also sends a message with its serial number with the ID: 0x05014400 with the serial number:
Byte 0 - 7, 0x14, 0x14, 0x71, 0x11, 0x08, 0x20, 0x00, 0x00
Translates to a serial number of 141471110820. Useful if the lable is missing on the FP.
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

johu wrote: Fri Jan 15, 2021 4:34 pm Great info, I also use these in a test setup 32S LFP.
bexander wrote: Fri Jan 15, 2021 4:28 pm NOTE: The CAN-bus on the 48V Flatpacks is related to DC-side minus connector i.e. not isolated so when connected in series the CAN-bus needs to be isolated from the controlling microprocessor. I did this by using ISO1050 CAN-trancievers.
Are you really sure? I also use them in series but just connected the CAN buses straight through.

Also I never send the login. Maybe a different message, will check. I set the default voltage once (I think with login) and so low that it doesn't charge. It seems it has stored that setting to eeprom.
To get the CAN communitaction to work I have to connect the GND of the tranceiver to the negative output of the FP. When connecting two FP:s in series there will be a voltage difference of one FP output between the two negative outputs and hence a SC if not using isolated CAN.
This is the way I got it to work. There might be other ways that I'm not aware of. Please share more details of your connections.

There might also be differences between different FP:s. I use 48V Flatpack 2 HE.
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

This is my full code used in a Arduino Pro Mini to control my 3 FP. Alot of the code is for controling the LCD.
Keep in mind, I'm not a SW guy so use eye protection when reading through...

Code: Select all

// Include libraries
#include <mcp_can.h>
#include <SPI.h>
#include <Wire.h>

enum State_enum 
{
	START, SELECT, PLOT, INIT, MAIN, DONE, STOP, END
};
	
// Stated declaration
State_enum state;

// Constant declaration
// newData MASK
const uint8_t statusFPMaster = B00000001;
const uint8_t statusFPSlave1 = B00000010;
const uint8_t statusFPSlave2 = B00000100;
const uint8_t statusBMSMain  = B00001000;
const uint8_t statusBMSHiLo	 = B00010000;
const uint8_t statusBMStemp  = B00100000;
const uint8_t statusFPall		 = B00000111;
const uint8_t statusAllCAN	 = B00111111;

// stopReason MASK
const uint8_t stopDone				= B00000001;
const uint8_t stopButton 			= B00000010;
const uint8_t stopLowVoltage 	= B00000100;
const uint8_t stopTemperature = B00001000;
const uint8_t stopBMS					= B00010000;
const uint8_t stopWatchdog    = B00100000;

// chargerState MASK
const uint8_t chargerStateCurrentLimiting		= B00000001;
const uint8_t chargerStateCurrentRegulating	= B00000010;
const uint8_t chargerStateEndVoltage 				= B00000100;

// Timing constants
const uint32_t timePeriod100ms = 100; // Delay 100ms
const uint32_t timePeriod1s = 1000; // Delay, 1s
const uint32_t timePeriod3s = 3000; // Delay 3s
const uint32_t timePeriod5s = 5000; // Delay, 5s
const uint32_t timePeriod30s = 30000; // Delay 30s

// Configuration
const uint16_t startVoltage = 4370; // Set start voltage to 43.70 V (divide by 100)
const uint16_t initialLimitVoltage = 4900; // Set switch voltage to 49.00 V (divide by 100), must be lower than end voltage
const uint16_t endVoltage = 5040; // Set end voltage to 50.40 V (divide by 100)
const uint16_t overVoltage = 5110; // Set the overVoltage protection limit at 51.10 V (divide by 100)

const uint16_t maxCurrent1x10AAC = 135; // set charge current to 13.5 A (divide by 10) for max 10AAC at 230V
const uint16_t maxCurrent1x16AAC = 216; // set charge current to 21.6 A (divide by 10) for max 16AAC at 230V
const uint16_t maxCurrent3x16AAC = 600; // set charge current to 60.0 A (divide by 10), no limit, for 3-phase 16A
const uint16_t maxCurrent = 700; // Set slave maximum current to 70.0 A (divide by 10)
const uint16_t endCurrent = 10; // Set endCurrent to 1.0 A (divide by 10)

const uint16_t lowestCellVoltageLimit = 2500; // Set the lowest voltage acceptable to allow charging
const int16_t maxAllowedTemperature = 70; // Set max allowed BMS card temperature
const int8_t temperatureCorrection[4] = {1, 5, 0, 1};

// Display commands
const uint8_t address = 0x3C;
const uint8_t commands = 0x00;
const uint8_t onecommand = 0x80;
const uint8_t data = 0x40;
const uint8_t onedata = 0xC0;

// Variable declaration
// For FlatPacks
uint8_t mainBMS[8];
uint8_t highLowBMS[8];
uint8_t tempBMS[8];
uint16_t measuredMasterCurrent;
uint8_t newData = 0;
uint8_t stopReason = 0;
uint8_t chargerState = 0;

// Set pin number
const uint8_t SPI_CS_PIN0 = 10; // CAN0, BMS
const uint8_t SPI_CS_PIN1 = 15; // CAN1, FlatPack Master (Current limiting)
const uint8_t SPI_CS_PIN2 = 14; // CAN2, FlatPack Slave 1
const uint8_t SPI_CS_PIN3 = 16; // CAN3, FlatPack Slave 2

const uint8_t threePhasePIN = 17; // Input PIN for 3-phase detection
const uint8_t contactorPIN = 9; // Contactor control and +12V supply to BMS
const uint8_t buttonPIN = 8; // Button to select charge current

// Set CS pin for CAN-buses
MCP_CAN CAN0(SPI_CS_PIN0);
MCP_CAN CAN1(SPI_CS_PIN1);
MCP_CAN CAN2(SPI_CS_PIN2);
MCP_CAN CAN3(SPI_CS_PIN3);

// Function declaration
// For FlatPack
void chargerMain();
void voltageControl();
uint16_t chargeCurrentCalc();
void checkLowestCell();
int16_t checkTemperature();
void updateDisplay();
void plotChargerState();
void stopCharging();
void BMSStopCharging();
void readCAN();
uint16_t readMasterCAN();
uint16_t readSlave1CAN();
uint16_t readSlave2CAN();
void sendCAN();
void sendFlatPackCAN();
void sendMasterCAN();
void sendSlave1CAN();
void sendSlave2CAN();
// HMI
void clearDisplay();
void initDisplay();
void plotChar();
void plotText();
void plotValue();
void moveMarker();
uint16_t selectCurrent();
void buttonStopCharging();

// OLED display
// Character set for text - stored in program memory
const uint8_t CharMap[96][6] PROGMEM = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 32: "Space"
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, // 33: !
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, // 34: "
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, // 35: #
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, // 36: $
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, // 37: %
{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, // 38: &
{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, // 39: '
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, // 40: (
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, // 41: )
{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, // 42: *
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, // 43: +
{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, // 44: ,
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, // 45: -
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // 46: .
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // 47: /
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, // 48: 0
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, // 49: 1
{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, // 50: 2
{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, // 51: 3
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, // 52: 4
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, // 53: 5
{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, // 54: 6
{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, // 55: 7
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 56: 8
{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, // 57: 9
{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, // 58: :
{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, // 59: ;
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 60: <
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, // 61: =
{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, // 62: >
{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, // 63: ?
{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, // 64: @
{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, // 65: A
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 66: B
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, // 67: C
{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, // 68: D
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, // 69: E
{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, // 70: F
{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, // 71: G
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, // 72: H
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, // 73: I
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, // 74: J
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 75: K
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 76: L
{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, // 77: M
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, // 78: N
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, // 79: O
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, // 80: P
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, // 81: Q
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, // 82: R
{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, // 83: S
{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, // 84: T
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, // 85: U
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, // 86: V
{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, // 87: W
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, // 88: X
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, // 89: Y
{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, // 90: Z
{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, // 91: [
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, // 92: backslash
{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, // 93: ]
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, // 94: ^
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 95: _
{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, // 96: `
{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, // 97: a
{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, // 98: b
{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, // 99: c
{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, // 100: d
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, // 101: e
{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, // 102: f
{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, // 103: g
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 104: h
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, // 105: i
{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, // 106: j
{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, // 107: k
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, // 108: l
{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, // 109: m
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 110: n
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, // 111: o
{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, // 112: p
{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, // 113: q
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, // 114: r
{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, // 115: s
{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, // 116: t
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, // 117: u
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, // 118: v
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, // 119: w
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, // 120: x
{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, // 121: y
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, // 122: z
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, // 123: {
{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, // 124: |
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, // 125: }
{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // 126: degree symbol = '~'
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } // 127: DEL
};


/********
* SETUP *
********/
void setup()
{
	pinMode(buttonPIN, INPUT_PULLUP);
	
	pinMode(contactorPIN, OUTPUT);
	digitalWrite(contactorPIN, LOW);
	  
  Wire.begin();
  initDisplay(); 
  clearDisplay();
	
	//Serial.begin(115200);
 
	while (CAN_OK != CAN0.begin(MCP_STDEXT, CAN_250KBPS, MCP_8MHZ))    // Init car CAN bus : baudrate = 250k
  { delay(100); }

  CAN0.init_Mask(0,0,0x07FF0000);                // Init first mask
  CAN0.init_Filt(0,0,0x01800000);                // Init first filter
  CAN0.init_Filt(1,0,0x01810000);                // Init second filter
  
  CAN0.init_Mask(1,0,0x07FF0000);                // Init second mask 
  CAN0.init_Filt(2,0,0x01800000);                // Init third filter
  CAN0.init_Filt(3,0,0x01810000);                // Init fouth filter
  CAN0.init_Filt(4,0,0x01820000);                // Init fifth filter
  CAN0.init_Filt(5,0,0x01820000);                // Init sixth filter
  
  CAN0.setMode(MCP_NORMAL);  // Set operation mode to normal so the MCP2515 sends acks to received data.
	
	while (CAN_OK != CAN1.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Master CAN bus : baudrate = 125k
  { delay(100); }

  // These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
  CAN1.init_Mask(0,1,0x1FFFFFFF); // Init first mask
  CAN1.init_Filt(0,1,0x05014004); // Init first filter
  CAN1.init_Filt(1,1,0x05014008); // Init second filter
  
  CAN1.init_Mask(1,1,0x1FFFFFFF); // Init second mask 
  CAN1.init_Filt(2,1,0x05014004); // Init third filter
  CAN1.init_Filt(3,1,0x05014008); // Init fouth filter
  CAN1.init_Filt(4,1,0x05014004); // Init fifth filter
  CAN1.init_Filt(5,1,0x05014004); // Init sixth filter
  
  CAN1.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
  
  while (CAN_OK != CAN2.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Slave 1 CAN bus : baudrate = 125k
  { delay(100); }

  // These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
  CAN2.init_Mask(0,1,0x1FFFFFFF); // Init first mask
  CAN2.init_Filt(0,1,0x05014004); // Init first filter
  CAN2.init_Filt(1,1,0x05014004); // Init second filter
  
  CAN2.init_Mask(1,1,0x1FFFFFFF); // Init second mask 
  CAN2.init_Filt(2,1,0x05014004); // Init third filter
  CAN2.init_Filt(3,1,0x05014004); // Init fouth filter
  CAN2.init_Filt(4,1,0x05014004); // Init fifth filter
  CAN2.init_Filt(5,1,0x05014004); // Init sixth filter
  
  CAN2.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
  
  while (CAN_OK != CAN3.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Slave 2 CAN bus : baudrate = 125k
  { delay(100); }

  // These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
  CAN3.init_Mask(0,1,0x1FFFFFFF); // Init first mask
  CAN3.init_Filt(0,1,0x05014004); // Init first filter
  CAN3.init_Filt(1,1,0x05014004); // Init second filter
  
  CAN3.init_Mask(1,1,0x1FFFFFFF); // Init second mask 
  CAN3.init_Filt(2,1,0x05014004); // Init third filter
  CAN3.init_Filt(3,1,0x05014004); // Init fouth filter
  CAN3.init_Filt(4,1,0x05014004); // Init fifth filter
  CAN3.init_Filt(5,1,0x05014004); // Init sixth filter
  
  CAN3.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.

	state = START;  
}


/*******
* LOOP *
*******/
void loop() 
{
	readCAN();
	uint16_t measuredMasterVoltage = readMasterCAN();
	uint16_t measuredSlave1Voltage = readSlave1CAN();
	uint16_t measuredSlave2Voltage = readSlave2CAN();
	chargerMain(measuredMasterVoltage, measuredSlave1Voltage, measuredSlave2Voltage);
}


/************************************************************************
* Sets the output both master and slave. Checks if charging is complete *
************************************************************************/
void chargerMain(uint16_t measuredMasterVoltage, uint16_t measuredSlave1Voltage, uint16_t measuredSlave2Voltage)
{
	static uint16_t startCapacity;
	static uint16_t selectedMaxCurrent = 0;
  uint16_t endCapacity;
  
	switch (state) 
	{
		case START:
			plotText("U:000.0V T:00", 0, 0);
			plotChar(126, 13, 0); // Degree symbol
			plotChar('C', 14, 0);
      plotText("Is:00.0A I:00.0A", 0, 1);
      plotText("Cs:00.0Ah C:00.0Ah", 0, 2);
      plotText("Status:", 0, 3);
      plotChar('*', 0, 4);
      plotText("IAC=1x10A", 2, 4);
      plotText("IAC=1x16A", 2, 5);
      plotText("IAC=3x16A", 2, 6);
			
			state = SELECT;
			break;
		
		case SELECT:
			static uint32_t lastMillisTimer = millis(); // Reset timer
      selectedMaxCurrent = selectCurrent();
      
      if(selectedMaxCurrent != 0)
      {
        state = PLOT;
      }
      
      if((millis() - lastMillisTimer) > timePeriod30s)
      {
        selectedMaxCurrent = maxCurrent1x10AAC;
        state = PLOT;
      }
			break;
		
		case PLOT:
			plotText("           ", 0, 4);
      plotText("           ", 0, 5);
      plotText("           ", 0, 6);
      plotValue(selectedMaxCurrent, 10.0, 1, 3, 1);

      state = INIT;
			break;
			
		case INIT:
      digitalWrite(contactorPIN, HIGH); // Turn on contactor and +12V supply to BMS
    
			sendFlatPackCAN(startVoltage, startVoltage, selectedMaxCurrent); // Send data
						
			if((newData & statusAllCAN) == statusAllCAN) // All FlatPacks and BMS are responding
			{
				startCapacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack capacity
				plotValue(startCapacity, 10.0, 1, 3, 2);
				
				state = MAIN;
			}

			break;
		
		case MAIN:
			if((newData & statusAllCAN) == statusAllCAN) // All FlatPacks and BMS are responding
			{
				static uint32_t lastMillisDo = millis();
				
				if(millis() - lastMillisDo >= timePeriod1s)
				{
					lastMillisDo = millis(); // Reset timer
			
					voltageControl(selectedMaxCurrent, measuredMasterVoltage);
          checkLowestCell();
          int16_t maxTemperature = checkTemperature();
          updateDisplay(maxTemperature);
				}

        if(((measuredMasterVoltage + measuredSlave1Voltage + measuredSlave2Voltage) >= (endVoltage * 3)) && (measuredMasterCurrent <= endCurrent)) // Check if charging is complete
        {
          stopReason = stopDone;
          state = STOP;
        }
			}
			
      buttonStopCharging();
      BMSStopCharging();
			break;
		
		case STOP:
			sendFlatPackCAN(startVoltage, startVoltage, endCurrent); // Send data to stop FlatPack power output
			delay(2000);
			endCapacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack capacity
			
			if(stopReason & stopDone) // Check if battery capacity needs reset
			{
				while(!(mainBMS[6] & B00000001)) // Do until battery capacity is reset
				{
					sendCAN(); // Reset BMS battery capacity
					delay(100);
					readCAN(); // Readback to check reset
				}
			}
						
			stopCharging(startCapacity, endCapacity);
			
      state = END;
      break;
			
		case END:
			
			break;

    default:
      // Error
      break;
	}
}


/******************************************
* Sets the FP output voltages and current *
******************************************/
void voltageControl(uint16_t selectedMaxCurrent, uint16_t measuredMasterVoltage)
{
	static uint16_t setMasterVoltage = startVoltage;
	static uint16_t setSlaveVoltage = startVoltage;
	static uint16_t limitVoltage = initialLimitVoltage;
	
	((setMasterVoltage + 10) < endVoltage) ? setMasterVoltage += 10 : setMasterVoltage = endVoltage; // Increases setMasterVoltage with 100mV
								
	if(measuredMasterVoltage >= limitVoltage)
	{
		((setSlaveVoltage + 10) < endVoltage) ? setSlaveVoltage += 10 : setSlaveVoltage = endVoltage; // Increases setSlaveVoltage with 100mV
	}
	
	if(setSlaveVoltage > limitVoltage)
	{
		((limitVoltage + 10) < endVoltage) ? limitVoltage += 10 : limitVoltage = endVoltage; // Increases limitVoltage with 100mV
	}
	
	uint16_t chargeCurrent = chargeCurrentCalc(selectedMaxCurrent);
	sendFlatPackCAN(setMasterVoltage, setSlaveVoltage, chargeCurrent); // Send new data
	
	newData &= ~statusAllCAN; // Reset FlatPack and BMS bits to check if new data is received
	
	if(limitVoltage == endVoltage) // Check and set charger state constant voltage
	{
		chargerState |= chargerStateEndVoltage;
	}
}


/************************************************************************************
* Calculates the max allowed charging current based on highest battery cell voltage *
************************************************************************************/
uint16_t chargeCurrentCalc(uint16_t selectedMaxCurrent)
{
	uint16_t chargeCurrent;
	uint16_t highestCellVoltage = (highLowBMS[1] << 8) + highLowBMS[2]; // Highest cell voltage
	const uint16_t highVoltageLowerLimit = 3590; // Set highest cell lower voltage for start of current limiting, in mV
	const uint16_t highVoltageUpperLimit = 3640; // Set highest cell upper voltage for max current limiting, in mV
	
	if(highestCellVoltage < highVoltageLowerLimit)
	{
		chargeCurrent = selectedMaxCurrent;
	}
	else if((highestCellVoltage >= highVoltageLowerLimit) && (highestCellVoltage <= highVoltageUpperLimit))
	{
		//chargeCurrent = selectedMaxCurrent * (highVoltageUpperLimit - highestCellVoltage) / (highVoltageUpperLimit - highVoltageLowerLimit);
		
		chargeCurrent = (11L * highestCellVoltage * highestCellVoltage - 80118L * highestCellVoltage + 145883920L) / 49;
		
		if (chargeCurrent > selectedMaxCurrent)
		{
			chargeCurrent = selectedMaxCurrent;
		}
	}
	else
	{
		chargeCurrent = 0;
	}
	
	(chargeCurrent < selectedMaxCurrent) ? chargerState |= chargerStateCurrentRegulating : chargerState &= ~chargerStateCurrentRegulating; // Check and set charger state current regulation
 
	return chargeCurrent;
}


/**********************************************************************
* Checks that lowest battery cell is above limit, else stops charging *
**********************************************************************/
void checkLowestCell()
{
	uint16_t lowestCellVoltage = (highLowBMS[4] << 8) + highLowBMS[5]; // Lowest cell voltage
  if(lowestCellVoltage < lowestCellVoltageLimit)
  {
		state = STOP; // Abort charging
		stopReason = stopLowVoltage;
	}
}


/*****************************************************************************************************************
* Finds max temperature after board temperatures have been corrected, checks if below limit, else stops charging *
*****************************************************************************************************************/
int16_t checkTemperature()
{
	int16_t maxTemperature = 0;
	int16_t tempModule;
		
	for(int i=0; i<4; i++)
  {
		tempModule = (tempBMS[i*2] << 8) + tempBMS[(i*2)+1]; // BMS module card temperature in degree C
		tempModule = tempModule - temperatureCorrection[i]; // Correction so all boards show the same temperature for the same ambient temperature
		if(maxTemperature < tempModule)
		{
			maxTemperature = tempModule;
		}
  }
	
	if(maxTemperature > maxAllowedTemperature)
	{
		state = STOP; // Abort charging
		stopReason = stopTemperature;
	}
	
	return maxTemperature;
}


/***********************************
* Updates display with latest data *
***********************************/
void updateDisplay(int16_t maxTemperature)
{
	uint16_t capacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack remaining capacity
	uint16_t totalBatteryVoltage = (mainBMS[2] << 8) + mainBMS[3]; // Battery pack total voltage
					
	plotValue(totalBatteryVoltage, 10.0, 1, 2, 0);
	plotValue(maxTemperature, 1, 0, 11, 0);
	plotValue(measuredMasterCurrent, 10.0, 1, 11, 1);
	plotValue(capacity, 10.0, 1, 12, 2);
	
	plotChargerState();
}


/**********************************
* Blinks charger state on display *
**********************************/
void plotChargerState()
{
	static uint8_t flash = 0;
	
	if(flash == 0)
	{
		if(!chargerState)
		{
			plotText("RV", 7, 3);
		}
		else if((chargerState & chargerStateCurrentLimiting) && !(chargerState & chargerStateCurrentRegulating))
		{
			plotText("CC", 7, 3);
		}
		else if((chargerState & chargerStateCurrentLimiting) && (chargerState & chargerStateCurrentRegulating))
		{
			plotText("CR", 7, 3);
		}		
		else if(!(chargerState & chargerStateCurrentLimiting) && (chargerState & chargerStateEndVoltage))
		{
			plotText("CV", 7, 3);
		}
		else
		{
			plotText("EE", 7, 3);
		}
		
		flash = 1;
	}
	else
	{
		plotText("  ", 7, 3);
		flash = 0;
	}
}


/**********************************************
* Stops charging and prints reason on display *
**********************************************/
void stopCharging(uint16_t startCapacity, uint16_t endCapacity)
{
	digitalWrite(contactorPIN, LOW); // Turn off contactor and +12V supply to BMS
	
	if(stopReason & stopDone)
	{
		plotText("Done! ", 0, 5);
	}
	else if (stopReason & stopButton)
	{
		plotText("Stop! ", 0, 5);
	}
	else if (stopReason & stopLowVoltage)
	{
		plotText("LowV! ", 0, 5);
	}
	else if (stopReason & stopTemperature)
	{
		plotText("Temp! ", 0, 5);
	}
	else if (stopReason & stopBMS)
	{
		plotText("BMS!  ", 0, 5);
	}
  else if (stopReason & stopWatchdog)
  {
    plotText("Wdog! ", 0, 5);
  }
	else
	{
		plotText("Err!  ", 0, 5);
	}
	
	plotValue((endCapacity - startCapacity), 10.0, 1, 6, 5);
	plotText("Ah", 10, 5);
}


/***************************************************
* Stops charging if BMS sends charging not allowed *
***************************************************/
void BMSStopCharging()
{
	if(!(mainBMS[6] & B00001000))
	{
		state = STOP;
		stopReason = stopBMS;
	}
}


/*********************************************************************************************
* Checks BMS CAN-bus buffer and if new data is available stores it in appropriate data array *
*********************************************************************************************/
void readCAN()
{
	uint32_t rxId;
  uint8_t len;
  uint8_t rxBuf[8];  
        
  if(CAN_MSGAVAIL == CAN0.checkReceive()) // Check if data is coming
  {
    CAN0.readMsgBuf(&rxId, &len, rxBuf); // read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
    
    rxId &= 0x7FF; // Remove bits outside of the 11-bits ID
    
    if(rxId == 0x180) // Main data from BMS
    {
      newData |= statusBMSMain;
      for(int i=0; i<8; i++)
      {
				mainBMS[i] = rxBuf[i];
			}
    }
		if(rxId == 0x181) // Highest and lowest cell data from BMS
    {
      newData |= statusBMSHiLo;
      for(int i=0; i<8; i++)
      {
				highLowBMS[i] = rxBuf[i];
			}
    }
		if(rxId == 0x182) // Temperature data from BMS
    {
      newData |= statusBMStemp;
      for(int i=0; i<8; i++)
      {
				tempBMS[i] = rxBuf[i];
			}
    }
  }
}


/*****************************************************************************************************************************
* Checks FlatPack Master CAN-bus buffer and if new data is available stores it in appropriate variable. Also resets watchdog *
*****************************************************************************************************************************/
uint16_t readMasterCAN()
{
	static uint32_t lastMillisWatchDog = millis();
	uint32_t rxId;
  uint8_t len;
  uint8_t rxBuf[8];
  uint16_t measuredMasterVoltage;
        
  if(CAN_MSGAVAIL == CAN1.checkReceive()) // Check if data is coming
  {
    CAN1.readMsgBuf(&rxId, &len, rxBuf); // Read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf

    rxId &= 0x1FFFFFFF; // Remove bits outside of the 29-bits ID
    
    lastMillisWatchDog = millis(); // Reset WatchDog
    newData |= statusFPMaster;
    measuredMasterVoltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
    measuredMasterCurrent = (rxBuf[2] << 8) + rxBuf[1]; // The current measured at the output in deciAmpere
    
    (rxId == 0x05014008) ? chargerState |= chargerStateCurrentLimiting : chargerState &= ~chargerStateCurrentLimiting; // Check and set charger state current limiting
  }
  
  if((newData & statusFPMaster) && (millis() - lastMillisWatchDog >= timePeriod5s) && (state != END))
  {
		lastMillisWatchDog = millis(); // Reset WatchDog
		newData &= ~statusFPMaster;
    
    state = STOP; // Abort charging
    stopReason = stopWatchdog;
		//state = START;
	}
	
	return measuredMasterVoltage;
}


/********************************************************************************************************
* Checks FlatPack Slave 1 CAN-bus buffer and if new data is available stores it in appropriate variable *
********************************************************************************************************/
uint16_t readSlave1CAN()
{
	uint32_t rxId;
  uint8_t len;
  uint8_t rxBuf[8];
  uint16_t measuredSlave1Voltage;
        
  if(CAN_MSGAVAIL == CAN2.checkReceive())    // check if data is coming
  {
    CAN2.readMsgBuf(&rxId, &len, rxBuf); // read data,  rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf

    newData |= statusFPSlave1;
    measuredSlave1Voltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
  }
  
  return measuredSlave1Voltage;
}


/********************************************************************************************************
* Checks FlatPack Slave 2 CAN-bus buffer and if new data is available stores it in appropriate variable *
********************************************************************************************************/
uint16_t readSlave2CAN()
{
	uint32_t rxId;
  uint8_t len;
  uint8_t rxBuf[8];
  uint16_t measuredSlave2Voltage;
        
  if(CAN_MSGAVAIL == CAN3.checkReceive())    // check if data is coming
  {
    CAN3.readMsgBuf(&rxId, &len, rxBuf); // read data,  rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
    
    newData |= statusFPSlave2;
    measuredSlave2Voltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
  }
  
  return measuredSlave2Voltage;
}


/******************************************
* Sends reset capacity to BMS via CAN-bus *
******************************************/
void sendCAN()
{
  uint8_t reset[1] = {'Z'};
  CAN0.sendMsgBuf(0x170, 0, 1, reset);
}


/***********************************************************
* Sends setVoltage and setCurrent to FlatPacks via CAN-bus *
***********************************************************/
void sendFlatPackCAN(uint16_t setMasterVoltage, uint16_t setSlaveVoltage, uint16_t setCurrent)
{
	sendMasterCAN(setMasterVoltage, setCurrent);
	sendSlave1CAN(setSlaveVoltage);
	sendSlave2CAN(setSlaveVoltage);
}



/*****************************************************************
* Sends setVoltage and setCurrent to FlatPack Master via CAN-bus *
*****************************************************************/
void sendMasterCAN(uint16_t setVoltage, uint16_t setCurrent)
{
	uint8_t login[8] = {0x14, 0x43, 0x71, 0x03, 0x97, 0x57, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
	CAN1.sendMsgBuf(0x05004804, 1, 8, login);                          //send message to log in
	uint8_t config[8] = {lowByte(setCurrent), highByte(setCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)};  // set rectifiers maxCurrent, outputVoltage and OVP
	CAN1.sendMsgBuf(0x05FF4004, 1, 8, config);                         // short walk-in 4004, for long walk-in set 4005

	//Serial.print(setVoltage);
	//Serial.print(" ");
	//Serial.println(setCurrent);
}


/***************************************************
* Sends setVoltage to FlatPack Slave 1 via CAN-bus *
***************************************************/
void sendSlave1CAN(uint16_t setVoltage)
{
	uint8_t login[8] = {0x15, 0x10, 0x72, 0x00, 0x77, 0x78, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
	CAN2.sendMsgBuf(0x05004804, 1, 8, login);                          //send message to log in
	uint8_t config[8] = {lowByte(maxCurrent), highByte(maxCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)};  // set rectifiers maxCurrent, outputVoltage and OVP
	CAN2.sendMsgBuf(0x05FF4004, 1, 8, config);                         // short walk-in 4004, for long walk-in set 4005

	//Serial.println(setVoltage);
}


/***************************************************
* Sends setVoltage to FlatPack Slave 2 via CAN-bus *
***************************************************/
void sendSlave2CAN(uint16_t setVoltage)
{
	uint8_t login[8] = {0x14, 0x22, 0x71, 0x14, 0x90, 0x69, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
	CAN3.sendMsgBuf(0x05004804, 1, 8, login);                          //send message to log in
	uint8_t config[8] = {lowByte(maxCurrent), highByte(maxCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)};  // set rectifiers maxCurrent, outputVoltage and OVP
	CAN3.sendMsgBuf(0x05FF4004, 1, 8, config);                         // short walk-in 4004, for long walk-in set 4005

	//Serial.println(setVoltage);
}


/***************
* Initiate LCD *
***************/   
void initDisplay()
{
  Wire.beginTransmission(address);
  Wire.write(commands);
  Wire.write(0xA0); // No flip
  Wire.write(0xAF); // Display on
  Wire.write(0x40); // Display start at bottom line
  
  Wire.write(0x81); // Set contrast
  Wire.write(0x00);
  Wire.endTransmission();
}


/****************
* Clear the LCD *
****************/
void clearDisplay()
{
  for (int p = 0 ; p < 8; p++)
  {
    Wire.beginTransmission(address);
    Wire.write(commands);
    Wire.write(0xB0 + p); // Page
    Wire.write(0x02); // Column low nibble
    Wire.write(0x10); // Column high nibble
    Wire.endTransmission();
  
    for (int q = 0 ; q < 5; q++)
    {
      Wire.beginTransmission(address);
      Wire.write(data);
      for (int i = 0 ; i < 26; i++) 
      {
        Wire.write(0x00);
      }
      Wire.endTransmission();
    }
  }
}


/*****************************************************************
* Plot an ASCII character with bottom left corner at x and row y *
*****************************************************************/
void plotChar (uint8_t c, uint8_t x, uint8_t y)
{
  if(x > 20 || y > 7) return;
  
  x = (x * 6) + 1;
  
  Wire.beginTransmission(address);
  Wire.write(commands);
  Wire.write(0xB0 + y); // Page
  Wire.write(0x00 + ((x+2) & 0x0F)); // Column low nibble
  Wire.write(0x10 + ((x+2)>>4)); // Column high nibble    
  Wire.endTransmission();

  Wire.beginTransmission(address);
  Wire.write(data);
  for (int col=0; col<6; col++)
  {
    uint8_t bits = pgm_read_byte(&CharMap[c-32][col]);
    Wire.write(bits);
  }
  Wire.endTransmission();
}


/****************************************************
* Plot text starting with bottom left corner at x,y *
****************************************************/
void plotText(String textToPlot, uint8_t x, uint8_t y)
{
  int8_t lengthOfString = textToPlot.length();
  int8_t characters[lengthOfString + 1];

  textToPlot.toCharArray(characters, (lengthOfString + 1));
  
  for (int i=0; i<lengthOfString; i++)
  {
    plotChar(characters[i], x, y);
    x++;
  }
}


/*******************************************************
* Plot a value starting with bottom left corner at x,y *
*******************************************************/
void plotValue(uint16_t valueToPlot, float division, uint8_t decimal, uint8_t x, uint8_t y)
{
  String textToPlot = String(valueToPlot / division, decimal);
  plotText(textToPlot, x, y);
}


/**************************************************************
* Reads button and moves marker or selects max charge current *
**************************************************************/
uint16_t selectCurrent()
{
  static uint32_t lastMillisButton = millis();
  static uint8_t buttonPressed = 0;
  static uint8_t row = 4;
  uint16_t selectedMaxCurrent = 0;

  if(digitalRead(buttonPIN) == HIGH) // Button not pressed
  {
    lastMillisButton = millis(); // reset timer
    buttonPressed &= B110;
  }
  else if((millis() - lastMillisButton) > timePeriod100ms) // Button pressed for 100ms
  {
    buttonPressed |= B011;
  }

  if((buttonPressed == B011) && (millis() - lastMillisButton > timePeriod3s)) // Button pressed for 3s -> select current and stop further marker movment
  {
    buttonPressed |= B100;
    plotChar('-', 1, row);
    if(row == 4) selectedMaxCurrent = maxCurrent1x10AAC;
    else if (row == 5) selectedMaxCurrent = maxCurrent1x16AAC;
    else if (row == 6) selectedMaxCurrent = maxCurrent3x16AAC;
  }

  if(buttonPressed == B010) // Button released, before timeout, after having been depressed -> move marker
  {
    plotChar(' ', 0, row);
    row = (row>=6) ? 4 : row + 1;
    plotChar('*', 0, row);
    
    buttonPressed &= B101;
  }
  
  return selectedMaxCurrent;
}


/******************************************
* Stops charging if button is held for 3s *
******************************************/
void buttonStopCharging()
{
  static uint32_t lastMillisButton = millis();

  if(digitalRead(buttonPIN) == HIGH) // Button not pressed
  {
    lastMillisButton = millis(); // reset timer
  }
  else if((millis() - lastMillisButton) > timePeriod3s) // Button pressed for 3s
  {
    state = STOP;
    stopReason = stopButton;
  }
}
User avatar
johu
Site Admin
Posts: 5684
Joined: Thu Nov 08, 2018 10:52 pm
Location: Kassel/Germany
Has thanked: 153 times
Been thanked: 960 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by johu »

Of course I had to go and check :)

I measured infinite resistance between CANL and negative rail.

They are FLATPACK2 48/3000 HE G2
Attachments
IMG_20210115_181534.jpg
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
User avatar
johu
Site Admin
Posts: 5684
Joined: Thu Nov 08, 2018 10:52 pm
Location: Kassel/Germany
Has thanked: 153 times
Been thanked: 960 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by johu »

bexander wrote: Fri Jan 15, 2021 5:09 pm Keep in mind, I'm not a SW guy so use eye protection when reading through...
Haha :)

So I just mapped the following BMS values to CAN

can tx chgmaxvtg 100614149 48 16 102
can tx chgmaxvtg 100614149 16 16 100
can tx chgmaxvtg 100614149 32 16 100
can tx chargecur 100614149 0 16 10

It is hex 0x5FF4005
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

johu wrote: Fri Jan 15, 2021 5:21 pm Of course I had to go and check :)

I measured infinite resistance between CANL and negative rail.

They are FLATPACK2 48/3000 HE G2
If I remember correctly I also have infinite resistance between CANL and negative rail. I don't have the FP:s available at the moment so can't check.
But when I didn't connect the negative rail to GND of the tranceiver (TJA1050) I couldn't get the communication to work. I might have made a mistake and falsely concluded that this is neaccesary. I will test this again when possible. Would be great to not need the isolated tranceivers.

What CAN-tranceiver are you using?
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

I finally got a chance today to test this.
Indeed I can connect three FP i series and put them on the same CAN-bus without causing any shorts or similar, but I can't get the communication to work reliably. At startup all three responds and sets commanded voltage but after 10s (time it takes for FP to fall back to default) two of the three FP falls back to default. If I make a GND connection between a FP and my CAN tranciever, that FP responds to commands.
Also when using three FP it is always the one in the middle that take commands and the other two that loses them after startup.

My conclusion is that there are some kind of capacitive coupling that makes the FP-CAN lose there ground reference somehow. I have no idea of how to work around this other than to use a separate CAN-tranciever for every FP in series.
arber333
Posts: 3241
Joined: Mon Dec 24, 2018 1:37 pm
Location: Slovenia
Has thanked: 74 times
Been thanked: 223 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by arber333 »

bexander wrote: Wed Mar 03, 2021 4:01 pm I finally got a chance today to test this.
Indeed I can connect three FP i series and put them on the same CAN-bus without causing any shorts or similar, but I can't get the communication to work reliably. At startup all three responds and sets commanded voltage but after 10s (time it takes for FP to fall back to default) two of the three FP falls back to default. If I make a GND connection between a FP and my CAN tranciever, that FP responds to commands.
Also when using three FP it is always the one in the middle that take commands and the other two that loses them after startup.

My conclusion is that there are some kind of capacitive coupling that makes the FP-CAN lose there ground reference somehow. I have no idea of how to work around this other than to use a separate CAN-tranciever for every FP in series.
Maybe each of them have their own terminator resistor and final parallel resistance is too much (low) for CAN transcievers. Can you see if they use some jumper or DIP setting to add 120R resistor in circuit?
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

arber333 wrote: Wed Mar 03, 2021 7:07 pm
bexander wrote: Wed Mar 03, 2021 4:01 pm I finally got a chance today to test this.
Indeed I can connect three FP i series and put them on the same CAN-bus without causing any shorts or similar, but I can't get the communication to work reliably. At startup all three responds and sets commanded voltage but after 10s (time it takes for FP to fall back to default) two of the three FP falls back to default. If I make a GND connection between a FP and my CAN tranciever, that FP responds to commands.
Also when using three FP it is always the one in the middle that take commands and the other two that loses them after startup.

My conclusion is that there are some kind of capacitive coupling that makes the FP-CAN lose there ground reference somehow. I have no idea of how to work around this other than to use a separate CAN-tranciever for every FP in series.
Maybe each of them have their own terminator resistor and final parallel resistance is too much (low) for CAN transcievers. Can you see if they use some jumper or DIP setting to add 120R resistor in circuit?
Good idea, but there are no dip switch or jumper as there are no internal termination resistors.
I have measured the resistance between CAN H and CAN L. With my termination resistors, in each end, I have a total resistance of 60ohm.
arber333
Posts: 3241
Joined: Mon Dec 24, 2018 1:37 pm
Location: Slovenia
Has thanked: 74 times
Been thanked: 223 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by arber333 »

bexander wrote: Thu Mar 04, 2021 6:42 am
Good idea, but there are no dip switch or jumper as there are no internal termination resistors.
I have measured the resistance between CAN H and CAN L. With my termination resistors, in each end, I have a total resistance of 60ohm.
There are other possibilities for CAN not working. I speak out of experience.
1. You may have wire pairs twisted in the reverse way of the other :). It happens i should know...
2. Length of wire from the junction to the device could be too long and inductance still appears. I suggest you use not more than 10cm cables from CAN junction to the device.
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

arber333 wrote: Thu Mar 04, 2021 9:09 am
bexander wrote: Thu Mar 04, 2021 6:42 am
Good idea, but there are no dip switch or jumper as there are no internal termination resistors.
I have measured the resistance between CAN H and CAN L. With my termination resistors, in each end, I have a total resistance of 60ohm.
There are other possibilities for CAN not working. I speak out of experience.
1. You may have wire pairs twisted in the reverse way of the other :). It happens i should know...
2. Length of wire from the junction to the device could be too long and inductance still appears. I suggest you use not more than 10cm cables from CAN junction to the device.
I did not know that the direction of the twisted wires mattered? Does it over very short distances as well?
I tried this over a crude test setup. I use slow speed (125kbit).
I never had similar problems due to this. I do however know that the CAN signal usually need a gnd reference.
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

I can confirm that using 6pc of Flatpack 2 HE in series to charge a 84S Leaf cell pack with 18kW works.
User avatar
johu
Site Admin
Posts: 5684
Joined: Thu Nov 08, 2018 10:52 pm
Location: Kassel/Germany
Has thanked: 153 times
Been thanked: 960 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by johu »

With common GND?
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

No, CAN-bus for FP is done by using 6 isolated CAN-buses with respective "gnd" connected to the negative DC-output of each FP.
If not done like this I'm unable to get reliable communication.
User avatar
geduxaz
Posts: 125
Joined: Wed Jun 23, 2021 7:00 am
Has thanked: 3 times

Re: Communicate and control Eltek Flatpack 2 HE

Post by geduxaz »

I have some pieces of these FP2 if someone will be interested. Also SmartPack controllers with/without web interface.
I am going same way to make a charger for 80s pack.
User avatar
geduxaz
Posts: 125
Joined: Wed Jun 23, 2021 7:00 am
Has thanked: 3 times

Re: Communicate and control Eltek Flatpack 2 HE

Post by geduxaz »

Maybe it will be useful. In Eltek power systems/cabinets, gnd are connected to Positive leads (positive is grounded). Negative, fused, goes to load.
Also flatpacks have no option on resistors. Main can bus network is terminated by two resistors.
Attachments
Screenshot_20220218_223447.jpg
User avatar
geduxaz
Posts: 125
Joined: Wed Jun 23, 2021 7:00 am
Has thanked: 3 times

Re: Communicate and control Eltek Flatpack 2 HE

Post by geduxaz »

System manual
Attachments
smartpack.pdf
(668.58 KiB) Downloaded 677 times
ionutzu
Posts: 1
Joined: Wed Sep 14, 2022 2:07 am

Re: Communicate and control Eltek Flatpack 2 HE

Post by ionutzu »

Hey there, I have a flatpack2 (non-HE) 48v/2000.

I'm able to send a login message to 0x05004804 with the serial number successfully. After login, for 5 seconds I see messages come from 05014004 with status.

I'm also able to send messages to 0x05019C00 with data (0x29, 0x15, 0x00, 0xC0, 0x12) and 0x05FF4004 (or 0x05FF4005) with data (0x2C, 0x01, 0xC0, 0x12, 0xC0, 0x12, 0x24, 0x13) to set voltage to 48V. For 5 seconds, the voltage sticks to 48V but it returns to 53.5V after that. I send all messages together, login, set default, and set voltage.

I can't get it to stick to 48V however... any idea why? Thank you for your help.
aairon
Posts: 3
Joined: Mon Nov 28, 2022 2:39 am
Has thanked: 4 times

Re: Communicate and control Eltek Flatpack 2 HE

Post by aairon »

bexander wrote: Sun Jun 27, 2021 1:43 pm No, CAN-bus for FP is done by using 6 isolated CAN-buses with respective "gnd" connected to the negative DC-output of each FP.
If not done like this I'm unable to get reliable communication.
Hello I'm sorry to bother, but I am an old ham radio operator and retired marine electronic tech.
I have one of these supply's and I have a usb to can bus cable.
I'm wondering if you are using a terminal program to communicate with the Elpak?
I can pretty much understand all of it, but how you are actually connected and communicating.
Any help would be greatly appreciated. Aaron AA7IS
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

ionutzu wrote: Wed Sep 14, 2022 2:08 am Hey there, I have a flatpack2 (non-HE) 48v/2000.

I'm able to send a login message to 0x05004804 with the serial number successfully. After login, for 5 seconds I see messages come from 05014004 with status.

I'm also able to send messages to 0x05019C00 with data (0x29, 0x15, 0x00, 0xC0, 0x12) and 0x05FF4004 (or 0x05FF4005) with data (0x2C, 0x01, 0xC0, 0x12, 0xC0, 0x12, 0x24, 0x13) to set voltage to 48V. For 5 seconds, the voltage sticks to 48V but it returns to 53.5V after that. I send all messages together, login, set default, and set voltage.

I can't get it to stick to 48V however... any idea why? Thank you for your help.
Sorry, totaly missed your post. :(

You need to send at least 0x05FF4004 (or 05) repeatedly after login is complete.
In my case I send login message (0x05004804) with serial number followed by 0x05FF4004 with desired data every 1 second.

To set default voltage I send login followed by 0x05019C00 with data as per your post for 48,00V then power off FP. When restarted it should go to defaut voltage of 48,00V after walk-in is complete (should take approx 5s or 60s).
aairon
Posts: 3
Joined: Mon Nov 28, 2022 2:39 am
Has thanked: 4 times

Re: Communicate and control Eltek Flatpack 2 HE

Post by aairon »

bexander wrote: Mon Nov 28, 2022 6:20 pm Sorry, totaly missed your post. :(

You need to send at least 0x05FF4004 (or 05) repeatedly after login is complete.
In my case I send login message (0x05004804) with serial number followed by 0x05FF4004 with desired data every 1 second.

To set default voltage I send login followed by 0x05019C00 with data as per your post for 48,00V then power off FP. When restarted it should go to defaut voltage of 48,00V after walk-in is complete (should take approx 5s or 60s).
Hello are you using a terminal program like putty to communicate with the Eltek ?
I have a usb to Can converter.
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: Communicate and control Eltek Flatpack 2 HE

Post by bexander »

aairon wrote: Mon Nov 28, 2022 2:57 am Hello I'm sorry to bother, but I am an old ham radio operator and retired marine electronic tech.
I have one of these supply's and I have a usb to can bus cable.
I'm wondering if you are using a terminal program to communicate with the Elpak?
I can pretty much understand all of it, but how you are actually connected and communicating.
Any help would be greatly appreciated. Aaron AA7IS
I'm using an Atmega328p (Arduino Uno, Mini, Nano) together with CAN controller and CAN tranciever. Some code is also required to make the micro controller do what I want.

It is very much possible to use a terminal program or CAN-program to send required messages however I have not personally done it. SavvyCAN perhaps can communicate with your usb to CAN cable? That program should be capable to keep communitation running with the FlatPack.

What kind of usb to CAN cable do you have?
User avatar
johu
Site Admin
Posts: 5684
Joined: Thu Nov 08, 2018 10:52 pm
Location: Kassel/Germany
Has thanked: 153 times
Been thanked: 960 times
Contact:

Re: Communicate and control Eltek Flatpack 2 HE

Post by johu »

Is it possible to put flatpack into some kind of sleep mode over CAN?
I'm using it for my solar storage and it consumes around 15W when idling - that is 130 kWh per year!

So I'd like to turn it off during the night. Otherwise I'll just cut power with a relay.

BTW, got some Flatpack python code here https://github.com/jsphuebner/esp-egyco ... charger.py
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
Post Reply