diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index f4be1c58b..7a60ec631 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -55,6 +55,7 @@ jobs: - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY + - FOXESS_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY - KIA_E_GMP_BATTERY diff --git a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml index 1606e334e..5955fe9b4 100644 --- a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml +++ b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml @@ -60,6 +60,7 @@ jobs: - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY + - FOXESS_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY - KIA_E_GMP_BATTERY diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index ba3c43979..b4bc32980 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -14,6 +14,7 @@ //#define BMW_PHEV_BATTERY //#define BOLT_AMPERA_BATTERY //#define BYD_ATTO_3_BATTERY +//#define FOXESS_BATTERY //#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define IMIEV_CZERO_ION_BATTERY diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index ac8ea1b62..5ad2417f8 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -38,6 +38,10 @@ void setup_can_shunt(); #include "CHADEMO-SHUNTS.h" #endif +#ifdef FOXESS_BATTERY +#include "FOXESS-BATTERY.h" +#endif + #ifdef SONO_BATTERY #include "SONO-BATTERY.h" #endif diff --git a/Software/src/battery/FOXESS-BATTERY.cpp b/Software/src/battery/FOXESS-BATTERY.cpp new file mode 100644 index 000000000..8afffa6a8 --- /dev/null +++ b/Software/src/battery/FOXESS-BATTERY.cpp @@ -0,0 +1,659 @@ +#include "../include.h" +#ifdef FOXESS_BATTERY +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "FOXESS-BATTERY.h" + +/* +Can bus @ 500k - all Extended ID, little endian +All info from https://github.com/FozzieUK/FoxESS-Canbus-Protocol + +TODO: +- Check that current is signed right way +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send + +CAN_frame FOX_1871 = {.FD = false, //Inverter request data from battery. Content varies depending on state + .ext_ID = true, + .DLC = 8, + .ID = 0x1871, + .data = {0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}}; +static uint32_t total_watt_hours = 0; +static uint16_t max_charge_power_dA = 0; +static uint16_t max_discharge_power_dA = 0; +static uint16_t cut_mv_max = 0; +static uint16_t cut_mv_min = 0; +static uint16_t cycle_count = 0; +static uint16_t max_ac_voltage = 0; +static uint16_t cellvoltages_mV[128] = {0}; +static int16_t temperature_average = 0; +static int16_t pack1_current_sensor = 0; +static int16_t pack2_current_sensor = 0; +static int16_t pack3_current_sensor = 0; +static int16_t pack4_current_sensor = 0; +static int16_t pack5_current_sensor = 0; +static int16_t pack6_current_sensor = 0; +static int16_t pack7_current_sensor = 0; +static int16_t pack8_current_sensor = 0; +static int16_t pack1_temperature_avg_high = 0; +static int16_t pack2_temperature_avg_high = 0; +static int16_t pack3_temperature_avg_high = 0; +static int16_t pack4_temperature_avg_high = 0; +static int16_t pack5_temperature_avg_high = 0; +static int16_t pack6_temperature_avg_high = 0; +static int16_t pack7_temperature_avg_high = 0; +static int16_t pack8_temperature_avg_high = 0; +static int16_t pack1_temperature_avg_low = 0; +static int16_t pack2_temperature_avg_low = 0; +static int16_t pack3_temperature_avg_low = 0; +static int16_t pack4_temperature_avg_low = 0; +static int16_t pack5_temperature_avg_low = 0; +static int16_t pack6_temperature_avg_low = 0; +static int16_t pack7_temperature_avg_low = 0; +static int16_t pack8_temperature_avg_low = 0; +static uint16_t pack1_voltage = 0; +static uint16_t pack2_voltage = 0; +static uint16_t pack3_voltage = 0; +static uint16_t pack4_voltage = 0; +static uint16_t pack5_voltage = 0; +static uint16_t pack6_voltage = 0; +static uint16_t pack7_voltage = 0; +static uint16_t pack8_voltage = 0; +static uint8_t pack1_SOC = 0; +static uint8_t pack2_SOC = 0; +static uint8_t pack3_SOC = 0; +static uint8_t pack4_SOC = 0; +static uint8_t pack5_SOC = 0; +static uint8_t pack6_SOC = 0; +static uint8_t pack7_SOC = 0; +static uint8_t pack8_SOC = 0; +static uint8_t pack_error = 0; +static uint8_t firmware_pack_minor = 0; +static uint8_t firmware_pack_major = 0; +static uint8_t STATUS_OPERATIONAL_PACKS = + 0; //0x1875 b2 contains status for operational packs (responding) in binary so 01111111 is pack 8 not operational, 11101101 is pack 5 & 2 not operational +static uint8_t NUMBER_OF_PACKS = 0; //1-8 +static uint8_t contactor_status = 0; +static uint8_t statemachine_polling = 0; +static bool charging_disabled = false; +static bool b0_idle = false; +static bool b1_ok_discharge = false; +static bool b2_ok_charge = false; +static bool b3_discharging = false; +static bool b4_charging = false; +static bool b5_operational = false; +static bool b6_active_error = false; + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + + datalayer.battery.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + + datalayer.battery.status.max_discharge_power_W = + ((datalayer.battery.status.voltage_dV * max_discharge_power_dA) / 100); + + datalayer.battery.status.max_charge_power_W = ((datalayer.battery.status.voltage_dV * max_charge_power_dA) / 100); + + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, 128 * sizeof(uint16_t)); + + switch (NUMBER_OF_PACKS) { + case 1: //HS2.6 (48V invalid combo for most HV inverters) + datalayer.battery.info.number_of_cells = 16; + datalayer.battery.info.max_design_voltage_dV = 584; + datalayer.battery.info.min_design_voltage_dV = 400; + break; + case 2: //HS5.2 + datalayer.battery.info.number_of_cells = 32; + datalayer.battery.info.max_design_voltage_dV = 1168; + datalayer.battery.info.min_design_voltage_dV = 800; + break; + case 3: //HS7.8 + datalayer.battery.info.number_of_cells = 48; + datalayer.battery.info.max_design_voltage_dV = 1752; + datalayer.battery.info.min_design_voltage_dV = 1200; + break; + case 4: //HS10.4 + datalayer.battery.info.number_of_cells = 64; + datalayer.battery.info.max_design_voltage_dV = 2336; + datalayer.battery.info.min_design_voltage_dV = 1600; + break; + case 5: //HS13 + datalayer.battery.info.number_of_cells = 80; + datalayer.battery.info.max_design_voltage_dV = 2920; + datalayer.battery.info.min_design_voltage_dV = 2000; + break; + case 6: //HS15.6 + datalayer.battery.info.number_of_cells = 96; + datalayer.battery.info.max_design_voltage_dV = 3504; + datalayer.battery.info.min_design_voltage_dV = 2400; + break; + case 7: //HS18.2 + datalayer.battery.info.number_of_cells = 112; + datalayer.battery.info.max_design_voltage_dV = 4088; + datalayer.battery.info.min_design_voltage_dV = 2800; + break; + case 8: //HS20.8 + datalayer.battery.info.number_of_cells = 128; + datalayer.battery.info.max_design_voltage_dV = 4672; + datalayer.battery.info.min_design_voltage_dV = 3200; + break; + default: + //Data not available yet + break; + } +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x1872: //BMS_Limits + datalayer.battery.info.max_design_voltage_dV = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + datalayer.battery.info.min_design_voltage_dV = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + if (!charging_disabled) { + max_charge_power_dA = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + } + max_discharge_power_dA = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x1873: //BMS_PackData + datalayer.battery.status.voltage_dV = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + datalayer.battery.status.current_dA = + (int16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //TODO: Direction right way? + datalayer.battery.status.real_soc = (rx_frame.data.u8[4] * 100); + datalayer.battery.status.remaining_capacity_Wh = ((rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 10); + break; + case 0x1874: //BMS_CellData + datalayer.battery.status.temperature_max_dC = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + datalayer.battery.status.temperature_min_dC = (int16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cut_mv_max = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cut_mv_min = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x1875: //BMS_Status + temperature_average = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + STATUS_OPERATIONAL_PACKS = (uint8_t)rx_frame.data.u8[2]; + NUMBER_OF_PACKS = (uint8_t)rx_frame.data.u8[3]; + contactor_status = (uint8_t)rx_frame.data.u8[4]; + cycle_count = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x1876: //BMS_PackTemps + // 0x1876 b0 bit 0 appears to be 1 when at maxsoc and BMS says charge is not allowed - + // when at 0 indicates charge is possible - additional note there is something more to it than this, + // it's not as straight forward - needs more testing to find what sets/unsets bit0 of byte0 + if ((rx_frame.data.u8[0] & 0x01) > 0) { + max_charge_power_dA = 0; + charging_disabled = true; + } else { + charging_disabled = false; + } + + datalayer.battery.status.cell_max_voltage_mV = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + datalayer.battery.status.cell_min_voltage_mV = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x1877: //BMS_ErrorsBrand + pack_error = (uint8_t)(rx_frame.data.u8[0]); + firmware_pack_minor = (uint8_t)(rx_frame.data.u8[6] & 0x0F); + firmware_pack_major = (uint8_t)((rx_frame.data.u8[6] & 0xF0) >> 4); + break; + case 0x1878: //BMS_PackStats + max_ac_voltage = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + total_watt_hours = (uint32_t)(rx_frame.data.u8[7] << 24 | rx_frame.data.u8[6] << 16 | rx_frame.data.u8[5] << 8 | + rx_frame.data.u8[4]); + break; + case 0x1879: //BMS_PackState + b0_idle = (bool)(rx_frame.data.u8[1] & 0x01); + b1_ok_discharge = (bool)((rx_frame.data.u8[1] & 0x02) >> 1); + b2_ok_charge = (bool)((rx_frame.data.u8[1] & 0x04) >> 2); + b3_discharging = (bool)((rx_frame.data.u8[1] & 0x08) >> 3); + b4_charging = (bool)((rx_frame.data.u8[1] & 0x20) >> 4); + b5_operational = (bool)((rx_frame.data.u8[1] & 0x40) >> 5); + b6_active_error = (bool)((rx_frame.data.u8[1] & 0x80) >> 6); + break; + case 0x1881: //These contain serial number. No interest to us + case 0x1882: //These contain serial number. No interest to us + case 0x1883: //These contain serial number. No interest to us + break; + case 0x0C05: //Pack 1 + pack1_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack1_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack1_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack1_SOC = (uint8_t)rx_frame.data.u8[4]; + pack1_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C06: //Pack 2 + pack2_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack2_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack2_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack2_SOC = (uint8_t)rx_frame.data.u8[4]; + pack2_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C07: //Pack 3 + pack3_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack3_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack3_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack3_SOC = (uint8_t)rx_frame.data.u8[4]; + pack3_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C08: //Pack 4 + pack4_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack4_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack4_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack4_SOC = (uint8_t)rx_frame.data.u8[4]; + pack4_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C09: //Pack 5 + pack5_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack5_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack5_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack5_SOC = (uint8_t)rx_frame.data.u8[4]; + pack5_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C0A: //Pack 6 + pack6_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack6_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack6_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack6_SOC = (uint8_t)rx_frame.data.u8[4]; + pack6_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C0B: //Pack 7 + pack7_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack7_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack7_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack7_SOC = (uint8_t)rx_frame.data.u8[4]; + pack7_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C0C: //Pack 8 + pack8_current_sensor = (int16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + pack8_temperature_avg_high = (int16_t)(rx_frame.data.u8[2] - 50); + pack8_temperature_avg_low = (int16_t)(rx_frame.data.u8[3] - 50); + pack8_SOC = (uint8_t)rx_frame.data.u8[4]; + pack8_voltage = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0D21: //These CAN messages contain raw cell temperatures + case 0x0D29: //They are not required, we already read min/max from each pack + case 0x0D31: + case 0x0D39: + case 0x0D41: + case 0x0D49: + case 0x0D51: + case 0x0D59: + break; + case 0x0C1D: // Cellvolts 1 + cellvoltages_mV[0] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[1] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[2] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[3] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C21: // Cellvolts 2 + cellvoltages_mV[4] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[5] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[6] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[7] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C25: // Cellvolts 3 + cellvoltages_mV[8] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[9] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[10] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[11] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C29: // Cellvolts 4 + cellvoltages_mV[12] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[13] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[14] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[15] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C2D: // Cellvolts 5 + cellvoltages_mV[16] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[17] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[18] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[19] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C31: // Cellvolts 6 + cellvoltages_mV[20] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[21] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[22] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[23] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C35: // Cellvolts 7 + cellvoltages_mV[24] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[25] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[26] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[27] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C39: // Cellvolts 8 + cellvoltages_mV[28] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[29] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[30] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[31] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C3D: // Cellvolts 9 + cellvoltages_mV[32] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[33] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[34] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[35] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C41: // Cellvolts 10 + cellvoltages_mV[36] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[37] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[38] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[39] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C45: // Cellvolts 11 + cellvoltages_mV[40] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[41] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[42] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[43] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C49: // Cellvolts 12 + cellvoltages_mV[44] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[45] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[46] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[47] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C4D: // Cellvolts 13 + cellvoltages_mV[48] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[49] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[50] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[51] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C51: // Cellvolts 14 + cellvoltages_mV[52] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[53] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[54] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[55] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C55: // Cellvolts 15 + cellvoltages_mV[56] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[57] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[58] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[59] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C59: // Cellvolts 16 + cellvoltages_mV[60] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[61] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[62] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[63] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C5D: // Cellvolts 17 + cellvoltages_mV[64] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[65] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[66] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[67] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C61: // Cellvolts 18 + cellvoltages_mV[68] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[69] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[70] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[71] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C65: // Cellvolts 19 + cellvoltages_mV[72] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[73] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[74] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[75] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C69: // Cellvolts 20 + cellvoltages_mV[76] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[77] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[78] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[79] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C6D: // Cellvolts 21 + cellvoltages_mV[80] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[81] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[82] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[83] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C71: // Cellvolts 22 + cellvoltages_mV[84] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[85] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[86] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[87] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C75: // Cellvolts 23 + cellvoltages_mV[88] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[89] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[90] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[91] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C79: // Cellvolts 24 + cellvoltages_mV[92] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[93] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[94] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[95] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C7D: // Cellvolts 25 + cellvoltages_mV[96] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[97] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[98] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[99] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C81: // Cellvolts 26 + cellvoltages_mV[100] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[101] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[102] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[103] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C85: // Cellvolts 27 + cellvoltages_mV[104] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[105] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[106] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[107] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C89: // Cellvolts 28 + cellvoltages_mV[108] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[109] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[110] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[111] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C8D: // Cellvolts 29 + cellvoltages_mV[112] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[113] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[114] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[115] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C91: // Cellvolts 30 + cellvoltages_mV[116] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[117] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[118] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[119] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C95: // Cellvolts 31 + cellvoltages_mV[120] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[121] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[122] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[123] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x0C99: // Cellvolts 32 + cellvoltages_mV[124] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + cellvoltages_mV[125] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + cellvoltages_mV[126] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + cellvoltages_mV[127] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + default: + break; + } +} +void transmit_can_battery() { + unsigned long currentMillis = millis(); + + // Send 500ms CAN Message + if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { + previousMillis500 = currentMillis; + + switch (statemachine_polling) { + case 0: //0.5s + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_statistics + break; + case 1: //1s + FOX_1871.data.u8[0] = 0x02; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_stop_sending + break; + case 2: //1.5s + FOX_1871.data.u8[0] = 0x05; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_serial_request + break; + case 3: //2.0s + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_statistics + break; + case 4: //2.5s + FOX_1871.data.u8[0] = 0x02; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_stop_sending + break; + case 5: //3.0s cell temp and voltages + //0x1871 [0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00] + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x02; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_cell_volts + //0x1871 [0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00] + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x04; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_cell_temps + break; + case 6: //3.5s + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_statistics + break; + case 7: //4.0s + FOX_1871.data.u8[0] = 0x02; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_stop_sending + break; + case 8: //4.5s + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_statistics + break; + case 9: //5.0s + FOX_1871.data.u8[0] = 0x02; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x00; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_stop_sending + break; + case 10: //5.5s + //0x1871 [0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00] + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x02; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_cell_volts + //0x1871 [0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00] + FOX_1871.data.u8[0] = 0x01; + FOX_1871.data.u8[1] = 0x00; + FOX_1871.data.u8[2] = 0x01; + FOX_1871.data.u8[3] = 0x00; + FOX_1871.data.u8[4] = 0x04; + FOX_1871.data.u8[5] = 0x00; + FOX_1871.data.u8[6] = 0x00; + FOX_1871.data.u8[7] = 0x00; + transmit_can_frame(&FOX_1871, can_config.battery); //bms_send_pack_cell_temps + break; + case 11: //6.0s 0x1871 [0x03, 0x06, 0x17, 0x05, 0x09, 0x09, 0x28, 0x22] + FOX_1871.data.u8[0] = 0x03; + FOX_1871.data.u8[1] = 0x06; + FOX_1871.data.u8[2] = 0x17; + FOX_1871.data.u8[3] = 0x05; + FOX_1871.data.u8[4] = 0x09; + FOX_1871.data.u8[5] = 0x09; + FOX_1871.data.u8[6] = 0x28; + FOX_1871.data.u8[7] = 0x22; + transmit_can_frame(&FOX_1871, can_config.battery); //timestamp + break; + default: + statemachine_polling = 0; + break; + } + + statemachine_polling = (statemachine_polling + 1) % 12; + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "FoxESS HV2600/ECS4100 OEM battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.battery.info.number_of_cells = 0; //Startup with no cells, populates later when we know packsize + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; +} + +#endif diff --git a/Software/src/battery/FOXESS-BATTERY.h b/Software/src/battery/FOXESS-BATTERY.h new file mode 100644 index 000000000..6eebdcafa --- /dev/null +++ b/Software/src/battery/FOXESS-BATTERY.h @@ -0,0 +1,16 @@ +#ifndef FOXESS_BATTERY_H +#define FOXESS_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +#define MAX_PACK_VOLTAGE_DV 4672 //467.2V for HS20.8 (used during startup, refined later) +#define MIN_PACK_VOLTAGE_DV 800 //80.V for HS5.2 (used during startup, refined later) +#define MAX_CELL_DEVIATION_MV 250 +#define MAX_CELL_VOLTAGE_MV 3800 //LiFePO4 Prismaticc Cell +#define MIN_CELL_VOLTAGE_MV 2700 //LiFePO4 Prismatic Cell +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif