mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
- Added support for Dual CAN Bus Using MCP2515
- Added support for PWM on the contactors - Added contactor on/off conrol from the inverter - Moved #defines from Software.ino.ino to config.h to be able to use #ifdefs in SolAX-CAN.cpp Author identity unknown
This commit is contained in:
parent
42023b972e
commit
452a9ab439
4 changed files with 330 additions and 150 deletions
|
@ -1,35 +1,61 @@
|
||||||
#include "SOLAX-CAN.h"
|
#include "SOLAX-CAN.h"
|
||||||
#include "ESP32CAN.h"
|
|
||||||
#include "CAN_config.h"
|
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was sent
|
|
||||||
static const int interval100ms = 100; // interval (ms) at which send CAN Messages
|
|
||||||
static int temp = 0; //Temporary variable used for bitshifting
|
|
||||||
static int max_charge_rate_amp = 0;
|
static int max_charge_rate_amp = 0;
|
||||||
static int max_discharge_rate_amp = 0;
|
static int max_discharge_rate_amp = 0;
|
||||||
|
static int temperature_average = 0;
|
||||||
|
static int STATE = BATTERY_ANNOUNCE;
|
||||||
|
static unsigned long LastFrameTime = 0;
|
||||||
|
static unsigned short BatteryModuleFirmware = 2;
|
||||||
|
|
||||||
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
|
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
|
||||||
|
|
||||||
CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x8A, 0xF, 0x52, 0xC, 0xCD, 0x0, 0x5E, 0x1}}; //BMS_Limits
|
CAN_frame_t SOLAX_1801 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1801,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||||
CAN_frame_t SOLAX_1873 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1873,.data = {0x6D, 0xD, 0x0, 0x0, 0x5D, 0x0, 0xA3, 0x1}}; //BMS_PackData
|
CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Limits
|
||||||
CAN_frame_t SOLAX_1874 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1874,.data = {0xCE, 0x0, 0xBC, 0x0, 0x29, 0x0, 0x28, 0x0}}; //BMS_CellData
|
CAN_frame_t SOLAX_1873 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1873,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackData
|
||||||
CAN_frame_t SOLAX_1875 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1875,.data = {0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3A, 0x0}}; //BMS_Status
|
CAN_frame_t SOLAX_1874 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1874,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_CellData
|
||||||
CAN_frame_t SOLAX_1876 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1876,.data = {0x0, 0x0, 0xD4, 0x0F, 0x0, 0x0, 0xC9, 0x0F}}; //BMS_PackTemps
|
CAN_frame_t SOLAX_1875 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1875,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Status
|
||||||
CAN_frame_t SOLAX_1877 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1877,.data = {0x0, 0x0, 0x0, 0x0, 0x53, 0x0, 0x1D, 0x10}};
|
CAN_frame_t SOLAX_1876 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1876,.data = {0x0, 0x0, 0xE2, 0x0C, 0x0, 0x0, 0xD7, 0x0C}}; //BMS_PackTemps
|
||||||
CAN_frame_t SOLAX_1878 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1878,.data = {0x6D, 0xD, 0x0, 0x0, 0xB0, 0x3, 0x4, 0x0}}; //BMS_PackStats
|
CAN_frame_t SOLAX_1877 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1877,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||||
CAN_frame_t SOLAX_1879 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1879,.data = {0x1, 0x8, 0x1, 0x2, 0x1, 0x2, 0x0, 0x3}};
|
CAN_frame_t SOLAX_1878 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1878,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackStats
|
||||||
CAN_frame_t SOLAX_1801 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1801,.data = {0x2, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0}};
|
CAN_frame_t SOLAX_1879 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1879,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||||
CAN_frame_t SOLAX_1881 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1881,.data = {0x0, 0x36, 0x53, 0x42, 0x4D, 0x53, 0x46, 0x41}}; // 0 6 S B M S F A
|
CAN_frame_t SOLAX_1881 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1881,.data = {0x00, 0x36, 0x53, 0x42, 0x4D, 0x53, 0x46, 0x41}}; // E.g.: 0 6 S B M S F A
|
||||||
CAN_frame_t SOLAX_1882 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1882,.data = {0x0, 0x32, 0x33, 0x41, 0x42, 0x30, 0x35, 0x32}}; // 0 2 3 A B 0 5 2
|
CAN_frame_t SOLAX_1882 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1882,.data = {0x00, 0x32, 0x33, 0x41, 0x42, 0x30, 0x35, 0x32}}; // E.g.: 0 2 3 A B 0 5 2
|
||||||
CAN_frame_t SOLAX_100A001 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x100A001,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
CAN_frame_t SOLAX_100A001 = {.FIR = {.B = {.DLC = 0,.FF = CAN_frame_ext,}},.MsgID = 0x100A001,.data = {}};
|
||||||
|
|
||||||
|
// __builtin_bswap64 needed to convert to ESP32 little endian format
|
||||||
|
// Byte[4] defines the requested con-tactor state: 1 = Closed , 0 = Open
|
||||||
|
#define Contactor_Open_Payload __builtin_bswap64(0x0200010000000000)
|
||||||
|
#define Contactor_Close_Payload __builtin_bswap64(0x0200010001000000)
|
||||||
|
|
||||||
|
void CAN_WriteFrame(CAN_frame_t* tx_frame)
|
||||||
|
{
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library
|
||||||
|
MCP2515Frame.id = tx_frame->MsgID;
|
||||||
|
MCP2515Frame.ext = tx_frame->FIR.B.FF;
|
||||||
|
MCP2515Frame.len = tx_frame->FIR.B.DLC;
|
||||||
|
for (uint8_t i=0 ; i<MCP2515Frame.len ; i++) {
|
||||||
|
MCP2515Frame.data[i] = tx_frame->data.u8[i];
|
||||||
|
}
|
||||||
|
can.tryToSend(MCP2515Frame);
|
||||||
|
//Serial.println("Solax CAN Frame sent in Bus 2");
|
||||||
|
#else
|
||||||
|
ESP32Can.CANWriteFrame(tx_frame);
|
||||||
|
//Serial.println("Solax CAN Frame sent in Bus 1");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void update_values_can_solax()
|
void update_values_can_solax()
|
||||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||||
|
// If not receiveing any communication from the inverter, open contactos and return to announce stage
|
||||||
//SOC (100.00%)
|
if (millis() - LastFrameTime >= SolaxTimeout)
|
||||||
temp = SOC/100; //Remove decimals, inverter takes only integer in a byte
|
{
|
||||||
SOLAX_1873.data.u8[4] = temp;
|
inverterAllowsContactorClosing = 0;
|
||||||
|
STATE = BATTERY_ANNOUNCE;
|
||||||
|
}
|
||||||
|
//Calculate the required values
|
||||||
|
temperature_average = ((temperature_max + temperature_min)/2);
|
||||||
|
|
||||||
//max_target_charge_power (30000W max)
|
//max_target_charge_power (30000W max)
|
||||||
if(SOC > 9999) //99.99%
|
if(SOC > 9999) //99.99%
|
||||||
|
@ -47,12 +73,6 @@ void update_values_can_solax()
|
||||||
max_charge_rate_amp = (max_target_charge_power/(battery_voltage*0.1)); // P/U = I
|
max_charge_rate_amp = (max_target_charge_power/(battery_voltage*0.1)); // P/U = I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Increase decimal amount
|
|
||||||
max_charge_rate_amp = max_charge_rate_amp*10;
|
|
||||||
|
|
||||||
//Write the calculated charge rate to the CAN message
|
|
||||||
SOLAX_1872.data.u8[4] = (uint8_t) max_charge_rate_amp; //TODO, test that values are OK
|
|
||||||
SOLAX_1872.data.u8[5] = (max_charge_rate_amp << 8);
|
|
||||||
|
|
||||||
//max_target_discharge_power (30000W max)
|
//max_target_discharge_power (30000W max)
|
||||||
if(SOC < 100) //1.00%
|
if(SOC < 100) //1.00%
|
||||||
|
@ -70,47 +90,129 @@ void update_values_can_solax()
|
||||||
max_discharge_rate_amp = (max_target_discharge_power/(battery_voltage*0.1)); // P/U = I
|
max_discharge_rate_amp = (max_target_discharge_power/(battery_voltage*0.1)); // P/U = I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Increase decimal amount
|
|
||||||
max_discharge_rate_amp = max_discharge_rate_amp*10;
|
|
||||||
|
|
||||||
//Write the calculated charge rate to the CAN message
|
//Put the values into the CAN messages
|
||||||
SOLAX_1872.data.u8[6] = (uint8_t) max_discharge_rate_amp; //TODO, test that values are OK
|
|
||||||
SOLAX_1872.data.u8[7] = (max_discharge_rate_amp << 8);
|
|
||||||
|
|
||||||
//Todo (ranked in priority)
|
//BMS_Limits
|
||||||
//Add current
|
SOLAX_1872.data.u8[0] = (uint8_t) max_volt_solax_can; //Todo, scaling OK?
|
||||||
//Add voltage
|
SOLAX_1872.data.u8[1] = (max_volt_solax_can >> 8);
|
||||||
//Add remaining kWh
|
SOLAX_1872.data.u8[2] = (uint8_t) min_volt_solax_can; //Todo, scaling OK?
|
||||||
//Add temperature
|
SOLAX_1872.data.u8[3] = (min_volt_solax_can >> 8);
|
||||||
//Add cell voltages
|
SOLAX_1872.data.u8[4] = (uint8_t) (max_charge_rate_amp*10); //Todo, scaling OK?
|
||||||
//Add pack voltage min/max for alarms
|
SOLAX_1872.data.u8[5] = ((max_charge_rate_amp*10) >> 8);
|
||||||
//Add cell voltage min/max for alarms
|
SOLAX_1872.data.u8[6] = (uint8_t) (max_discharge_rate_amp*10); //Todo, scaling OK?
|
||||||
|
SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp*10) >> 8);
|
||||||
|
|
||||||
|
//BMS_PackData
|
||||||
|
SOLAX_1873.data.u8[0] = (uint8_t) battery_voltage; //Todo, scaling OK?
|
||||||
|
SOLAX_1873.data.u8[1] = (battery_voltage >> 8);
|
||||||
|
SOLAX_1873.data.u8[2] = (int8_t) stat_batt_power; //Todo, scaling OK? Signed?
|
||||||
|
SOLAX_1873.data.u8[3] = (stat_batt_power >> 8);
|
||||||
|
SOLAX_1873.data.u8[4] = (uint8_t) (SOC/100); //SOC (100.00%)
|
||||||
|
//SOLAX_1873.data.u8[5] = //Seems like this is not required? Or shall we put SOC decimals here?
|
||||||
|
SOLAX_1873.data.u8[6] = (uint8_t) (remaining_capacity_Wh/100); //Todo, scaling OK?
|
||||||
|
SOLAX_1873.data.u8[7] = ((remaining_capacity_Wh/100) >> 8);
|
||||||
|
|
||||||
|
//BMS_CellData
|
||||||
|
SOLAX_1874.data.u8[0] = (uint8_t) temperature_max;
|
||||||
|
SOLAX_1874.data.u8[1] = (temperature_max >> 8);
|
||||||
|
SOLAX_1874.data.u8[2] = (uint8_t) temperature_min;
|
||||||
|
SOLAX_1874.data.u8[3] = (temperature_min >> 8);
|
||||||
|
SOLAX_1874.data.u8[4] = (uint8_t) (cell_max_voltage); //Todo, scaling OK? Supposed to be alarm trigger absolute cell max?
|
||||||
|
SOLAX_1874.data.u8[5] = (cell_max_voltage >> 8);
|
||||||
|
SOLAX_1874.data.u8[6] = (uint8_t) (cell_min_voltage); //Todo, scaling OK? Supposed to be alarm trigger absolute cell min?
|
||||||
|
SOLAX_1874.data.u8[7] = (cell_min_voltage >> 8);
|
||||||
|
|
||||||
|
//BMS_Status
|
||||||
|
SOLAX_1875.data.u8[0] = (uint8_t) temperature_average;
|
||||||
|
SOLAX_1875.data.u8[1] = (temperature_average >> 8);
|
||||||
|
SOLAX_1875.data.u8[2] = (uint8_t) 0; // Number of slave batteries
|
||||||
|
SOLAX_1875.data.u8[4] = (uint8_t) 0; // Contactor Status 0=off, 1=on.
|
||||||
|
|
||||||
|
//BMS_PackTemps (strange name, since it has voltages?)
|
||||||
|
SOLAX_1876.data.u8[2] = (uint8_t) cell_max_voltage; //Todo, scaling OK?
|
||||||
|
SOLAX_1876.data.u8[3] = (cell_min_voltage >> 8);
|
||||||
|
|
||||||
|
SOLAX_1876.data.u8[6] = (uint8_t) cell_min_voltage; //Todo, scaling OK?
|
||||||
|
SOLAX_1876.data.u8[7] = (cell_min_voltage >> 8);
|
||||||
|
|
||||||
|
//Unknown
|
||||||
|
SOLAX_1877.data.u8[4] = (uint8_t) 0x53;
|
||||||
|
SOLAX_1877.data.u8[6] = (BatteryModuleFirmware >> 8);
|
||||||
|
SOLAX_1877.data.u8[7] = (uint8_t) BatteryModuleFirmware;
|
||||||
|
|
||||||
|
//BMS_PackStats
|
||||||
|
SOLAX_1878.data.u8[0] = (uint8_t) (battery_voltage/10); //TODO, should this be max or current voltage?
|
||||||
|
SOLAX_1878.data.u8[1] = ((battery_voltage/10) >> 8);
|
||||||
|
|
||||||
|
SOLAX_1878.data.u8[4] = (uint8_t) capacity_Wh; //TODO, scaling OK?
|
||||||
|
SOLAX_1878.data.u8[5] = (capacity_Wh >> 8);
|
||||||
|
|
||||||
|
// BMS_Answer
|
||||||
|
SOLAX_1801.data.u8[0] = 2;
|
||||||
|
SOLAX_1801.data.u8[2] = 1;
|
||||||
|
SOLAX_1801.data.u8[4] = 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_can_solax()
|
void send_can_solax() {
|
||||||
{
|
// Deprecated - All transmissions should be initiated in response to inverter polling.
|
||||||
unsigned long currentMillis = millis();
|
|
||||||
// Send 100ms CAN Message
|
|
||||||
if (currentMillis - previousMillis100ms >= interval100ms)
|
|
||||||
{
|
|
||||||
previousMillis100ms = currentMillis;
|
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1872);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1873);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1874);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1875);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1876);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1877);
|
|
||||||
ESP32Can.CANWriteFrame(&SOLAX_1878);
|
|
||||||
|
|
||||||
//Todo, how often should the messages be sent? And the other messages, only on bootup?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void receive_can_solax(CAN_frame_t rx_frame)
|
void receive_can_solax(CAN_frame_t rx_frame)
|
||||||
{
|
{
|
||||||
//Serial.println("Inverter sending CAN message");
|
if (rx_frame.MsgID == 0x1871) {
|
||||||
//0x1871 [0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
|
LastFrameTime = millis();
|
||||||
//Todo, should we respond with something once the inverter sends a message?
|
switch(STATE)
|
||||||
|
{
|
||||||
|
case(BATTERY_ANNOUNCE):
|
||||||
|
Serial.println("Solax Battery State: Announce");
|
||||||
|
inverterAllowsContactorClosing = 0;
|
||||||
|
SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
|
||||||
|
CAN_WriteFrame(&SOLAX_100A001); //BMS Announce
|
||||||
|
CAN_WriteFrame(&SOLAX_1872);
|
||||||
|
CAN_WriteFrame(&SOLAX_1873);
|
||||||
|
CAN_WriteFrame(&SOLAX_1874);
|
||||||
|
CAN_WriteFrame(&SOLAX_1875);
|
||||||
|
CAN_WriteFrame(&SOLAX_1876);
|
||||||
|
CAN_WriteFrame(&SOLAX_1877);
|
||||||
|
CAN_WriteFrame(&SOLAX_1878);
|
||||||
|
// Message from the inverter to proceed to contactor closing
|
||||||
|
// Byte 4 changes from 0 to 1
|
||||||
|
if (rx_frame.data.u64 == Contactor_Close_Payload)
|
||||||
|
STATE = WAITING_FOR_CONTACTOR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case(WAITING_FOR_CONTACTOR):
|
||||||
|
SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
|
||||||
|
CAN_WriteFrame(&SOLAX_1872);
|
||||||
|
CAN_WriteFrame(&SOLAX_1873);
|
||||||
|
CAN_WriteFrame(&SOLAX_1874);
|
||||||
|
CAN_WriteFrame(&SOLAX_1875);
|
||||||
|
CAN_WriteFrame(&SOLAX_1876);
|
||||||
|
CAN_WriteFrame(&SOLAX_1877);
|
||||||
|
CAN_WriteFrame(&SOLAX_1878);
|
||||||
|
CAN_WriteFrame(&SOLAX_1801); // Announce that the battery will be connected
|
||||||
|
STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State
|
||||||
|
Serial.println("Solax Battery State: Contactor Closed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case(CONTACTOR_CLOSED):
|
||||||
|
inverterAllowsContactorClosing = 1;
|
||||||
|
SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
|
||||||
|
CAN_WriteFrame(&SOLAX_1872);
|
||||||
|
CAN_WriteFrame(&SOLAX_1873);
|
||||||
|
CAN_WriteFrame(&SOLAX_1874);
|
||||||
|
CAN_WriteFrame(&SOLAX_1875);
|
||||||
|
CAN_WriteFrame(&SOLAX_1876);
|
||||||
|
CAN_WriteFrame(&SOLAX_1877);
|
||||||
|
CAN_WriteFrame(&SOLAX_1878);
|
||||||
|
// Message from the inverter to open contactor
|
||||||
|
// Byte 4 changes from 1 to 0
|
||||||
|
if (rx_frame.data.u64 == Contactor_Open_Payload)
|
||||||
|
STATE = BATTERY_ANNOUNCE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,11 @@
|
||||||
#define SOLAX_CAN_H
|
#define SOLAX_CAN_H
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "ESP32CAN.h"
|
#include "ESP32CAN.h"
|
||||||
|
#include "config.h"
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
#include "ACAN2515.h"
|
||||||
|
extern ACAN2515 can;
|
||||||
|
#endif
|
||||||
|
|
||||||
extern uint16_t SOC;
|
extern uint16_t SOC;
|
||||||
extern uint16_t StateOfHealth;
|
extern uint16_t StateOfHealth;
|
||||||
|
@ -17,15 +22,21 @@ extern uint16_t stat_batt_power;
|
||||||
extern uint16_t temperature_min;
|
extern uint16_t temperature_min;
|
||||||
extern uint16_t temperature_max;
|
extern uint16_t temperature_max;
|
||||||
extern uint16_t CANerror;
|
extern uint16_t CANerror;
|
||||||
extern uint16_t min_volt_byd_can;
|
extern uint16_t min_volt_solax_can;
|
||||||
extern uint16_t max_volt_byd_can;
|
extern uint16_t max_volt_solax_can;
|
||||||
// Definitions for BMS status
|
extern uint16_t cell_max_voltage;
|
||||||
#define STANDBY 0
|
extern uint16_t cell_min_voltage;
|
||||||
#define INACTIVE 1
|
extern uint8_t inverterAllowsContactorClosing;
|
||||||
#define DARKSTART 2
|
|
||||||
#define ACTIVE 3
|
// Timeout in milliseconds
|
||||||
#define FAULT 4
|
#define SolaxTimeout 2000
|
||||||
#define UPDATING 5
|
|
||||||
|
//SOLAX BMS States Definition
|
||||||
|
#define BATTERY_ANNOUNCE 0
|
||||||
|
#define WAITING_FOR_CONTACTOR 1
|
||||||
|
#define CONTACTOR_CLOSED 2
|
||||||
|
#define FAULT 3
|
||||||
|
#define UPDATING_FW 4
|
||||||
|
|
||||||
void update_values_can_solax();
|
void update_values_can_solax();
|
||||||
void send_can_solax();
|
void send_can_solax();
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
/* Select battery used */
|
|
||||||
#define BATTERY_TYPE_LEAF // See NISSAN-LEAF-BATTERY.h for more LEAF battery settings
|
|
||||||
//#define TESLA_MODEL_3_BATTERY // See TESLA-MODEL-3-BATTERY.h for more Tesla battery settings
|
|
||||||
//#define RENAULT_ZOE_BATTERY // See RENAULT-ZOE-BATTERY.h for more Zoe battery settings
|
|
||||||
//#define BMW_I3_BATTERY // See BMW-I3-BATTERY.h for more i3 battery settings
|
|
||||||
//#define IMIEV_ION_CZERO_BATTERY // See IMIEV-CZERO-ION-BATTERY.h for more triplet battery settings
|
|
||||||
//#define KIA_HYUNDAI_64_BATTERY // See KIA-HYUNDAI-64-BATTERY.h for more battery settings
|
|
||||||
//#define CHADEMO // See CHADEMO.h for more Chademo related settings
|
|
||||||
|
|
||||||
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
|
|
||||||
#define MODBUS_BYD //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
|
||||||
//#define CAN_BYD //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
|
|
||||||
//#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
|
|
||||||
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
|
||||||
|
|
||||||
/* Do not change any code below this line unless you are sure what you are doing */
|
/* Do not change any code below this line unless you are sure what you are doing */
|
||||||
/* Only change battery specific settings and limits in their respective .h files */
|
/* Only change battery specific settings and limits in their respective .h files */
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "HardwareSerial.h"
|
#include "HardwareSerial.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -27,12 +11,21 @@
|
||||||
#include "Adafruit_NeoPixel.h"
|
#include "Adafruit_NeoPixel.h"
|
||||||
#include "BATTERIES.h"
|
#include "BATTERIES.h"
|
||||||
#include "INVERTERS.h"
|
#include "INVERTERS.h"
|
||||||
|
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
#include <ACAN2515.h>
|
||||||
|
static const uint32_t QUARTZ_FREQUENCY = 8UL * 1000UL * 1000UL ; // 8 MHz
|
||||||
|
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
|
||||||
|
static ACAN2515_Buffer16 gBuffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
//CAN parameters
|
//CAN parameters
|
||||||
|
#define MAX_CAN_FAILURES 5000 //Amount of malformed CAN messages to allow before raising a warning
|
||||||
CAN_device_t CAN_cfg; // CAN Config
|
CAN_device_t CAN_cfg; // CAN Config
|
||||||
const int rx_queue_size = 10; // Receive Queue size
|
const int rx_queue_size = 10; // Receive Queue size
|
||||||
|
|
||||||
//Interval settings
|
//Interval settings
|
||||||
const int intervalInverterTask = 4800; //Interval at which to refresh modbus registers / inverter values
|
const int intervalInverterTask = 800; //Interval at which to refresh modbus registers / inverter values
|
||||||
const int interval10 = 10; //Interval for 10ms tasks
|
const int interval10 = 10; //Interval for 10ms tasks
|
||||||
unsigned long previousMillis10ms = 50;
|
unsigned long previousMillis10ms = 50;
|
||||||
|
|
||||||
|
@ -72,6 +65,8 @@ uint16_t temperature_min = 60; //reads from battery later
|
||||||
uint16_t bms_char_dis_status; //0 idle, 1 discharging, 2, charging
|
uint16_t bms_char_dis_status; //0 idle, 1 discharging, 2, charging
|
||||||
uint16_t bms_status = ACTIVE; //ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
|
uint16_t bms_status = ACTIVE; //ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
|
||||||
uint16_t stat_batt_power = 0; //power going in/out of battery
|
uint16_t stat_batt_power = 0; //power going in/out of battery
|
||||||
|
uint16_t cell_max_voltage = 3700; //Stores the highest cell voltage value in the system
|
||||||
|
uint16_t cell_min_voltage = 3700; //Stores the minimum cell voltage value in the system
|
||||||
|
|
||||||
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
|
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
|
||||||
ModbusServerRTU MBserver(Serial2, 2000);
|
ModbusServerRTU MBserver(Serial2, 2000);
|
||||||
|
@ -83,12 +78,12 @@ ModbusServerRTU MBserver(Serial2, 2000);
|
||||||
Adafruit_NeoPixel pixels(1, WS2812_PIN, NEO_GRB + NEO_KHZ800);
|
Adafruit_NeoPixel pixels(1, WS2812_PIN, NEO_GRB + NEO_KHZ800);
|
||||||
static uint8_t brightness = 0;
|
static uint8_t brightness = 0;
|
||||||
static bool rampUp = true;
|
static bool rampUp = true;
|
||||||
const uint8_t maxBrightness = 255;
|
const uint8_t maxBrightness = 100;
|
||||||
uint8_t LEDcolor = GREEN;
|
uint8_t LEDcolor = GREEN;
|
||||||
|
|
||||||
//Contactor parameters
|
//Contactor parameters
|
||||||
enum State {
|
enum State {
|
||||||
WAITING_FOR_BATTERY,
|
DISCONNECTED,
|
||||||
PRECHARGE,
|
PRECHARGE,
|
||||||
NEGATIVE,
|
NEGATIVE,
|
||||||
POSITIVE,
|
POSITIVE,
|
||||||
|
@ -96,19 +91,32 @@ enum State {
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
SHUTDOWN_REQUESTED
|
SHUTDOWN_REQUESTED
|
||||||
};
|
};
|
||||||
State contactorStatus = WAITING_FOR_BATTERY;
|
State contactorStatus = DISCONNECTED;
|
||||||
|
|
||||||
#define PRECHARGE_TIME_MS 160
|
#define PRECHARGE_TIME_MS 160
|
||||||
#define NEGATIVE_CONTACTOR_TIME_MS 1000
|
#define NEGATIVE_CONTACTOR_TIME_MS 1000
|
||||||
#define POSITIVE_CONTACTOR_TIME_MS 2000
|
#define POSITIVE_CONTACTOR_TIME_MS 2000
|
||||||
|
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
|
||||||
|
#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100%
|
||||||
|
#define PWM_Hold_Duty 250
|
||||||
|
#define POSITIVE_PWM_Ch 0
|
||||||
|
#define NEGATIVE_PWM_Ch 1
|
||||||
unsigned long prechargeStartTime = 0;
|
unsigned long prechargeStartTime = 0;
|
||||||
unsigned long negativeStartTime = 0;
|
unsigned long negativeStartTime = 0;
|
||||||
unsigned long timeSpentInFaultedMode = 0;
|
unsigned long timeSpentInFaultedMode = 0;
|
||||||
uint8_t batteryAllowsContactorClosing = 0;
|
uint8_t batteryAllowsContactorClosing = 0;
|
||||||
uint8_t inverterAllowsContactorClosing = 1; //Startup with always allowing closing from inverter side. Only a few inverters disallow it
|
uint8_t inverterAllowsContactorClosing = 0;
|
||||||
|
|
||||||
// Setup() - initialization happens here
|
// Setup() - initialization happens here
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
// Init Serial monitor
|
||||||
|
Serial.begin(9600);
|
||||||
|
while (!Serial)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Serial.println("__ OK __");
|
||||||
|
|
||||||
//CAN pins
|
//CAN pins
|
||||||
pinMode(CAN_SE_PIN, OUTPUT);
|
pinMode(CAN_SE_PIN, OUTPUT);
|
||||||
digitalWrite(CAN_SE_PIN, LOW);
|
digitalWrite(CAN_SE_PIN, LOW);
|
||||||
|
@ -120,20 +128,29 @@ void setup()
|
||||||
ESP32Can.CANInit();
|
ESP32Can.CANInit();
|
||||||
Serial.println(CAN_cfg.speed);
|
Serial.println(CAN_cfg.speed);
|
||||||
|
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
gBuffer.initWithSize(25);
|
||||||
|
SPI.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
|
||||||
|
Serial.println ("Configure ACAN2515") ;
|
||||||
|
ACAN2515Settings settings (QUARTZ_FREQUENCY, 500UL * 1000UL) ; // CAN bit rate 500 kb/s
|
||||||
|
settings.mRequestedMode = ACAN2515Settings::NormalMode ; // Select loopback mode
|
||||||
|
can.begin (settings, [] { can.isr (); });
|
||||||
|
#endif
|
||||||
|
|
||||||
//Init contactor pins
|
//Init contactor pins
|
||||||
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
|
|
||||||
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
ledcAttachPin(POSITIVE_CONTACTOR_PIN, POSITIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
||||||
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
ledcAttachPin(NEGATIVE_CONTACTOR_PIN, NEGATIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
||||||
digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW);
|
|
||||||
|
ledcSetup(POSITIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
||||||
|
ledcSetup(NEGATIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
||||||
|
|
||||||
|
ledcWrite(POSITIVE_PWM_Ch, 0); // Set Positive PWM to 0%
|
||||||
|
ledcWrite(NEGATIVE_PWM_Ch, 0); // Set Negative PWM to 0%
|
||||||
|
|
||||||
pinMode(PRECHARGE_PIN, OUTPUT);
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
digitalWrite(PRECHARGE_PIN, LOW);
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
|
||||||
// Init Serial monitor
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Serial.println("__ OK __");
|
|
||||||
|
|
||||||
//Set up Modbus RTU Server
|
//Set up Modbus RTU Server
|
||||||
pinMode(RS485_EN_PIN, OUTPUT);
|
pinMode(RS485_EN_PIN, OUTPUT);
|
||||||
|
@ -166,18 +183,11 @@ void setup()
|
||||||
pixels.setPixelColor(0, pixels.Color(0, 0, 255)); // Blue LED full brightness while battery and CAN is starting.
|
pixels.setPixelColor(0, pixels.Color(0, 0, 255)); // Blue LED full brightness while battery and CAN is starting.
|
||||||
pixels.show(); // Incase of crash due to CAN polarity / termination, LED will remain BLUE
|
pixels.show(); // Incase of crash due to CAN polarity / termination, LED will remain BLUE
|
||||||
|
|
||||||
//Inverter Setup
|
|
||||||
#ifdef SOLAX_CAN
|
|
||||||
inverterAllowsContactorClosing = 0; //The inverter needs to allow first!
|
|
||||||
Serial.println("SOLAX CAN protocol selected");
|
|
||||||
#endif
|
|
||||||
#ifdef MODBUS_BYD
|
|
||||||
Serial.println("BYD Modbus RTU protocol selected");
|
|
||||||
#endif
|
|
||||||
#ifdef CAN_BYD
|
|
||||||
Serial.println("BYD CAN protocol selected");
|
|
||||||
#endif
|
|
||||||
//Inform user what setup is used
|
//Inform user what setup is used
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
Serial.println("Dual CAN Bus (ESP32+MCP2515) selected");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BATTERY_TYPE_LEAF
|
#ifdef BATTERY_TYPE_LEAF
|
||||||
Serial.println("Nissan LEAF battery selected");
|
Serial.println("Nissan LEAF battery selected");
|
||||||
#endif
|
#endif
|
||||||
|
@ -187,15 +197,9 @@ void setup()
|
||||||
#ifdef RENAULT_ZOE_BATTERY
|
#ifdef RENAULT_ZOE_BATTERY
|
||||||
Serial.println("Renault Zoe / Kangoo battery selected");
|
Serial.println("Renault Zoe / Kangoo battery selected");
|
||||||
#endif
|
#endif
|
||||||
#ifdef BMW_I3_BATTERY
|
|
||||||
Serial.println("BMW i3 battery selected");
|
|
||||||
#endif
|
|
||||||
#ifdef IMIEV_ION_CZERO_BATTERY
|
#ifdef IMIEV_ION_CZERO_BATTERY
|
||||||
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
|
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
|
||||||
#endif
|
#endif
|
||||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
|
||||||
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform main program functions
|
// perform main program functions
|
||||||
|
@ -203,6 +207,10 @@ void loop()
|
||||||
{
|
{
|
||||||
handle_can(); //runs as fast as possible, handle CAN routines
|
handle_can(); //runs as fast as possible, handle CAN routines
|
||||||
|
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
handle_can2();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (millis() - previousMillis10ms >= interval10) //every 10ms
|
if (millis() - previousMillis10ms >= interval10) //every 10ms
|
||||||
{
|
{
|
||||||
previousMillis10ms = millis();
|
previousMillis10ms = millis();
|
||||||
|
@ -235,15 +243,9 @@ void handle_can()
|
||||||
#ifdef RENAULT_ZOE_BATTERY
|
#ifdef RENAULT_ZOE_BATTERY
|
||||||
receive_can_zoe_battery(rx_frame);
|
receive_can_zoe_battery(rx_frame);
|
||||||
#endif
|
#endif
|
||||||
#ifdef BMW_I3_BATTERY
|
|
||||||
receive_can_i3_battery(rx_frame);
|
|
||||||
#endif
|
|
||||||
#ifdef IMIEV_ION_CZERO_BATTERY
|
#ifdef IMIEV_ION_CZERO_BATTERY
|
||||||
receive_can_imiev_battery(rx_frame);
|
receive_can_imiev_battery(rx_frame);
|
||||||
#endif
|
#endif
|
||||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
|
||||||
receive_can_kiaHyundai_64_battery(rx_frame);
|
|
||||||
#endif
|
|
||||||
#ifdef CAN_BYD
|
#ifdef CAN_BYD
|
||||||
receive_can_byd(rx_frame);
|
receive_can_byd(rx_frame);
|
||||||
#endif
|
#endif
|
||||||
|
@ -258,9 +260,9 @@ void handle_can()
|
||||||
#ifdef SOLAX_CAN
|
#ifdef SOLAX_CAN
|
||||||
receive_can_solax(rx_frame);
|
receive_can_solax(rx_frame);
|
||||||
#endif
|
#endif
|
||||||
#ifdef PYLON_CAN
|
#ifdef PYLON_CAN
|
||||||
receive_can_pylon(rx_frame);
|
receive_can_pylon(rx_frame);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//When we are done checking if a CAN message has arrived, we can focus on sending CAN messages
|
//When we are done checking if a CAN message has arrived, we can focus on sending CAN messages
|
||||||
|
@ -281,40 +283,76 @@ void handle_can()
|
||||||
#ifdef RENAULT_ZOE_BATTERY
|
#ifdef RENAULT_ZOE_BATTERY
|
||||||
send_can_zoe_battery();
|
send_can_zoe_battery();
|
||||||
#endif
|
#endif
|
||||||
#ifdef BMW_I3_BATTERY
|
|
||||||
send_can_i3_battery();
|
|
||||||
#endif
|
|
||||||
#ifdef IMIEV_ION_CZERO_BATTERY
|
#ifdef IMIEV_ION_CZERO_BATTERY
|
||||||
send_can_imiev_battery();
|
send_can_imiev_battery();
|
||||||
#endif
|
#endif
|
||||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
|
||||||
send_can_kiaHyundai_64_battery();
|
|
||||||
#endif
|
|
||||||
#ifdef CHADEMO
|
#ifdef CHADEMO
|
||||||
send_can_chademo_battery();
|
send_can_chademo_battery();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DUAL_CAN
|
||||||
|
void handle_can2()
|
||||||
|
{ //This function is similar to handle_can, but just takes care of inverters in the 2nd bus.
|
||||||
|
//Depending on which inverter is selected, we forward this to their respective CAN routines
|
||||||
|
CAN_frame_t rx_frame2; //Struct with ESP32Can library format, compatible with the rest of the program
|
||||||
|
CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use thw MCP2515 library
|
||||||
|
|
||||||
|
if ( can.available() )
|
||||||
|
{
|
||||||
|
can.receive(MCP2515Frame);
|
||||||
|
|
||||||
|
rx_frame2.MsgID = MCP2515Frame.id;
|
||||||
|
rx_frame2.FIR.B.FF = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
|
||||||
|
rx_frame2.FIR.B.RTR = MCP2515Frame.rtr ? CAN_RTR : CAN_no_RTR;
|
||||||
|
rx_frame2.FIR.B.DLC = MCP2515Frame.len;
|
||||||
|
for (uint8_t i=0 ; i<MCP2515Frame.len ; i++) {
|
||||||
|
rx_frame2.data.u8[i] = MCP2515Frame.data[i] ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_frame2.FIR.B.FF == CAN_frame_std)
|
||||||
|
{
|
||||||
|
//Serial.println("New standard frame");
|
||||||
|
#ifdef CAN_BYD
|
||||||
|
receive_can_byd(rx_frame2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Serial.println("New extended frame");
|
||||||
|
#ifdef SOLAX_CAN
|
||||||
|
receive_can_solax(rx_frame2);
|
||||||
|
#endif
|
||||||
|
#ifdef PYLON_CAN
|
||||||
|
receive_can_pylon(rx_frame2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//When we are done checking if a CAN message has arrived, we can focus on sending CAN messages
|
||||||
|
//Inverter sending
|
||||||
|
#ifdef CAN_BYD
|
||||||
|
send_can_byd();
|
||||||
|
#endif
|
||||||
|
#ifdef SOLAX_CAN
|
||||||
|
send_can_solax();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void handle_inverter()
|
void handle_inverter()
|
||||||
{
|
{
|
||||||
#ifdef BATTERY_TYPE_LEAF
|
#ifdef BATTERY_TYPE_LEAF
|
||||||
update_values_leaf_battery(); //Map the values to the correct registers
|
update_values_leaf_battery(); //Map the values to the correct registers
|
||||||
#endif
|
#endif
|
||||||
#ifdef TESLA_MODEL_3_BATTERY
|
#ifdef TESLA_MODEL_3_BATTERY
|
||||||
update_values_tesla_model_3_battery(); //Map the values to the correct registers
|
update_values_tesla_model_3_battery(); //Map the values to the correct registers
|
||||||
#endif
|
#endif
|
||||||
#ifdef RENAULT_ZOE_BATTERY
|
#ifdef RENAULT_ZOE_BATTERY
|
||||||
update_values_zoe_battery(); //Map the values to the correct registers
|
update_values_zoe_battery(); //Map the values to the correct registers
|
||||||
#endif
|
#endif
|
||||||
#ifdef BMW_I3_BATTERY
|
|
||||||
update_values_i3_battery(); //Map the values to the correct registers
|
|
||||||
#endif
|
|
||||||
#ifdef IMIEV_ION_CZERO_BATTERY
|
#ifdef IMIEV_ION_CZERO_BATTERY
|
||||||
update_values_imiev_battery(); //Map the values to the correct registers
|
update_values_imiev_battery(); //Map the values to the correct registers
|
||||||
#endif
|
#endif
|
||||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
|
||||||
update_values_kiaHyundai_64_battery(); //Map the values to the correct registers
|
|
||||||
#endif
|
|
||||||
#ifdef SOLAX_CAN
|
#ifdef SOLAX_CAN
|
||||||
update_values_can_solax();
|
update_values_can_solax();
|
||||||
#endif
|
#endif
|
||||||
|
@ -335,12 +373,12 @@ void handle_inverter()
|
||||||
|
|
||||||
void handle_contactors()
|
void handle_contactors()
|
||||||
{
|
{
|
||||||
//First check if we have any active errors, incase we do, turn off the battery after 15 seconds
|
//First check if we have any active errors, incase we do, turn off the battery after 5 seconds
|
||||||
if(bms_status == FAULT)
|
if(bms_status == FAULT)
|
||||||
{
|
{
|
||||||
timeSpentInFaultedMode++;
|
timeSpentInFaultedMode++;
|
||||||
}
|
}
|
||||||
if(timeSpentInFaultedMode > 1500)
|
if(timeSpentInFaultedMode > 500)
|
||||||
{
|
{
|
||||||
contactorStatus = SHUTDOWN_REQUESTED;
|
contactorStatus = SHUTDOWN_REQUESTED;
|
||||||
}
|
}
|
||||||
|
@ -353,8 +391,12 @@ void handle_contactors()
|
||||||
}
|
}
|
||||||
|
|
||||||
//After that, check if we are OK to start turning on the battery
|
//After that, check if we are OK to start turning on the battery
|
||||||
if(contactorStatus == WAITING_FOR_BATTERY)
|
if(contactorStatus == DISCONNECTED)
|
||||||
{
|
{
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
ledcWrite(POSITIVE_PWM_Ch, 0);
|
||||||
|
ledcWrite(NEGATIVE_PWM_Ch, 0);
|
||||||
|
|
||||||
if(batteryAllowsContactorClosing && inverterAllowsContactorClosing)
|
if(batteryAllowsContactorClosing && inverterAllowsContactorClosing)
|
||||||
{
|
{
|
||||||
contactorStatus = PRECHARGE;
|
contactorStatus = PRECHARGE;
|
||||||
|
@ -362,7 +404,9 @@ void handle_contactors()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(contactorStatus == COMPLETED)
|
if(contactorStatus == COMPLETED)
|
||||||
{ //Skip running the state machine below if it has already completed
|
{
|
||||||
|
if (!inverterAllowsContactorClosing) contactorStatus = DISCONNECTED;
|
||||||
|
//Skip running the state machine below if it has already completed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +421,7 @@ void handle_contactors()
|
||||||
|
|
||||||
case NEGATIVE:
|
case NEGATIVE:
|
||||||
if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) {
|
if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) {
|
||||||
digitalWrite(NEGATIVE_CONTACTOR_PIN, HIGH);
|
ledcWrite(NEGATIVE_PWM_Ch, 1023);
|
||||||
negativeStartTime = currentTime;
|
negativeStartTime = currentTime;
|
||||||
contactorStatus = POSITIVE;
|
contactorStatus = POSITIVE;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +429,7 @@ void handle_contactors()
|
||||||
|
|
||||||
case POSITIVE:
|
case POSITIVE:
|
||||||
if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
|
if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
|
||||||
digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH);
|
ledcWrite(POSITIVE_PWM_Ch, 1023);
|
||||||
contactorStatus = PRECHARGE_OFF;
|
contactorStatus = PRECHARGE_OFF;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -393,6 +437,8 @@ void handle_contactors()
|
||||||
case PRECHARGE_OFF:
|
case PRECHARGE_OFF:
|
||||||
if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) {
|
if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) {
|
||||||
digitalWrite(PRECHARGE_PIN, LOW);
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
ledcWrite(NEGATIVE_PWM_Ch, PWM_Hold_Duty);
|
||||||
|
ledcWrite(POSITIVE_PWM_Ch, PWM_Hold_Duty);
|
||||||
contactorStatus = COMPLETED;
|
contactorStatus = COMPLETED;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
#ifndef __CONFIG_H__
|
#ifndef __CONFIG_H__
|
||||||
#define __CONFIG_H__
|
#define __CONFIG_H__
|
||||||
|
|
||||||
|
/* Select battery used */
|
||||||
|
#define BATTERY_TYPE_LEAF // See NISSAN-LEAF-BATTERY.h for more LEAF battery settings
|
||||||
|
//#define TESLA_MODEL_3_BATTERY // See TESLA-MODEL-3-BATTERY.h for more Tesla battery settings
|
||||||
|
//#define RENAULT_ZOE_BATTERY // See RENAULT-ZOE-BATTERY.h for more Zoe battery settings
|
||||||
|
//#define IMIEV_ION_CZERO_BATTERY // See IMIEV-CZERO-ION-BATTERY.h for more triplet battery settings
|
||||||
|
//#define CHADEMO // See CHADEMO.h for more Chademo related settings
|
||||||
|
|
||||||
|
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
|
||||||
|
//#define MODBUS_BYD //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
||||||
|
//#define CAN_BYD //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
|
||||||
|
#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
|
||||||
|
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
||||||
|
|
||||||
|
#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using MCP2515 controller (Needed for FoxESS inverts)
|
||||||
|
|
||||||
// PIN
|
// PIN
|
||||||
#define PIN_5V_EN 16
|
#define PIN_5V_EN 16
|
||||||
|
|
||||||
|
@ -8,6 +23,12 @@
|
||||||
#define CAN_RX_PIN 26
|
#define CAN_RX_PIN 26
|
||||||
#define CAN_SE_PIN 23
|
#define CAN_SE_PIN 23
|
||||||
|
|
||||||
|
#define MCP2515_SCK 12 // SCK input of MCP2515
|
||||||
|
#define MCP2515_MOSI 5 // SDI input of MCP2515
|
||||||
|
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
|
||||||
|
#define MCP2515_CS 18 // CS input of MCP2515
|
||||||
|
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
|
||||||
|
|
||||||
#define RS485_EN_PIN 17 // 17 /RE
|
#define RS485_EN_PIN 17 // 17 /RE
|
||||||
#define RS485_TX_PIN 22 // 21
|
#define RS485_TX_PIN 22 // 21
|
||||||
#define RS485_RX_PIN 21 // 22
|
#define RS485_RX_PIN 21 // 22
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue