Move charge W capping to Safety file

This commit is contained in:
Daniel 2024-05-19 11:44:00 +03:00
parent 68f6ff81f9
commit b3ba72d0d1
9 changed files with 63 additions and 141 deletions

View file

@ -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();
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_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
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

View file

@ -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;
}
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
}
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));

View file

@ -48,19 +48,10 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(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?

View file

@ -68,18 +68,12 @@ void update_values_battery() { //This function maps all the values fetched via
(static_cast<double>(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
//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
//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

View file

@ -155,17 +155,9 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(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;
}
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;
}
//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

View file

@ -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;
}
// 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
}
//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");

View file

@ -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<uint32_t>(
(static_cast<double>(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 {
//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);

View file

@ -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

View file

@ -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;
}
}