From f83bbc35eb5faf91a1b81a36bbf09c0865643cfe Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 20 Apr 2024 20:39:59 +0300 Subject: [PATCH 01/11] Minimum viable product for double battery --- Software/Software.ino | 24 +- Software/USER_SETTINGS.cpp | 6 +- Software/USER_SETTINGS.h | 7 +- Software/src/battery/BATTERIES.h | 5 + Software/src/battery/BMW-I3-BATTERY.cpp | 412 ++++++++++++++++-- Software/src/battery/BMW-I3-BATTERY.h | 3 + Software/src/datalayer/datalayer.h | 3 + Software/src/devboard/utils/events.cpp | 3 + Software/src/devboard/utils/events.h | 3 +- Software/src/devboard/webserver/webserver.cpp | 88 ++++ 10 files changed, 521 insertions(+), 33 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 2c4654d5..9ccf534f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -216,6 +216,9 @@ void core_loop(void* task_time_us) { led_exe(); #ifdef CONTACTOR_CONTROL handle_contactors(); // Take care of startup precharge/contactor closing +#endif +#ifdef DOUBLE_BATTERY + handle_CAN_contactors(); #endif } END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); @@ -590,6 +593,9 @@ void receive_can2() { // This function is similar to receive_can, but just take if (rx_frame2.FIR.B.FF == CAN_frame_std) { // New standard frame #ifdef BYD_CAN receive_can_byd(rx_frame2); +#endif +#ifdef DOUBLE_BATTERY + receive_can_battery2(rx_frame2); #endif } else { // New extended frame #ifdef PYLON_CAN @@ -611,6 +617,14 @@ void send_can2() { } #endif +#ifdef DOUBLE_BATTERY +void handle_CAN_contactors() { + if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 50) { + datalayer.system.status.battery2_allows_contactor_closing = true; + } //TODO: Shall we handle opening incase of fault here? +} +#endif + #ifdef CONTACTOR_CONTROL void handle_contactors() { // First check if we have any active errors, incase we do, turn off the battery @@ -733,9 +747,17 @@ void update_SOC() { } } +void summarize_battery_values() { + // TODO: What needs to be summed? +} + void update_values() { // Battery - update_values_battery(); // Map the fake values to the correct registers + update_values_battery(); +#ifdef DOUBLE_BATTERY + update_values_battery2(); + summarize_battery_values(); +#endif // Inverter #ifdef BYD_CAN update_values_can_byd(); diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index c35fee69..7975ecac 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -14,9 +14,9 @@ volatile float CHARGER_END_A = 1.0; // Current at which charging is consid #ifdef WEBSERVER volatile uint8_t AccessPointEnabled = true; //Set to either true or false incase you want the board to enable a direct wifi access point -const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters; -const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters; -const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; +const char* ssid = "ZTE_5G_VACCINE"; // Maximum of 63 characters; +const char* password = "secretpassword"; // Minimum of 8 characters; +const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open const uint8_t wifi_channel = 0; // set to 0 for automatic channel selection diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 5537a05c..83324670 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -8,7 +8,7 @@ /* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/ /* Select battery used */ -//#define BMW_I3_BATTERY +#define BMW_I3_BATTERY //#define CHADEMO_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define KIA_HYUNDAI_64_BATTERY @@ -20,10 +20,11 @@ //#define TESLA_MODEL_3_BATTERY //#define VOLVO_SPA_BATTERY //#define TEST_FAKE_BATTERY +#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus @@ -36,7 +37,7 @@ //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation -//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters) +#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for some inverters / double battery) //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2517FD controller (Needed for some batteries) //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index ef35110c..c29feb08 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -69,4 +69,9 @@ void update_values_battery(); void send_can_battery(); void setup_battery(void); +#ifdef DOUBLE_BATTERY +void update_values_battery2(); +void receive_can_battery2(CAN_frame_t rx_frame); +#endif + #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 54172cee..604284dd 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -7,24 +7,26 @@ #include "BMW-I3-BATTERY.h" /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send -static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send -static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send -static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send -static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send -static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send -static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send -static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send -static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive -static uint16_t CANerror = 0; // counter on how many CAN errors encountered -#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 - -static const uint16_t WUPonDuration = 477; // in milliseconds how long WUP should be ON after poweron -static const uint16_t WUPoffDuration = 105; // in milliseconds how long WUP should be OFF after on pulse -unsigned long lastChangeTime; // Variables to store timestamps -unsigned long turnOnTime; // Variables to store timestamps -enum State { POWERON, STATE_ON, STATE_OFF }; -static State WUPState = POWERON; +static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send +static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive +static unsigned long previousMillis20_2 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100_2 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200_2 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500_2 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640_2 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000_2 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000_2 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000_2 = 0; // will store last time a 10000ms CAN Message was send +static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive +static uint16_t CANerror = 0; // counter on how many CAN errors encountered +#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG }; static CmdState cmdState = SOH; @@ -323,6 +325,7 @@ static uint8_t BMW_13E_counter = 0; static uint8_t BMW_380_counter = 0; static uint32_t BMW_328_counter = 0; static bool battery_awake = false; +static bool battery2_awake = false; static uint32_t battery_serial_number = 0; static uint32_t battery_available_power_shortterm_charge = 0; @@ -353,7 +356,6 @@ static uint16_t battery_soc = 0; static uint16_t battery_soc_hvmax = 0; static uint16_t battery_soc_hvmin = 0; static uint16_t battery_capacity_cah = 0; - static int16_t battery_temperature_HV = 0; static int16_t battery_temperature_heat_exchanger = 0; static int16_t battery_temperature_max = 0; @@ -394,6 +396,75 @@ static uint8_t battery_ID2 = 0; static uint8_t battery_cellvoltage_mux = 0; static uint8_t battery_soh = 0; +static uint32_t battery2_serial_number = 0; +static uint32_t battery2_available_power_shortterm_charge = 0; +static uint32_t battery2_available_power_shortterm_discharge = 0; +static uint32_t battery2_available_power_longterm_charge = 0; +static uint32_t battery2_available_power_longterm_discharge = 0; +static uint32_t battery2_BEV_available_power_shortterm_charge = 0; +static uint32_t battery2_BEV_available_power_shortterm_discharge = 0; +static uint32_t battery2_BEV_available_power_longterm_charge = 0; +static uint32_t battery2_BEV_available_power_longterm_discharge = 0; +static uint16_t battery2_energy_content_maximum_kWh = 0; +static uint16_t battery2_display_SOC = 0; +static uint16_t battery2_volts = 0; +static uint16_t battery2_HVBatt_SOC = 0; +static uint16_t battery2_DC_link_voltage = 0; +static uint16_t battery2_max_charge_voltage = 0; +static uint16_t battery2_min_discharge_voltage = 0; +static uint16_t battery2_predicted_energy_charge_condition = 0; +static uint16_t battery2_predicted_energy_charging_target = 0; +static uint16_t battery2_actual_value_power_heating = 0; //0 - 4094 W +static uint16_t battery2_prediction_voltage_shortterm_charge = 0; +static uint16_t battery2_prediction_voltage_shortterm_discharge = 0; +static uint16_t battery2_prediction_voltage_longterm_charge = 0; +static uint16_t battery2_prediction_voltage_longterm_discharge = 0; +static uint16_t battery2_prediction_duration_charging_minutes = 0; +static uint16_t battery2_target_voltage_in_CV_mode = 0; +static uint16_t battery2_soc = 0; +static uint16_t battery2_soc_hvmax = 0; +static uint16_t battery2_soc_hvmin = 0; +static uint16_t battery2_capacity_cah = 0; +static int16_t battery2_temperature_HV = 0; +static int16_t battery2_temperature_heat_exchanger = 0; +static int16_t battery2_temperature_max = 0; +static int16_t battery2_temperature_min = 0; +static int16_t battery2_max_charge_amperage = 0; +static int16_t battery2_max_discharge_amperage = 0; +static int16_t battery2_power = 0; +static int16_t battery2_current = 0; +static uint8_t battery2_status_error_isolation_external_Bordnetz = 0; +static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0; +static uint8_t battery2_request_cooling = 0; +static uint8_t battery2_status_valve_cooling = 0; +static uint8_t battery2_status_error_locking = 0; +static uint8_t battery2_status_precharge_locked = 0; +static uint8_t battery2_status_disconnecting_switch = 0; +static uint8_t battery2_status_emergency_mode = 0; +static uint8_t battery2_request_service = 0; +static uint8_t battery2_error_emergency_mode = 0; +static uint8_t battery2_status_error_disconnecting_switch = 0; +static uint8_t battery2_status_warning_isolation = 0; +static uint8_t battery2_status_cold_shutoff_valve = 0; +static uint8_t battery2_request_open_contactors = 0; +static uint8_t battery2_request_open_contactors_instantly = 0; +static uint8_t battery2_request_open_contactors_fast = 0; +static uint8_t battery2_charging_condition_delta = 0; +static uint8_t battery2_status_service_disconnection_plug = 0; +static uint8_t battery2_status_measurement_isolation = 0; +static uint8_t battery2_request_abort_charging = 0; +static uint8_t battery2_prediction_time_end_of_charging_minutes = 0; +static uint8_t battery2_request_operating_mode = 0; +static uint8_t battery2_request_charging_condition_minimum = 0; +static uint8_t battery2_request_charging_condition_maximum = 0; +static uint8_t battery2_status_cooling_HV = 0; //1 works, 2 does not start +static uint8_t battery2_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired +static uint8_t battery2_status_diagnosis_powertrain_maximum_multiplexer = 0; +static uint8_t battery2_status_diagnosis_powertrain_immediate_multiplexer = 0; +static uint8_t battery2_ID2 = 0; +static uint8_t battery2_cellvoltage_mux = 0; +static uint8_t battery2_soh = 0; + static uint8_t message_data[50]; static uint8_t next_data = 0; @@ -413,7 +484,66 @@ static uint8_t increment_alive_counter(uint8_t counter) { return counter; } -void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus +void CAN_WriteFrame(CAN_frame_t* tx_frame) { + CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 + 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); +} + +void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer + + datalayer.battery2.status.real_soc = (battery2_HVBatt_SOC * 10); + + datalayer.battery2.status.voltage_dV = battery2_volts; //Unit V+1 (5000 = 500.0V) + + datalayer.battery2.status.current_dA = battery2_current; + + datalayer.battery2.status.remaining_capacity_Wh = (battery2_energy_content_maximum_kWh * 1000); // Convert kWh to Wh + + datalayer.battery2.status.soh_pptt = battery2_soh * 100; + + if (battery2_BEV_available_power_longterm_discharge > 65000) { + datalayer.battery2.status.max_discharge_power_W = 65000; + } else { + datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge; + } + if (battery2_BEV_available_power_longterm_charge > 65000) { + datalayer.battery2.status.max_charge_power_W = 65000; + } else { + datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge; + } + + battery2_power = (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); + + datalayer.battery2.status.active_power_W = battery2_power; + + datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal + + datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal + + datalayer.battery2.status.cell_min_voltage_mV = datalayer.battery2.status.cell_voltages_mV[0]; + datalayer.battery2.status.cell_max_voltage_mV = datalayer.battery2.status.cell_voltages_mV[1]; + + /* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/ + if (!CAN2stillAlive) { + set_event(EVENT_CAN2_RX_FAILURE, 2); + datalayer.battery2.status.bms_status = FAULT; //TODO: Refactor handling of event for battery2 + } else { + CAN2stillAlive--; + clear_event(EVENT_CAN2_RX_FAILURE); + } + // Check if we have encountered any malformed CAN messages + if (CANerror > MAX_CAN_FAILURES) { + set_event(EVENT_CAN_RX_WARNING, 2); + } +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer datalayer.battery.status.real_soc = (battery_HVBatt_SOC * 10); @@ -492,6 +622,7 @@ void receive_can_battery(CAN_frame_t rx_frame) { CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A) battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V + datalayer.battery.status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]); battery_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6; battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03); @@ -650,6 +781,172 @@ void receive_can_battery(CAN_frame_t rx_frame) { break; } } +void receive_can_battery2(CAN_frame_t rx_frame) { + switch (rx_frame.MsgID) { + case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2 + battery2_awake = true; + CAN2stillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V + battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A) + battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V + datalayer.battery2.status.voltage_dV = battery2_volts; // Update the datalayer as soon as possible with this info + battery2_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]); + battery2_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6; + battery2_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03); + battery2_request_open_contactors_fast = (rx_frame.data.u8[6] & 0x0C) >> 2; + battery2_charging_condition_delta = (rx_frame.data.u8[6] & 0xF0) >> 4; + battery2_DC_link_voltage = rx_frame.data.u8[7]; + break; + case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1 + battery2_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03); + battery2_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery2_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4; + battery2_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6; + battery2_status_error_locking = (rx_frame.data.u8[1] & 0x03); + battery2_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2; + battery2_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4; + battery2_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6; + battery2_request_service = (rx_frame.data.u8[2] & 0x03); + battery2_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2; + battery2_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4; + battery2_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6; + battery2_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); + battery2_temperature_HV = (rx_frame.data.u8[4] - 50); + battery2_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); + battery2_temperature_max = (rx_frame.data.u8[6] - 50); + battery2_temperature_min = (rx_frame.data.u8[7] - 50); + break; + case 0x239: //BMS [200ms] + battery2_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh + battery2_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh + break; + case 0x2BD: //BMS [100ms] Status diagnosis high voltage - 1 + battery2_awake = true; + if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) { + //If calculated CRC does not match transmitted CRC, increase CANerror counter + CANerror++; + break; + } + battery2_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F); + break; + case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations + battery2_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery2_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2); + battery2_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery2_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2); + break; + case 0x2FF: //BMS [100ms] Status Heating High-Voltage Battery + battery2_awake = true; + battery2_actual_value_power_heating = (rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4); + break; + case 0x363: //BMS [1s] Identification High-Voltage Battery + battery2_serial_number = + (rx_frame.data.u8[3] << 24 | rx_frame.data.u8[2] << 16 | rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + break; + case 0x3C2: //BMS (94AH exclusive) - Status diagnostics OBD 2 powertrain + battery2_status_diagnosis_powertrain_maximum_multiplexer = + ((rx_frame.data.u8[1] & 0x03) << 4 | rx_frame.data.u8[0] >> 4); + battery2_status_diagnosis_powertrain_immediate_multiplexer = (rx_frame.data.u8[0] & 0xFC) >> 2; + break; + case 0x3EB: //BMS [1s] Status of charging high-voltage storage - 3 + battery2_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3; + battery2_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3; + battery2_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3; + battery2_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3; + break; + case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1 + battery2_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3; + battery2_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3; + battery2_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3; + battery2_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3; + break; + case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2 + battery2_status_cooling_HV = (rx_frame.data.u8[1] & 0x03); + break; + case 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command? + battery2_cellvoltage_mux = rx_frame.data.u8[0]; + if (battery2_cellvoltage_mux == 0) { + datalayer.battery2.status.cell_voltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800); + } + break; + case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2 + battery2_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery2_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery2_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery2_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x431: //BMS [200ms] Data High-Voltage Battery Unit + battery2_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F); + battery2_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery2_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4; + battery2_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery2_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4]; + battery2_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50; + break; + case 0x432: //BMS [200ms] SOC% info + battery2_request_operating_mode = (rx_frame.data.u8[0] & 0x03); + battery2_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10; + battery2_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2); + battery2_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2); + battery2_display_SOC = (rx_frame.data.u8[4] / 2); + break; + case 0x507: //BMS [640ms] Network Management - 2 - This message is sent on the bus for sleep coordination purposes + break; + case 0x587: //BMS [5s] Services + battery2_ID2 = rx_frame.data.u8[0]; + break; + case 0x607: //BMS - responses to message requests on 0x615 + if (rx_frame.FIR.B.DLC > 6 && next_data == 0 && rx_frame.data.u8[0] == 0xf1) { + uint8_t count2 = 6; + while (count2 < rx_frame.FIR.B.DLC && next_data < 49) { + message_data[next_data++] = rx_frame.data.u8[count2++]; + } + //ESP32Can.CANWriteFrame(&BMW_6F1_CONTINUE); // tell battery to send additional messages TODO: Make this send to Can2 instead of CAN1 + + } else if (rx_frame.FIR.B.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 && + ((rx_frame.data.u8[1] & 0xF0) == 0x20)) { + uint8_t count2 = 2; + while (count2 < rx_frame.FIR.B.DLC && next_data < 49) { + message_data[next_data++] = rx_frame.data.u8[count2++]; + } + + switch (cmdState) { + case CELL_VOLTAGE: + if (next_data >= 4) { + datalayer.battery2.status.cell_voltages_mV[0] = (message_data[0] << 8 | message_data[1]); + datalayer.battery2.status.cell_voltages_mV[2] = (message_data[2] << 8 | message_data[3]); + } + break; + case CELL_VOLTAGE_AVG: + if (next_data >= 30) { + datalayer.battery2.status.cell_voltages_mV[1] = (message_data[10] << 8 | message_data[11]) / 10; + battery2_capacity_cah = (message_data[4] << 8 | message_data[5]); + } + break; + case SOH: + if (next_data >= 4) { + battery2_soh = message_data[3]; + } + break; + case SOC: + if (next_data >= 6) { + battery2_soc = (message_data[0] << 8 | message_data[1]); + battery2_soc_hvmax = (message_data[2] << 8 | message_data[3]); + battery2_soc_hvmin = (message_data[4] << 8 | message_data[5]); + } + break; + } + } + break; + default: + break; + } +} void send_can_battery() { unsigned long currentMillis = millis(); @@ -668,10 +965,6 @@ void send_can_battery() { BMW_10B.data.u8[1] = 0x10; // Close contactors } - if (datalayer.battery.status.bms_status == FAULT) { - BMW_10B.data.u8[1] = 0x00; // Open contactors (TODO: test if this works) - } - BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms); BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F); @@ -680,7 +973,17 @@ void send_can_battery() { BMW_13E_counter++; BMW_13E.data.u8[4] = BMW_13E_counter; - ESP32Can.CANWriteFrame(&BMW_10B); + if (datalayer.battery.status.bms_status == FAULT) { + } //If battery is not in Fault mode, allow contactor to close by sending 10B + else { + ESP32Can.CANWriteFrame(&BMW_10B); + } + +#ifdef DOUBLE_BATTERY //If second battery is allowed to join in, also send 10B + if (datalayer.system.status.battery2_allows_contactor_closing == true) { + CAN_WriteFrame(&BMW_10B); + } +#endif } // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { @@ -692,6 +995,9 @@ void send_can_battery() { alive_counter_100ms = increment_alive_counter(alive_counter_100ms); ESP32Can.CANWriteFrame(&BMW_12F); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_12F); +#endif } // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { @@ -703,6 +1009,9 @@ void send_can_battery() { alive_counter_200ms = increment_alive_counter(alive_counter_200ms); ESP32Can.CANWriteFrame(&BMW_19B); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_19B); +#endif } // Send 500ms CAN Message if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { @@ -714,6 +1023,9 @@ void send_can_battery() { alive_counter_500ms = increment_alive_counter(alive_counter_500ms); ESP32Can.CANWriteFrame(&BMW_30B); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_30B); +#endif } // Send 640ms CAN Message if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { @@ -721,6 +1033,10 @@ void send_can_battery() { ESP32Can.CANWriteFrame(&BMW_512); // Keep BMS alive ESP32Can.CANWriteFrame(&BMW_5F8); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_512); + CAN_WriteFrame(&BMW_5F8); +#endif } // Send 1000ms CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { @@ -762,6 +1078,24 @@ void send_can_battery() { ESP32Can.CANWriteFrame(&BMW_192); ESP32Can.CANWriteFrame(&BMW_13E); ESP32Can.CANWriteFrame(&BMW_433); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_3E8); + CAN_WriteFrame(&BMW_328); + CAN_WriteFrame(&BMW_3F9); + CAN_WriteFrame(&BMW_2E2); + CAN_WriteFrame(&BMW_41D); + CAN_WriteFrame(&BMW_3D0); + CAN_WriteFrame(&BMW_3CA); + CAN_WriteFrame(&BMW_3A7); + CAN_WriteFrame(&BMW_2CA); + CAN_WriteFrame(&BMW_3FB); + CAN_WriteFrame(&BMW_418); + CAN_WriteFrame(&BMW_1D0); + CAN_WriteFrame(&BMW_3EC); + CAN_WriteFrame(&BMW_192); + CAN_WriteFrame(&BMW_13E); + CAN_WriteFrame(&BMW_433); +#endif BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this @@ -778,11 +1112,21 @@ void send_can_battery() { ESP32Can.CANWriteFrame(&BMW_3A0); ESP32Can.CANWriteFrame(&BMW_592_0); ESP32Can.CANWriteFrame(&BMW_592_1); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_3FC); + CAN_WriteFrame(&BMW_3C5); + CAN_WriteFrame(&BMW_3A0); + CAN_WriteFrame(&BMW_592_0); + CAN_WriteFrame(&BMW_592_1); +#endif alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms); if (BMW_380_counter < 3) { ESP32Can.CANWriteFrame(&BMW_380); // This message stops after 3 times on startup +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_380); +#endif BMW_380_counter++; } } @@ -793,9 +1137,17 @@ void send_can_battery() { ESP32Can.CANWriteFrame(&BMW_3E5); //Order comes from CAN logs ESP32Can.CANWriteFrame(&BMW_3E4); ESP32Can.CANWriteFrame(&BMW_37B); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_3E5); + CAN_WriteFrame(&BMW_3E4); + CAN_WriteFrame(&BMW_37B); +#endif next_data = 0; ESP32Can.CANWriteFrame(&BMW_6F1_CELL); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&BMW_6F1_CELL); +#endif BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this } @@ -811,6 +1163,16 @@ void setup_battery(void) { // Performs one time setup at startup 4040; // 404.4V, over this, charging is not possible (goes into forced discharge) datalayer.battery.info.min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled + datalayer.system.status.battery_allows_contactor_closing = true; + +#ifdef DOUBLE_BATTERY + Serial.println("Another BMW i3 battery also selected!"); + datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; + datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; + datalayer.battery2.status.voltage_dV = + 0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding +#endif + digitalWrite(WUP_PIN, HIGH); // Wake up the battery } diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 73690635..7a7feb81 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -3,6 +3,9 @@ #include #include "../include.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" +#include "../lib/pierremolinaro-acan2515/ACAN2515.h" + +extern ACAN2515 can; #define BATTERY_SELECTED diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 4c44ccfc..938bb431 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -142,6 +142,8 @@ typedef struct { #endif /** True if the battery allows for the contactors to close */ bool battery_allows_contactor_closing = false; + /** True if the second battery allows for the contactors to close */ + bool battery2_allows_contactor_closing = false; /** True if the inverter allows for the contactors to close */ bool inverter_allows_contactor_closing = true; } DATALAYER_SYSTEM_STATUS_TYPE; @@ -158,6 +160,7 @@ typedef struct { class DataLayer { public: DATALAYER_BATTERY_TYPE battery; + DATALAYER_BATTERY_TYPE battery2; DATALAYER_SYSTEM_TYPE system; }; diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 0ff59ae1..6dda684f 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -129,6 +129,7 @@ void init_events(void) { events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO; events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR; + events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR; @@ -194,6 +195,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "CAN message failed to send within defined time. Contact developers, CPU load might be too high."; case EVENT_CAN_RX_FAILURE: return "No CAN communication detected for 60s. Shutting down battery control."; + case EVENT_CAN2_RX_FAILURE: + return "No CAN communication detected for 60s on CAN2. Shutting down the secondary battery control."; case EVENT_CANFD_RX_FAILURE: return "No CANFD communication detected for 60s. Shutting down battery control."; case EVENT_CAN_RX_WARNING: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 5058e4a9..954169fc 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0002 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0003 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -29,6 +29,7 @@ XX(EVENT_CANFD_INIT_FAILURE) \ XX(EVENT_CAN_OVERRUN) \ XX(EVENT_CAN_RX_FAILURE) \ + XX(EVENT_CAN2_RX_FAILURE) \ XX(EVENT_CANFD_RX_FAILURE) \ XX(EVENT_CAN_RX_WARNING) \ XX(EVENT_CAN_TX_FAILURE) \ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 4c2f5a75..13555f08 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -466,6 +466,9 @@ String processor(const String& var) { #endif #ifdef TEST_FAKE_BATTERY content += "Fake battery for testing purposes"; +#endif +#ifdef DOUBLE_BATTERY + content += " (Double battery)"; #endif content += ""; @@ -483,8 +486,15 @@ String processor(const String& var) { // Close the block content += ""; +#ifdef DOUBLE_BATTERY + // Start a new block with a specific background color. Color changes depending on BMS status + content += "
"; + content += "
(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100 + socScaledFloat = + static_cast(datalayer.battery2.status.reported_soc) / 100.0; // Convert to float and divide by 100 + sohFloat = static_cast(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100 + voltageFloat = + static_cast(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10 + currentFloat = + static_cast(datalayer.battery2.status.current_dA) / 10.0; // Convert to float and divide by 10 + powerFloat = static_cast(datalayer.battery2.status.active_power_W); // Convert to float + tempMaxFloat = static_cast(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float + tempMinFloat = static_cast(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float + + content += "

Real SOC: " + String(socRealFloat, 2) + "

"; + content += "

Scaled SOC: " + String(socScaledFloat, 2) + "

"; + content += "

SOH: " + String(sohFloat, 2) + "

"; + content += "

Voltage: " + String(voltageFloat, 1) + " V

"; + content += "

Current: " + String(currentFloat, 1) + " A

"; + content += formatPowerValue("Power", powerFloat, "", 1); + content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0); + content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1); + content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); + content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); + content += "

Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV

"; + content += "

Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV

"; + content += "

Temperature max: " + String(tempMaxFloat, 1) + " C

"; + content += "

Temperature min: " + String(tempMinFloat, 1) + " C

"; + if (datalayer.battery2.status.bms_status == ACTIVE) { + content += "

BMS Status: OK

"; + } else if (datalayer.battery2.status.bms_status == UPDATING) { + content += "

BMS Status: UPDATING

"; + } else { + content += "

BMS Status: FAULT

"; + } + if (datalayer.battery2.status.current_dA == 0) { + content += "

Battery idle

"; + } else if (datalayer.battery2.status.current_dA < 0) { + content += "

Battery discharging!

"; + } else { // > 0 + content += "

Battery charging!

"; + } + + content += "

Automatic contactor closing allowed:

"; + content += "

Battery: "; + if (datalayer.system.status.battery2_allows_contactor_closing == true) { + content += ""; + } else { + content += ""; + } + + content += " Inverter: "; + if (datalayer.system.status.inverter_allows_contactor_closing == true) { + content += "

"; + } else { + content += ""; + } + + content += "
"; + content += "
"; +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Start a new block with orange background color content += "
"; From 997866ef8cb0a0a58ae159dff5ad7ec69622632a Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 20 Apr 2024 21:45:41 +0300 Subject: [PATCH 02/11] Tweak logic for contactor closing --- Software/Software.ino | 38 +++++++++++++++++-- Software/src/battery/BMW-I3-BATTERY.cpp | 36 ++++++++---------- Software/src/devboard/utils/events.cpp | 3 ++ Software/src/devboard/utils/events.h | 1 + Software/src/devboard/webserver/webserver.cpp | 2 +- 5 files changed, 55 insertions(+), 25 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 9ccf534f..7e08d51a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -619,9 +619,19 @@ void send_can2() { #ifdef DOUBLE_BATTERY void handle_CAN_contactors() { - if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 50) { - datalayer.system.status.battery2_allows_contactor_closing = true; - } //TODO: Shall we handle opening incase of fault here? + if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) { + return; // Both voltage values need to be available to start check + } + + if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 30) { // If we are within 3.0V + clear_event(EVENT_VOLTAGE_DIFFERENCE); + if (datalayer.battery2.status.bms_status != FAULT) { // Only proceed if BMS on battery2 is not faulted + datalayer.system.status.battery2_allows_contactor_closing = true; + } + } else { //We are over 3.0V diff + set_event(EVENT_VOLTAGE_DIFFERENCE, + (uint8_t)(abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) / 10)); + } } #endif @@ -744,7 +754,29 @@ void update_SOC() { datalayer.battery.status.reported_soc = calc_soc; } else { // No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; +#ifdef DOUBLE_BATTERY + datalayer.battery.status.reported_soc = + (datalayer.battery.status.real_soc + datalayer.battery2.status.real_soc) / 2; +#endif } +#ifdef DOUBLE_BATTERY + datalayer.battery.status.reported_soc = (datalayer.battery.status.real_soc + datalayer.battery2.status.real_soc) / 2; + + if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + } + if (datalayer.battery2.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + } + + if (datalayer.battery.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + } + if (datalayer.battery2.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + } + +#endif //TODO: Constrain according to the user settings. Help wanted on algoritm to use. } void summarize_battery_values() { diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 604284dd..02986406 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -7,26 +7,18 @@ #include "BMW-I3-BATTERY.h" /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send -static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send -static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send -static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send -static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send -static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send -static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send -static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send -static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive -static unsigned long previousMillis20_2 = 0; // will store last time a 20ms CAN Message was send -static unsigned long previousMillis100_2 = 0; // will store last time a 100ms CAN Message was send -static unsigned long previousMillis200_2 = 0; // will store last time a 200ms CAN Message was send -static unsigned long previousMillis500_2 = 0; // will store last time a 500ms CAN Message was send -static unsigned long previousMillis640_2 = 0; // will store last time a 600ms CAN Message was send -static unsigned long previousMillis1000_2 = 0; // will store last time a 1000ms CAN Message was send -static unsigned long previousMillis5000_2 = 0; // will store last time a 5000ms CAN Message was send -static unsigned long previousMillis10000_2 = 0; // will store last time a 10000ms CAN Message was send -static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive -static uint16_t CANerror = 0; // counter on how many CAN errors encountered -#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 +static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send +static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive +static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive +static uint16_t CANerror = 0; // counter on how many CAN errors encountered +#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG }; static CmdState cmdState = SOH; @@ -533,6 +525,7 @@ void update_values_battery2() { //This function maps all the values fetched via if (!CAN2stillAlive) { set_event(EVENT_CAN2_RX_FAILURE, 2); datalayer.battery2.status.bms_status = FAULT; //TODO: Refactor handling of event for battery2 + datalayer.system.status.battery2_allows_contactor_closing = false; } else { CAN2stillAlive--; clear_event(EVENT_CAN2_RX_FAILURE); @@ -788,7 +781,8 @@ void receive_can_battery2(CAN_frame_t rx_frame) { CAN2stillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A) battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V - datalayer.battery2.status.voltage_dV = battery2_volts; // Update the datalayer as soon as possible with this info + datalayer.battery2.status.voltage_dV = + battery2_volts; // Update the datalayer as soon as possible with this info, needed for contactor control battery2_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]); battery2_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6; battery2_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03); diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 6dda684f..4686bf26 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -143,6 +143,7 @@ void init_events(void) { events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR; events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR; events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR; + events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO; events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR; events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR; @@ -227,6 +228,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "Info: COLD BATTERY! Battery requesting heating pads to activate!"; case EVENT_BATTERY_WARMED_UP: return "Info: Battery requesting heating pads to stop. The battery is now warm enough."; + case EVENT_VOLTAGE_DIFFERENCE: + return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link"; case EVENT_LOW_SOH: return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle " "battery."; diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 954169fc..1d73a71b 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -45,6 +45,7 @@ XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \ XX(EVENT_BATTERY_REQUESTS_HEAT) \ XX(EVENT_BATTERY_WARMED_UP) \ + XX(EVENT_VOLTAGE_DIFFERENCE) \ XX(EVENT_LOW_SOH) \ XX(EVENT_HVIL_FAILURE) \ XX(EVENT_INTERNAL_OPEN_FAULT) \ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 13555f08..7199025b 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -599,7 +599,7 @@ String processor(const String& var) { socRealFloat = static_cast(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100 socScaledFloat = - static_cast(datalayer.battery2.status.reported_soc) / 100.0; // Convert to float and divide by 100 + static_cast(datalayer.battery.status.reported_soc) / 100.0; // Convert to float and divide by 100 sohFloat = static_cast(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100 voltageFloat = static_cast(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10 From 8b952b805c24152b5932a6786e9f0f7274102a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 11 Jul 2024 13:38:47 +0300 Subject: [PATCH 03/11] Add support for 2x LEAF batteries --- Software/USER_SETTINGS.h | 4 +- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 849 ++++++++++++++----- Software/src/battery/NISSAN-LEAF-BATTERY.h | 3 + Software/src/devboard/safety/safety.cpp | 17 + 4 files changed, 676 insertions(+), 197 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 70ce763a..5c3188d5 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -26,7 +26,7 @@ //#define TESLA_MODEL_3_BATTERY //#define VOLVO_SPA_BATTERY //#define TEST_FAKE_BATTERY -#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) +//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus @@ -48,7 +48,7 @@ //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation -#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for some inverters / double battery) +//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for some inverters / double battery) //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2517FD controller (Needed for some batteries) //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index ea77a6d7..12c18df4 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -16,7 +16,6 @@ static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Me static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message static uint8_t mprun10 = 0; //counter 0-3 static uint8_t mprun100 = 0; //counter 0-3 -static bool can_bus_alive = false; CAN_frame_t LEAF_1F2 = {.FIR = {.B = { @@ -55,13 +54,13 @@ CAN_frame_t LEAF_GROUP_REQUEST = {.FIR = {.B = }}, .MsgID = 0x79B, .data = {2, 0x21, 1, 0, 0, 0, 0, 0}}; -const CAN_frame_t LEAF_NEXT_LINE_REQUEST = {.FIR = {.B = - { - .DLC = 8, - .FF = CAN_frame_std, - }}, - .MsgID = 0x79B, - .data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; +CAN_frame_t LEAF_NEXT_LINE_REQUEST = {.FIR = {.B = + { + .DLC = 8, + .FF = CAN_frame_std, + }}, + .MsgID = 0x79B, + .data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many // groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second // replies with all the battery’s cells voltages in millivolt, the third and the fifth one are still unknown, the @@ -87,44 +86,37 @@ static uint8_t crctable[256] = { #define ZE0_BATTERY 0 #define AZE0_BATTERY 1 #define ZE1_BATTERY 2 -static uint8_t LEAF_Battery_Type = ZE0_BATTERY; -#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value -#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value -#define WH_PER_GID 77 //One GID is this amount of Watt hours -static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW -static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW -static int16_t LB_MAX_POWER_FOR_CHARGER = 0; //Limit in kW -static int16_t LB_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery -static uint16_t LB_TEMP = 0; //Temporary value used in status checks -static uint16_t LB_Wh_Remaining = 0; //Amount of energy in battery, in Wh -static uint16_t LB_GIDS = 273; //Startup in 24kWh mode -static uint16_t LB_MAX = 0; -static uint16_t LB_Max_GIDS = 273; //Startup in 24kWh mode -static uint16_t LB_StateOfHealth = 99; //State of health % -static uint16_t LB_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] -static int16_t LB_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] -static int16_t LB_Power = 0; //Watts going in/out of battery -static int16_t LB_HistData_Temperature_MAX = 6; //-40 to 86*C -static int16_t LB_HistData_Temperature_MIN = 5; //-40 to 86*C -static int16_t LB_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55 -static uint8_t LB_Relay_Cut_Request = 0; //LB_FAIL -static uint8_t LB_Failsafe_Status = 0; //LB_STATUS = 000b = normal start Request - //001b = Main Relay OFF Request - //010b = Charging Mode Stop Request - //011b = Main Relay OFF Request - //100b = Caution Lamp Request - //101b = Caution Lamp Request & Main Relay OFF Request - //110b = Caution Lamp Request & Charging Mode Stop Request - //111b = Caution Lamp Request & Main Relay OFF Request -static bool LB_Interlock = +static uint8_t LEAF_battery_Type = ZE0_BATTERY; +static bool battery_can_alive = false; +#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value +#define WH_PER_GID 77 //One GID is this amount of Watt hours +static uint16_t battery_Discharge_Power_Limit = 0; //Limit in kW +static uint16_t battery_Charge_Power_Limit = 0; //Limit in kW +static int16_t battery_MAX_POWER_FOR_CHARGER = 0; //Limit in kW +static int16_t battery_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery +static uint16_t battery_TEMP = 0; //Temporary value used in status checks +static uint16_t battery_Wh_Remaining = 0; //Amount of energy in battery, in Wh +static uint16_t battery_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery_MAX = 0; +static uint16_t battery_Max_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery_StateOfHealth = 99; //State of health % +static uint16_t battery_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] +static int16_t battery_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] +static int16_t battery_HistData_Temperature_MAX = 6; //-40 to 86*C +static int16_t battery_HistData_Temperature_MIN = 5; //-40 to 86*C +static int16_t battery_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55 +static uint8_t battery_Relay_Cut_Request = 0; //battery_FAIL +static uint8_t battery_Failsafe_Status = 0; //battery_STATUS +static bool battery_Interlock = true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted) -static bool LB_Full_CHARGE_flag = false; //LB_FCHGEND , Goes to 1 if battery is fully charged -static bool LB_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1 -static bool LB_Capacity_Empty = false; //LB_EMPTY, , Goes to 1 if battery is empty -static bool LB_HeatExist = false; //LB_HEATEXIST, Specifies if battery pack is equipped with heating elements -static bool LB_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request -static bool LB_Heating_Start = false; //When transitioning from 1->0, signals a START heat request -static bool Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening +static bool battery_Full_CHARGE_flag = false; //battery_FCHGEND , Goes to 1 if battery is fully charged +static bool battery_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1 +static bool battery_Capacity_Empty = false; //battery_EMPTY, , Goes to 1 if battery is empty +static bool battery_HeatExist = false; //battery_HEATEXIST, Specifies if battery pack is equipped with heating elements +static bool battery_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request +static bool battery_Heating_Start = false; //When transitioning from 1->0, signals a START heat request +static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening // Nissan LEAF battery data from polled CAN messages static uint8_t battery_request_idx = 0; @@ -132,20 +124,69 @@ static uint8_t group_7bb = 0; static uint8_t group = 1; static bool stop_battery_query = true; static uint8_t hold_off_with_polling_10seconds = 10; -static uint16_t cell_voltages[97]; //array with all the cellvoltages -static uint8_t cellcounter = 0; -static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV -static uint16_t HX = 0; //Internal resistance -static uint16_t insulation = 0; //Insulation resistance -static uint16_t temp_raw_1 = 0; -static uint8_t temp_raw_2_highnibble = 0; -static uint16_t temp_raw_2 = 0; -static uint16_t temp_raw_3 = 0; -static uint16_t temp_raw_4 = 0; -static uint16_t temp_raw_max = 0; -static uint16_t temp_raw_min = 0; -static int16_t temp_polled_max = 0; -static int16_t temp_polled_min = 0; +static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages +static uint8_t battery_cellcounter = 0; +static uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV +static uint16_t battery_HX = 0; //Internal resistance +static uint16_t battery_insulation = 0; //Insulation resistance +static uint16_t battery_temp_raw_1 = 0; +static uint8_t battery_temp_raw_2_highnibble = 0; +static uint16_t battery_temp_raw_2 = 0; +static uint16_t battery_temp_raw_3 = 0; +static uint16_t battery_temp_raw_4 = 0; +static uint16_t battery_temp_raw_max = 0; +static uint16_t battery_temp_raw_min = 0; +static int16_t battery_temp_polled_max = 0; +static int16_t battery_temp_polled_min = 0; + +#ifdef DOUBLE_BATTERY +static uint8_t LEAF_battery2_Type = ZE0_BATTERY; +static bool battery2_can_alive = false; +static uint16_t battery2_Discharge_Power_Limit = 0; //Limit in kW +static uint16_t battery2_Charge_Power_Limit = 0; //Limit in kW +static int16_t battery2_MAX_POWER_FOR_CHARGER = 0; //Limit in kW +static int16_t battery2_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery +static uint16_t battery2_TEMP = 0; //Temporary value used in status checks +static uint16_t battery2_Wh_Remaining = 0; //Amount of energy in battery, in Wh +static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery2_MAX = 0; +static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery2_StateOfHealth = 99; //State of health % +static uint16_t battery2_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] +static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] +static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C +static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C +static int16_t battery2_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55 +static uint8_t battery2_Relay_Cut_Request = 0; //battery2_FAIL +static uint8_t battery2_Failsafe_Status = 0; //battery2_STATUS +static bool battery2_Interlock = + true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted) +static bool battery2_Full_CHARGE_flag = false; //battery2_FCHGEND , Goes to 1 if battery is fully charged +static bool battery2_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1 +static bool battery2_Capacity_Empty = false; //battery2_EMPTY, , Goes to 1 if battery is empty +static bool battery2_HeatExist = + false; //battery2_HEATEXIST, Specifies if battery pack is equipped with heating elements +static bool battery2_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request +static bool battery2_Heating_Start = false; //When transitioning from 1->0, signals a START heat request +static bool battery2_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening +// Polled values +static uint8_t battery2_group_7bb = 0; +static uint8_t battery2_request_idx = 0; +static uint16_t battery2_cell_voltages[97]; //array with all the cellvoltages +static uint8_t battery2_cellcounter = 0; +static uint16_t battery2_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV +static uint16_t battery2_HX = 0; //Internal resistance +static uint16_t battery2_insulation = 0; //Insulation resistance +static uint16_t battery2_temp_raw_1 = 0; +static uint8_t battery2_temp_raw_2_highnibble = 0; +static uint16_t battery2_temp_raw_2 = 0; +static uint16_t battery2_temp_raw_3 = 0; +static uint16_t battery2_temp_raw_4 = 0; +static uint16_t battery2_temp_raw_max = 0; +static uint16_t battery2_temp_raw_min = 0; +static int16_t battery2_temp_polled_max = 0; +static int16_t battery2_temp_polled_min = 0; +#endif // DOUBLE_BATTERY void print_with_units(char* header, int value, char* units) { Serial.print(header); @@ -156,84 +197,85 @@ void print_with_units(char* header, int value, char* units) { void update_values_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */ /* Start with mapping all values */ - datalayer.battery.status.soh_pptt = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00% + datalayer.battery.status.soh_pptt = (battery_StateOfHealth * 100); //Increase range from 99% -> 99.00% - datalayer.battery.status.real_soc = (LB_SOC * 10); + datalayer.battery.status.real_soc = (battery_SOC * 10); datalayer.battery.status.voltage_dV = - (LB_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701) + (battery_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701) - datalayer.battery.status.current_dA = (LB_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11) + datalayer.battery.status.current_dA = + (battery_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11) - datalayer.battery.info.total_capacity_Wh = (LB_Max_GIDS * WH_PER_GID); + datalayer.battery.info.total_capacity_Wh = (battery_Max_GIDS * WH_PER_GID); - datalayer.battery.status.remaining_capacity_Wh = LB_Wh_Remaining; + datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining; - LB_Power = - ((LB_Total_Voltage2 * LB_Current2) / 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive) - - datalayer.battery.status.active_power_W = LB_Power; + datalayer.battery.status.active_power_W = ((battery_Total_Voltage2 * battery_Current2) / + 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive) //Update temperature readings. Method depends on which generation LEAF battery is used - if (LEAF_Battery_Type == ZE0_BATTERY) { + if (LEAF_battery_Type == ZE0_BATTERY) { //Since we only have average value, send the minimum as -1.0 degrees below average datalayer.battery.status.temperature_min_dC = - ((LB_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C - datalayer.battery.status.temperature_max_dC = (LB_AverageTemperature * 10); //Increase range from C to C+1 - } else if (LEAF_Battery_Type == AZE0_BATTERY) { + ((battery_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C + datalayer.battery.status.temperature_max_dC = (battery_AverageTemperature * 10); //Increase range from C to C+1 + } else if (LEAF_battery_Type == AZE0_BATTERY) { //Use the value sent constantly via CAN in 5C0 (only available on AZE0) - datalayer.battery.status.temperature_min_dC = (LB_HistData_Temperature_MIN * 10); //Increase range from C to C+1 - datalayer.battery.status.temperature_max_dC = (LB_HistData_Temperature_MAX * 10); //Increase range from C to C+1 + datalayer.battery.status.temperature_min_dC = + (battery_HistData_Temperature_MIN * 10); //Increase range from C to C+1 + datalayer.battery.status.temperature_max_dC = + (battery_HistData_Temperature_MAX * 10); //Increase range from C to C+1 } else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value) - if (temp_raw_min != 0) //We have a polled value available + if (battery_temp_raw_min != 0) //We have a polled value available { - temp_polled_min = ((Temp_fromRAW_to_F(temp_raw_min) - 320) * 5) / 9; //Convert from F to C - temp_polled_max = ((Temp_fromRAW_to_F(temp_raw_max) - 320) * 5) / 9; //Convert from F to C - if (temp_polled_min < temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function - datalayer.battery.status.temperature_min_dC = temp_polled_min; - datalayer.battery.status.temperature_max_dC = temp_polled_max; + battery_temp_polled_min = ((Temp_fromRAW_to_F(battery_temp_raw_min) - 320) * 5) / 9; //Convert from F to C + battery_temp_polled_max = ((Temp_fromRAW_to_F(battery_temp_raw_max) - 320) * 5) / 9; //Convert from F to C + if (battery_temp_polled_min < battery_temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function + datalayer.battery.status.temperature_min_dC = battery_temp_polled_min; + datalayer.battery.status.temperature_max_dC = battery_temp_polled_max; } else { - datalayer.battery.status.temperature_min_dC = temp_polled_max; - datalayer.battery.status.temperature_max_dC = temp_polled_min; + datalayer.battery.status.temperature_min_dC = battery_temp_polled_max; + datalayer.battery.status.temperature_max_dC = battery_temp_polled_min; } } } - datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W + datalayer.battery.status.max_discharge_power_W = (battery_Discharge_Power_Limit * 1000); //kW to W - datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W + datalayer.battery.status.max_charge_power_W = (battery_Charge_Power_Limit * 1000); //kW to W /*Extra safety functions below*/ - if (LB_GIDS < 10) //700Wh left in battery! - { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. + if (battery_GIDS < 10) //700Wh left in battery! + { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. set_event(EVENT_BATTERY_EMPTY, 0); datalayer.battery.status.real_soc = 0; datalayer.battery.status.max_discharge_power_W = 0; } - if (LB_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already + if (battery_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already set_event(EVENT_BATTERY_FULL, 0); datalayer.battery.status.max_charge_power_W = 0; } else { clear_event(EVENT_BATTERY_FULL); } - if (LB_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already + if (battery_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already set_event(EVENT_BATTERY_EMPTY, 0); datalayer.battery.status.max_discharge_power_W = 0; } else { clear_event(EVENT_BATTERY_EMPTY); } - if (LB_Relay_Cut_Request) { //LB_FAIL, BMS requesting shutdown and contactors to be opened + if (battery_Relay_Cut_Request) { //battery_FAIL, BMS requesting shutdown and contactors to be opened //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario datalayer.battery.status.max_discharge_power_W = 0; datalayer.battery.status.max_charge_power_W = 0; } - if (LB_Failsafe_Status > 0) // 0 is normal, start charging/discharging + if (battery_Failsafe_Status > 0) // 0 is normal, start charging/discharging { - switch (LB_Failsafe_Status) { + switch (battery_Failsafe_Status) { case (1): //Normal Stop Request //This means that battery is fully discharged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here. @@ -265,25 +307,25 @@ void update_values_battery() { /* This function maps all the values fetched via default: break; } - } else { //LB_Failsafe_Status == 0 + } else { //battery_Failsafe_Status == 0 clear_event(EVENT_BATTERY_DISCHG_STOP_REQ); clear_event(EVENT_BATTERY_CHG_STOP_REQ); clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ); } #ifdef INTERLOCK_REQUIRED - if (!LB_Interlock) { + if (!battery_Interlock) { set_event(EVENT_HVIL_FAILURE, 0); } else { clear_event(EVENT_HVIL_FAILURE); } #endif - if (LB_HeatExist) { - if (LB_Heating_Stop) { + if (battery_HeatExist) { + if (battery_Heating_Stop) { set_event(EVENT_BATTERY_WARMED_UP, 0); } - if (LB_Heating_Start) { + if (battery_Heating_Start) { set_event(EVENT_BATTERY_REQUESTS_HEAT, 0); } } @@ -291,15 +333,417 @@ void update_values_battery() { /* This function maps all the values fetched via /*Finally print out values to serial if configured to do so*/ #ifdef DEBUG_VIA_USB Serial.println("Values from battery"); - print_with_units("Real SOC%: ", (LB_SOC * 0.1), "% "); - print_with_units(", GIDS: ", LB_GIDS, " (x77Wh) "); - print_with_units(", Battery gen: ", LEAF_Battery_Type, " "); - print_with_units(", Has heater: ", LB_HeatExist, " "); + print_with_units("Real SOC%: ", (battery_SOC * 0.1), "% "); + print_with_units(", GIDS: ", battery_GIDS, " (x77Wh) "); + print_with_units(", Battery gen: ", LEAF_battery_Type, " "); + print_with_units(", Has heater: ", battery_HeatExist, " "); print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV "); print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV "); #endif } +#ifdef DOUBLE_BATTERY + +void CAN_WriteFrame(CAN_frame_t* tx_frame) { + CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 + 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); +} + +void update_values_battery2() { // Handle the values coming in from battery #2 + /* Start with mapping all values */ + + datalayer.battery2.status.soh_pptt = (battery2_StateOfHealth * 100); //Increase range from 99% -> 99.00% + + datalayer.battery2.status.real_soc = (battery2_SOC * 10); + + datalayer.battery2.status.voltage_dV = + (battery2_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701) + + datalayer.battery2.status.current_dA = + (battery2_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11) + + datalayer.battery2.info.total_capacity_Wh = (battery2_Max_GIDS * WH_PER_GID); + + datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining; + + datalayer.battery2.status.active_power_W = + ((battery2_Total_Voltage2 * battery2_Current2) / + 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive) + + //Update temperature readings. Method depends on which generation LEAF battery is used + if (LEAF_battery2_Type == ZE0_BATTERY) { + //Since we only have average value, send the minimum as -1.0 degrees below average + datalayer.battery2.status.temperature_min_dC = + ((battery2_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C + datalayer.battery2.status.temperature_max_dC = (battery2_AverageTemperature * 10); //Increase range from C to C+1 + } else if (LEAF_battery2_Type == AZE0_BATTERY) { + //Use the value sent constantly via CAN in 5C0 (only available on AZE0) + datalayer.battery2.status.temperature_min_dC = + (battery2_HistData_Temperature_MIN * 10); //Increase range from C to C+1 + datalayer.battery2.status.temperature_max_dC = + (battery2_HistData_Temperature_MAX * 10); //Increase range from C to C+1 + } else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value) + if (battery2_temp_raw_min != 0) //We have a polled value available + { + battery2_temp_polled_min = ((Temp_fromRAW_to_F(battery2_temp_raw_min) - 320) * 5) / 9; //Convert from F to C + battery2_temp_polled_max = ((Temp_fromRAW_to_F(battery2_temp_raw_max) - 320) * 5) / 9; //Convert from F to C + if (battery2_temp_polled_min < battery2_temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function + datalayer.battery2.status.temperature_min_dC = battery2_temp_polled_min; + datalayer.battery2.status.temperature_max_dC = battery2_temp_polled_max; + } else { + datalayer.battery2.status.temperature_min_dC = battery2_temp_polled_max; + datalayer.battery2.status.temperature_max_dC = battery2_temp_polled_min; + } + } + } + + datalayer.battery2.status.max_discharge_power_W = (battery2_Discharge_Power_Limit * 1000); //kW to W + + datalayer.battery2.status.max_charge_power_W = (battery2_Charge_Power_Limit * 1000); //kW to W + + /*Extra safety functions below*/ + if (battery2_GIDS < 10) //700Wh left in battery! + { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. + set_event(EVENT_BATTERY_EMPTY, 0); + datalayer.battery2.status.real_soc = 0; + datalayer.battery2.status.max_discharge_power_W = 0; + } + + if (battery2_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already + set_event(EVENT_BATTERY_FULL, 0); + datalayer.battery2.status.max_charge_power_W = 0; + } else { + clear_event(EVENT_BATTERY_FULL); + } + + if (battery2_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already + set_event(EVENT_BATTERY_EMPTY, 0); + datalayer.battery2.status.max_discharge_power_W = 0; + } else { + clear_event(EVENT_BATTERY_EMPTY); + } + + if (battery2_Relay_Cut_Request) { //battery2_FAIL, BMS requesting shutdown and contactors to be opened + //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario + datalayer.battery2.status.max_discharge_power_W = 0; + datalayer.battery2.status.max_charge_power_W = 0; + } + + if (battery2_Failsafe_Status > 0) // 0 is normal, start charging/discharging + { + switch (battery2_Failsafe_Status) { + case (1): + //Normal Stop Request + //This means that battery is fully discharged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here. + break; + case (2): + //Charging Mode Stop Request + //This means that battery is fully charged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here. + break; + case (3): + //Charging Mode Stop Request & Normal Stop Request + //Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this. + break; + case (4): + //Caution Lamp Request + set_event(EVENT_BATTERY_CAUTION, 0); + break; + case (5): + //Caution Lamp Request & Normal Stop Request + set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0); + break; + case (6): + //Caution Lamp Request & Charging Mode Stop Request + set_event(EVENT_BATTERY_CHG_STOP_REQ, 0); + break; + case (7): + //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request + set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0); + break; + default: + break; + } + } else { //battery2_Failsafe_Status == 0 + clear_event(EVENT_BATTERY_DISCHG_STOP_REQ); + clear_event(EVENT_BATTERY_CHG_STOP_REQ); + clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ); + } + +#ifdef INTERLOCK_REQUIRED + if (!battery2_Interlock) { + set_event(EVENT_HVIL_FAILURE, 0); + } else { + clear_event(EVENT_HVIL_FAILURE); + } +#endif + + if (battery2_HeatExist) { + if (battery2_Heating_Stop) { + set_event(EVENT_BATTERY_WARMED_UP, 0); + } + if (battery2_Heating_Start) { + set_event(EVENT_BATTERY_REQUESTS_HEAT, 0); + } + } +} +void receive_can_battery2(CAN_frame_t rx_frame) { + switch (rx_frame.MsgID) { + case 0x1DB: + if (is_message_corrupt(rx_frame)) { + datalayer.battery2.status.CAN_error_counter++; + break; //Message content malformed, abort reading data from it + } + battery2_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5; + if (battery2_Current2 & 0x0400) { + // negative so extend the sign bit + battery2_Current2 |= 0xf800; + } //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit + + battery2_Total_Voltage2 = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit + + //Collect various data from the BMS + battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); + battery2_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); + battery2_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5); + if (battery2_MainRelayOn_flag) { + datalayer.system.status.battery2_allows_contactor_closing = true; + } else { + datalayer.system.status.battery2_allows_contactor_closing = false; + } + battery2_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4); + battery2_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3); + break; + case 0x1DC: + if (is_message_corrupt(rx_frame)) { + datalayer.battery2.status.CAN_error_counter++; + break; //Message content malformed, abort reading data from it + } + battery2_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0); + battery2_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0); + battery2_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10); + break; + case 0x55B: + if (is_message_corrupt(rx_frame)) { + datalayer.battery2.status.CAN_error_counter++; + break; //Message content malformed, abort reading data from it + } + battery2_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6); + if (battery2_TEMP != 0x3ff) { //3FF is unavailable value + battery2_SOC = battery2_TEMP; + } + battery2_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7); + break; + case 0x5BC: + battery2_can_alive = true; + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN + + battery2_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4); + if (battery2_MAX) { + battery2_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); + //Max gids active, do nothing + //Only the 30/40/62kWh packs have this mux + } else { //Normal current GIDS value is transmitted + battery2_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); + battery2_Wh_Remaining = (battery2_GIDS * WH_PER_GID); + } + + if (LEAF_battery2_Type == ZE0_BATTERY) { + battery2_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55 + } + + battery2_TEMP = (rx_frame.data.u8[4] >> 1); + if (battery2_TEMP != 0) { + battery2_StateOfHealth = battery2_TEMP; //Collect state of health from battery + } + break; + case 0x5C0: + //This temperature only works for 2013-2017 AZE0 LEAF packs, the mux is different on other generations + if (LEAF_battery2_Type == AZE0_BATTERY) { + if ((rx_frame.data.u8[0] >> 6) == + 1) { // Battery MAX temperature. Effectively has only 7-bit precision, as the bottom bit is always 0. + battery2_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40); + } + if ((rx_frame.data.u8[0] >> 6) == + 3) { // Battery MIN temperature. Effectively has only 7-bit precision, as the bottom bit is always 0. + battery2_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40); + } + } + + battery2_HeatExist = (rx_frame.data.u8[4] & 0x01); + battery2_Heating_Stop = ((rx_frame.data.u8[0] & 0x10) >> 4); + battery2_Heating_Start = ((rx_frame.data.u8[0] & 0x20) >> 5); + battery2_Batt_Heater_Mail_Send_Request = (rx_frame.data.u8[1] & 0x01); + + break; + case 0x59E: + //AZE0 2013-2017 or ZE1 2018-2023 battery detected + //Only detect as AZE0 if not already set as ZE1 + if (LEAF_battery2_Type != ZE1_BATTERY) { + LEAF_battery2_Type = AZE0_BATTERY; + } + break; + case 0x1ED: + case 0x1C2: + //ZE1 2018-2023 battery detected! + LEAF_battery2_Type = ZE1_BATTERY; + break; + case 0x79B: + stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling! + hold_off_with_polling_10seconds = 10; //Polling is paused for 100s + break; + case 0x7BB: + //First check which group data we are getting + if (rx_frame.data.u8[0] == 0x10) { //First message of a group + battery2_group_7bb = rx_frame.data.u8[3]; + if (battery2_group_7bb != 1 && battery2_group_7bb != 2 && + battery2_group_7bb != 4) { //We are only interested in groups 1,2 and 4 + break; + } + } + + if (stop_battery_query) { //Leafspy is active, stop our own polling + break; + } + + CAN_WriteFrame(&LEAF_NEXT_LINE_REQUEST); // CAN2 + + if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc. + { + if (rx_frame.data.u8[0] == 0x10) { //First frame + //High precision battery2_current_1 resides here, but has been deemed unusable by 62kWh owners + } + if (rx_frame.data.u8[0] == 0x21) { //Second frame + //High precision battery2_current_2 resides here, but has been deemed unusable by 62kWh owners + } + + if (rx_frame.data.u8[0] == 0x23) { // Fourth frame + battery2_insulation = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); + } + + if (rx_frame.data.u8[0] == 0x24) { // Fifth frame + battery2_HX = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4; + } + } + + if (battery2_group_7bb == 2) //Cell Voltages + { + if (rx_frame.data.u8[0] == 0x10) { //first frame is anomalous + battery2_request_idx = 0; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + } + if (rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C) { //Last frame + //Last frame does not contain any cell data, calculate the result + + //Map all cell voltages to the global array + memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cell_voltages, 96 * sizeof(uint16_t)); + + //calculate min/max voltages + battery2_min_max_voltage[0] = 9999; + battery2_min_max_voltage[1] = 0; + for (battery2_cellcounter = 0; battery2_cellcounter < 96; battery2_cellcounter++) { + if (battery2_min_max_voltage[0] > battery2_cell_voltages[battery2_cellcounter]) + battery2_min_max_voltage[0] = battery2_cell_voltages[battery2_cellcounter]; + if (battery2_min_max_voltage[1] < battery2_cell_voltages[battery2_cellcounter]) + battery2_min_max_voltage[1] = battery2_cell_voltages[battery2_cellcounter]; + } + + datalayer.battery2.status.cell_max_voltage_mV = battery2_min_max_voltage[1]; + datalayer.battery2.status.cell_min_voltage_mV = battery2_min_max_voltage[0]; + + if (battery2_min_max_voltage[1] >= MAX_CELL_VOLTAGE) { + set_event(EVENT_CELL_OVER_VOLTAGE, 0); + } + if (battery2_min_max_voltage[0] <= MIN_CELL_VOLTAGE) { + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); + } + break; + } + + if ((rx_frame.data.u8[0] % 2) == 0) { //even frames + battery2_cell_voltages[battery2_request_idx++] |= rx_frame.data.u8[1]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + } else { //odd frames + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; + battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]; + battery2_cell_voltages[battery2_request_idx] = (rx_frame.data.u8[7] << 8); + } + } + + if (battery2_group_7bb == 4) { //Temperatures + if (rx_frame.data.u8[0] == 0x10) { //First message + battery2_temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery2_temp_raw_2_highnibble = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x21) { //Second message + battery2_temp_raw_2 = (battery2_temp_raw_2_highnibble << 8) | rx_frame.data.u8[1]; + battery2_temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; + battery2_temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x22) { //Third message + //All values read, let's figure out the min/max! + + if (battery2_temp_raw_3 == 65535) { //We are on a 2013+ pack that only has three temp sensors. + //Start with finding max value + battery2_temp_raw_max = battery2_temp_raw_1; + if (battery2_temp_raw_2 > battery2_temp_raw_max) { + battery2_temp_raw_max = battery2_temp_raw_2; + } + if (battery2_temp_raw_4 > battery2_temp_raw_max) { + battery2_temp_raw_max = battery2_temp_raw_4; + } + //Then find min + battery2_temp_raw_min = battery2_temp_raw_1; + if (battery2_temp_raw_2 < battery2_temp_raw_min) { + battery2_temp_raw_min = battery2_temp_raw_2; + } + if (battery2_temp_raw_4 < battery2_temp_raw_min) { + battery2_temp_raw_min = battery2_temp_raw_4; + } + } else { //All 4 temp sensors available on 2011-2012 + //Start with finding max value + battery2_temp_raw_max = battery2_temp_raw_1; + if (battery2_temp_raw_2 > battery2_temp_raw_max) { + battery2_temp_raw_max = battery2_temp_raw_2; + } + if (battery2_temp_raw_3 > battery2_temp_raw_max) { + battery2_temp_raw_max = battery2_temp_raw_3; + } + if (battery2_temp_raw_4 > battery2_temp_raw_max) { + battery2_temp_raw_max = battery2_temp_raw_4; + } + //Then find min + battery2_temp_raw_min = battery2_temp_raw_1; + if (battery2_temp_raw_2 < battery2_temp_raw_min) { + battery2_temp_raw_min = battery2_temp_raw_2; + } + if (battery2_temp_raw_3 < battery2_temp_raw_min) { + battery2_temp_raw_min = battery2_temp_raw_2; + } + if (battery2_temp_raw_4 < battery2_temp_raw_min) { + battery2_temp_raw_min = battery2_temp_raw_4; + } + } + } + } + + break; + default: + break; + } +} +#endif // DOUBLE_BATTERY + void receive_can_battery(CAN_frame_t rx_frame) { switch (rx_frame.MsgID) { case 0x1DB: @@ -307,99 +751,99 @@ void receive_can_battery(CAN_frame_t rx_frame) { datalayer.battery.status.CAN_error_counter++; break; //Message content malformed, abort reading data from it } - LB_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5; - if (LB_Current2 & 0x0400) { + battery_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5; + if (battery_Current2 & 0x0400) { // negative so extend the sign bit - LB_Current2 |= 0xf800; + battery_Current2 |= 0xf800; } //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit - LB_Total_Voltage2 = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit + battery_Total_Voltage2 = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit //Collect various data from the BMS - LB_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); - LB_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); - LB_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5); - if (LB_MainRelayOn_flag) { + battery_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); + battery_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); + battery_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5); + if (battery_MainRelayOn_flag) { datalayer.system.status.battery_allows_contactor_closing = true; } else { datalayer.system.status.battery_allows_contactor_closing = false; } - LB_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4); - LB_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3); + battery_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4); + battery_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3); break; case 0x1DC: if (is_message_corrupt(rx_frame)) { datalayer.battery.status.CAN_error_counter++; break; //Message content malformed, abort reading data from it } - LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0); - LB_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0); - LB_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10); + battery_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0); + battery_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0); + battery_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10); break; case 0x55B: if (is_message_corrupt(rx_frame)) { datalayer.battery.status.CAN_error_counter++; break; //Message content malformed, abort reading data from it } - LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6); - if (LB_TEMP != 0x3ff) { //3FF is unavailable value - LB_SOC = LB_TEMP; + battery_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6); + if (battery_TEMP != 0x3ff) { //3FF is unavailable value + battery_SOC = battery_TEMP; } - LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7); + battery_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7); break; case 0x5BC: - can_bus_alive = true; + battery_can_alive = true; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN - LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4); - if (LB_MAX) { - LB_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); + battery_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4); + if (battery_MAX) { + battery_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); //Max gids active, do nothing //Only the 30/40/62kWh packs have this mux } else { //Normal current GIDS value is transmitted - LB_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); - LB_Wh_Remaining = (LB_GIDS * WH_PER_GID); + battery_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); + battery_Wh_Remaining = (battery_GIDS * WH_PER_GID); } - if (LEAF_Battery_Type == ZE0_BATTERY) { - LB_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55 + if (LEAF_battery_Type == ZE0_BATTERY) { + battery_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55 } - LB_TEMP = (rx_frame.data.u8[4] >> 1); - if (LB_TEMP != 0) { - LB_StateOfHealth = LB_TEMP; //Collect state of health from battery + battery_TEMP = (rx_frame.data.u8[4] >> 1); + if (battery_TEMP != 0) { + battery_StateOfHealth = battery_TEMP; //Collect state of health from battery } break; case 0x5C0: //This temperature only works for 2013-2017 AZE0 LEAF packs, the mux is different on other generations - if (LEAF_Battery_Type == AZE0_BATTERY) { + if (LEAF_battery_Type == AZE0_BATTERY) { if ((rx_frame.data.u8[0] >> 6) == 1) { // Battery MAX temperature. Effectively has only 7-bit precision, as the bottom bit is always 0. - LB_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40); + battery_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40); } if ((rx_frame.data.u8[0] >> 6) == 3) { // Battery MIN temperature. Effectively has only 7-bit precision, as the bottom bit is always 0. - LB_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40); + battery_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40); } } - LB_HeatExist = (rx_frame.data.u8[4] & 0x01); - LB_Heating_Stop = ((rx_frame.data.u8[0] & 0x10) >> 4); - LB_Heating_Start = ((rx_frame.data.u8[0] & 0x20) >> 5); - Batt_Heater_Mail_Send_Request = (rx_frame.data.u8[1] & 0x01); + battery_HeatExist = (rx_frame.data.u8[4] & 0x01); + battery_Heating_Stop = ((rx_frame.data.u8[0] & 0x10) >> 4); + battery_Heating_Start = ((rx_frame.data.u8[0] & 0x20) >> 5); + battery_Batt_Heater_Mail_Send_Request = (rx_frame.data.u8[1] & 0x01); break; case 0x59E: //AZE0 2013-2017 or ZE1 2018-2023 battery detected //Only detect as AZE0 if not already set as ZE1 - if (LEAF_Battery_Type != ZE1_BATTERY) { - LEAF_Battery_Type = AZE0_BATTERY; + if (LEAF_battery_Type != ZE1_BATTERY) { + LEAF_battery_Type = AZE0_BATTERY; } break; case 0x1ED: case 0x1C2: //ZE1 2018-2023 battery detected! - LEAF_Battery_Type = ZE1_BATTERY; + LEAF_battery_Type = ZE1_BATTERY; break; case 0x79B: stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling! @@ -430,11 +874,11 @@ void receive_can_battery(CAN_frame_t rx_frame) { } if (rx_frame.data.u8[0] == 0x23) { // Fourth frame - insulation = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); + battery_insulation = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); } if (rx_frame.data.u8[0] == 0x24) { // Fifth frame - HX = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4; + battery_HX = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4; } } @@ -442,103 +886,103 @@ void receive_can_battery(CAN_frame_t rx_frame) { { if (rx_frame.data.u8[0] == 0x10) { //first frame is anomalous battery_request_idx = 0; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; break; } if (rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C) { //Last frame //Last frame does not contain any cell data, calculate the result //Map all cell voltages to the global array - memcpy(datalayer.battery.status.cell_voltages_mV, cell_voltages, 96 * sizeof(uint16_t)); + memcpy(datalayer.battery.status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t)); //calculate min/max voltages - min_max_voltage[0] = 9999; - min_max_voltage[1] = 0; - for (cellcounter = 0; cellcounter < 96; cellcounter++) { - if (min_max_voltage[0] > cell_voltages[cellcounter]) - min_max_voltage[0] = cell_voltages[cellcounter]; - if (min_max_voltage[1] < cell_voltages[cellcounter]) - min_max_voltage[1] = cell_voltages[cellcounter]; + battery_min_max_voltage[0] = 9999; + battery_min_max_voltage[1] = 0; + for (battery_cellcounter = 0; battery_cellcounter < 96; battery_cellcounter++) { + if (battery_min_max_voltage[0] > battery_cell_voltages[battery_cellcounter]) + battery_min_max_voltage[0] = battery_cell_voltages[battery_cellcounter]; + if (battery_min_max_voltage[1] < battery_cell_voltages[battery_cellcounter]) + battery_min_max_voltage[1] = battery_cell_voltages[battery_cellcounter]; } - datalayer.battery.status.cell_max_voltage_mV = min_max_voltage[1]; - datalayer.battery.status.cell_min_voltage_mV = min_max_voltage[0]; + datalayer.battery.status.cell_max_voltage_mV = battery_min_max_voltage[1]; + datalayer.battery.status.cell_min_voltage_mV = battery_min_max_voltage[0]; - if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) { + if (battery_min_max_voltage[1] >= MAX_CELL_VOLTAGE) { set_event(EVENT_CELL_OVER_VOLTAGE, 0); } - if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) { + if (battery_min_max_voltage[0] <= MIN_CELL_VOLTAGE) { set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } break; } if ((rx_frame.data.u8[0] % 2) == 0) { //even frames - cell_voltages[battery_request_idx++] |= rx_frame.data.u8[1]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + battery_cell_voltages[battery_request_idx++] |= rx_frame.data.u8[1]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; } else { //odd frames - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; - cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]; - cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8); + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; + battery_cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]; + battery_cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8); } } if (group_7bb == 4) { //Temperatures if (rx_frame.data.u8[0] == 0x10) { //First message - temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; - temp_raw_2_highnibble = rx_frame.data.u8[7]; + battery_temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + battery_temp_raw_2_highnibble = rx_frame.data.u8[7]; } if (rx_frame.data.u8[0] == 0x21) { //Second message - temp_raw_2 = (temp_raw_2_highnibble << 8) | rx_frame.data.u8[1]; - temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; - temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + battery_temp_raw_2 = (battery_temp_raw_2_highnibble << 8) | rx_frame.data.u8[1]; + battery_temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; + battery_temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; } if (rx_frame.data.u8[0] == 0x22) { //Third message //All values read, let's figure out the min/max! - if (temp_raw_3 == 65535) { //We are on a 2013+ pack that only has three temp sensors. + if (battery_temp_raw_3 == 65535) { //We are on a 2013+ pack that only has three temp sensors. //Start with finding max value - temp_raw_max = temp_raw_1; - if (temp_raw_2 > temp_raw_max) { - temp_raw_max = temp_raw_2; + battery_temp_raw_max = battery_temp_raw_1; + if (battery_temp_raw_2 > battery_temp_raw_max) { + battery_temp_raw_max = battery_temp_raw_2; } - if (temp_raw_4 > temp_raw_max) { - temp_raw_max = temp_raw_4; + if (battery_temp_raw_4 > battery_temp_raw_max) { + battery_temp_raw_max = battery_temp_raw_4; } //Then find min - temp_raw_min = temp_raw_1; - if (temp_raw_2 < temp_raw_min) { - temp_raw_min = temp_raw_2; + battery_temp_raw_min = battery_temp_raw_1; + if (battery_temp_raw_2 < battery_temp_raw_min) { + battery_temp_raw_min = battery_temp_raw_2; } - if (temp_raw_4 < temp_raw_min) { - temp_raw_min = temp_raw_4; + if (battery_temp_raw_4 < battery_temp_raw_min) { + battery_temp_raw_min = battery_temp_raw_4; } } else { //All 4 temp sensors available on 2011-2012 //Start with finding max value - temp_raw_max = temp_raw_1; - if (temp_raw_2 > temp_raw_max) { - temp_raw_max = temp_raw_2; + battery_temp_raw_max = battery_temp_raw_1; + if (battery_temp_raw_2 > battery_temp_raw_max) { + battery_temp_raw_max = battery_temp_raw_2; } - if (temp_raw_3 > temp_raw_max) { - temp_raw_max = temp_raw_3; + if (battery_temp_raw_3 > battery_temp_raw_max) { + battery_temp_raw_max = battery_temp_raw_3; } - if (temp_raw_4 > temp_raw_max) { - temp_raw_max = temp_raw_4; + if (battery_temp_raw_4 > battery_temp_raw_max) { + battery_temp_raw_max = battery_temp_raw_4; } //Then find min - temp_raw_min = temp_raw_1; - if (temp_raw_2 < temp_raw_min) { - temp_raw_min = temp_raw_2; + battery_temp_raw_min = battery_temp_raw_1; + if (battery_temp_raw_2 < battery_temp_raw_min) { + battery_temp_raw_min = battery_temp_raw_2; } - if (temp_raw_3 < temp_raw_min) { - temp_raw_min = temp_raw_2; + if (battery_temp_raw_3 < battery_temp_raw_min) { + battery_temp_raw_min = battery_temp_raw_2; } - if (temp_raw_4 < temp_raw_min) { - temp_raw_min = temp_raw_4; + if (battery_temp_raw_4 < battery_temp_raw_min) { + battery_temp_raw_min = battery_temp_raw_4; } } } @@ -550,7 +994,7 @@ void receive_can_battery(CAN_frame_t rx_frame) { } } void send_can_battery() { - if (can_bus_alive) { + if (battery_can_alive) { unsigned long currentMillis = millis(); @@ -583,6 +1027,9 @@ void send_can_battery() { break; } ESP32Can.CANWriteFrame(&LEAF_1D4); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&LEAF_1D4); // CAN2 +#endif // DOUBLE_BATTERY switch (mprun10r) { case (0): @@ -676,6 +1123,9 @@ void send_can_battery() { //Only send this message when NISSANLEAF_CHARGER is not defined (otherwise it will collide!) #ifndef NISSANLEAF_CHARGER ESP32Can.CANWriteFrame(&LEAF_1F2); //Contains (CHG_STA_RQ == 1 == Normal Charge) +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&LEAF_1F2); // CAN2 +#endif // DOUBLE_BATTERY #endif mprun10r = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0 @@ -688,7 +1138,7 @@ void send_can_battery() { previousMillis100 = currentMillis; //When battery requests heating pack status change, ack this - if (Batt_Heater_Mail_Send_Request) { + if (battery_Batt_Heater_Mail_Send_Request) { LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK } else { LEAF_50B.data.u8[6] = 0x00; //Batt_Heater_Mail_Send_NG @@ -696,6 +1146,9 @@ void send_can_battery() { // VCM message, containing info if battery should sleep or stay awake ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&LEAF_50B); // CAN2 +#endif // DOUBLE_BATTERY LEAF_50C.data.u8[3] = mprun100; switch (mprun100) { @@ -717,6 +1170,9 @@ void send_can_battery() { break; } ESP32Can.CANWriteFrame(&LEAF_50C); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&LEAF_50C); // CAN2 +#endif // DOUBLE_BATTERY mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1... } @@ -731,6 +1187,9 @@ void send_can_battery() { // Cycle between group 1, 2, and 4 using ternary operation LEAF_GROUP_REQUEST.data.u8[2] = group; ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST); +#ifdef DOUBLE_BATTERY + CAN_WriteFrame(&LEAF_GROUP_REQUEST); // CAN2 +#endif // DOUBLE_BATTERY } if (hold_off_with_polling_10seconds > 0) { diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index e2b1a7d9..e7649a03 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -4,6 +4,9 @@ #include "../include.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" +#include "../lib/pierremolinaro-acan2515/ACAN2515.h" + +extern ACAN2515 can; #define BATTERY_SELECTED #define MAX_CELL_DEVIATION_MV 500 diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 8ddb676c..172645d9 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -133,4 +133,21 @@ void update_machineryprotection() { } else { clear_event(EVENT_CAN_RX_WARNING); } + +#ifdef DOUBLE_BATTERY + // Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error + if (!datalayer.battery2.status.CAN_battery_still_alive) { + set_event(EVENT_CAN_RX_FAILURE, 0); + } else { + datalayer.battery2.status.CAN_battery_still_alive--; + clear_event(EVENT_CAN_RX_FAILURE); + } + + // Too many malformed CAN messages recieved! + if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) { + set_event(EVENT_CAN_RX_WARNING, 0); + } else { + clear_event(EVENT_CAN_RX_WARNING); + } +#endif } From 12ec967e5d209ce853e9c37e175070102aa773d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 11 Jul 2024 19:09:21 +0300 Subject: [PATCH 04/11] Safety tweaks for double battery --- Software/Software.ino | 2 +- Software/src/battery/BMW-I3-BATTERY.cpp | 2 +- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 2 +- Software/src/devboard/safety/safety.cpp | 38 ++++++++++++++----- Software/src/devboard/utils/events.cpp | 7 +++- Software/src/devboard/utils/events.h | 5 ++- Software/src/devboard/webserver/webserver.cpp | 15 ++++---- 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 438be072..b0e12606 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -606,7 +606,7 @@ void handle_CAN_contactors() { if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 30) { // If we are within 3.0V clear_event(EVENT_VOLTAGE_DIFFERENCE); - if (datalayer.battery2.status.bms_status != FAULT) { // Only proceed if BMS on battery2 is not faulted + if (datalayer.battery.status.bms_status != FAULT) { // Only proceed if we are not in faulted state datalayer.system.status.battery2_allows_contactor_closing = true; } } else { //We are over 3.0V diff diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index fde63b8f..5c8e93dc 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -457,7 +457,7 @@ static uint8_t battery2_status_diagnosis_powertrain_maximum_multiplexer = 0; static uint8_t battery2_status_diagnosis_powertrain_immediate_multiplexer = 0; static uint8_t battery2_ID2 = 0; static uint8_t battery2_cellvoltage_mux = 0; -static uint8_t battery2_soh = 0; +static uint8_t battery2_soh = 99; static uint8_t message_data[50]; static uint8_t next_data = 0; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 12c18df4..99c13777 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -811,7 +811,7 @@ void receive_can_battery(CAN_frame_t rx_frame) { battery_TEMP = (rx_frame.data.u8[4] >> 1); if (battery_TEMP != 0) { - battery_StateOfHealth = battery_TEMP; //Collect state of health from battery + battery_StateOfHealth = (uint8_t)battery_TEMP; //Collect state of health from battery } break; case 0x5C0: diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 172645d9..65617a83 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -7,6 +7,8 @@ static uint8_t discharge_limit_failures = 0; static bool battery_full_event_fired = false; static bool battery_empty_event_fired = false; +#define MAX_SOH_DEVIATION_PPTT 2500 + void update_machineryprotection() { // Start checking that the battery is within reason. Incase we see any funny business, raise an event! @@ -67,9 +69,9 @@ void update_machineryprotection() { // Battery is extremely degraded, not fit for secondlifestorage! if (datalayer.battery.status.soh_pptt < 2500) { - set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt); + set_event(EVENT_SOH_LOW, datalayer.battery.status.soh_pptt); } else { - clear_event(EVENT_LOW_SOH); + clear_event(EVENT_SOH_LOW); } // Check if SOC% is plausible @@ -129,25 +131,43 @@ void update_machineryprotection() { // Too many malformed CAN messages recieved! if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) { - set_event(EVENT_CAN_RX_WARNING, 0); + set_event(EVENT_CAN_RX_WARNING, 1); } else { clear_event(EVENT_CAN_RX_WARNING); } -#ifdef DOUBLE_BATTERY - // Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error +#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here + // Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise an error if (!datalayer.battery2.status.CAN_battery_still_alive) { - set_event(EVENT_CAN_RX_FAILURE, 0); + set_event(EVENT_CAN2_RX_FAILURE, 0); } else { datalayer.battery2.status.CAN_battery_still_alive--; - clear_event(EVENT_CAN_RX_FAILURE); + clear_event(EVENT_CAN2_RX_FAILURE); } // Too many malformed CAN messages recieved! if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) { - set_event(EVENT_CAN_RX_WARNING, 0); + set_event(EVENT_CAN_RX_WARNING, 2); } else { clear_event(EVENT_CAN_RX_WARNING); } -#endif + + // Check if SOH% between the packs is too large + if((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)){ + // Both values available, check diff + uint16_t soh_diff_pptt; + if(datalayer.battery.status.soh_pptt > datalayer.battery2.status.soh_pptt) { + soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery2.status.soh_pptt; + } else { + soh_diff_pptt = datalayer.battery2.status.soh_pptt - datalayer.battery.status.soh_pptt; + } + + if(soh_diff_pptt > MAX_SOH_DEVIATION_PPTT){ + set_event(EVENT_SOH_DIFFERENCE, MAX_SOH_DEVIATION_PPTT); + } else { + clear_event(EVENT_SOH_DIFFERENCE); + } + } + +#endif // DOUBLE_BATTERY } diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index eca9c20b..14dc1ea8 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -157,7 +157,8 @@ void init_events(void) { events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING; events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO; - events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR; + events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING; + events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR; events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO; events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR; @@ -276,7 +277,9 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!"; case EVENT_VOLTAGE_DIFFERENCE: return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link"; - case EVENT_LOW_SOH: + case EVENT_SOH_DIFFERENCE: + return "Warning: Large deviation in State of health between packs. Inspect battery."; + case EVENT_SOH_LOW: return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle " "battery."; case EVENT_HVIL_FAILURE: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index afda0f1e..850d5d17 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0009 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0010 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -53,7 +53,8 @@ XX(EVENT_BATTERY_REQUESTS_HEAT) \ XX(EVENT_BATTERY_WARMED_UP) \ XX(EVENT_VOLTAGE_DIFFERENCE) \ - XX(EVENT_LOW_SOH) \ + XX(EVENT_SOH_DIFFERENCE) \ + XX(EVENT_SOH_LOW) \ XX(EVENT_HVIL_FAILURE) \ XX(EVENT_PRECHARGE_FAILURE) \ XX(EVENT_INTERNAL_OPEN_FAULT) \ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index c1f4fdb9..f3fe9e53 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -643,7 +643,7 @@ String processor(const String& var) { #ifdef DOUBLE_BATTERY content += "
Back to main page"; + // Start a new block with a specific background color content += "
"; @@ -33,7 +45,26 @@ String cellmonitor_processor(const String& var) { // Close the block content += "
"; + +#ifdef DOUBLE_BATTERY + // Start a new block with a specific background color + content += "
"; + + // Display max, min, and deviation voltage values + content += "
"; + // Display cells + content += "
"; + // Display bars + content += "
"; + // Display single hovered value + content += "
Value: ...
"; + + // Close the block + content += "
"; + content += ""; +#endif // DOUBLE_BATTERY + content += ""; return content; From 5f0301d67292c7194976cac9106856a634d1ae16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 11 Jul 2024 23:25:08 +0300 Subject: [PATCH 07/11] Make builds pass --- Software/USER_SETTINGS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 5c3188d5..d584402b 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -30,7 +30,7 @@ /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus From f46dd6048fce2cd7b3825b3d351c94da39549421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 12 Jul 2024 15:05:00 +0300 Subject: [PATCH 08/11] Add double battery support for Tesla S/3/X/Y --- Software/Software.ino | 4 +- Software/USER_SETTINGS.h | 2 +- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 16 +- .../src/battery/TESLA-MODEL-3-BATTERY.cpp | 1245 ++++++++++++----- Software/src/battery/TESLA-MODEL-3-BATTERY.h | 5 + 5 files changed, 939 insertions(+), 333 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 008ffea8..d38a5132 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -228,7 +228,7 @@ void core_loop(void* task_time_us) { handle_contactors(); // Take care of startup precharge/contactor closing #endif #ifdef DOUBLE_BATTERY - handle_CAN_contactors(); + check_interconnect_available(); #endif } END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); @@ -599,7 +599,7 @@ void send_can2() { #endif #ifdef DOUBLE_BATTERY -void handle_CAN_contactors() { +void check_interconnect_available() { if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) { return; // Both voltage values need to be available to start check } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index d584402b..41115478 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -43,7 +43,7 @@ //#define HW_STARK /* Other options */ -//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) +#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) //#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production) //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 48e527cb..53b02413 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -452,19 +452,19 @@ void update_values_battery2() { // Handle the values coming in from battery #2 break; case (4): //Caution Lamp Request - set_event(EVENT_BATTERY_CAUTION, 0); + set_event(EVENT_BATTERY_CAUTION, 2); break; case (5): //Caution Lamp Request & Normal Stop Request - set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0); + set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 2); break; case (6): //Caution Lamp Request & Charging Mode Stop Request - set_event(EVENT_BATTERY_CHG_STOP_REQ, 0); + set_event(EVENT_BATTERY_CHG_STOP_REQ, 2); break; case (7): //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request - set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0); + set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 2); break; default: break; @@ -477,7 +477,7 @@ void update_values_battery2() { // Handle the values coming in from battery #2 #ifdef INTERLOCK_REQUIRED if (!battery2_Interlock) { - set_event(EVENT_HVIL_FAILURE, 0); + set_event(EVENT_HVIL_FAILURE, 2); } else { clear_event(EVENT_HVIL_FAILURE); } @@ -485,10 +485,10 @@ void update_values_battery2() { // Handle the values coming in from battery #2 if (battery2_HeatExist) { if (battery2_Heating_Stop) { - set_event(EVENT_BATTERY_WARMED_UP, 0); + set_event(EVENT_BATTERY_WARMED_UP, 2); } if (battery2_Heating_Start) { - set_event(EVENT_BATTERY_REQUESTS_HEAT, 0); + set_event(EVENT_BATTERY_REQUESTS_HEAT, 2); } } } @@ -1257,4 +1257,4 @@ void setup_battery(void) { // Performs one time setup at startup #endif //DOUBLE_BATTERY } -#endif +#endif //NISSAN_LEAF_BATTERY diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index e88e476b..7981ffce 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -28,105 +28,214 @@ CAN_frame_t TESLA_221_2 = { .MsgID = 0x221, .data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive -static uint32_t total_discharge = 0; -static uint32_t total_charge = 0; -static uint16_t volts = 0; // V -static int16_t amps = 0; // A -static uint16_t raw_amps = 0; // A -static int16_t max_temp = 0; // C* -static int16_t min_temp = 0; // C* -static uint16_t energy_buffer = 0; -static uint16_t energy_to_charge_complete = 0; -static uint16_t expected_energy_remaining = 0; -static uint8_t full_charge_complete = 0; -static uint16_t ideal_energy_remaining = 0; -static uint16_t nominal_energy_remaining = 0; -static uint16_t nominal_full_pack_energy = 600; -static uint16_t bat_beginning_of_life = 600; +static uint32_t battery_total_discharge = 0; +static uint32_t battery_total_charge = 0; +static uint16_t battery_volts = 0; // V +static int16_t battery_amps = 0; // A +static uint16_t battery_raw_amps = 0; // A +static int16_t battery_max_temp = 0; // C* +static int16_t battery_min_temp = 0; // C* +static uint16_t battery_energy_buffer = 0; +static uint16_t battery_energy_to_charge_complete = 0; +static uint16_t battery_expected_energy_remaining = 0; +static uint8_t battery_full_charge_complete = 0; +static uint16_t battery_ideal_energy_remaining = 0; +static uint16_t battery_nominal_energy_remaining = 0; +static uint16_t battery_nominal_full_pack_energy = 600; +static uint16_t battery_beginning_of_life = 600; static uint16_t battery_charge_time_remaining = 0; // Minutes -static uint16_t regenerative_limit = 0; -static uint16_t discharge_limit = 0; -static uint16_t max_heat_park = 0; -static uint16_t hvac_max_power = 0; -static uint16_t max_discharge_current = 0; -static uint16_t max_charge_current = 0; -static uint16_t bms_max_voltage = 0; -static uint16_t bms_min_voltage = 0; -static uint16_t high_voltage = 0; -static uint16_t low_voltage = 0; -static uint16_t output_current = 0; -static uint16_t soc_min = 0; -static uint16_t soc_max = 0; -static uint16_t soc_vi = 0; -static uint16_t soc_ave = 0; -static uint16_t cell_max_v = 3700; -static uint16_t cell_min_v = 3700; -static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV -static uint8_t max_vno = 0; -static uint8_t min_vno = 0; -static uint8_t contactor = 0; //State of contactor -static uint8_t hvil_status = 0; -static uint8_t packContNegativeState = 0; -static uint8_t packContPositiveState = 0; -static uint8_t packContactorSetState = 0; -static uint8_t packCtrsClosingAllowed = 0; -static uint8_t pyroTestInProgress = 0; -static uint8_t send221still = 10; +static uint16_t battery_regenerative_limit = 0; +static uint16_t battery_discharge_limit = 0; +static uint16_t battery_max_heat_park = 0; +static uint16_t battery_hvac_max_power = 0; +static uint16_t battery_max_discharge_current = 0; +static uint16_t battery_max_charge_current = 0; +static uint16_t battery_bms_max_voltage = 0; +static uint16_t battery_bms_min_voltage = 0; +static uint16_t battery_high_voltage = 0; +static uint16_t battery_low_voltage = 0; +static uint16_t battery_output_current = 0; +static uint16_t battery_soc_min = 0; +static uint16_t battery_soc_max = 0; +static uint16_t battery_soc_vi = 0; +static uint16_t battery_soc_ave = 0; +static uint16_t battery_cell_max_v = 3700; +static uint16_t battery_cell_min_v = 3700; +static uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV +static uint8_t battery_max_vno = 0; +static uint8_t battery_min_vno = 0; +static uint8_t battery_contactor = 0; //State of contactor +static uint8_t battery_hvil_status = 0; +static uint8_t battery_packContNegativeState = 0; +static uint8_t battery_packContPositiveState = 0; +static uint8_t battery_packContactorSetState = 0; +static uint8_t battery_packCtrsClosingAllowed = 0; +static uint8_t battery_pyroTestInProgress = 0; //Fault codes -static uint8_t WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. -static uint8_t PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. -static uint8_t SwAssertion = 0; //An internal software assertion has failed. -static uint8_t CrashEvent = 0; //Warns if the crash signal is detected by HVP -static uint8_t OverDchgCurrentFault = 0; //Warns if the pack discharge is above max discharge current limit -static uint8_t OverChargeCurrentFault = 0; //Warns if the pack discharge current is above max charge current limit -static uint8_t OverCurrentFault = 0; //Warns if the pack current (discharge or charge) is above max current limit. -static uint8_t OverTemperatureFault = 0; //A pack module temperature is above maximum temperature limit -static uint8_t OverVoltageFault = 0; //A brick voltage is above maximum voltage limit -static uint8_t UnderVoltageFault = 0; //A brick voltage is below minimum voltage limit -static uint8_t PrimaryBmbMiaFault = 0; //Warns if the voltage and temperature readings from primary BMB chain are mia -static uint8_t SecondaryBmbMiaFault = +static uint8_t battery_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. +static uint8_t battery_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. +static uint8_t battery_SwAssertion = 0; //An internal software assertion has failed. +static uint8_t battery_CrashEvent = 0; //Warns if the crash signal is detected by HVP +static uint8_t battery_OverDchgCurrentFault = 0; //Warns if the pack discharge is above max discharge current limit +static uint8_t battery_OverChargeCurrentFault = + 0; //Warns if the pack discharge current is above max charge current limit +static uint8_t battery_OverCurrentFault = + 0; //Warns if the pack current (discharge or charge) is above max current limit. +static uint8_t battery_OverTemperatureFault = 0; //A pack module temperature is above maximum temperature limit +static uint8_t battery_OverVoltageFault = 0; //A brick voltage is above maximum voltage limit +static uint8_t battery_UnderVoltageFault = 0; //A brick voltage is below minimum voltage limit +static uint8_t battery_PrimaryBmbMiaFault = + 0; //Warns if the voltage and temperature readings from primary BMB chain are mia +static uint8_t battery_SecondaryBmbMiaFault = 0; //Warns if the voltage and temperature readings from secondary BMB chain are mia -static uint8_t BmbMismatchFault = - 0; //Warns if the primary and secondary BMB chain readings don't match with each other -static uint8_t BmsHviMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t CpMiaFault = 0; //Warns if the CP node is mia on HVS CAN -static uint8_t PcsMiaFault = 0; //The PCS node is mia on HVS CAN -static uint8_t BmsFault = 0; //Warns if the BMS ECU has faulted -static uint8_t PcsFault = 0; //Warns if the PCS ECU has faulted -static uint8_t CpFault = 0; //Warns if the CP ECU has faulted -static uint8_t ShuntHwMiaFault = 0; //Warns if the shunt current reading is not available -static uint8_t PyroMiaFault = 0; //Warns if the pyro squib is not connected -static uint8_t hvsMiaFault = 0; //Warns if the pack contactor hw fault -static uint8_t hviMiaFault = 0; //Warns if the FC contactor hw fault -static uint8_t Supply12vFault = 0; //Warns if the low voltage (12V) battery is below minimum voltage threshold -static uint8_t VerSupplyFault = 0; //Warns if the Energy reserve voltage supply is below minimum voltage threshold -static uint8_t HvilFault = 0; //Warn if a High Voltage Inter Lock fault is detected -static uint8_t BmsHvsMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t PackVoltMismatchFault = - 0; //Warns if the pack voltage doesn't match approximately with sum of brick voltages -static uint8_t EnsMiaFault = 0; //Warns if the ENS line is not connected to HVC -static uint8_t PackPosCtrArcFault = 0; //Warns if the HVP detectes series arc at pack contactor -static uint8_t packNegCtrArcFault = 0; //Warns if the HVP detectes series arc at FC contactor -static uint8_t ShuntHwAndBmsMiaFault = 0; -static uint8_t fcContHwFault = 0; -static uint8_t robinOverVoltageFault = 0; -static uint8_t packContHwFault = 0; -static uint8_t pyroFuseBlown = 0; -static uint8_t pyroFuseFailedToBlow = 0; -static uint8_t CpilFault = 0; -static uint8_t PackContactorFellOpen = 0; -static uint8_t FcContactorFellOpen = 0; -static uint8_t packCtrCloseBlocked = 0; -static uint8_t fcCtrCloseBlocked = 0; -static uint8_t packContactorForceOpen = 0; -static uint8_t fcContactorForceOpen = 0; -static uint8_t dcLinkOverVoltage = 0; -static uint8_t shuntOverTemperature = 0; -static uint8_t passivePyroDeploy = 0; -static uint8_t logUploadRequest = 0; -static uint8_t packCtrCloseFailed = 0; -static uint8_t fcCtrCloseFailed = 0; -static uint8_t shuntThermistorMia = 0; +static uint8_t battery_BmbMismatchFault = + 0; //Warns if the primary and secondary BMB chain readings don't match with each other +static uint8_t battery_BmsHviMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN +static uint8_t battery_CpMiaFault = 0; //Warns if the CP node is mia on HVS CAN +static uint8_t battery_PcsMiaFault = 0; //The PCS node is mia on HVS CAN +static uint8_t battery_BmsFault = 0; //Warns if the BMS ECU has faulted +static uint8_t battery_PcsFault = 0; //Warns if the PCS ECU has faulted +static uint8_t battery_CpFault = 0; //Warns if the CP ECU has faulted +static uint8_t battery_ShuntHwMiaFault = 0; //Warns if the shunt current reading is not available +static uint8_t battery_PyroMiaFault = 0; //Warns if the pyro squib is not connected +static uint8_t battery_hvsMiaFault = 0; //Warns if the pack contactor hw fault +static uint8_t battery_hviMiaFault = 0; //Warns if the FC contactor hw fault +static uint8_t battery_Supply12vFault = 0; //Warns if the low voltage (12V) battery is below minimum voltage threshold +static uint8_t battery_VerSupplyFault = + 0; //Warns if the Energy reserve voltage supply is below minimum voltage threshold +static uint8_t battery_HvilFault = 0; //Warn if a High Voltage Inter Lock fault is detected +static uint8_t battery_BmsHvsMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN +static uint8_t battery_PackVoltMismatchFault = + 0; //Warns if the pack voltage doesn't match approximately with sum of brick voltages +static uint8_t battery_EnsMiaFault = 0; //Warns if the ENS line is not connected to HVC +static uint8_t battery_PackPosCtrArcFault = 0; //Warns if the HVP detectes series arc at pack contactor +static uint8_t battery_packNegCtrArcFault = 0; //Warns if the HVP detectes series arc at FC contactor +static uint8_t battery_ShuntHwAndBmsMiaFault = 0; +static uint8_t battery_fcContHwFault = 0; +static uint8_t battery_robinOverVoltageFault = 0; +static uint8_t battery_packContHwFault = 0; +static uint8_t battery_pyroFuseBlown = 0; +static uint8_t battery_pyroFuseFailedToBlow = 0; +static uint8_t battery_CpilFault = 0; +static uint8_t battery_PackContactorFellOpen = 0; +static uint8_t battery_FcContactorFellOpen = 0; +static uint8_t battery_packCtrCloseBlocked = 0; +static uint8_t battery_fcCtrCloseBlocked = 0; +static uint8_t battery_packContactorForceOpen = 0; +static uint8_t battery_fcContactorForceOpen = 0; +static uint8_t battery_dcLinkOverVoltage = 0; +static uint8_t battery_shuntOverTemperature = 0; +static uint8_t battery_passivePyroDeploy = 0; +static uint8_t battery_logUploadRequest = 0; +static uint8_t battery_packCtrCloseFailed = 0; +static uint8_t battery_fcCtrCloseFailed = 0; +static uint8_t battery_shuntThermistorMia = 0; + +#ifdef DOUBLE_BATTERY +static uint32_t battery2_total_discharge = 0; +static uint32_t battery2_total_charge = 0; +static uint16_t battery2_volts = 0; // V +static int16_t battery2_amps = 0; // A +static uint16_t battery2_raw_amps = 0; // A +static int16_t battery2_max_temp = 0; // C* +static int16_t battery2_min_temp = 0; // C* +static uint16_t battery2_energy_buffer = 0; +static uint16_t battery2_energy_to_charge_complete = 0; +static uint16_t battery2_expected_energy_remaining = 0; +static uint8_t battery2_full_charge_complete = 0; +static uint16_t battery2_ideal_energy_remaining = 0; +static uint16_t battery2_nominal_energy_remaining = 0; +static uint16_t battery2_nominal_full_pack_energy = 600; +static uint16_t battery2_beginning_of_life = 600; +static uint16_t battery2_charge_time_remaining = 0; // Minutes +static uint16_t battery2_regenerative_limit = 0; +static uint16_t battery2_discharge_limit = 0; +static uint16_t battery2_max_heat_park = 0; +static uint16_t battery2_hvac_max_power = 0; +static uint16_t battery2_max_discharge_current = 0; +static uint16_t battery2_max_charge_current = 0; +static uint16_t battery2_bms_max_voltage = 0; +static uint16_t battery2_bms_min_voltage = 0; +static uint16_t battery2_high_voltage = 0; +static uint16_t battery2_low_voltage = 0; +static uint16_t battery2_output_current = 0; +static uint16_t battery2_soc_min = 0; +static uint16_t battery2_soc_max = 0; +static uint16_t battery2_soc_vi = 0; +static uint16_t battery2_soc_ave = 0; +static uint16_t battery2_cell_max_v = 3700; +static uint16_t battery2_cell_min_v = 3700; +static uint16_t battery2_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV +static uint8_t battery2_max_vno = 0; +static uint8_t battery2_min_vno = 0; +static uint8_t battery2_contactor = 0; //State of contactor +static uint8_t battery2_hvil_status = 0; +static uint8_t battery2_packContNegativeState = 0; +static uint8_t battery2_packContPositiveState = 0; +static uint8_t battery2_packContactorSetState = 0; +static uint8_t battery2_packCtrsClosingAllowed = 0; +static uint8_t battery2_pyroTestInProgress = 0; +//Fault codes +static uint8_t battery2_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. +static uint8_t battery2_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. +static uint8_t battery2_SwAssertion = 0; //An internal software assertion has failed. +static uint8_t battery2_CrashEvent = 0; //Warns if the crash signal is detected by HVP +static uint8_t battery2_OverDchgCurrentFault = 0; //Warns if the pack discharge is above max discharge current limit +static uint8_t battery2_OverChargeCurrentFault = + 0; //Warns if the pack discharge current is above max charge current limit +static uint8_t battery2_OverCurrentFault = + 0; //Warns if the pack current (discharge or charge) is above max current limit. +static uint8_t battery2_OverTemperatureFault = 0; //A pack module temperature is above maximum temperature limit +static uint8_t battery2_OverVoltageFault = 0; //A brick voltage is above maximum voltage limit +static uint8_t battery2_UnderVoltageFault = 0; //A brick voltage is below minimum voltage limit +static uint8_t battery2_PrimaryBmbMiaFault = + 0; //Warns if the voltage and temperature readings from primary BMB chain are mia +static uint8_t battery2_SecondaryBmbMiaFault = + 0; //Warns if the voltage and temperature readings from secondary BMB chain are mia +static uint8_t battery2_BmbMismatchFault = + 0; //Warns if the primary and secondary BMB chain readings don't match with each other +static uint8_t battery2_BmsHviMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN +static uint8_t battery2_CpMiaFault = 0; //Warns if the CP node is mia on HVS CAN +static uint8_t battery2_PcsMiaFault = 0; //The PCS node is mia on HVS CAN +static uint8_t battery2_BmsFault = 0; //Warns if the BMS ECU has faulted +static uint8_t battery2_PcsFault = 0; //Warns if the PCS ECU has faulted +static uint8_t battery2_CpFault = 0; //Warns if the CP ECU has faulted +static uint8_t battery2_ShuntHwMiaFault = 0; //Warns if the shunt current reading is not available +static uint8_t battery2_PyroMiaFault = 0; //Warns if the pyro squib is not connected +static uint8_t battery2_hvsMiaFault = 0; //Warns if the pack contactor hw fault +static uint8_t battery2_hviMiaFault = 0; //Warns if the FC contactor hw fault +static uint8_t battery2_Supply12vFault = 0; //Warns if the low voltage (12V) battery is below minimum voltage threshold +static uint8_t battery2_VerSupplyFault = + 0; //Warns if the Energy reserve voltage supply is below minimum voltage threshold +static uint8_t battery2_HvilFault = 0; //Warn if a High Voltage Inter Lock fault is detected +static uint8_t battery2_BmsHvsMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN +static uint8_t battery2_PackVoltMismatchFault = + 0; //Warns if the pack voltage doesn't match approximately with sum of brick voltages +static uint8_t battery2_EnsMiaFault = 0; //Warns if the ENS line is not connected to HVC +static uint8_t battery2_PackPosCtrArcFault = 0; //Warns if the HVP detectes series arc at pack contactor +static uint8_t battery2_packNegCtrArcFault = 0; //Warns if the HVP detectes series arc at FC contactor +static uint8_t battery2_ShuntHwAndBmsMiaFault = 0; +static uint8_t battery2_fcContHwFault = 0; +static uint8_t battery2_robinOverVoltageFault = 0; +static uint8_t battery2_packContHwFault = 0; +static uint8_t battery2_pyroFuseBlown = 0; +static uint8_t battery2_pyroFuseFailedToBlow = 0; +static uint8_t battery2_CpilFault = 0; +static uint8_t battery2_PackContactorFellOpen = 0; +static uint8_t battery2_FcContactorFellOpen = 0; +static uint8_t battery2_packCtrCloseBlocked = 0; +static uint8_t battery2_fcCtrCloseBlocked = 0; +static uint8_t battery2_packContactorForceOpen = 0; +static uint8_t battery2_fcContactorForceOpen = 0; +static uint8_t battery2_dcLinkOverVoltage = 0; +static uint8_t battery2_shuntOverTemperature = 0; +static uint8_t battery2_passivePyroDeploy = 0; +static uint8_t battery2_logUploadRequest = 0; +static uint8_t battery2_packCtrCloseFailed = 0; +static uint8_t battery2_fcCtrCloseFailed = 0; +static uint8_t battery2_shuntThermistorMia = 0; +#endif //DOUBLE_BATTERY + static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", "UNKNOWN(10)", "UNKNOWN(11)", "UNKNOWN(12)"}; @@ -165,36 +274,37 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% - datalayer.battery.status.real_soc = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + datalayer.battery.status.real_soc = (battery_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 - datalayer.battery.status.voltage_dV = (volts * 10); //One more decimal needed (370 -> 3700) + datalayer.battery.status.voltage_dV = (battery_volts * 10); //One more decimal needed (370 -> 3700) - datalayer.battery.status.current_dA = amps; //13.0A + datalayer.battery.status.current_dA = battery_amps; //13.0A //Calculate the remaining Wh amount from SOC% and max Wh value. datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); // Define the allowed discharge power - datalayer.battery.status.max_discharge_power_W = (max_discharge_current * volts); + datalayer.battery.status.max_discharge_power_W = (battery_max_discharge_current * battery_volts); // Cap the allowed discharge power if higher than the maximum discharge power allowed if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) { datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; } //The allowed charge power behaves strangely. We instead estimate this value - if (soc_vi > 990) { + if (battery_soc_vi > 990) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; - } else if (soc_vi > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 + } else if (battery_soc_vi > + RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery.status.max_charge_power_W = - MAXCHARGEPOWERALLOWED * (1 - (soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + MAXCHARGEPOWERALLOWED * (1 - (battery_soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); //If the cellvoltages start to reach overvoltage, only allow a small amount of power in if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { - if (cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { + if (battery_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; } } else { //NCM/A - if (cell_max_v > (MAX_CELL_VOLTAGE_NCA_NCM - FLOAT_START_MV)) { + if (battery_cell_max_v > (MAX_CELL_VOLTAGE_NCA_NCM - FLOAT_START_MV)) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; } } @@ -202,25 +312,26 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; } - datalayer.battery.status.active_power_W = ((volts / 10) * amps); + datalayer.battery.status.active_power_W = ((battery_volts / 10) * battery_amps); - datalayer.battery.status.temperature_min_dC = min_temp; + datalayer.battery.status.temperature_min_dC = battery_min_temp; - datalayer.battery.status.temperature_max_dC = max_temp; + datalayer.battery.status.temperature_max_dC = battery_max_temp; - datalayer.battery.status.cell_max_voltage_mV = cell_max_v; + datalayer.battery.status.cell_max_voltage_mV = battery_cell_max_v; - datalayer.battery.status.cell_min_voltage_mV = cell_min_v; + datalayer.battery.status.cell_min_voltage_mV = battery_cell_min_v; /* Value mapping is completed. Start to check all safeties */ - if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use + if (battery_hvil_status == + 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use set_event(EVENT_INTERNAL_OPEN_FAULT, 0); } else { clear_event(EVENT_INTERNAL_OPEN_FAULT); } - cell_deviation_mV = (cell_max_v - cell_min_v); + battery_cell_deviation_mV = (battery_cell_max_v - battery_cell_min_v); // NCM/A batteries have 96s, LFP has 102-106s // Drawback with this check is that it takes 3-5minutes before all cells have been counted! @@ -247,40 +358,33 @@ void update_values_battery() { //This function maps all the values fetched via } //Check if BMS is in need of recalibration - if (nominal_full_pack_energy > 1 && nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { -#ifdef DEBUG_VIA_USB - Serial.println("Warning: kWh remaining " + String(nominal_full_pack_energy) + - " reported by battery not plausible. Battery needs cycling."); -#endif - set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); - } else if (nominal_full_pack_energy <= 1) { -#ifdef DEBUG_VIA_USB - Serial.println("Info: kWh remaining battery is not reporting kWh remaining."); -#endif - set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); + if (battery_nominal_full_pack_energy > 1 && battery_nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, battery_nominal_full_pack_energy); + } else if (battery_nominal_full_pack_energy <= 1) { + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, battery_nominal_full_pack_energy); } if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { //LFP limits used for voltage safeties - if (cell_max_v >= MAX_CELL_VOLTAGE_LFP) { - set_event(EVENT_CELL_OVER_VOLTAGE, (cell_max_v - MAX_CELL_VOLTAGE_LFP)); + if (battery_cell_max_v >= MAX_CELL_VOLTAGE_LFP) { + set_event(EVENT_CELL_OVER_VOLTAGE, (battery_cell_max_v - MAX_CELL_VOLTAGE_LFP)); } - if (cell_min_v <= MIN_CELL_VOLTAGE_LFP) { - set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_LFP - cell_min_v)); + if (battery_cell_min_v <= MIN_CELL_VOLTAGE_LFP) { + set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_LFP - battery_cell_min_v)); } - if (cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { - set_event(EVENT_CELL_DEVIATION_HIGH, cell_deviation_mV); + if (battery_cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { + set_event(EVENT_CELL_DEVIATION_HIGH, battery_cell_deviation_mV); } else { clear_event(EVENT_CELL_DEVIATION_HIGH); } } else { //NCA/NCM limits used - if (cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { - set_event(EVENT_CELL_OVER_VOLTAGE, (cell_max_v - MAX_CELL_VOLTAGE_NCA_NCM)); + if (battery_cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { + set_event(EVENT_CELL_OVER_VOLTAGE, (battery_cell_max_v - MAX_CELL_VOLTAGE_NCA_NCM)); } - if (cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { - set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_NCA_NCM - cell_min_v)); + if (battery_cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { + set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_NCA_NCM - battery_cell_min_v)); } - if (cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { - set_event(EVENT_CELL_DEVIATION_HIGH, cell_deviation_mV); + if (battery_cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { + set_event(EVENT_CELL_DEVIATION_HIGH, battery_cell_deviation_mV); } else { clear_event(EVENT_CELL_DEVIATION_HIGH); } @@ -293,27 +397,27 @@ void update_values_battery() { //This function maps all the values fetched via printFaultCodesIfActive(); Serial.print("STATUS: Contactor: "); - Serial.print(contactorText[contactor]); //Display what state the contactor is in + Serial.print(contactorText[battery_contactor]); //Display what state the contactor is in Serial.print(", HVIL: "); - Serial.print(hvilStatusState[hvil_status]); + Serial.print(hvilStatusState[battery_hvil_status]); Serial.print(", NegativeState: "); - Serial.print(contactorState[packContNegativeState]); + Serial.print(contactorState[battery_packContNegativeState]); Serial.print(", PositiveState: "); - Serial.print(contactorState[packContPositiveState]); + Serial.print(contactorState[battery_packContPositiveState]); Serial.print(", setState: "); - Serial.print(contactorState[packContactorSetState]); + Serial.print(contactorState[battery_packContactorSetState]); Serial.print(", close allowed: "); - Serial.print(packCtrsClosingAllowed); + Serial.print(battery_packCtrsClosingAllowed); Serial.print(", Pyrotest: "); - Serial.println(pyroTestInProgress); + Serial.println(battery_pyroTestInProgress); Serial.print("Battery values: "); Serial.print("Real SOC: "); - Serial.print(soc_vi / 10.0, 1); - print_int_with_units(", Battery voltage: ", volts, "V"); - print_int_with_units(", Battery HV current: ", (amps * 0.1), "A"); + Serial.print(battery_soc_vi / 10.0, 1); + print_int_with_units(", Battery voltage: ", battery_volts, "V"); + print_int_with_units(", Battery HV current: ", (battery_amps * 0.1), "A"); Serial.print(", Fully charged?: "); - if (full_charge_complete) + if (battery_full_charge_complete) Serial.print("YES, "); else Serial.print("NO, "); @@ -322,22 +426,22 @@ void update_values_battery() { //This function maps all the values fetched via } Serial.println(""); Serial.print("Cellstats, Max: "); - Serial.print(cell_max_v); + Serial.print(battery_cell_max_v); Serial.print("mV (cell "); - Serial.print(max_vno); + Serial.print(battery_max_vno); Serial.print("), Min: "); - Serial.print(cell_min_v); + Serial.print(battery_cell_min_v); Serial.print("mV (cell "); - Serial.print(min_vno); + Serial.print(battery_min_vno); Serial.print("), Imbalance: "); - Serial.print(cell_deviation_mV); + Serial.print(battery_cell_deviation_mV); Serial.println("mV."); - print_int_with_units("High Voltage Output Pins: ", high_voltage, "V"); + print_int_with_units("High Voltage Output Pins: ", battery_high_voltage, "V"); Serial.print(", "); - print_int_with_units("Low Voltage: ", low_voltage, "V"); + print_int_with_units("Low Voltage: ", battery_low_voltage, "V"); Serial.println(""); - print_int_with_units("DC/DC 12V current: ", output_current, "A"); + print_int_with_units("DC/DC 12V current: ", battery_output_current, "A"); Serial.println(""); Serial.println("Values passed to the inverter: "); @@ -350,52 +454,56 @@ void update_values_battery() { //This function maps all the values fetched via Serial.print(", "); print_int_with_units(" Min temperature: ", ((int16_t)datalayer.battery.status.temperature_max_dC * 0.1), "°C"); Serial.println(""); -#endif +#endif //DEBUG_VIA_USB } void receive_can_battery(CAN_frame_t rx_frame) { - static int mux = 0; - static int temp = 0; + static uint8_t mux = 0; + static uint16_t temp = 0; switch (rx_frame.MsgID) { case 0x352: //SOC - nominal_full_pack_energy = (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh - expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) - ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh - energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh - energy_buffer = + battery_nominal_full_pack_energy = + (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * + 0.1; //Example 1247 * 0.1 = 124.7kWh + battery_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) + battery_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * + 0.1; //Example 311 * 0.1 = 31.1kWh + battery_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * + 0.1; //Example 147 * 0.1 = 14.7kWh + battery_energy_buffer = (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 - full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + battery_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); break; case 0x20A: //Contactor state - packContNegativeState = (rx_frame.data.u8[0] & 0x07); - packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; - contactor = (rx_frame.data.u8[1] & 0x0F); - packContactorSetState = (rx_frame.data.u8[1] & 0x0F); - packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; - pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; - hvil_status = (rx_frame.data.u8[5] & 0x0F); + battery_packContNegativeState = (rx_frame.data.u8[0] & 0x07); + battery_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; + battery_contactor = (rx_frame.data.u8[1] & 0x0F); + battery_packContactorSetState = (rx_frame.data.u8[1] & 0x0F); + battery_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; + battery_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; + battery_hvil_status = (rx_frame.data.u8[5] & 0x0F); break; case 0x252: //Limits - regenerative_limit = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW - discharge_limit = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117??? - max_heat_park = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW - hvac_max_power = + battery_regenerative_limit = + ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW + battery_discharge_limit = + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117??? + battery_max_heat_park = + (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW + battery_hvac_max_power = (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW? break; case 0x132: //battery amps/volts - volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V - amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) - raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? + battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V + battery_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) + battery_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? battery_charge_time_remaining = (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min if (battery_charge_time_remaining == 4095) { @@ -405,12 +513,12 @@ void receive_can_battery(CAN_frame_t rx_frame) { break; case 0x3D2: // total charge/discharge kwh - total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | - rx_frame.data.u8[0]) * - 0.001; - total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | - rx_frame.data.u8[4]) * - 0.001; + battery_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.001; + battery_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | + rx_frame.data.u8[4]) * + 0.001; break; case 0x332: //min/max hist values @@ -420,17 +528,18 @@ void receive_can_battery(CAN_frame_t rx_frame) { { temp = ((rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[0] >> 2)); temp = (temp & 0xFFF); - cell_max_v = temp * 2; + battery_cell_max_v = temp * 2; temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); temp = (temp & 0xFFF); - cell_min_v = temp * 2; - max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage - min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage + battery_cell_min_v = temp * 2; + battery_max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage + battery_min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage } if (mux == 0) //Temperature sensors { - max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit - min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) + battery_max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit + battery_min_temp = + (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) } break; case 0x401: // Cell stats @@ -466,85 +575,487 @@ void receive_can_battery(CAN_frame_t rx_frame) { break; case 0x2d2: //Min / max limits - bms_min_voltage = + battery_bms_min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V - bms_max_voltage = + battery_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V - max_charge_current = + battery_max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? - max_discharge_current = + battery_max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? break; case 0x2b4: - low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; - high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; - output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + battery_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; + battery_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; + battery_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; break; case 0x292: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS - bat_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); - soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); - soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); - soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + battery_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); + battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); + battery_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); + battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); + battery_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); break; case 0x3aa: //HVP_alertMatrix1 - WatchdogReset = (rx_frame.data.u8[0] & 0x01); - PowerLossReset = ((rx_frame.data.u8[0] & 0x02) >> 1); - SwAssertion = ((rx_frame.data.u8[0] & 0x04) >> 2); - CrashEvent = ((rx_frame.data.u8[0] & 0x08) >> 3); - OverDchgCurrentFault = ((rx_frame.data.u8[0] & 0x10) >> 4); - OverChargeCurrentFault = ((rx_frame.data.u8[0] & 0x20) >> 5); - OverCurrentFault = ((rx_frame.data.u8[0] & 0x40) >> 6); - OverTemperatureFault = ((rx_frame.data.u8[1] & 0x80) >> 7); - OverVoltageFault = (rx_frame.data.u8[1] & 0x01); - UnderVoltageFault = ((rx_frame.data.u8[1] & 0x02) >> 1); - PrimaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x04) >> 2); - SecondaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x08) >> 3); - BmbMismatchFault = ((rx_frame.data.u8[1] & 0x10) >> 4); - BmsHviMiaFault = ((rx_frame.data.u8[1] & 0x20) >> 5); - CpMiaFault = ((rx_frame.data.u8[1] & 0x40) >> 6); - PcsMiaFault = ((rx_frame.data.u8[1] & 0x80) >> 7); - BmsFault = (rx_frame.data.u8[2] & 0x01); - PcsFault = ((rx_frame.data.u8[2] & 0x02) >> 1); - CpFault = ((rx_frame.data.u8[2] & 0x04) >> 2); - ShuntHwMiaFault = ((rx_frame.data.u8[2] & 0x08) >> 3); - PyroMiaFault = ((rx_frame.data.u8[2] & 0x10) >> 4); - hvsMiaFault = ((rx_frame.data.u8[2] & 0x20) >> 5); - hviMiaFault = ((rx_frame.data.u8[2] & 0x40) >> 6); - Supply12vFault = ((rx_frame.data.u8[2] & 0x80) >> 7); - VerSupplyFault = (rx_frame.data.u8[3] & 0x01); - HvilFault = ((rx_frame.data.u8[3] & 0x02) >> 1); - BmsHvsMiaFault = ((rx_frame.data.u8[3] & 0x04) >> 2); - PackVoltMismatchFault = ((rx_frame.data.u8[3] & 0x08) >> 3); - EnsMiaFault = ((rx_frame.data.u8[3] & 0x10) >> 4); - PackPosCtrArcFault = ((rx_frame.data.u8[3] & 0x20) >> 5); - packNegCtrArcFault = ((rx_frame.data.u8[3] & 0x40) >> 6); - ShuntHwAndBmsMiaFault = ((rx_frame.data.u8[3] & 0x80) >> 7); - fcContHwFault = (rx_frame.data.u8[4] & 0x01); - robinOverVoltageFault = ((rx_frame.data.u8[4] & 0x02) >> 1); - packContHwFault = ((rx_frame.data.u8[4] & 0x04) >> 2); - pyroFuseBlown = ((rx_frame.data.u8[4] & 0x08) >> 3); - pyroFuseFailedToBlow = ((rx_frame.data.u8[4] & 0x10) >> 4); - CpilFault = ((rx_frame.data.u8[4] & 0x20) >> 5); - PackContactorFellOpen = ((rx_frame.data.u8[4] & 0x40) >> 6); - FcContactorFellOpen = ((rx_frame.data.u8[4] & 0x80) >> 7); - packCtrCloseBlocked = (rx_frame.data.u8[5] & 0x01); - fcCtrCloseBlocked = ((rx_frame.data.u8[5] & 0x02) >> 1); - packContactorForceOpen = ((rx_frame.data.u8[5] & 0x04) >> 2); - fcContactorForceOpen = ((rx_frame.data.u8[5] & 0x08) >> 3); - dcLinkOverVoltage = ((rx_frame.data.u8[5] & 0x10) >> 4); - shuntOverTemperature = ((rx_frame.data.u8[5] & 0x20) >> 5); - passivePyroDeploy = ((rx_frame.data.u8[5] & 0x40) >> 6); - logUploadRequest = ((rx_frame.data.u8[5] & 0x80) >> 7); - packCtrCloseFailed = (rx_frame.data.u8[6] & 0x01); - fcCtrCloseFailed = ((rx_frame.data.u8[6] & 0x02) >> 1); - shuntThermistorMia = ((rx_frame.data.u8[6] & 0x04) >> 2); + battery_WatchdogReset = (rx_frame.data.u8[0] & 0x01); + battery_PowerLossReset = ((rx_frame.data.u8[0] & 0x02) >> 1); + battery_SwAssertion = ((rx_frame.data.u8[0] & 0x04) >> 2); + battery_CrashEvent = ((rx_frame.data.u8[0] & 0x08) >> 3); + battery_OverDchgCurrentFault = ((rx_frame.data.u8[0] & 0x10) >> 4); + battery_OverChargeCurrentFault = ((rx_frame.data.u8[0] & 0x20) >> 5); + battery_OverCurrentFault = ((rx_frame.data.u8[0] & 0x40) >> 6); + battery_OverTemperatureFault = ((rx_frame.data.u8[1] & 0x80) >> 7); + battery_OverVoltageFault = (rx_frame.data.u8[1] & 0x01); + battery_UnderVoltageFault = ((rx_frame.data.u8[1] & 0x02) >> 1); + battery_PrimaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x04) >> 2); + battery_SecondaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x08) >> 3); + battery_BmbMismatchFault = ((rx_frame.data.u8[1] & 0x10) >> 4); + battery_BmsHviMiaFault = ((rx_frame.data.u8[1] & 0x20) >> 5); + battery_CpMiaFault = ((rx_frame.data.u8[1] & 0x40) >> 6); + battery_PcsMiaFault = ((rx_frame.data.u8[1] & 0x80) >> 7); + battery_BmsFault = (rx_frame.data.u8[2] & 0x01); + battery_PcsFault = ((rx_frame.data.u8[2] & 0x02) >> 1); + battery_CpFault = ((rx_frame.data.u8[2] & 0x04) >> 2); + battery_ShuntHwMiaFault = ((rx_frame.data.u8[2] & 0x08) >> 3); + battery_PyroMiaFault = ((rx_frame.data.u8[2] & 0x10) >> 4); + battery_hvsMiaFault = ((rx_frame.data.u8[2] & 0x20) >> 5); + battery_hviMiaFault = ((rx_frame.data.u8[2] & 0x40) >> 6); + battery_Supply12vFault = ((rx_frame.data.u8[2] & 0x80) >> 7); + battery_VerSupplyFault = (rx_frame.data.u8[3] & 0x01); + battery_HvilFault = ((rx_frame.data.u8[3] & 0x02) >> 1); + battery_BmsHvsMiaFault = ((rx_frame.data.u8[3] & 0x04) >> 2); + battery_PackVoltMismatchFault = ((rx_frame.data.u8[3] & 0x08) >> 3); + battery_EnsMiaFault = ((rx_frame.data.u8[3] & 0x10) >> 4); + battery_PackPosCtrArcFault = ((rx_frame.data.u8[3] & 0x20) >> 5); + battery_packNegCtrArcFault = ((rx_frame.data.u8[3] & 0x40) >> 6); + battery_ShuntHwAndBmsMiaFault = ((rx_frame.data.u8[3] & 0x80) >> 7); + battery_fcContHwFault = (rx_frame.data.u8[4] & 0x01); + battery_robinOverVoltageFault = ((rx_frame.data.u8[4] & 0x02) >> 1); + battery_packContHwFault = ((rx_frame.data.u8[4] & 0x04) >> 2); + battery_pyroFuseBlown = ((rx_frame.data.u8[4] & 0x08) >> 3); + battery_pyroFuseFailedToBlow = ((rx_frame.data.u8[4] & 0x10) >> 4); + battery_CpilFault = ((rx_frame.data.u8[4] & 0x20) >> 5); + battery_PackContactorFellOpen = ((rx_frame.data.u8[4] & 0x40) >> 6); + battery_FcContactorFellOpen = ((rx_frame.data.u8[4] & 0x80) >> 7); + battery_packCtrCloseBlocked = (rx_frame.data.u8[5] & 0x01); + battery_fcCtrCloseBlocked = ((rx_frame.data.u8[5] & 0x02) >> 1); + battery_packContactorForceOpen = ((rx_frame.data.u8[5] & 0x04) >> 2); + battery_fcContactorForceOpen = ((rx_frame.data.u8[5] & 0x08) >> 3); + battery_dcLinkOverVoltage = ((rx_frame.data.u8[5] & 0x10) >> 4); + battery_shuntOverTemperature = ((rx_frame.data.u8[5] & 0x20) >> 5); + battery_passivePyroDeploy = ((rx_frame.data.u8[5] & 0x40) >> 6); + battery_logUploadRequest = ((rx_frame.data.u8[5] & 0x80) >> 7); + battery_packCtrCloseFailed = (rx_frame.data.u8[6] & 0x01); + battery_fcCtrCloseFailed = ((rx_frame.data.u8[6] & 0x02) >> 1); + battery_shuntThermistorMia = ((rx_frame.data.u8[6] & 0x04) >> 2); break; default: break; } } + +#ifdef DOUBLE_BATTERY + +void CAN_WriteFrame(CAN_frame_t* tx_frame) { + CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 + 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); +} + +void receive_can_battery2(CAN_frame_t rx_frame) { + static uint8_t mux = 0; + static uint16_t temp = 0; + + switch (rx_frame.MsgID) { + case 0x352: + //SOC + battery2_nominal_full_pack_energy = + (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery2_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * + 0.1; //Example 1247 * 0.1 = 124.7kWh + battery2_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) + battery2_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * + 0.1; //Example 311 * 0.1 = 31.1kWh + battery2_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * + 0.1; //Example 147 * 0.1 = 14.7kWh + battery2_energy_buffer = + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 + battery2_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + break; + case 0x20A: + //Contactor state + battery2_packContNegativeState = (rx_frame.data.u8[0] & 0x07); + battery2_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; + battery2_contactor = (rx_frame.data.u8[1] & 0x0F); + battery2_packContactorSetState = (rx_frame.data.u8[1] & 0x0F); + battery2_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; + battery2_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; + battery2_hvil_status = (rx_frame.data.u8[5] & 0x0F); + break; + case 0x252: + //Limits + battery2_regenerative_limit = + ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW + battery2_discharge_limit = + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117??? + battery2_max_heat_park = + (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW + battery2_hvac_max_power = + (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW? + break; + case 0x132: + //battery amps/volts + battery2_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V + battery2_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) + battery2_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? + battery2_charge_time_remaining = + (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min + if (battery2_charge_time_remaining == 4095) { + battery2_charge_time_remaining = 0; + } + + break; + case 0x3D2: + // total charge/discharge kwh + battery2_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.001; + battery2_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | + rx_frame.data.u8[4]) * + 0.001; + break; + case 0x332: + //min/max hist values + mux = (rx_frame.data.u8[0] & 0x03); + + if (mux == 1) //Cell voltages + { + temp = ((rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[0] >> 2)); + temp = (temp & 0xFFF); + battery2_cell_max_v = temp * 2; + temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); + temp = (temp & 0xFFF); + battery2_cell_min_v = temp * 2; + battery2_max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage + battery2_min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage + } + if (mux == 0) //Temperature sensors + { + battery2_max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit + battery2_min_temp = + (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) + } + break; + case 0x401: // Cell stats + mux = (rx_frame.data.u8[0]); + + static uint16_t volts; + static uint8_t mux_zero_counter = 0u; + static uint8_t mux_max = 0u; + + if (rx_frame.data.u8[1] == 0x2A) // status byte must be 0x2A to read cellvoltages + { + // Example, frame3=0x89,frame2=0x1D = 35101 / 10 = 3510mV + volts = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) / 10; + datalayer.battery.status.cell_voltages_mV[mux * 3] = volts; + volts = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) / 10; + datalayer.battery.status.cell_voltages_mV[1 + mux * 3] = volts; + volts = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) / 10; + datalayer.battery.status.cell_voltages_mV[2 + mux * 3] = volts; + + // Track the max value of mux. If we've seen two 0 values for mux, we've probably gathered all + // cell voltages. Then, 2 + mux_max * 3 + 1 is the number of cell voltages. + mux_max = (mux > mux_max) ? mux : mux_max; + if (mux_zero_counter < 2 && mux == 0u) { + mux_zero_counter++; + if (mux_zero_counter == 2u) { + // The max index will be 2 + mux_max * 3 (see above), so "+ 1" for the number of cells + datalayer.battery.info.number_of_cells = 2 + 3 * mux_max + 1; + // Increase the counter arbitrarily another time to make the initial if-statement evaluate to false + mux_zero_counter++; + } + } + } + break; + case 0x2d2: + //Min / max limits + battery2_bms_min_voltage = + ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V + battery2_bms_max_voltage = + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V + battery2_max_charge_current = + (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? + battery2_max_discharge_current = + (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? + break; + case 0x2b4: + battery2_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; + battery2_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; + battery2_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + break; + case 0x292: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS + battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); + battery2_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); + battery2_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); + battery2_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); + battery2_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + break; + case 0x3aa: //HVP_alertMatrix1 + battery2_WatchdogReset = (rx_frame.data.u8[0] & 0x01); + battery2_PowerLossReset = ((rx_frame.data.u8[0] & 0x02) >> 1); + battery2_SwAssertion = ((rx_frame.data.u8[0] & 0x04) >> 2); + battery2_CrashEvent = ((rx_frame.data.u8[0] & 0x08) >> 3); + battery2_OverDchgCurrentFault = ((rx_frame.data.u8[0] & 0x10) >> 4); + battery2_OverChargeCurrentFault = ((rx_frame.data.u8[0] & 0x20) >> 5); + battery2_OverCurrentFault = ((rx_frame.data.u8[0] & 0x40) >> 6); + battery2_OverTemperatureFault = ((rx_frame.data.u8[1] & 0x80) >> 7); + battery2_OverVoltageFault = (rx_frame.data.u8[1] & 0x01); + battery2_UnderVoltageFault = ((rx_frame.data.u8[1] & 0x02) >> 1); + battery2_PrimaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x04) >> 2); + battery2_SecondaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x08) >> 3); + battery2_BmbMismatchFault = ((rx_frame.data.u8[1] & 0x10) >> 4); + battery2_BmsHviMiaFault = ((rx_frame.data.u8[1] & 0x20) >> 5); + battery2_CpMiaFault = ((rx_frame.data.u8[1] & 0x40) >> 6); + battery2_PcsMiaFault = ((rx_frame.data.u8[1] & 0x80) >> 7); + battery2_BmsFault = (rx_frame.data.u8[2] & 0x01); + battery2_PcsFault = ((rx_frame.data.u8[2] & 0x02) >> 1); + battery2_CpFault = ((rx_frame.data.u8[2] & 0x04) >> 2); + battery2_ShuntHwMiaFault = ((rx_frame.data.u8[2] & 0x08) >> 3); + battery2_PyroMiaFault = ((rx_frame.data.u8[2] & 0x10) >> 4); + battery2_hvsMiaFault = ((rx_frame.data.u8[2] & 0x20) >> 5); + battery2_hviMiaFault = ((rx_frame.data.u8[2] & 0x40) >> 6); + battery2_Supply12vFault = ((rx_frame.data.u8[2] & 0x80) >> 7); + battery2_VerSupplyFault = (rx_frame.data.u8[3] & 0x01); + battery2_HvilFault = ((rx_frame.data.u8[3] & 0x02) >> 1); + battery2_BmsHvsMiaFault = ((rx_frame.data.u8[3] & 0x04) >> 2); + battery2_PackVoltMismatchFault = ((rx_frame.data.u8[3] & 0x08) >> 3); + battery2_EnsMiaFault = ((rx_frame.data.u8[3] & 0x10) >> 4); + battery2_PackPosCtrArcFault = ((rx_frame.data.u8[3] & 0x20) >> 5); + battery2_packNegCtrArcFault = ((rx_frame.data.u8[3] & 0x40) >> 6); + battery2_ShuntHwAndBmsMiaFault = ((rx_frame.data.u8[3] & 0x80) >> 7); + battery2_fcContHwFault = (rx_frame.data.u8[4] & 0x01); + battery2_robinOverVoltageFault = ((rx_frame.data.u8[4] & 0x02) >> 1); + battery2_packContHwFault = ((rx_frame.data.u8[4] & 0x04) >> 2); + battery2_pyroFuseBlown = ((rx_frame.data.u8[4] & 0x08) >> 3); + battery2_pyroFuseFailedToBlow = ((rx_frame.data.u8[4] & 0x10) >> 4); + battery2_CpilFault = ((rx_frame.data.u8[4] & 0x20) >> 5); + battery2_PackContactorFellOpen = ((rx_frame.data.u8[4] & 0x40) >> 6); + battery2_FcContactorFellOpen = ((rx_frame.data.u8[4] & 0x80) >> 7); + battery2_packCtrCloseBlocked = (rx_frame.data.u8[5] & 0x01); + battery2_fcCtrCloseBlocked = ((rx_frame.data.u8[5] & 0x02) >> 1); + battery2_packContactorForceOpen = ((rx_frame.data.u8[5] & 0x04) >> 2); + battery2_fcContactorForceOpen = ((rx_frame.data.u8[5] & 0x08) >> 3); + battery2_dcLinkOverVoltage = ((rx_frame.data.u8[5] & 0x10) >> 4); + battery2_shuntOverTemperature = ((rx_frame.data.u8[5] & 0x20) >> 5); + battery2_passivePyroDeploy = ((rx_frame.data.u8[5] & 0x40) >> 6); + battery2_logUploadRequest = ((rx_frame.data.u8[5] & 0x80) >> 7); + battery2_packCtrCloseFailed = (rx_frame.data.u8[6] & 0x01); + battery2_fcCtrCloseFailed = ((rx_frame.data.u8[6] & 0x02) >> 1); + battery2_shuntThermistorMia = ((rx_frame.data.u8[6] & 0x04) >> 2); + break; + default: + break; + } +} + +void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + //After values are mapped, we perform some safety checks, and do some serial printouts + + datalayer.battery2.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% + + datalayer.battery2.status.real_soc = (battery2_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + + datalayer.battery2.status.voltage_dV = (battery2_volts * 10); //One more decimal needed (370 -> 3700) + + datalayer.battery2.status.current_dA = battery2_amps; //13.0A + + //Calculate the remaining Wh amount from SOC% and max Wh value. + datalayer.battery2.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh); + + // Define the allowed discharge power + datalayer.battery2.status.max_discharge_power_W = (battery2_max_discharge_current * battery2_volts); + // Cap the allowed discharge power if higher than the maximum discharge power allowed + if (datalayer.battery2.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) { + datalayer.battery2.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; + } + + //The allowed charge power behaves strangely. We instead estimate this value + if (battery2_soc_vi > 990) { + datalayer.battery2.status.max_charge_power_W = FLOAT_MAX_POWER_W; + } else if (battery2_soc_vi > + RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 + datalayer.battery2.status.max_charge_power_W = + MAXCHARGEPOWERALLOWED * (1 - (battery2_soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + //If the cellvoltages start to reach overvoltage, only allow a small amount of power in + if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { + if (battery2_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { + datalayer.battery2.status.max_charge_power_W = FLOAT_MAX_POWER_W; + } + } else { //NCM/A + if (battery2_cell_max_v > (MAX_CELL_VOLTAGE_NCA_NCM - FLOAT_START_MV)) { + datalayer.battery2.status.max_charge_power_W = FLOAT_MAX_POWER_W; + } + } + } else { // No limits, max charging power allowed + datalayer.battery2.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; + } + + datalayer.battery2.status.active_power_W = ((battery2_volts / 10) * battery2_amps); + + datalayer.battery2.status.temperature_min_dC = battery2_min_temp; + + datalayer.battery2.status.temperature_max_dC = battery2_max_temp; + + datalayer.battery2.status.cell_max_voltage_mV = battery2_cell_max_v; + + datalayer.battery2.status.cell_min_voltage_mV = battery2_cell_min_v; + + /* Value mapping is completed. Start to check all safeties */ + + if (battery2_hvil_status == + 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use + set_event(EVENT_INTERNAL_OPEN_FAULT, 2); + } else { + clear_event(EVENT_INTERNAL_OPEN_FAULT); + } + + battery2_cell_deviation_mV = (battery2_cell_max_v - battery2_cell_min_v); + + // NCM/A batteries have 96s, LFP has 102-106s + // Drawback with this check is that it takes 3-5minutes before all cells have been counted! + if (datalayer.battery2.info.number_of_cells > 101) { + datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP; + } + + //Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits + if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { + datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP; + datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP; + } else { // NCM/A chemistry + datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA; + datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA; + } + + //Check if SOC% is plausible + if (datalayer.battery2.status.voltage_dV > + (datalayer.battery2.info.max_design_voltage_dV - + 20)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT + if (datalayer.battery2.status.real_soc < 5000) { //When SOC is less than 50.00% when approaching max voltage + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery2.status.real_soc / 100); + } + } + + //Check if BMS is in need of recalibration + if (battery2_nominal_full_pack_energy > 1 && battery2_nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, battery2_nominal_full_pack_energy); + } else if (battery2_nominal_full_pack_energy <= 1) { + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, battery2_nominal_full_pack_energy); + } + + if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { //LFP limits used for voltage safeties + if (battery2_cell_max_v >= MAX_CELL_VOLTAGE_LFP) { + set_event(EVENT_CELL_OVER_VOLTAGE, (battery2_cell_max_v - MAX_CELL_VOLTAGE_LFP)); + } + if (battery2_cell_min_v <= MIN_CELL_VOLTAGE_LFP) { + set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_LFP - battery2_cell_min_v)); + } + if (battery2_cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { + set_event(EVENT_CELL_DEVIATION_HIGH, battery2_cell_deviation_mV); + } else { + clear_event(EVENT_CELL_DEVIATION_HIGH); + } + } else { //NCA/NCM limits used + if (battery2_cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { + set_event(EVENT_CELL_OVER_VOLTAGE, (battery2_cell_max_v - MAX_CELL_VOLTAGE_NCA_NCM)); + } + if (battery2_cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { + set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_NCA_NCM - battery2_cell_min_v)); + } + if (battery2_cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { + set_event(EVENT_CELL_DEVIATION_HIGH, battery2_cell_deviation_mV); + } else { + clear_event(EVENT_CELL_DEVIATION_HIGH); + } + } + + /* Safeties verified. Perform USB serial printout if configured to do so */ + +#ifdef DEBUG_VIA_USB + + printFaultCodesIfActive_battery2(); + + Serial.print("STATUS: Contactor: "); + Serial.print(contactorText[battery2_contactor]); //Display what state the contactor is in + Serial.print(", HVIL: "); + Serial.print(hvilStatusState[battery2_hvil_status]); + Serial.print(", NegativeState: "); + Serial.print(contactorState[battery2_packContNegativeState]); + Serial.print(", PositiveState: "); + Serial.print(contactorState[battery2_packContPositiveState]); + Serial.print(", setState: "); + Serial.print(contactorState[battery2_packContactorSetState]); + Serial.print(", close allowed: "); + Serial.print(battery2_packCtrsClosingAllowed); + Serial.print(", Pyrotest: "); + Serial.println(battery2_pyroTestInProgress); + + Serial.print("Battery2 values: "); + Serial.print("Real SOC: "); + Serial.print(battery2_soc_vi / 10.0, 1); + print_int_with_units(", Battery2 voltage: ", battery2_volts, "V"); + print_int_with_units(", Battery2 HV current: ", (battery2_amps * 0.1), "A"); + Serial.print(", Fully charged?: "); + if (battery2_full_charge_complete) + Serial.print("YES, "); + else + Serial.print("NO, "); + if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { + Serial.print("LFP chemistry detected!"); + } + Serial.println(""); + Serial.print("Cellstats, Max: "); + Serial.print(battery2_cell_max_v); + Serial.print("mV (cell "); + Serial.print(battery2_max_vno); + Serial.print("), Min: "); + Serial.print(battery2_cell_min_v); + Serial.print("mV (cell "); + Serial.print(battery2_min_vno); + Serial.print("), Imbalance: "); + Serial.print(battery2_cell_deviation_mV); + Serial.println("mV."); + + print_int_with_units("High Voltage Output Pins: ", battery2_high_voltage, "V"); + Serial.print(", "); + print_int_with_units("Low Voltage: ", battery2_low_voltage, "V"); + Serial.println(""); + print_int_with_units("DC/DC 12V current: ", battery2_output_current, "A"); + Serial.println(""); + + Serial.println("Values passed to the inverter: "); + print_SOC(" SOC: ", datalayer.battery2.status.reported_soc); + print_int_with_units(" Max discharge power: ", datalayer.battery2.status.max_discharge_power_W, "W"); + Serial.print(", "); + print_int_with_units(" Max charge power: ", datalayer.battery2.status.max_charge_power_W, "W"); + Serial.println(""); + print_int_with_units(" Max temperature: ", ((int16_t)datalayer.battery2.status.temperature_min_dC * 0.1), "°C"); + Serial.print(", "); + print_int_with_units(" Min temperature: ", ((int16_t)datalayer.battery2.status.temperature_max_dC * 0.1), "°C"); + Serial.println(""); +#endif // DEBUG_VIA_USB +} + +#endif //DOUBLE_BATTERY + void send_can_battery() { /*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then, to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so @@ -563,18 +1074,14 @@ the first, for a few cycles, then stop all messages which causes the contactor previousMillis30 = currentMillis; if (datalayer.system.status.inverter_allows_contactor_closing) { - if (datalayer.battery.status.bms_status == ACTIVE) { - send221still = 50; - datalayer.system.status.battery_allows_contactor_closing = true; - ESP32Can.CANWriteFrame(&TESLA_221_1); - ESP32Can.CANWriteFrame(&TESLA_221_2); - } else { //datalayer.battery.status.bms_status == FAULT or inverter requested opening contactors - if (send221still > 0) { - datalayer.system.status.battery_allows_contactor_closing = false; - ESP32Can.CANWriteFrame(&TESLA_221_1); - send221still--; - } + ESP32Can.CANWriteFrame(&TESLA_221_1); + ESP32Can.CANWriteFrame(&TESLA_221_2); +#ifdef DOUBLE_BATTERY + if (datalayer.system.status.battery2_allows_contactor_closing) { + CAN_WriteFrame(&TESLA_221_1); // CAN2 connected to battery 2 + CAN_WriteFrame(&TESLA_221_2); } +#endif //DOUBLE_BATTERY } } } @@ -597,11 +1104,11 @@ void print_SOC(char* header, int SOC) { } void printFaultCodesIfActive() { - if (packCtrsClosingAllowed == 0) { + if (battery_packCtrsClosingAllowed == 0) { Serial.println( "ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: "); } - if (pyroTestInProgress == 1) { + if (battery_pyroTestInProgress == 1) { Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); } if (datalayer.system.status.inverter_allows_contactor_closing == false) { @@ -610,61 +1117,144 @@ void printFaultCodesIfActive() { "disable the inverter protocol to proceed with contactor closing"); } // Check each symbol and print debug information if its value is 1 - printDebugIfActive(WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); - printDebugIfActive(PowerLossReset, "ERROR: The processor has experienced a reset due to power loss"); - printDebugIfActive(SwAssertion, "ERROR: An internal software assertion has failed"); - printDebugIfActive(CrashEvent, "ERROR: crash signal is detected by HVP"); - printDebugIfActive(OverDchgCurrentFault, + printDebugIfActive(battery_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); + printDebugIfActive(battery_PowerLossReset, "ERROR: The processor has experienced a reset due to power loss"); + printDebugIfActive(battery_SwAssertion, "ERROR: An internal software assertion has failed"); + printDebugIfActive(battery_CrashEvent, "ERROR: crash signal is detected by HVP"); + printDebugIfActive(battery_OverDchgCurrentFault, "ERROR: Pack discharge current is above the safe max discharge current limit!"); - printDebugIfActive(OverChargeCurrentFault, "ERROR: Pack charge current is above the safe max charge current limit!"); - printDebugIfActive(OverCurrentFault, "ERROR: Pack current (discharge or charge) is above max current limit!"); - printDebugIfActive(OverTemperatureFault, "ERROR: A pack module temperature is above the max temperature limit!"); - printDebugIfActive(OverVoltageFault, "ERROR: A brick voltage is above maximum voltage limit"); - printDebugIfActive(UnderVoltageFault, "ERROR: A brick voltage is below minimum voltage limit"); - printDebugIfActive(PrimaryBmbMiaFault, "ERROR: voltage and temperature readings from primary BMB chain are mia"); - printDebugIfActive(SecondaryBmbMiaFault, "ERROR: voltage and temperature readings from secondary BMB chain are mia"); - printDebugIfActive(BmbMismatchFault, "ERROR: primary and secondary BMB chain readings don't match with each other"); - printDebugIfActive(BmsHviMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); - //printDebugIfActive(CpMiaFault, "ERROR: CP node is mia on HVS CAN"); //Uncommented due to not affecting usage - printDebugIfActive(PcsMiaFault, "ERROR: PCS node is mia on HVS CAN"); - //printDebugIfActive(BmsFault, "ERROR: BmsFault is active"); //Uncommented due to not affecting usage - printDebugIfActive(PcsFault, "ERROR: PcsFault is active"); - //printDebugIfActive(CpFault, "ERROR: CpFault is active"); //Uncommented due to not affecting usage - printDebugIfActive(ShuntHwMiaFault, "ERROR: shunt current reading is not available"); - printDebugIfActive(PyroMiaFault, "ERROR: pyro squib is not connected"); - printDebugIfActive(hvsMiaFault, "ERROR: pack contactor hw fault"); - printDebugIfActive(hviMiaFault, "ERROR: FC contactor hw fault"); - printDebugIfActive(Supply12vFault, "ERROR: Low voltage (12V) battery is below minimum voltage threshold"); - printDebugIfActive(VerSupplyFault, "ERROR: Energy reserve voltage supply is below minimum voltage threshold"); - printDebugIfActive(HvilFault, "ERROR: High Voltage Inter Lock fault is detected"); - printDebugIfActive(BmsHvsMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); - printDebugIfActive(PackVoltMismatchFault, + printDebugIfActive(battery_OverChargeCurrentFault, + "ERROR: Pack charge current is above the safe max charge current limit!"); + printDebugIfActive(battery_OverCurrentFault, "ERROR: Pack current (discharge or charge) is above max current limit!"); + printDebugIfActive(battery_OverTemperatureFault, + "ERROR: A pack module temperature is above the max temperature limit!"); + printDebugIfActive(battery_OverVoltageFault, "ERROR: A brick voltage is above maximum voltage limit"); + printDebugIfActive(battery_UnderVoltageFault, "ERROR: A brick voltage is below minimum voltage limit"); + printDebugIfActive(battery_PrimaryBmbMiaFault, + "ERROR: voltage and temperature readings from primary BMB chain are mia"); + printDebugIfActive(battery_SecondaryBmbMiaFault, + "ERROR: voltage and temperature readings from secondary BMB chain are mia"); + printDebugIfActive(battery_BmbMismatchFault, + "ERROR: primary and secondary BMB chain readings don't match with each other"); + printDebugIfActive(battery_BmsHviMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); + //printDebugIfActive(battery_CpMiaFault, "ERROR: CP node is mia on HVS CAN"); //Uncommented due to not affecting usage + printDebugIfActive(battery_PcsMiaFault, "ERROR: PCS node is mia on HVS CAN"); + //printDebugIfActive(battery_BmsFault, "ERROR: BmsFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery_PcsFault, "ERROR: PcsFault is active"); + //printDebugIfActive(battery_CpFault, "ERROR: CpFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery_ShuntHwMiaFault, "ERROR: shunt current reading is not available"); + printDebugIfActive(battery_PyroMiaFault, "ERROR: pyro squib is not connected"); + printDebugIfActive(battery_hvsMiaFault, "ERROR: pack contactor hw fault"); + printDebugIfActive(battery_hviMiaFault, "ERROR: FC contactor hw fault"); + printDebugIfActive(battery_Supply12vFault, "ERROR: Low voltage (12V) battery is below minimum voltage threshold"); + printDebugIfActive(battery_VerSupplyFault, "ERROR: Energy reserve voltage supply is below minimum voltage threshold"); + printDebugIfActive(battery_HvilFault, "ERROR: High Voltage Inter Lock fault is detected"); + printDebugIfActive(battery_BmsHvsMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); + printDebugIfActive(battery_PackVoltMismatchFault, "ERROR: Pack voltage doesn't match approximately with sum of brick voltages"); - //printDebugIfActive(EnsMiaFault, "ERROR: ENS line is not connected to HVC"); //Uncommented due to not affecting usage - printDebugIfActive(PackPosCtrArcFault, "ERROR: HVP detectes series arc at pack contactor"); - printDebugIfActive(packNegCtrArcFault, "ERROR: HVP detectes series arc at FC contactor"); - printDebugIfActive(ShuntHwAndBmsMiaFault, "ERROR: ShuntHwAndBmsMiaFault is active"); - printDebugIfActive(fcContHwFault, "ERROR: fcContHwFault is active"); - printDebugIfActive(robinOverVoltageFault, "ERROR: robinOverVoltageFault is active"); - printDebugIfActive(packContHwFault, "ERROR: packContHwFault is active"); - printDebugIfActive(pyroFuseBlown, "ERROR: pyroFuseBlown is active"); - printDebugIfActive(pyroFuseFailedToBlow, "ERROR: pyroFuseFailedToBlow is active"); - //printDebugIfActive(CpilFault, "ERROR: CpilFault is active"); //Uncommented due to not affecting usage - printDebugIfActive(PackContactorFellOpen, "ERROR: PackContactorFellOpen is active"); - printDebugIfActive(FcContactorFellOpen, "ERROR: FcContactorFellOpen is active"); - printDebugIfActive(packCtrCloseBlocked, "ERROR: packCtrCloseBlocked is active"); - printDebugIfActive(fcCtrCloseBlocked, "ERROR: fcCtrCloseBlocked is active"); - printDebugIfActive(packContactorForceOpen, "ERROR: packContactorForceOpen is active"); - printDebugIfActive(fcContactorForceOpen, "ERROR: fcContactorForceOpen is active"); - printDebugIfActive(dcLinkOverVoltage, "ERROR: dcLinkOverVoltage is active"); - printDebugIfActive(shuntOverTemperature, "ERROR: shuntOverTemperature is active"); - printDebugIfActive(passivePyroDeploy, "ERROR: passivePyroDeploy is active"); - printDebugIfActive(logUploadRequest, "ERROR: logUploadRequest is active"); - printDebugIfActive(packCtrCloseFailed, "ERROR: packCtrCloseFailed is active"); - printDebugIfActive(fcCtrCloseFailed, "ERROR: fcCtrCloseFailed is active"); - printDebugIfActive(shuntThermistorMia, "ERROR: shuntThermistorMia is active"); + //printDebugIfActive(battery_EnsMiaFault, "ERROR: ENS line is not connected to HVC"); //Uncommented due to not affecting usage + printDebugIfActive(battery_PackPosCtrArcFault, "ERROR: HVP detectes series arc at pack contactor"); + printDebugIfActive(battery_packNegCtrArcFault, "ERROR: HVP detectes series arc at FC contactor"); + printDebugIfActive(battery_ShuntHwAndBmsMiaFault, "ERROR: ShuntHwAndBmsMiaFault is active"); + printDebugIfActive(battery_fcContHwFault, "ERROR: fcContHwFault is active"); + printDebugIfActive(battery_robinOverVoltageFault, "ERROR: robinOverVoltageFault is active"); + printDebugIfActive(battery_packContHwFault, "ERROR: packContHwFault is active"); + printDebugIfActive(battery_pyroFuseBlown, "ERROR: pyroFuseBlown is active"); + printDebugIfActive(battery_pyroFuseFailedToBlow, "ERROR: pyroFuseFailedToBlow is active"); + //printDebugIfActive(battery_CpilFault, "ERROR: CpilFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery_PackContactorFellOpen, "ERROR: PackContactorFellOpen is active"); + printDebugIfActive(battery_FcContactorFellOpen, "ERROR: FcContactorFellOpen is active"); + printDebugIfActive(battery_packCtrCloseBlocked, "ERROR: packCtrCloseBlocked is active"); + printDebugIfActive(battery_fcCtrCloseBlocked, "ERROR: fcCtrCloseBlocked is active"); + printDebugIfActive(battery_packContactorForceOpen, "ERROR: packContactorForceOpen is active"); + printDebugIfActive(battery_fcContactorForceOpen, "ERROR: fcContactorForceOpen is active"); + printDebugIfActive(battery_dcLinkOverVoltage, "ERROR: dcLinkOverVoltage is active"); + printDebugIfActive(battery_shuntOverTemperature, "ERROR: shuntOverTemperature is active"); + printDebugIfActive(battery_passivePyroDeploy, "ERROR: passivePyroDeploy is active"); + printDebugIfActive(battery_logUploadRequest, "ERROR: logUploadRequest is active"); + printDebugIfActive(battery_packCtrCloseFailed, "ERROR: packCtrCloseFailed is active"); + printDebugIfActive(battery_fcCtrCloseFailed, "ERROR: fcCtrCloseFailed is active"); + printDebugIfActive(battery_shuntThermistorMia, "ERROR: shuntThermistorMia is active"); } +#ifdef DOUBLE_BATTERY +void printFaultCodesIfActive_battery2() { + if (battery2_packCtrsClosingAllowed == 0) { + Serial.println( + "ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: "); + } + if (battery2_pyroTestInProgress == 1) { + Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); + } + if (datalayer.system.status.inverter_allows_contactor_closing == false) { + Serial.println( + "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR " + "disable the inverter protocol to proceed with contactor closing"); + } + // Check each symbol and print debug information if its value is 1 + printDebugIfActive(battery2_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); + printDebugIfActive(battery2_PowerLossReset, "ERROR: The processor has experienced a reset due to power loss"); + printDebugIfActive(battery2_SwAssertion, "ERROR: An internal software assertion has failed"); + printDebugIfActive(battery2_CrashEvent, "ERROR: crash signal is detected by HVP"); + printDebugIfActive(battery2_OverDchgCurrentFault, + "ERROR: Pack discharge current is above the safe max discharge current limit!"); + printDebugIfActive(battery2_OverChargeCurrentFault, + "ERROR: Pack charge current is above the safe max charge current limit!"); + printDebugIfActive(battery2_OverCurrentFault, + "ERROR: Pack current (discharge or charge) is above max current limit!"); + printDebugIfActive(battery2_OverTemperatureFault, + "ERROR: A pack module temperature is above the max temperature limit!"); + printDebugIfActive(battery2_OverVoltageFault, "ERROR: A brick voltage is above maximum voltage limit"); + printDebugIfActive(battery2_UnderVoltageFault, "ERROR: A brick voltage is below minimum voltage limit"); + printDebugIfActive(battery2_PrimaryBmbMiaFault, + "ERROR: voltage and temperature readings from primary BMB chain are mia"); + printDebugIfActive(battery2_SecondaryBmbMiaFault, + "ERROR: voltage and temperature readings from secondary BMB chain are mia"); + printDebugIfActive(battery2_BmbMismatchFault, + "ERROR: primary and secondary BMB chain readings don't match with each other"); + printDebugIfActive(battery2_BmsHviMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); + //printDebugIfActive(battery2_CpMiaFault, "ERROR: CP node is mia on HVS CAN"); //Uncommented due to not affecting usage + printDebugIfActive(battery2_PcsMiaFault, "ERROR: PCS node is mia on HVS CAN"); + //printDebugIfActive(battery2_BmsFault, "ERROR: BmsFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery2_PcsFault, "ERROR: PcsFault is active"); + //printDebugIfActive(battery2_CpFault, "ERROR: CpFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery2_ShuntHwMiaFault, "ERROR: shunt current reading is not available"); + printDebugIfActive(battery2_PyroMiaFault, "ERROR: pyro squib is not connected"); + printDebugIfActive(battery2_hvsMiaFault, "ERROR: pack contactor hw fault"); + printDebugIfActive(battery2_hviMiaFault, "ERROR: FC contactor hw fault"); + printDebugIfActive(battery2_Supply12vFault, "ERROR: Low voltage (12V) battery is below minimum voltage threshold"); + printDebugIfActive(battery2_VerSupplyFault, + "ERROR: Energy reserve voltage supply is below minimum voltage threshold"); + printDebugIfActive(battery2_HvilFault, "ERROR: High Voltage Inter Lock fault is detected"); + printDebugIfActive(battery2_BmsHvsMiaFault, "ERROR: BMS node is mia on HVS or HVI CAN"); + printDebugIfActive(battery2_PackVoltMismatchFault, + "ERROR: Pack voltage doesn't match approximately with sum of brick voltages"); + //printDebugIfActive(battery2_EnsMiaFault, "ERROR: ENS line is not connected to HVC"); //Uncommented due to not affecting usage + printDebugIfActive(battery2_PackPosCtrArcFault, "ERROR: HVP detectes series arc at pack contactor"); + printDebugIfActive(battery2_packNegCtrArcFault, "ERROR: HVP detectes series arc at FC contactor"); + printDebugIfActive(battery2_ShuntHwAndBmsMiaFault, "ERROR: ShuntHwAndBmsMiaFault is active"); + printDebugIfActive(battery2_fcContHwFault, "ERROR: fcContHwFault is active"); + printDebugIfActive(battery2_robinOverVoltageFault, "ERROR: robinOverVoltageFault is active"); + printDebugIfActive(battery2_packContHwFault, "ERROR: packContHwFault is active"); + printDebugIfActive(battery2_pyroFuseBlown, "ERROR: pyroFuseBlown is active"); + printDebugIfActive(battery2_pyroFuseFailedToBlow, "ERROR: pyroFuseFailedToBlow is active"); + //printDebugIfActive(battery2_CpilFault, "ERROR: CpilFault is active"); //Uncommented due to not affecting usage + printDebugIfActive(battery2_PackContactorFellOpen, "ERROR: PackContactorFellOpen is active"); + printDebugIfActive(battery2_FcContactorFellOpen, "ERROR: FcContactorFellOpen is active"); + printDebugIfActive(battery2_packCtrCloseBlocked, "ERROR: packCtrCloseBlocked is active"); + printDebugIfActive(battery2_fcCtrCloseBlocked, "ERROR: fcCtrCloseBlocked is active"); + printDebugIfActive(battery2_packContactorForceOpen, "ERROR: packContactorForceOpen is active"); + printDebugIfActive(battery2_fcContactorForceOpen, "ERROR: fcContactorForceOpen is active"); + printDebugIfActive(battery2_dcLinkOverVoltage, "ERROR: dcLinkOverVoltage is active"); + printDebugIfActive(battery2_shuntOverTemperature, "ERROR: shuntOverTemperature is active"); + printDebugIfActive(battery2_passivePyroDeploy, "ERROR: passivePyroDeploy is active"); + printDebugIfActive(battery2_logUploadRequest, "ERROR: logUploadRequest is active"); + printDebugIfActive(battery2_packCtrCloseFailed, "ERROR: packCtrCloseFailed is active"); + printDebugIfActive(battery2_fcCtrCloseFailed, "ERROR: fcCtrCloseFailed is active"); + printDebugIfActive(battery2_shuntThermistorMia, "ERROR: shuntThermistorMia is active"); +} +#endif //DOUBLE_BATTERY + void printDebugIfActive(uint8_t symbol, const char* message) { if (symbol == 1) { Serial.println(message); @@ -676,13 +1266,24 @@ void setup_battery(void) { // Performs one time setup at startup Serial.println("Tesla Model 3 battery selected"); #endif + datalayer.system.status.battery_allows_contactor_closing = true; + #ifdef LFP_CHEMISTRY datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP; -#else +#ifdef DOUBLE_BATTERY + datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP; + datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP; + datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP; +#endif +#else // Startup in NCM/A mode datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA; +#ifdef DOUBLE_BATTERY + datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA; + datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA; +#endif #endif } diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.h b/Software/src/battery/TESLA-MODEL-3-BATTERY.h index 5c803c7b..7d7c2057 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.h +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.h @@ -24,5 +24,10 @@ void printDebugIfActive(uint8_t symbol, const char* message); void print_int_with_units(char* header, int value, char* units); void print_SOC(char* header, int SOC); void setup_battery(void); +#ifdef DOUBLE_BATTERY +#include "../lib/pierremolinaro-acan2515/ACAN2515.h" +extern ACAN2515 can; +void printFaultCodesIfActive_battery2(); +#endif //DOUBLE_BATTERY #endif From 239cfeb03c774722488dadb3aa105601c8133ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 12 Jul 2024 17:45:48 +0300 Subject: [PATCH 09/11] Fix compilation error --- Software/USER_SETTINGS.h | 2 +- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 41115478..d584402b 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -43,7 +43,7 @@ //#define HW_STARK /* Other options */ -#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) +//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) //#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production) //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 53b02413..bfa08fb5 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -337,8 +337,8 @@ void update_values_battery() { /* This function maps all the values fetched via print_with_units(", GIDS: ", battery_GIDS, " (x77Wh) "); print_with_units(", Battery gen: ", LEAF_battery_Type, " "); print_with_units(", Has heater: ", battery_HeatExist, " "); - print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV "); - print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV "); + print_with_units(", Max cell voltage: ", battery_min_max_voltage[1], "mV "); + print_with_units(", Min cell voltage: ", battery_min_max_voltage[0], "mV "); #endif } From 5bf24b2c2d95dba8a5d689b977646415255b8055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 30 Jul 2024 13:42:07 +0300 Subject: [PATCH 10/11] Pre-commit fix --- Software/src/battery/TESLA-MODEL-3-BATTERY.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index e6f25bc9..7074cfd9 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -357,7 +357,6 @@ void update_values_battery() { //This function maps all the values fetched via } } - if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { //LFP limits used for voltage safeties if (battery_cell_max_v >= MAX_CELL_VOLTAGE_LFP) { set_event(EVENT_CELL_OVER_VOLTAGE, (battery_cell_max_v - MAX_CELL_VOLTAGE_LFP)); From a83437668f1e0cb16a1173941d23a02715b6aa9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 30 Jul 2024 13:43:56 +0300 Subject: [PATCH 11/11] Remove broken SOC% handling for double packs --- Software/Software.ino | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index d38a5132..2ae4fb14 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -736,14 +736,9 @@ void update_SOC() { datalayer.battery.status.reported_soc = calc_soc; } else { // No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; -#ifdef DOUBLE_BATTERY - datalayer.battery.status.reported_soc = - (datalayer.battery.status.real_soc + datalayer.battery2.status.real_soc) / 2; -#endif //DOUBLE_BATTERY } #ifdef DOUBLE_BATTERY - datalayer.battery.status.reported_soc = (datalayer.battery.status.real_soc + datalayer.battery2.status.real_soc) / 2; - + // Perform extra SOC sanity checks on double battery setups if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; } @@ -757,7 +752,6 @@ void update_SOC() { if (datalayer.battery2.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; } - #endif //DOUBLE_BATTERY }