From b3ba72d0d1a36b14644515517f18c9103119edf2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 19 May 2024 11:44:00 +0300 Subject: [PATCH] Move charge W capping to Safety file --- Software/Software.ino | 14 +++---- Software/src/battery/BMW-I3-BATTERY.cpp | 19 +-------- .../src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 15 ++----- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 22 +++-------- .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 18 +-------- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 39 +------------------ .../src/battery/RENAULT-KANGOO-BATTERY.cpp | 21 ++-------- .../src/battery/TESLA-MODEL-3-BATTERY.cpp | 20 ++-------- Software/src/devboard/safety/safety.cpp | 36 +++++++++++++++++ 9 files changed, 63 insertions(+), 141 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 78f81f0d..e20aa00c 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -233,10 +233,11 @@ void core_loop(void* task_time_us) { START_TIME_MEASUREMENT(time_5s); if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 5s normally { - previousMillisUpdateVal = millis(); - update_machineryprotection(); - update_SOC(); // Check if real or calculated SOC% value should be sent - update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + previousMillisUpdateVal = millis(); // Order matters on the update_loop! + update_values_battery(); // Fetch battery values + update_SOC(); // Check if real or calculated SOC% value should be sent + update_machineryprotection(); // Check safeties + update_values_inverter(); // Update values heading towards inverter if (DUMMY_EVENT_ENABLED) { set_event(EVENT_DUMMY_ERROR, (uint8_t)millis()); } @@ -686,10 +687,7 @@ void update_SOC() { } } -void update_values() { - // Battery - update_values_battery(); - // Inverter +void update_values_inverter() { #ifdef CAN_INVERTER_SELECTED update_values_can_inverter(); #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index de73a2a1..d75eddee 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -429,24 +429,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = battery_soh * 100; - if (battery_BEV_available_power_longterm_discharge > 65000) { - datalayer.battery.status.max_discharge_power_W = 65000; - } else { - datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge; - } - if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% is 0.00%, we should not discharge battery further - datalayer.battery.status.max_discharge_power_W = 0; - } + datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge; - if (battery_BEV_available_power_longterm_charge > 65000) { - datalayer.battery.status.max_charge_power_W = 65000; - } else { - datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge; - } - if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00% - { - datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0 - } + datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge; battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f07e9bbb..778e4773 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -48,19 +48,10 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); - //We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC% - if (datalayer.battery.status.reported_soc == 10000) { //100.00% - datalayer.battery.status.max_charge_power_W = 0; //When battery is 100% full, set allowed charge W to 0 - } else { - datalayer.battery.status.max_charge_power_W = 10000; //Otherwise we can push 10kW into the pack! - } + //We do not know the max charge/discharge power is sent by the battery. We hardcode value for now. + datalayer.battery.status.max_charge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded - if (datalayer.battery.status.reported_soc < 200) { //2.00% - datalayer.battery.status.max_discharge_power_W = - 0; //When battery is empty (below 2%), set allowed discharge W to 0 - } else { - datalayer.battery.status.max_discharge_power_W = 10000; //Otherwise we can discharge 10kW from the pack! - } + datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling? diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 58b851cb..97f05fb1 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -68,18 +68,12 @@ void update_values_battery() { //This function maps all the values fetched via (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); //datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts - //The allowed charge power is not available. We estimate this value - if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0 - datalayer.battery.status.max_charge_power_W = 0; - } else { // No limits, max charging power allowed - datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; - } + //The allowed charge power is not available. We hardcode this value for now + datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; + //datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts - if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0 - datalayer.battery.status.max_discharge_power_W = 0; - } else { // No limits, max charging power allowed - datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; - } + //The allowed discharge power is not available. We hardcode this value for now + datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; powerWatt = ((batteryVoltage * batteryAmps) / 100); @@ -117,12 +111,6 @@ void update_values_battery() { //This function maps all the values fetched via set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } - if (datalayer.battery.status.bms_status == - FAULT) { //Incase we enter a critical fault state, zero out the allowed limits - datalayer.battery.status.max_charge_power_W = 0; - datalayer.battery.status.max_discharge_power_W = 0; - } - /* Safeties verified. Perform USB serial printout if configured to do so */ #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 074d5a70..c51eb03e 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -155,17 +155,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); - if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0 - datalayer.battery.status.max_charge_power_W = 0; - } else { // Limit according to CAN value - datalayer.battery.status.max_charge_power_W = allowedChargePower * 10; - } + datalayer.battery.status.max_charge_power_W = allowedChargePower * 10; - if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0 - datalayer.battery.status.max_discharge_power_W = 0; - } else { // Limit according to CAN value - datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10; - } + datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10; //Power in watts, Negative = charging batt datalayer.battery.status.active_power_W = @@ -212,12 +204,6 @@ void update_values_battery() { //This function maps all the values fetched via set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } - if (datalayer.battery.status.bms_status == - FAULT) { //Incase we enter a critical fault state, zero out the allowed limits - datalayer.battery.status.max_charge_power_W = 0; - datalayer.battery.status.max_discharge_power_W = 0; - } - /* Safeties verified. Perform USB serial printout if configured to do so */ #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 8ffa43ab..1e9ae6f6 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -198,27 +198,9 @@ void update_values_battery() { /* This function maps all the values fetched via } } - // Define power able to be discharged from battery - if (LB_Discharge_Power_Limit > 60) { //if >60kW can be pulled from battery - datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over uint16 value - } else { - datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W - } - if (datalayer.battery.status.reported_soc == - 0) { //Scaled SOC% value is 0.00%, we should not discharge battery further - datalayer.battery.status.max_discharge_power_W = 0; - } + datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W - // Define power able to be put into the battery - if (LB_Charge_Power_Limit > 60) { //if >60kW can be put into the battery - datalayer.battery.status.max_charge_power_W = 60000; //cap value so we don't go over uint16 value - } else { - datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W - } - if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00% - { - datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0 - } + datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W //Map all cell voltages to the global array for (int i = 0; i < 96; ++i) { @@ -233,17 +215,6 @@ void update_values_battery() { /* This function maps all the values fetched via datalayer.battery.status.max_discharge_power_W = 0; } - //Check if SOC% is plausible - if (datalayer.battery.status.voltage_dV > - (datalayer.battery.info.max_design_voltage_dV - - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT - if (LB_SOC < 650) { - set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); // Set event with the SOC as data - } else { - clear_event(EVENT_SOC_PLAUSIBILITY_ERROR); - } - } - if (LB_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already set_event(EVENT_BATTERY_FULL, 0); datalayer.battery.status.max_charge_power_W = 0; @@ -321,12 +292,6 @@ void update_values_battery() { /* This function maps all the values fetched via } } - if (datalayer.battery.status.bms_status == - FAULT) { //Incase we enter a critical fault state, zero out the allowed limits - datalayer.battery.status.max_charge_power_W = 0; - datalayer.battery.status.max_discharge_power_W = 0; - } - /*Finally print out values to serial if configured to do so*/ #ifdef DEBUG_VIA_USB Serial.println("Values from battery"); diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index fce4d5fb..5d50fe26 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -23,7 +23,6 @@ This page has info on the larger 33kWh pack: https://openinverter.org/wiki/Renau /* Do not change code below unless you are sure what you are doing */ static uint32_t LB_Battery_Voltage = 3700; static uint32_t LB_Charge_Power_Limit_Watts = 0; -static uint32_t LB_Discharge_Power_Limit_Watts = 0; static int32_t LB_Current = 0; static int16_t LB_MAX_TEMPERATURE = 0; static int16_t LB_MIN_TEMPERATURE = 0; @@ -96,26 +95,12 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); - LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts /* Define power able to be discharged from battery */ - if (LB_Discharge_Power_Limit_Watts > 60000) //if >60kW can be pulled from battery - { - datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over the uint16 limit - } else { - datalayer.battery.status.max_discharge_power_W = LB_Discharge_Power_Limit_Watts; - } - if (datalayer.battery.status.reported_soc == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further - { - datalayer.battery.status.max_discharge_power_W = 0; - } + datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts - //The above value is 0 on some packs. We instead estimate this now. - if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100.00%, set allowed charge power to 0 - datalayer.battery.status.max_charge_power_W = 0; - } else { - datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W; - } + //The above value is 0 on some packs. We instead hardcode this now. + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W; datalayer.battery.status.active_power_W = ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 85f816bb..47490030 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -32,7 +32,6 @@ static uint32_t total_discharge = 0; static uint32_t total_charge = 0; static uint16_t volts = 0; // V static int16_t amps = 0; // A -static int16_t power = 0; // W static uint16_t raw_amps = 0; // A static int16_t max_temp = 0; // C* static int16_t min_temp = 0; // C* @@ -189,17 +188,13 @@ void update_values_battery() { //This function maps all the values fetched via // Define the allowed discharge power datalayer.battery.status.max_discharge_power_W = (max_discharge_current * volts); - // Cap the allowed discharge power if battery is empty, or discharge power is higher than the maximum discharge power allowed - if (datalayer.battery.status.reported_soc == 0) { - datalayer.battery.status.max_discharge_power_W = 0; - } else if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) { + // Cap the allowed discharge power if higher than the maximum discharge power allowed + if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) { datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; } //The allowed charge power behaves strangely. We instead estimate this value - if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100.00%, set allowed charge power to 0 - datalayer.battery.status.max_charge_power_W = 0; - } else if (soc_vi > 990) { + if (soc_vi > 990) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; } else if (soc_vi > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery.status.max_charge_power_W = @@ -218,8 +213,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; } - power = ((volts / 10) * amps); - datalayer.battery.status.active_power_W = power; + datalayer.battery.status.active_power_W = ((volts / 10) * amps); datalayer.battery.status.temperature_min_dC = min_temp; @@ -303,12 +297,6 @@ void update_values_battery() { //This function maps all the values fetched via } } - if (datalayer.battery.status.bms_status == - FAULT) { //Incase we enter a critical fault state, zero out the allowed limits - datalayer.battery.status.max_charge_power_W = 0; - datalayer.battery.status.max_discharge_power_W = 0; - } - /* Safeties verified. Perform USB serial printout if configured to do so */ #ifdef DEBUG_VIA_USB diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 7893f35b..7804b1b7 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -34,6 +34,25 @@ void update_machineryprotection() { clear_event(EVENT_BATTERY_UNDERVOLTAGE); } + // Battery is fully charged. Dont allow any more power into it + // Normally the BMS will send 0W allowed, but this acts as an additional layer of safety + if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00% + { + set_event(EVENT_BATTERY_FULL, 0); + datalayer.battery.status.max_charge_power_W = 0; + } else { + clear_event(EVENT_BATTERY_FULL); + } + + // Battery is empty. Do not allow further discharge. + // Normally the BMS will send 0W allowed, but this acts as an additional layer of safety + if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00% + set_event(EVENT_BATTERY_EMPTY, 0); + datalayer.battery.status.max_discharge_power_W = 0; + } else { + clear_event(EVENT_BATTERY_EMPTY); + } + // Battery is extremely degraded, not fit for secondlifestorage if (datalayer.battery.status.soh_pptt < 2500) { set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt); @@ -41,6 +60,17 @@ void update_machineryprotection() { clear_event(EVENT_LOW_SOH); } + // Check if SOC% is plausible + if (datalayer.battery.status.voltage_dV > + (datalayer.battery.info.max_design_voltage_dV - + 100)) { // When pack voltage is close to max, and SOC% is still low, raise event + if (datalayer.battery.status.real_soc < 6500) { // 65.00% + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc); + } else { + clear_event(EVENT_SOC_PLAUSIBILITY_ERROR); + } + } + // Check diff between highest and lowest cell cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV); if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { @@ -63,4 +93,10 @@ void update_machineryprotection() { } else { clear_event(EVENT_CAN_RX_WARNING); } + + //Incase we enter a critical fault state, zero out the allowed limits + if (datalayer.battery.status.bms_status == FAULT) { + datalayer.battery.status.max_charge_power_W = 0; + datalayer.battery.status.max_discharge_power_W = 0; + } }