From 2c3ae3d79d846bfa75225a75e43e9c709ab09b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 1 Apr 2025 14:47:41 +0300 Subject: [PATCH 1/2] Add support for 50kWh standard range --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 126 +++++++++++++++----- Software/src/battery/BYD-ATTO-3-BATTERY.h | 8 +- 2 files changed, 104 insertions(+), 30 deletions(-) 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..21454223 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -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 From 41d8d5cf272c3d7f97f97e1c8c0fcbee8fb0c446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 1 Apr 2025 23:48:13 +0300 Subject: [PATCH 2/2] Improve wording --- Software/src/battery/BYD-ATTO-3-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index 21454223..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