mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
Move charge W capping to Safety file
This commit is contained in:
parent
68f6ff81f9
commit
b3ba72d0d1
9 changed files with 63 additions and 141 deletions
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue