diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 50ffc6e9..0bcbd6d1 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 + - CMFA_EV_BATTERY - FOXESS_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY diff --git a/Software/Software.ino b/Software/Software.ino index f779ed5d..a1ba99bc 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -49,7 +49,7 @@ volatile unsigned long long bmsResetTimeOffset = 0; // The current software version, shown on webserver -const char* version_number = "8.10.dev"; +const char* version_number = "8.11.dev"; // Interval timers unsigned long previousMillis10ms = 0; @@ -336,6 +336,9 @@ void check_interconnect_available() { #endif // DOUBLE_BATTERY void update_calculated_values() { + /* Update CPU temperature*/ + datalayer.system.info.CPU_temperature = temperatureRead(); + /* Calculate allowed charge/discharge currents*/ if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage @@ -427,6 +430,12 @@ void update_calculated_values() { calc_soc = 10000 * (calc_soc - datalayer.battery.settings.min_percentage); calc_soc = calc_soc / (datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage); datalayer.battery.status.reported_soc = calc_soc; + //Extra safety since we allow scaling negatively, if real% is < 1.00%, zero it out + if (datalayer.battery.status.real_soc < 100) { + datalayer.battery.status.reported_soc = 0; + } else { + datalayer.battery.status.reported_soc = calc_soc; + } // Calculate the scaled remaining capacity in Wh if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index e3076787..b8015f16 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -18,6 +18,7 @@ //#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below #define GEELY_GEOMETRY_C_BATTERY +//#define CMFA_EV_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY //#define KIA_E_GMP_BATTERY diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 67ed0dab..236d8649 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 CMFA_EV_BATTERY +#include "CMFA-EV-BATTERY.h" +#endif + #ifdef FOXESS_BATTERY #include "FOXESS-BATTERY.h" #endif diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index e78d08c7..43583781 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -12,6 +12,10 @@ */ /* Do not change code below unless you are sure what you are doing */ +#define NOT_DETERMINED_YET 0 +#define STANDARD_RANGE 1 +#define EXTENDED_RANGE 2 +static uint8_t battery_type = NOT_DETERMINED_YET; static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send @@ -37,8 +41,7 @@ static int16_t BMS_average_cell_temperature = 0; static uint16_t BMS_lowest_cell_voltage_mV = 3300; static uint16_t BMS_highest_cell_voltage_mV = 3300; static uint8_t battery_frame_index = 0; -#define NOF_CELLS 126 -static uint16_t battery_cellvoltages[NOF_CELLS] = {0}; +static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0}; #ifdef DOUBLE_BATTERY static int16_t battery2_temperature_ambient = 0; static int16_t battery2_daughterboard_temperatures[10]; @@ -56,7 +59,7 @@ static int16_t BMS2_average_cell_temperature = 0; static uint16_t BMS2_lowest_cell_voltage_mV = 3300; static uint16_t BMS2_highest_cell_voltage_mV = 3300; static uint8_t battery2_frame_index = 0; -static uint16_t battery2_cellvoltages[NOF_CELLS] = {0}; +static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0}; #endif //DOUBLE_BATTERY #define POLL_FOR_BATTERY_SOC 0x05 #define POLL_FOR_BATTERY_VOLTAGE 0x08 @@ -90,20 +93,39 @@ CAN_frame ATTO_3_7E7_POLL = {.FD = false, // Define the data points for %SOC depending on pack voltage const uint8_t numPoints = 14; const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0}; -const uint16_t voltage[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130, - 4121, 4119, 4100, 4070, 4030, 3950, 3800}; +const uint16_t voltage_extended[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130, + 4121, 4119, 4100, 4070, 4030, 3950, 3800}; +const uint16_t voltage_standard[numPoints] = {3620, 3485, 3443, 3435, 3433, 3425, 3400, + 3392, 3390, 3375, 3350, 3315, 3250, 3140}; -uint16_t estimateSOC(uint16_t packVoltage) { // Linear interpolation function - if (packVoltage >= voltage[0]) { +uint16_t estimateSOCextended(uint16_t packVoltage) { // Linear interpolation function + if (packVoltage >= voltage_extended[0]) { return SOC[0]; } - if (packVoltage <= voltage[numPoints - 1]) { + if (packVoltage <= voltage_extended[numPoints - 1]) { return SOC[numPoints - 1]; } for (int i = 1; i < numPoints; ++i) { - if (packVoltage >= voltage[i]) { - double t = (packVoltage - voltage[i]) / (voltage[i - 1] - voltage[i]); + if (packVoltage >= voltage_extended[i]) { + double t = (packVoltage - voltage_extended[i]) / (voltage_extended[i - 1] - voltage_extended[i]); + return SOC[i] + t * (SOC[i - 1] - SOC[i]); + } + } + return 0; // Default return for safety, should never reach here +} + +uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation function + if (packVoltage >= voltage_standard[0]) { + return SOC[0]; + } + if (packVoltage <= voltage_standard[numPoints - 1]) { + return SOC[numPoints - 1]; + } + + for (int i = 1; i < numPoints; ++i) { + if (packVoltage >= voltage_standard[i]) { + double t = (packVoltage - voltage_standard[i]) / (voltage_standard[i - 1] - voltage_standard[i]); return SOC[i] + t * (SOC[i - 1] - SOC[i]); } } @@ -120,7 +142,12 @@ void update_values_battery() { //This function maps all the values fetched via // When the battery is crashed hard, it locks itself and SOC becomes unavailable. // We instead estimate the SOC% based on the battery voltage. // This is a bad solution, you wont be able to use 100% of the battery - datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); + if (battery_type == EXTENDED_RANGE) { + datalayer.battery.status.real_soc = estimateSOCextended(datalayer.battery.status.voltage_dV); + } + if (battery_type == STANDARD_RANGE) { + datalayer.battery.status.real_soc = estimateSOCstandard(datalayer.battery.status.voltage_dV); + } SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC datalayer.battery.status.real_soc = battery_highprecision_SOC * 10; @@ -141,7 +168,41 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; //Map all cell voltages to the global array - memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, NOF_CELLS * sizeof(uint16_t)); + memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); + + // Check if we are on Standard range or Extended range battery. + // We use a variety of checks to ensure we catch a potential Standard range battery + if ((battery_cellvoltages[125] > 0) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = EXTENDED_RANGE; + } + if ((battery_cellvoltages[104] == 4095) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //This cell reading is always 4095 on Standard range + } + if ((battery_daughterboard_temperatures[9] == 215) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //Sensor 10 is missing on Standard range + } + if ((battery_daughterboard_temperatures[8] == 215) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //Sensor 9 is missing on Standard range + } + + switch (battery_type) { + case STANDARD_RANGE: + datalayer.battery.info.total_capacity_Wh = 50000; + datalayer.battery.info.number_of_cells = CELLCOUNT_STANDARD; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; + break; + case EXTENDED_RANGE: + datalayer.battery.info.total_capacity_Wh = 60000; + datalayer.battery.info.number_of_cells = CELLCOUNT_EXTENDED; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV; + break; + case NOT_DETERMINED_YET: + default: + //Do nothing + break; + } #ifdef SKIP_TEMPERATURE_SENSOR_NUMBER // Initialize min and max variables for temperature calculation @@ -263,7 +324,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery_frame_index = rx_frame.data.u8[0]; - if (battery_frame_index < (NOF_CELLS / 3)) { + if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) { uint8_t base_index = battery_frame_index * 3; for (uint8_t i = 0; i < 3; i++) { battery_cellvoltages[base_index + i] = @@ -447,23 +508,19 @@ void transmit_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63); datalayer.system.info.battery_protocol[63] = '\0'; - datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; - 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_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range 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; - //Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V - //We now init the value to 380.1V to avoid false positive events. - datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1; #ifdef DOUBLE_BATTERY - datalayer.battery2.info.number_of_cells = 126; + datalayer.battery2.info.number_of_cells = CELLCOUNT_STANDARD; datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP; - datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; - datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; - datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; - datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + 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.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV; + datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV; datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; #endif //DOUBLE_BATTERY } @@ -478,7 +535,12 @@ void update_values_battery2() { //This function maps all the values fetched via // We instead estimate the SOC% based on the battery2 voltage // This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that! - datalayer.battery2.status.real_soc = estimateSOC(datalayer.battery2.status.voltage_dV); + if (battery_type == EXTENDED_RANGE) { + datalayer.battery2.status.real_soc = estimateSOCextended(datalayer.battery2.status.voltage_dV); + } + if (battery_type == STANDARD_RANGE) { + datalayer.battery2.status.real_soc = estimateSOCstandard(datalayer.battery2.status.voltage_dV); + } datalayer.battery2.status.current_dA = -BMS2_current; @@ -498,7 +560,12 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10; //Map all cell voltages to the global array - memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, NOF_CELLS * sizeof(uint16_t)); + memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); + + datalayer.battery2.info.total_capacity_Wh = datalayer.battery.info.total_capacity_Wh; + datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells; + 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; } void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { @@ -515,7 +582,10 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { case 0x286: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; - case 0x334 datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x338: + case 0x334: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x338: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x344: @@ -568,7 +638,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { case 0x43D: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery2_frame_index = rx_frame.data.u8[0]; - if (battery2_frame_index < (NOF_CELLS / 3)) { + if (battery2_frame_index < (CELLCOUNT_EXTENDED / 3)) { uint8_t base2_index = battery2_frame_index * 3; for (uint8_t i = 0; i < 3; i++) { battery2_cellvoltages[base2_index + i] = diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index f726ebc6..3060eb64 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -4,7 +4,7 @@ #include "../include.h" #define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ - // Uncomment this only if you know your BMS is unlocked and able to send SOC% + // Comment out this only if you know your BMS is unlocked and able to send SOC% #define MAXPOWER_CHARGE_W 10000 #define MAXPOWER_DISCHARGE_W 10000 @@ -14,8 +14,12 @@ /* Do not modify the rows below */ #define BATTERY_SELECTED -#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V -#define MIN_PACK_VOLTAGE_DV 3800 +#define CELLCOUNT_EXTENDED 126 +#define CELLCOUNT_STANDARD 104 +#define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range +#define MIN_PACK_VOLTAGE_EXTENDED_DV 3800 //Extended range +#define MAX_PACK_VOLTAGE_STANDARD_DV 3640 //Standard range +#define MIN_PACK_VOLTAGE_STANDARD_DV 3136 //Standard range #define MAX_CELL_DEVIATION_MV 150 #define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp new file mode 100644 index 00000000..2e6282ae --- /dev/null +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -0,0 +1,1036 @@ +#include "../include.h" +#ifdef CMFA_EV_BATTERY +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "../devboard/utils/events.h" +#include "CMFA-EV-BATTERY.h" + +/* Do not change code below unless you are sure what you are doing */ +CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}}; +CAN_frame CMFA_125 = {.FD = false, + .ext_ID = false, + .DLC = 7, + .ID = 0x125, + .data = {0x7D, 0x7D, 0x7D, 0x07, 0x82, 0x6A, 0x8A}}; +CAN_frame CMFA_134 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x134, + .data = {0x90, 0x8A, 0x7E, 0x3E, 0xB2, 0x4C, 0x80, 0x00}}; +CAN_frame CMFA_135 = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x135, .data = {0xD5, 0x85, 0x38, 0x80, 0x01}}; +CAN_frame CMFA_3D3 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x3D3, + .data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}}; +CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}}; +CAN_frame CMFA_ACK = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x79B, + .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame CMFA_POLLING_FRAME = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x79B, + .data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}}; +static bool end_of_charge = false; +static bool interlock_flag = false; +static uint16_t soc_z = 0; +static uint16_t soc_u = 0; +static uint16_t max_regen_power = 0; +static uint16_t max_discharge_power = 0; +static int16_t average_temperature = 0; +static int16_t minimum_temperature = 0; +static int16_t maximum_temperature = 0; +static uint16_t maximum_charge_power = 0; +static uint16_t SOH_available_power = 0; +static uint16_t SOH_generated_power = 0; +static uint32_t average_voltage_of_cells = 270000; +static uint16_t highest_cell_voltage_mv = 3700; +static uint16_t lowest_cell_voltage_mv = 3700; +static uint16_t lead_acid_voltage = 12000; +static uint8_t highest_cell_voltage_number = 0; +static uint8_t lowest_cell_voltage_number = 0; +static uint64_t cumulative_energy_when_discharging = 0; +static uint64_t cumulative_energy_when_charging = 0; +static uint64_t cumulative_energy_in_regen = 0; +static uint16_t soh_average = 10000; +static uint16_t cellvoltages_mv[72]; +static uint32_t poll_pid = PID_POLL_SOH_AVERAGE; +static uint16_t pid_reply = 0; + +static uint8_t counter_10ms = 0; +static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A, + 0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02}; +static uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5, + 0x05, 0x55, 0xA5, 0xF5, 0x45, 0x95, 0xE5, 0x35}; +static unsigned long previousMillis200ms = 0; +static unsigned long previousMillis100ms = 0; +static unsigned long previousMillis10ms = 0; + +#define MAXSOC 9000 //90.00 Raw SOC displays this value when battery is at 100% +#define MINSOC 500 //5.00 Raw SOC displays this value when battery is at 0% + +static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame +static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame +static uint32_t SOC_raw = 0; +static uint16_t SOH = 99; +static int16_t current = 0; +static uint16_t pack_voltage = 2700; +static int16_t highest_cell_temperature = 0; +static int16_t lowest_cell_temperature = 0; +static uint32_t discharge_power_w = 0; +static uint32_t charge_power_w = 0; + +/* The raw SOC value sits at 90% when the battery is full, so we should report back 100% once this value is reached +Same goes for low point, when 10% is reached we report 0% */ + +uint16_t rescale_raw_SOC(uint32_t raw_SOC) { + + uint32_t calc_soc; + calc_soc = (raw_SOC * 0.25); + if (calc_soc > MAXSOC) { //Constrain if needed + calc_soc = MAXSOC; + } + if (calc_soc < MINSOC) { //Constrain if needed + calc_soc = MINSOC; + } + // Perform scaling between the two points + calc_soc = 10000 * (calc_soc - MINSOC); + calc_soc = calc_soc / (MAXSOC - MINSOC); + + return (uint16_t)calc_soc; +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + datalayer.battery.status.soh_pptt = (SOH * 100); + + datalayer.battery.status.real_soc = rescale_raw_SOC(SOC_raw); + + datalayer.battery.status.current_dA = current * 10; + + datalayer.battery.status.voltage_dV = average_voltage_of_cells / 100; + + datalayer.battery.info.total_capacity_Wh = 27000; + + //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); + + datalayer.battery.status.max_discharge_power_W = discharge_power_w; + + datalayer.battery.status.max_charge_power_W = charge_power_w; + + datalayer.battery.status.temperature_min_dC = (lowest_cell_temperature * 10); + + datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); + + datalayer.battery.status.cell_min_voltage_mV = lowest_cell_voltage_mv; + + datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; + + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t)); + + if (lead_acid_voltage < 11000) { //11.000V + set_event(EVENT_12V_LOW, lead_acid_voltage); + } + + // Update webserver datalayer + datalayer_extended.CMFAEV.soc_u = soc_u; + datalayer_extended.CMFAEV.soc_z = soc_z; + datalayer_extended.CMFAEV.lead_acid_voltage = lead_acid_voltage; + datalayer_extended.CMFAEV.highest_cell_voltage_number = highest_cell_voltage_number; + datalayer_extended.CMFAEV.lowest_cell_voltage_number = lowest_cell_voltage_number; + datalayer_extended.CMFAEV.max_regen_power = max_regen_power; + datalayer_extended.CMFAEV.max_discharge_power = max_discharge_power; + datalayer_extended.CMFAEV.average_temperature = average_temperature; + datalayer_extended.CMFAEV.minimum_temperature = minimum_temperature; + datalayer_extended.CMFAEV.maximum_temperature = maximum_temperature; + datalayer_extended.CMFAEV.maximum_charge_power = maximum_charge_power; + datalayer_extended.CMFAEV.SOH_available_power = SOH_available_power; + datalayer_extended.CMFAEV.SOH_generated_power = SOH_generated_power; + datalayer_extended.CMFAEV.cumulative_energy_when_discharging = cumulative_energy_when_discharging; + datalayer_extended.CMFAEV.cumulative_energy_when_charging = cumulative_energy_when_charging; + datalayer_extended.CMFAEV.cumulative_energy_in_regen = cumulative_energy_in_regen; + datalayer_extended.CMFAEV.soh_average = soh_average; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + switch (rx_frame.ID) { //These frames are transmitted by the battery + case 0x127: //10ms , Same structure as old Zoe 0x155 message! + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500); + SOC_raw = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case 0x3D6: //100ms, Same structure as old Zoe 0x424 message! + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + charge_power_w = rx_frame.data.u8[2] * 500; + discharge_power_w = rx_frame.data.u8[3] * 500; + lowest_cell_temperature = (rx_frame.data.u8[4] - 40); + SOH = rx_frame.data.u8[5]; + heartbeat = rx_frame.data.u8[6]; + highest_cell_temperature = (rx_frame.data.u8[7] - 40); + break; + case 0x3D7: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F))); + break; + case 0x3D8: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //counter_3D8 = rx_frame.data.u8[3]; //? + //CRC_3D8 = rx_frame.data.u8[4]; //? + break; + case 0x43C: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame + break; + case 0x431: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //byte0 9C always + //byte1 40 always + break; + case 0x5A9: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5AB: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5C8: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5E1: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x7BB: // Reply from battery + if (rx_frame.data.u8[0] == 0x10) { //PID header + transmit_can_frame(&CMFA_ACK, can_config.battery); + } + + pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; + + switch (pid_reply) { + case PID_POLL_SOCZ: + soc_z = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_USOC: + soc_u = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_SOH_AVERAGE: + soh_average = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: + average_voltage_of_cells = + (uint32_t)((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + case PID_POLL_HIGHEST_CELL_VOLTAGE: + highest_cell_voltage_mv = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: + highest_cell_voltage_number = rx_frame.data.u8[4]; + break; + case PID_POLL_LOWEST_CELL_VOLTAGE: + lowest_cell_voltage_mv = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE: + lowest_cell_voltage_number = rx_frame.data.u8[4]; + break; + case PID_POLL_CURRENT_OFFSET: + //current_offset = + break; + case PID_POLL_INSTANT_CURRENT: + //instant_offset = + break; + case PID_POLL_MAX_REGEN: + max_regen_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_MAX_DISCHARGE_POWER: + max_discharge_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_12V_BATTERY: + lead_acid_voltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_AVERAGE_TEMPERATURE: + average_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); + break; + case PID_POLL_MIN_TEMPERATURE: + minimum_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); + break; + case PID_POLL_MAX_TEMPERATURE: + maximum_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); + break; + case PID_POLL_MAX_CHARGE_POWER: + maximum_charge_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_END_OF_CHARGE_FLAG: + end_of_charge = rx_frame.data.u8[4]; + break; + case PID_POLL_INTERLOCK_FLAG: + interlock_flag = rx_frame.data.u8[4]; + break; + case PID_POLL_SOH_AVAILABLE_POWER_CALCULATION: + SOH_available_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_SOH_GENERATED_POWER_CALCULATION: + SOH_generated_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: + cumulative_energy_when_discharging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING: + cumulative_energy_when_charging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: + cumulative_energy_in_regen = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + case PID_POLL_CELL_1: + cellvoltages_mv[0] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_2: + cellvoltages_mv[1] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_3: + cellvoltages_mv[2] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_4: + cellvoltages_mv[3] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_5: + cellvoltages_mv[4] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_6: + cellvoltages_mv[5] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_7: + cellvoltages_mv[6] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_8: + cellvoltages_mv[7] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_9: + cellvoltages_mv[8] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_10: + cellvoltages_mv[9] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_11: + cellvoltages_mv[10] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_12: + cellvoltages_mv[11] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_13: + cellvoltages_mv[12] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_14: + cellvoltages_mv[13] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_15: + cellvoltages_mv[14] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_16: + cellvoltages_mv[15] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_17: + cellvoltages_mv[16] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_18: + cellvoltages_mv[17] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_19: + cellvoltages_mv[18] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_20: + cellvoltages_mv[19] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_21: + cellvoltages_mv[20] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_22: + cellvoltages_mv[21] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_23: + cellvoltages_mv[22] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_24: + cellvoltages_mv[23] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_25: + cellvoltages_mv[24] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_26: + cellvoltages_mv[25] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_27: + cellvoltages_mv[26] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_28: + cellvoltages_mv[27] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_29: + cellvoltages_mv[28] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_30: + cellvoltages_mv[29] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_31: + cellvoltages_mv[30] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_32: + cellvoltages_mv[31] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_33: + cellvoltages_mv[32] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_34: + cellvoltages_mv[33] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_35: + cellvoltages_mv[34] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_36: + cellvoltages_mv[35] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_37: + cellvoltages_mv[36] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_38: + cellvoltages_mv[37] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_39: + cellvoltages_mv[38] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_40: + cellvoltages_mv[39] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_41: + cellvoltages_mv[40] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_42: + cellvoltages_mv[41] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_43: + cellvoltages_mv[42] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_44: + cellvoltages_mv[43] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_45: + cellvoltages_mv[44] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_46: + cellvoltages_mv[45] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_47: + cellvoltages_mv[46] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_48: + cellvoltages_mv[47] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_49: + cellvoltages_mv[48] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_50: + cellvoltages_mv[49] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_51: + cellvoltages_mv[50] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_52: + cellvoltages_mv[51] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_53: + cellvoltages_mv[52] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_54: + cellvoltages_mv[53] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_55: + cellvoltages_mv[54] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_56: + cellvoltages_mv[55] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_57: + cellvoltages_mv[56] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_58: + cellvoltages_mv[57] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_59: + cellvoltages_mv[58] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_60: + cellvoltages_mv[59] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_61: + cellvoltages_mv[60] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_62: + cellvoltages_mv[61] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_63: + cellvoltages_mv[62] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_64: + cellvoltages_mv[63] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_65: + cellvoltages_mv[64] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_66: + cellvoltages_mv[65] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_67: + cellvoltages_mv[66] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_68: + cellvoltages_mv[67] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_69: + cellvoltages_mv[68] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_70: + cellvoltages_mv[69] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_71: + cellvoltages_mv[70] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_72: + cellvoltages_mv[71] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + break; + default: + break; + } + + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + // Send 10ms CAN Message + if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms)); + } + previousMillis10ms = currentMillis; + transmit_can_frame(&CMFA_1EA, can_config.battery); + transmit_can_frame(&CMFA_135, can_config.battery); + transmit_can_frame(&CMFA_134, can_config.battery); + transmit_can_frame(&CMFA_125, can_config.battery); + + CMFA_135.data.u8[1] = content_135[counter_10ms]; + CMFA_125.data.u8[3] = content_125[counter_10ms]; + counter_10ms = (counter_10ms + 1) % 16; // counter_10ms cycles between 0-1-2-3..15-0-1... + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + transmit_can_frame(&CMFA_59B, can_config.battery); + transmit_can_frame(&CMFA_3D3, can_config.battery); + } + //Send 200ms message + if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { + previousMillis200ms = currentMillis; + + switch (poll_pid) { + case PID_POLL_SOH_AVERAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_SOH_AVERAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_SOH_AVERAGE; + poll_pid = PID_POLL_AVERAGE_VOLTAGE_OF_CELLS; + break; + case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_AVERAGE_VOLTAGE_OF_CELLS >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_AVERAGE_VOLTAGE_OF_CELLS; + poll_pid = PID_POLL_HIGHEST_CELL_VOLTAGE; + break; + case PID_POLL_HIGHEST_CELL_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_HIGHEST_CELL_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_HIGHEST_CELL_VOLTAGE; + poll_pid = PID_POLL_LOWEST_CELL_VOLTAGE; + break; + case PID_POLL_LOWEST_CELL_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_LOWEST_CELL_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_LOWEST_CELL_VOLTAGE; + poll_pid = PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE; + break; + case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE; + poll_pid = PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE; + break; + case PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE; + poll_pid = PID_POLL_12V_BATTERY; + break; + case PID_POLL_12V_BATTERY: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_12V_BATTERY >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_12V_BATTERY; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING; + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_IN_REGEN; + break; + case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_IN_REGEN >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_IN_REGEN; + poll_pid = PID_POLL_SOCZ; + break; + case PID_POLL_SOCZ: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_SOCZ >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_SOCZ; + poll_pid = PID_POLL_USOC; + break; + case PID_POLL_USOC: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_USOC >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_USOC; + poll_pid = PID_POLL_CURRENT_OFFSET; + break; + case PID_POLL_CURRENT_OFFSET: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CURRENT_OFFSET >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CURRENT_OFFSET; + poll_pid = PID_POLL_INSTANT_CURRENT; + break; + case PID_POLL_INSTANT_CURRENT: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_INSTANT_CURRENT >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_INSTANT_CURRENT; + poll_pid = PID_POLL_MAX_REGEN; + break; + case PID_POLL_MAX_REGEN: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_REGEN >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_REGEN; + poll_pid = PID_POLL_MAX_DISCHARGE_POWER; + break; + case PID_POLL_MAX_DISCHARGE_POWER: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_DISCHARGE_POWER >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_DISCHARGE_POWER; + poll_pid = PID_POLL_MAX_CHARGE_POWER; + break; + case PID_POLL_MAX_CHARGE_POWER: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_CHARGE_POWER >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_CHARGE_POWER; + poll_pid = PID_POLL_AVERAGE_TEMPERATURE; + break; + case PID_POLL_AVERAGE_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_AVERAGE_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_AVERAGE_TEMPERATURE; + poll_pid = PID_POLL_MIN_TEMPERATURE; + break; + case PID_POLL_MIN_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MIN_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MIN_TEMPERATURE; + poll_pid = PID_POLL_MAX_TEMPERATURE; + break; + case PID_POLL_MAX_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_TEMPERATURE; + poll_pid = PID_POLL_END_OF_CHARGE_FLAG; + break; + case PID_POLL_END_OF_CHARGE_FLAG: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_END_OF_CHARGE_FLAG >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_END_OF_CHARGE_FLAG; + poll_pid = PID_POLL_INTERLOCK_FLAG; + break; + case PID_POLL_INTERLOCK_FLAG: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_INTERLOCK_FLAG >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_INTERLOCK_FLAG; + poll_pid = PID_POLL_CELL_1; + break; + case PID_POLL_CELL_1: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_1 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_1; + poll_pid = PID_POLL_CELL_2; + break; + case PID_POLL_CELL_2: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_2 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_2; + poll_pid = PID_POLL_CELL_3; + break; + case PID_POLL_CELL_3: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_3 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_3; + poll_pid = PID_POLL_CELL_4; + break; + case PID_POLL_CELL_4: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_4 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_4; + poll_pid = PID_POLL_CELL_5; + break; + case PID_POLL_CELL_5: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_5 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_5; + poll_pid = PID_POLL_CELL_6; + break; + case PID_POLL_CELL_6: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_6 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_6; + poll_pid = PID_POLL_CELL_7; + break; + case PID_POLL_CELL_7: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_7 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_7; + poll_pid = PID_POLL_CELL_8; + break; + case PID_POLL_CELL_8: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_8 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_8; + poll_pid = PID_POLL_CELL_9; + break; + case PID_POLL_CELL_9: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_9 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_9; + poll_pid = PID_POLL_CELL_10; + break; + case PID_POLL_CELL_10: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_10 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_10; + poll_pid = PID_POLL_CELL_11; + break; + case PID_POLL_CELL_11: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_11 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_11; + poll_pid = PID_POLL_CELL_12; + break; + case PID_POLL_CELL_12: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_12 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_12; + poll_pid = PID_POLL_CELL_13; + break; + case PID_POLL_CELL_13: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_13 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_13; + poll_pid = PID_POLL_CELL_14; + break; + case PID_POLL_CELL_14: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_14 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_14; + poll_pid = PID_POLL_CELL_15; + break; + case PID_POLL_CELL_15: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_15 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_15; + poll_pid = PID_POLL_CELL_16; + break; + case PID_POLL_CELL_16: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_16 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_16; + poll_pid = PID_POLL_CELL_17; + break; + case PID_POLL_CELL_17: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_17 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_17; + poll_pid = PID_POLL_CELL_18; + break; + case PID_POLL_CELL_18: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_18 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_18; + poll_pid = PID_POLL_CELL_19; + break; + case PID_POLL_CELL_19: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_19 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_19; + poll_pid = PID_POLL_CELL_20; + break; + case PID_POLL_CELL_20: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_20 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_20; + poll_pid = PID_POLL_CELL_21; + break; + case PID_POLL_CELL_21: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_21 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_21; + poll_pid = PID_POLL_CELL_22; + break; + case PID_POLL_CELL_22: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_22 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_22; + poll_pid = PID_POLL_CELL_23; + break; + case PID_POLL_CELL_23: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_23 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_23; + poll_pid = PID_POLL_CELL_24; + break; + case PID_POLL_CELL_24: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_24 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_24; + poll_pid = PID_POLL_CELL_25; + break; + case PID_POLL_CELL_25: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_25 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_25; + poll_pid = PID_POLL_CELL_26; + break; + case PID_POLL_CELL_26: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_26 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_26; + poll_pid = PID_POLL_CELL_27; + break; + case PID_POLL_CELL_27: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_27 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_27; + poll_pid = PID_POLL_CELL_28; + break; + case PID_POLL_CELL_28: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_28 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_28; + poll_pid = PID_POLL_CELL_29; + break; + case PID_POLL_CELL_29: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_29 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_29; + poll_pid = PID_POLL_CELL_30; + break; + case PID_POLL_CELL_30: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_30 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_30; + poll_pid = PID_POLL_CELL_31; + break; + case PID_POLL_CELL_31: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_31 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_31; + poll_pid = PID_POLL_CELL_32; + break; + case PID_POLL_CELL_32: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_32 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_32; + poll_pid = PID_POLL_CELL_33; + break; + case PID_POLL_CELL_33: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_33 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_33; + poll_pid = PID_POLL_CELL_34; + break; + case PID_POLL_CELL_34: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_34 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_34; + poll_pid = PID_POLL_CELL_35; + break; + case PID_POLL_CELL_35: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_35 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_35; + poll_pid = PID_POLL_CELL_36; + break; + case PID_POLL_CELL_36: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_36 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_36; + poll_pid = PID_POLL_CELL_37; + break; + case PID_POLL_CELL_37: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_37 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_37; + poll_pid = PID_POLL_CELL_38; + break; + case PID_POLL_CELL_38: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_38 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_38; + poll_pid = PID_POLL_CELL_39; + break; + case PID_POLL_CELL_39: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_39 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_39; + poll_pid = PID_POLL_CELL_40; + break; + case PID_POLL_CELL_40: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_40 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_40; + poll_pid = PID_POLL_CELL_41; + break; + case PID_POLL_CELL_41: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_41 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_41; + poll_pid = PID_POLL_CELL_42; + break; + case PID_POLL_CELL_42: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_42 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_42; + poll_pid = PID_POLL_CELL_43; + break; + case PID_POLL_CELL_43: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_43 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_43; + poll_pid = PID_POLL_CELL_44; + break; + case PID_POLL_CELL_44: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_44 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_44; + poll_pid = PID_POLL_CELL_45; + break; + case PID_POLL_CELL_45: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_45 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_45; + poll_pid = PID_POLL_CELL_46; + break; + case PID_POLL_CELL_46: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_46 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_46; + poll_pid = PID_POLL_CELL_47; + break; + case PID_POLL_CELL_47: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_47 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_47; + poll_pid = PID_POLL_CELL_48; + break; + case PID_POLL_CELL_48: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_48 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_48; + poll_pid = PID_POLL_CELL_49; + break; + case PID_POLL_CELL_49: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_49 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_49; + poll_pid = PID_POLL_CELL_50; + break; + case PID_POLL_CELL_50: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_50 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_50; + poll_pid = PID_POLL_CELL_51; + break; + case PID_POLL_CELL_51: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_51 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_51; + poll_pid = PID_POLL_CELL_52; + break; + case PID_POLL_CELL_52: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_52 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_52; + poll_pid = PID_POLL_CELL_53; + break; + case PID_POLL_CELL_53: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_53 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_53; + poll_pid = PID_POLL_CELL_54; + break; + case PID_POLL_CELL_54: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_54 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_54; + poll_pid = PID_POLL_CELL_55; + break; + case PID_POLL_CELL_55: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_55 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_55; + poll_pid = PID_POLL_CELL_56; + break; + case PID_POLL_CELL_56: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_56 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_56; + poll_pid = PID_POLL_CELL_57; + break; + case PID_POLL_CELL_57: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_57 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_57; + poll_pid = PID_POLL_CELL_58; + break; + case PID_POLL_CELL_58: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_58 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_58; + poll_pid = PID_POLL_CELL_59; + break; + case PID_POLL_CELL_59: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_59 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_59; + poll_pid = PID_POLL_CELL_60; + break; + case PID_POLL_CELL_60: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_60 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_60; + poll_pid = PID_POLL_CELL_61; + break; + case PID_POLL_CELL_61: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_61 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_61; + poll_pid = PID_POLL_CELL_62; + break; + case PID_POLL_CELL_62: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_62 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_62; + poll_pid = PID_POLL_CELL_63; + break; + case PID_POLL_CELL_63: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_63 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_63; + poll_pid = PID_POLL_CELL_64; + break; + case PID_POLL_CELL_64: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_64 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_64; + poll_pid = PID_POLL_CELL_65; + break; + case PID_POLL_CELL_65: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_65 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_65; + poll_pid = PID_POLL_CELL_66; + break; + case PID_POLL_CELL_66: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_66 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_66; + poll_pid = PID_POLL_CELL_67; + break; + case PID_POLL_CELL_67: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_67 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_67; + poll_pid = PID_POLL_CELL_68; + break; + case PID_POLL_CELL_68: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_68 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_68; + poll_pid = PID_POLL_CELL_69; + break; + case PID_POLL_CELL_69: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_69 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_69; + poll_pid = PID_POLL_CELL_70; + break; + case PID_POLL_CELL_70: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_70 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_70; + poll_pid = PID_POLL_CELL_71; + break; + case PID_POLL_CELL_71: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_71 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_71; + poll_pid = PID_POLL_CELL_72; + break; + case PID_POLL_CELL_72: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_72 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_72; + poll_pid = PID_POLL_SOH_AVERAGE; + break; + poll_pid = PID_POLL_SOH_AVERAGE; + break; + } + + transmit_can_frame(&CMFA_POLLING_FRAME, can_config.battery); + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.system.status.battery_allows_contactor_closing = true; + datalayer.battery.info.number_of_cells = 72; + 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 //CMFA_EV_BATTERY diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h new file mode 100644 index 00000000..0b2b87eb --- /dev/null +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -0,0 +1,158 @@ +#ifndef CMFA_EV_BATTERY_H +#define CMFA_EV_BATTERY_H +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_PACK_VOLTAGE_DV 3040 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 2185 +#define MAX_CELL_DEVIATION_MV 100 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +// OBD2 PID polls. Some of these have been reverse engineered, but there are many unknown values still +#define PID_POLL_SOCZ 0x9001 //122 in log +#define PID_POLL_USOC 0x9002 //5531 (Possible SOC candidate) +#define PID_POLL_SOH_AVERAGE 0x9003 +#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 +#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 +#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008 +#define PID_POLL_LOWEST_CELL_VOLTAGE 0x9009 +#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A +#define PID_POLL_CURRENT_OFFSET 0x900C +#define PID_POLL_INSTANT_CURRENT 0x900D +#define PID_POLL_MAX_REGEN 0x900E +#define PID_POLL_MAX_DISCHARGE_POWER 0x900F +#define PID_POLL_12V_BATTERY 0x9011 +#define PID_POLL_AVERAGE_TEMPERATURE 0x9012 //749 in log +#define PID_POLL_MIN_TEMPERATURE 0x9013 //736 in log +#define PID_POLL_MAX_TEMPERATURE 0x9014 //752 in log +#define PID_POLL_MAX_CHARGE_POWER 0x9018 +#define PID_POLL_END_OF_CHARGE_FLAG 0x9019 +#define PID_POLL_INTERLOCK_FLAG 0x901A +#define PID_POLL_BATTERY_IDENTIFICATION 0x901B // Multi frame message +#define PID_POLL_CELL_1 0x9021 +#define PID_POLL_CELL_2 0x9022 +#define PID_POLL_CELL_3 0x9023 +#define PID_POLL_CELL_4 0x9024 +#define PID_POLL_CELL_5 0x9025 +#define PID_POLL_CELL_6 0x9026 +#define PID_POLL_CELL_7 0x9027 +#define PID_POLL_CELL_8 0x9028 +#define PID_POLL_CELL_9 0x9029 +#define PID_POLL_CELL_10 0x902A +#define PID_POLL_CELL_11 0x902B +#define PID_POLL_CELL_12 0x902C +#define PID_POLL_CELL_13 0x902D +#define PID_POLL_CELL_14 0x902E +#define PID_POLL_CELL_15 0x902F +#define PID_POLL_CELL_16 0x9030 +#define PID_POLL_CELL_17 0x9031 +#define PID_POLL_CELL_18 0x9032 +#define PID_POLL_CELL_19 0x9033 +#define PID_POLL_CELL_20 0x9034 +#define PID_POLL_CELL_21 0x9035 +#define PID_POLL_CELL_22 0x9036 +#define PID_POLL_CELL_23 0x9037 +#define PID_POLL_CELL_24 0x9038 +#define PID_POLL_CELL_25 0x9039 +#define PID_POLL_CELL_26 0x903A +#define PID_POLL_CELL_27 0x903B +#define PID_POLL_CELL_28 0x903C +#define PID_POLL_CELL_29 0x903D +#define PID_POLL_CELL_30 0x903E +#define PID_POLL_CELL_31 0x903F +#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060 0x9040 +#define PID_POLL_CELL_32 0x9041 +#define PID_POLL_CELL_33 0x9042 +#define PID_POLL_CELL_34 0x9043 +#define PID_POLL_CELL_35 0x9044 +#define PID_POLL_CELL_36 0x9045 +#define PID_POLL_CELL_37 0x9046 +#define PID_POLL_CELL_38 0x9047 +#define PID_POLL_CELL_39 0x9048 +#define PID_POLL_CELL_40 0x9049 +#define PID_POLL_CELL_41 0x904A +#define PID_POLL_CELL_42 0x904B +#define PID_POLL_CELL_43 0x904C +#define PID_POLL_CELL_44 0x904D +#define PID_POLL_CELL_45 0x904E +#define PID_POLL_CELL_46 0x904F +#define PID_POLL_CELL_47 0x9050 +#define PID_POLL_CELL_48 0x9051 +#define PID_POLL_CELL_49 0x9052 +#define PID_POLL_CELL_50 0x9053 +#define PID_POLL_CELL_51 0x9054 +#define PID_POLL_CELL_52 0x9055 +#define PID_POLL_CELL_53 0x9056 +#define PID_POLL_CELL_54 0x9057 +#define PID_POLL_CELL_55 0x9058 +#define PID_POLL_CELL_56 0x9059 +#define PID_POLL_CELL_57 0x905A +#define PID_POLL_CELL_58 0x905B +#define PID_POLL_CELL_59 0x905C +#define PID_POLL_CELL_60 0x905D +#define PID_POLL_CELL_61 0x905E +#define PID_POLL_CELL_62 0x905F +#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9061_9080 0x9060 +#define PID_POLL_CELL_63 0x9061 +#define PID_POLL_CELL_64 0x9062 +#define PID_POLL_CELL_65 0x9063 +#define PID_POLL_CELL_66 0x9064 +#define PID_POLL_CELL_67 0x9065 +#define PID_POLL_CELL_68 0x9066 +#define PID_POLL_CELL_69 0x9067 +#define PID_POLL_CELL_70 0x9068 +#define PID_POLL_CELL_71 0x9069 +#define PID_POLL_CELL_72 0x906A +/* +#define PID_POLL_UNKNOWNX 0x912F // Multi frame message, empty +#define PID_POLL_UNKNOWNX 0x9129 +#define PID_POLL_UNKNOWNX 0x9131 +#define PID_POLL_UNKNOWNX 0x9132 +#define PID_POLL_UNKNOWNX 0x9133 +#define PID_POLL_UNKNOWNX 0x9134 +#define PID_POLL_UNKNOWNX 0x9135 +#define PID_POLL_UNKNOWNX 0x9136 +#define PID_POLL_UNKNOWNX 0x9137 +#define PID_POLL_UNKNOWNX 0x9138 +#define PID_POLL_UNKNOWNX 0x9139 +#define PID_POLL_UNKNOWNX 0x913A +#define PID_POLL_UNKNOWNX 0x913B +#define PID_POLL_UNKNOWNX 0x913C +#define PID_POLL_UNKNOWN5 0x912F +#define PID_POLL_UNKNOWNX 0x91B7 +*/ +#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100% +#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100% +/* +#define PID_POLL_UNKNOWNX 0x91C1 +#define PID_POLL_UNKNOWNX 0x91CD +#define PID_POLL_UNKNOWNX 0x91CF +#define PID_POLL_UNKNOWNX 0x91F6 +#define PID_POLL_UNKNOWNX 0x91F7 +#define PID_POLL_UNKNOWNX 0x920F +#define PID_POLL_UNKNOWNx 0x9242 +*/ +#define PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING 0x9243 +#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245 +#define PID_POLL_CUMULATIVE_ENERGY_IN_REGEN 0x9247 +/* +#define PID_POLL_UNKNOWNx 0x9256 +#define PID_POLL_UNKNOWNx 0x9261 +#define PID_POLL_UNKNOWN7 0x9284 +#define PID_POLL_UNKNOWNx 0xF012 +#define PID_POLL_UNKNOWNx 0xF1A0 +#define PID_POLL_UNKNOWNx 0xF182 +#define PID_POLL_UNKNOWNx 0xF187 +#define PID_POLL_UNKNOWNx 0xF188 +#define PID_POLL_UNKNOWNx 0xF18A +#define PID_POLL_UNKNOWNx 0xF18C +#define PID_POLL_UNKNOWNx 0xF191 +#define PID_POLL_UNKNOWNx 0xF194 +#define PID_POLL_UNKNOWNx 0xF195 +*/ + +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 1a578ca9..ce70cd2c 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -74,15 +74,16 @@ const uint16_t SOC[] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9 2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0}; -const uint16_t voltage[] = {4200, 4171, 4143, 4117, 4093, 4070, 4050, 4031, 4013, 3998, 3985, 3973, 3964, 3957, 3952, - 3950, 3941, 3933, 3924, 3916, 3907, 3899, 3890, 3881, 3873, 3864, 3856, 3847, 3839, 3830, - 3821, 3813, 3804, 3796, 3787, 3779, 3770, 3761, 3753, 3744, 3736, 3727, 3719, 3710, 3701, - 3693, 3684, 3676, 3667, 3659, 3650, 3641, 3633, 3624, 3616, 3607, 3599, 3590, 3581, 3573, - 3564, 3556, 3547, 3539, 3530, 3521, 3513, 3504, 3496, 3487, 3479, 3470, 3461, 3453, 3444, - 3436, 3427, 3419, 3410, 3401, 3393, 3384, 3376, 3367, 3359, 3350, 3333, 3315, 3297, 3278, - 3258, 3237, 3215, 3192, 3166, 3139, 3108, 3074, 3033, 2979, 2850}; +const uint16_t voltage[] = {4200, 4173, 4148, 4124, 4102, 4080, 4060, 4041, 4023, 4007, 3993, 3980, 3969, 3959, 3953, + 3950, 3941, 3932, 3924, 3915, 3907, 3898, 3890, 3881, 3872, 3864, 3855, 3847, 3838, 3830, + 3821, 3812, 3804, 3795, 3787, 3778, 3770, 3761, 3752, 3744, 3735, 3727, 3718, 3710, 3701, + 3692, 3684, 3675, 3667, 3658, 3650, 3641, 3632, 3624, 3615, 3607, 3598, 3590, 3581, 3572, + 3564, 3555, 3547, 3538, 3530, 3521, 3512, 3504, 3495, 3487, 3478, 3470, 3461, 3452, 3444, + 3435, 3427, 3418, 3410, 3401, 3392, 3384, 3375, 3367, 3358, 3350, 3338, 3325, 3313, 3299, + 3285, 3271, 3255, 3239, 3221, 3202, 3180, 3156, 3127, 3090, 3000}; -uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function +// Function to estimate SOC based on cell voltage +uint16_t estimateSOCFromCell(uint16_t cellVoltage) { if (cellVoltage >= voltage[0]) { return SOC[0]; } @@ -92,7 +93,7 @@ uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function for (int i = 1; i < numPoints; ++i) { if (cellVoltage >= voltage[i]) { - // Fix: Cast to float or double to ensure proper floating-point division + // Cast to float for proper division float t = (float)(cellVoltage - voltage[i]) / (float)(voltage[i - 1] - voltage[i]); // Calculate interpolated SOC value @@ -105,12 +106,56 @@ uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function return 0; // Default return for safety, should never reach here } +// Simplified version of the pack-based SOC estimation with compensation +uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps) { + // If cell count is still the default 192 but we haven't confirmed it yet + if (!set_voltage_limits && cellCount == 192) { + // Fall back to BMS-reported SOC while cell count is uncertain + return (SOC_Display * 10); + } + + if (cellCount == 0) + return 0; + + // Convert pack voltage (decivolts) to millivolts + uint32_t packVoltageMv = packVoltage * 100; + + // Apply internal resistance compensation + // Current is in deciamps (-150 = -15.0A, 150 = 15.0A) + // Resistance is in milliohms + int32_t voltageDrop = (currentAmps * PACK_INTERNAL_RESISTANCE_MOHM) / 10; + + // Compensate the pack voltage (add the voltage drop) + uint32_t compensatedPackVoltageMv = packVoltageMv + voltageDrop; + + // Calculate average cell voltage in millivolts + uint16_t avgCellVoltage = compensatedPackVoltageMv / cellCount; + +#ifdef DEBUG_LOG + logging.print("Pack: "); + logging.print(packVoltage / 10.0); + logging.print("V, Current: "); + logging.print(currentAmps / 10.0); + logging.print("A, Drop: "); + logging.print(voltageDrop / 1000.0); + logging.print("V, Comp Pack: "); + logging.print(compensatedPackVoltageMv / 1000.0); + logging.print("V, Avg Cell: "); + logging.print(avgCellVoltage); + logging.println("mV"); +#endif + + // Use the cell voltage lookup table to estimate SOC + return estimateSOCFromCell(avgCellVoltage); +} + +// Fix: Change parameter types to uint16_t to match SOC values uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) { if (SOC_low == 0 || SOC_high == 0) { return 0; // If either value is 0, return 0 } if (SOC_low == 10000 || SOC_high == 10000) { - return 10000; // If either value is 100, return 100 + return 10000; // If either value is 100%, return 100% } return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value } @@ -687,9 +732,12 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_ void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus #ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE - SOC_estimated_lowest = estimateSOC(CellVoltMin_mV); - SOC_estimated_highest = estimateSOC(CellVoltMax_mV); - datalayer.battery.status.real_soc = selectSOC(SOC_estimated_lowest, SOC_estimated_highest); + // Use the simplified pack-based SOC estimation with proper compensation + datalayer.battery.status.real_soc = estimateSOC(batteryVoltage, datalayer.battery.info.number_of_cells, batteryAmps); + + // For comparison or fallback, we can still calculate from min/max cell voltages + SOC_estimated_lowest = estimateSOCFromCell(CellVoltMin_mV); + SOC_estimated_highest = estimateSOCFromCell(CellVoltMax_mV); #else datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00 #endif diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.h b/Software/src/battery/KIA-E-GMP-BATTERY.h index 11ca62a0..3539cc72 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.h +++ b/Software/src/battery/KIA-E-GMP-BATTERY.h @@ -19,6 +19,10 @@ extern ACAN2517FD canfd; #define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00% #define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing +// Used for SoC compensation - Define internal resistance value in milliohms for the entire pack +// How to calculate: voltage_drop_under_known_load [Volts] / load [Amps] = Resistance +#define PACK_INTERNAL_RESISTANCE_MOHM 200 // 200 milliohms for the whole pack + void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 86fbf16c..d0cc62e1 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -8,9 +8,11 @@ /* Do not change code below unless you are sure what you are doing */ /* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */ -static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was send -static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send -static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was sent +static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent static bool alternate243 = false; //0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms CAN_frame TESLA_221_1 = { @@ -50,12 +52,14 @@ CAN_frame TESLA_129 = {.FD = false, .DLC = 8, .ID = 0x129, .data = {0x21, 0x24, 0x36, 0x5F, 0x00, 0x20, 0xFF, 0x3F}}; +//0x612 UDS diagnostic requests - on demand CAN_frame TESLA_602 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x602, - .data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Diagnostic request + .data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; static uint8_t stateMachineClearIsolationFault = 0xFF; +static uint8_t stateMachineBMSReset = 0xFF; static uint16_t sendContactorClosingMessagesStill = 300; static uint16_t battery_cell_max_v = 3300; static uint16_t battery_cell_min_v = 3300; @@ -866,9 +870,18 @@ void update_values_battery() { //This function maps all the values fetched via #endif // TESLA_MODEL_3Y_BATTERY // Check if user requests some action - if (datalayer.battery.settings.user_requests_isolation_clear) { - stateMachineClearIsolationFault = 0; //Start the statemachine - datalayer.battery.settings.user_requests_isolation_clear = false; + if (datalayer.battery.settings.user_requests_tesla_isolation_clear) { + stateMachineClearIsolationFault = 0; //Start the isolation fault statemachine + datalayer.battery.settings.user_requests_tesla_isolation_clear = false; + } + if (datalayer.battery.settings.user_requests_tesla_bms_reset) { + if (battery_contactor == 1 && battery_BMS_a180_SW_ECU_reset_blocked == false) { + //Start the BMS ECU reset statemachine, only if contactors are OPEN and BMS ECU allows it + stateMachineBMSReset = 0; + datalayer.battery.settings.user_requests_tesla_bms_reset = false; + } else { + logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it"); + } } // Update webserver datalayer @@ -1801,6 +1814,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { BMS_SerialNumber[13] = rx_frame.data.u8[7]; } break; + case 0x612: // CAN UDS responses for BMS ECU reset + if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU unlocked"); + } else if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU reset request successful but ECU busy, response pending"); + } else if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU reset positive response, 1 second downtime"); + } + break; default: break; } @@ -1949,6 +1971,7 @@ the first, for a few cycles, then stop all messages which causes the contactor case 4: TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message indicating ECU unlocked stateMachineClearIsolationFault = 5; break; case 5: @@ -1961,6 +1984,58 @@ the first, for a few cycles, then stop all messages which causes the contactor stateMachineClearIsolationFault = 0xFF; break; } + if (stateMachineBMSReset != 0xFF) { + //This implementation should be rewritten to actually replying to the UDS replied sent by the BMS + //While this may work, it is not the correct way to implement this clearing logic + switch (stateMachineBMSReset) { + case 0: + TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 1; + break; + case 1: + TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 2; + break; + case 2: + TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 3; + break; + case 3: + TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 4; + break; + case 4: + TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message indicating ECU unlocked + stateMachineBMSReset = 5; + break; + case 5: + TESLA_602.data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 6; + break; + case 6: + TESLA_602.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 7; + break; + case 7: + TESLA_602.data = {0x02, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message(s) indicating ECU has reset + stateMachineBMSReset = 0xFF; + break; + default: + //Something went wrong. Reset all and cancel + stateMachineBMSReset = 0xFF; + break; + } + } } } } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index e9fc9867..58fc76c1 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -113,7 +113,7 @@ typedef struct { /** Minimum percentage setting. Set this value to the lowest real SOC * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 0% */ - uint16_t min_percentage = BATTERY_MINPERCENTAGE; + int16_t min_percentage = BATTERY_MINPERCENTAGE; /** Maximum percentage setting. Set this value to the highest real SOC * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 100% */ @@ -141,7 +141,8 @@ typedef struct { /** Tesla specific settings that are edited on the fly when manually forcing a balance charge for LFP chemistry */ /* Bool for specifying if user has requested manual function */ bool user_requests_balancing = false; - bool user_requests_isolation_clear = false; + bool user_requests_tesla_isolation_clear = false; + bool user_requests_tesla_bms_reset = false; /* Forced balancing max time & start timestamp */ uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms) uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started @@ -215,6 +216,8 @@ typedef struct { } DATALAYER_SHUNT_TYPE; typedef struct { + /** ESP32 main CPU temperature, for displaying on webserver and for safeties */ + float CPU_temperature = 0; /** array with type of battery used, for displaying on webserver */ char battery_protocol[64] = {0}; /** array with type of inverter protocol used, for displaying on webserver */ diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 0868ca49..10f740da 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -252,6 +252,26 @@ typedef struct { bool warning_Charger_not_responding = false; } DATALAYER_INFO_CELLPOWER; +typedef struct { + uint16_t soc_z = 0; + uint16_t soc_u = 0; + uint16_t soh_average = 0; + uint16_t max_regen_power = 0; + uint16_t max_discharge_power = 0; + int16_t average_temperature = 0; + int16_t minimum_temperature = 0; + int16_t maximum_temperature = 0; + uint16_t maximum_charge_power = 0; + uint16_t SOH_available_power = 0; + uint16_t SOH_generated_power = 0; + uint16_t lead_acid_voltage = 0; + uint8_t highest_cell_voltage_number = 0; + uint8_t lowest_cell_voltage_number = 0; + uint64_t cumulative_energy_when_discharging = 0; + uint64_t cumulative_energy_when_charging = 0; + uint64_t cumulative_energy_in_regen = 0; +} DATALAYER_INFO_CMFAEV; + typedef struct { /** uint8_t */ /** Battery serial numbers, stores raw HEX values for ASCII chars */ @@ -740,6 +760,7 @@ class DataLayerExtended { DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; + DATALAYER_INFO_CMFAEV CMFAEV; DATALAYER_INFO_GEELY_GEOMETRY_C geometryC; DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64; DATALAYER_INFO_TESLA tesla; diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 3ada2352..1db647ea 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -19,6 +19,13 @@ battery_pause_status emulator_pause_status = NORMAL; //battery pause status end void update_machineryprotection() { + // Check if the CPU is too hot + if (datalayer.system.info.CPU_temperature > 80.0f) { + set_event(EVENT_CPU_OVERHEAT, 0); + } else { + clear_event(EVENT_CPU_OVERHEAT); + } + // Check health status of CAN interfaces if (datalayer.system.info.can_native_send_fail) { set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0); diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index ed226b0b..7849e997 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -47,6 +47,7 @@ void init_events(void) { events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO; events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING; + events.entries[EVENT_CPU_OVERHEAT].level = EVENT_LEVEL_WARNING; events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; @@ -191,6 +192,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "Inverter not sending messages via CAN for the last 60 seconds. Check wiring!"; case EVENT_CONTACTOR_WELDED: return "Contactors sticking/welded. Inspect battery with caution!"; + case EVENT_CPU_OVERHEAT: + return "Battery-Emulator CPU overheating! Increase airflow/cooling to increase hardware lifespan!"; case EVENT_CHARGE_LIMIT_EXCEEDED: return "Inverter is charging faster than battery is allowing."; case EVENT_DISCHARGE_LIMIT_EXCEEDED: @@ -348,7 +351,7 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_MQTT_DISCONNECT: return "MQTT disconnected."; case EVENT_EQUIPMENT_STOP: - return "EQUIPMENT STOP ACTIVATED!!!"; + return "User requested stop, either via equipment stop circuit or webserver Open Contactor button"; case EVENT_SD_INIT_FAILED: return "SD card initialization failed, check hardware. Power must be removed to reset the SD card."; case EVENT_PERIODIC_BMS_RESET: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 9d558ff2..c651d80b 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -21,6 +21,7 @@ XX(EVENT_CAN_NATIVE_TX_FAILURE) \ XX(EVENT_CHARGE_LIMIT_EXCEEDED) \ XX(EVENT_CONTACTOR_WELDED) \ + XX(EVENT_CPU_OVERHEAT) \ XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \ XX(EVENT_WATER_INGRESS) \ XX(EVENT_12V_LOW) \ diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 3392d036..a2b7e035 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -432,12 +432,36 @@ String advanced_battery_processor(const String& var) { String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + ""; #endif //CELLPOWER_BMS +#ifdef CMFA_EV_BATTERY + content += "

SOC U: " + String(datalayer_extended.CMFAEV.soc_u) + "percent

"; + content += "

SOC Z: " + String(datalayer_extended.CMFAEV.soc_z) + "percent

"; + content += "

SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "pptt

"; + content += "

12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV

"; + content += "

Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "

"; + content += "

Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "

"; + content += "

Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + "

"; + content += "

Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + "

"; + content += "

Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + "

"; + content += "

SOH available power: " + String(datalayer_extended.CMFAEV.SOH_available_power) + "

"; + content += "

SOH generated power: " + String(datalayer_extended.CMFAEV.SOH_generated_power) + "

"; + content += "

Average temperature: " + String(datalayer_extended.CMFAEV.average_temperature) + "dC

"; + content += "

Maximum temperature: " + String(datalayer_extended.CMFAEV.maximum_temperature) + "dC

"; + content += "

Minimum temperature: " + String(datalayer_extended.CMFAEV.minimum_temperature) + "dC

"; + content += + "

Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) + + "Wh

"; + content += "

Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) + + "Wh

"; + content += + "

Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh

"; +#endif //CMFA_EV_BATTERY + #ifdef GEELY_GEOMETRY_C_BATTERY -char readableSerialNumber[29]; // One extra space for null terminator -memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber, - sizeof(datalayer_extended.geometryC.BatterySerialNumber)); -readableSerialNumber[15] = '\0'; // Null terminate the string -content += "

Serial number: " + String(readableSerialNumber) + "

"; + char readableSerialNumber[29]; // One extra space for null terminator + memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber, + sizeof(datalayer_extended.geometryC.BatterySerialNumber)); + readableSerialNumber[15] = '\0'; // Null terminate the string + content += "

Serial number: " + String(readableSerialNumber) + "

"; #endif //GEELY_GEOMETRY_C_BATTERY #ifdef KIA_HYUNDAI_64_BATTERY @@ -664,7 +688,8 @@ content += "

Serial number: " + String(readableSerialNumber) + "

"; static const char* Fault[] = {"NOT_ACTIVE", "ACTIVE"}; //Buttons for user action - content += ""; + content += ""; + content += ""; //0x20A 522 HVP_contatorState content += "

Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "

"; content += "

HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "

"; @@ -691,7 +716,7 @@ content += "

Serial number: " + String(readableSerialNumber) + "

"; sizeof(datalayer_extended.tesla.BMS_SerialNumber)); readableSerialNumber[14] = '\0'; // Null terminate the string content += "

BMS Serial number: " + String(readableSerialNumber) + "

"; - // Comment what data you would like to dislay, order can be changed. + // Comment what data you would like to display, order can be changed. //0x352 850 BMS_energyStatus if (datalayer_extended.tesla.BMS352_mux == false) { content += "

BMS 0x352 w/o mux

"; //if using older BMS <2021 and comment 0x352 without MUX @@ -1454,19 +1479,32 @@ content += "

Serial number: " + String(readableSerialNumber) + "

"; !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \ !defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && !defined(VOLVO_SPA_HYBRID_BATTERY) && \ - !defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) //Only the listed types have extra info + !defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) && \ + !defined(CMFA_EV_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif content += ""; content += ""; + content += "