MEB: improvements (#848)

- Don't regulate precharge voltage with external voltage 0.
- Improve log output
- Wait before starting precharge.
- MEB: Improve handling of data from battery BMS
- Use actual_Cellvoltage_lowest / highest instead of calculating min/max ourselves.
- Correct conversion of temperature_min/max _dC
- Correct total_capacity_Wh calculation
- Set overall battery_temperature_dC in extended datalayer
- Set celltemperature values in extended datalayer
- Set temp_points temperature values in extended datalayer
- Fix TODO comments that are already solved
- Add temp point PIDs and store in extended datalayer.
- Display temp points and cell temperatures in advanced battery page
- MEB: Add events start/end balancing
- Use actual precharge status for precharge bit send to BMS
- Only set lowest/highest cell voltage in datalayer when they have actually been received.
This commit is contained in:
Marijn van Galen 2025-02-04 08:42:54 +01:00 committed by GitHub
parent 21bcf3cf00
commit 566039be5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 273 additions and 90 deletions

View file

@ -185,4 +185,8 @@ extern IPAddress subnet;
#define DEBUG_LOG
#endif
#if defined(MEB_BATTERY)
#define PRECHARGE_CONTROL
#endif
#endif // __USER_SETTINGS_H__

View file

@ -10,15 +10,9 @@
/*
TODO list
- Check value mappings on the PID polls
- Check all TODO:s in the code
- 0x1B000044 & 1B00008F seems to be missing from logs? (Classic CAN)
- Scaled remaining capacity, should take already scaled total capacity into account, or we
should undo the scaling on the total capacity (which is calculated from the ah value now,
which is scaled already).
- Investigate why opening and then closing contactors from webpage does not always work
- Invertigate why contactors don't close when lilygo and battery are powered on simultaneously -> timeout on can msgs triggers to late, reset when open contactors is executed
- Find out how to get the battery in balancing mode
- remaining_capacity_Wh is based on a lower limit of 5% soc. This means that at 5% soc, remaining_capacity_Wh returns 0.
*/
/* Do not change code below unless you are sure what you are doing */
@ -144,7 +138,6 @@ static uint8_t target_flow_temperature_C = 0; //*0,5 -40
static uint8_t return_temperature_C = 0; //*0,5 -40
static uint8_t status_valve_1 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
static uint8_t status_valve_2 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
static uint8_t battery_temperature = 0;
static uint8_t temperature_request =
0; //0 high cooling, 1 medium cooling, 2 low cooling, 3 no temp requirement init, 4 low heating , 5 medium heating, 6 high heating, 7 circulation
static uint16_t performance_index_discharge_peak_temperature_percentage = 0;
@ -164,7 +157,6 @@ static uint16_t actual_cellvoltage_lowest_mV = 0; //bias 1000
static uint16_t predicted_power_dyn_standard_watt = 0;
static uint8_t predicted_time_dyn_standard_minutes = 0;
static uint8_t mux = 0;
static int8_t celltemperature[56] = {0}; //Temperatures 1-56. Value is 0xFD if sensor not present
static uint16_t cellvoltages[160] = {0};
static uint16_t duration_discharge_power_watt = 0;
static uint16_t duration_charge_power_watt = 0;
@ -197,6 +189,7 @@ static bool instrumentation_cluster_request = false;
static uint8_t seconds = 0;
static uint32_t first_can_msg = 0;
static uint32_t last_can_msg_timestamp = 0;
static bool hv_requested = false;
#define TIME_YEAR 2024
#define TIME_MONTH 8
@ -538,8 +531,7 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100 battery_soc_polled * 10;
//Alternatively use battery_SOC for more precision
datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100
datalayer.battery.status.soh_pptt;
@ -548,10 +540,9 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.current_dA = (BMS_current - 16300); // 0.1 * 10
datalayer.battery.info.total_capacity_Wh =
((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2;
((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2 * 1.13;
datalayer.battery.status.remaining_capacity_Wh = usable_energy_amount_Wh * 5;
//Alternatively use battery_Wh_left
datalayer.battery.status.max_charge_power_W = (max_charge_power_watt * 100);
@ -561,34 +552,15 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (battery_min_temp - 350) / 2;
// datalayer.battery.status.temperature_min_dC = actual_temperature_lowest_C*5 -400; // We use the value below, because it has better accuracy
datalayer.battery.status.temperature_min_dC = (battery_min_temp * 10) / 64;
datalayer.battery.status.temperature_max_dC = (battery_max_temp - 350) / 2;
// datalayer.battery.status.temperature_max_dC = actual_temperature_highest_C*5 -400; // We use the value below, because it has better accuracy
datalayer.battery.status.temperature_max_dC = (battery_max_temp * 10) / 64;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_polled, 108 * sizeof(uint16_t));
// Initialize min and max, lets find which cells are min and max!
uint16_t min_cell_mv_value = std::numeric_limits<uint16_t>::max();
uint16_t max_cell_mv_value = 0;
// Loop to find the min and max while ignoring zero values
for (uint8_t i = 0; i < 108; ++i) {
uint16_t voltage_mV = datalayer.battery.status.cell_voltages_mV[i];
if (voltage_mV != 0) { // Skip unread values (0)
min_cell_mv_value = std::min(min_cell_mv_value, voltage_mV);
max_cell_mv_value = std::max(max_cell_mv_value, voltage_mV);
}
}
// If all array values are 0, reset min/max to 3700
if (min_cell_mv_value == std::numeric_limits<uint16_t>::max()) {
min_cell_mv_value = 3700;
max_cell_mv_value = 3700;
}
datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value;
datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value;
//TODO, use actual_cellvoltage_lowest_mV instead to save performance
if (service_disconnect_switch_missing) {
set_event(EVENT_HVIL_FAILURE, 1);
} else {
@ -636,6 +608,13 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.meb.rt_cell_undervol = realtime_cell_undervoltage_warning;
datalayer_extended.meb.rt_cell_imbalance = realtime_cell_imbalance_warning;
datalayer_extended.meb.rt_battery_unathorized = realtime_warning_battery_unathorized;
if (balancing_active == 1 && datalayer_extended.meb.balancing_active != 1)
set_event_latched(EVENT_BALANCING_START, 0);
if (balancing_active == 2 && datalayer_extended.meb.balancing_active == 1)
set_event(EVENT_BALANCING_END, 0);
datalayer_extended.meb.balancing_active = balancing_active;
datalayer_extended.meb.balancing_request = balancing_request;
datalayer_extended.meb.charging_active = charging_active;
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
@ -713,7 +692,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
status_valve_1 = (rx_frame.data.u8[3] & 0x1C) >> 2;
status_valve_2 = (rx_frame.data.u8[3] & 0xE0) >> 5;
temperature_request = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7);
battery_temperature = rx_frame.data.u8[5]; //*0,5 -40
datalayer_extended.meb.battery_temperature_dC = rx_frame.data.u8[5] * 5 - 400; //*0,5 -40
target_flow_temperature_C = rx_frame.data.u8[6]; //*0,5 -40
return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40
break;
@ -737,6 +716,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
actual_temperature_lowest_C = rx_frame.data.u8[4]; //*0,5 -40
actual_cellvoltage_highest_mV = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]);
actual_cellvoltage_lowest_mV = ((rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4);
datalayer.battery.status.cell_min_voltage_mV = actual_cellvoltage_lowest_mV + 1000;
datalayer.battery.status.cell_max_voltage_mV = actual_cellvoltage_highest_mV + 1000;
}
break;
case 0x16A954F8: // BMS
@ -748,7 +729,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
switch (mux) {
case 0: // Temperatures 1-56. Value is 0xFD if sensor not present
for (uint8_t i = 0; i < 56; i++) {
celltemperature[i] = (rx_frame.data.u8[i + 1] / 2) - 40;
datalayer_extended.meb.celltemperature_dC[i] = (rx_frame.data.u8[i + 1] * 5) - 400;
}
break;
case 1: // Cellvoltages 1-42
@ -990,7 +971,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
can_msg_received |= RX_0x5CA;
BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3; //True/False
balancing_request = (rx_frame.data.u8[5] & 0x08) >>
3; // BMS requests a low current end charge to support balancing, maybe unused.
battery_diagnostic = (rx_frame.data.u8[3] & 0x07);
battery_Wh_left =
(rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*50 ! Not usable, seems to always contain 0x7F0
@ -1015,34 +997,37 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 6: // DC_CHARGING
#ifdef DEBUG_LOG
if (!datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB Contactors closed\n");
logging.printf("MEB: Contactors closed\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_ACTIVE;
datalayer.system.status.battery_allows_contactor_closing = true;
hv_requested = false;
break;
case 5: // Error
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB Contactors opened\n");
logging.printf("MEB: Contactors opened\n");
#endif
datalayer.battery.status.real_bms_status = BMS_FAULT;
datalayer.system.status.battery_allows_contactor_closing = false;
hv_requested = false;
break;
case 7: // Init
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB Contactors opened\n");
logging.printf("MEB: Contactors opened\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_STANDBY;
datalayer.system.status.battery_allows_contactor_closing = false;
hv_requested = false;
break;
case 2: // BALANCING
default:
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB Contactors opened\n");
logging.printf("MEB: Contactors opened\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_STANDBY;
@ -1084,6 +1069,60 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case PID_MIN_TEMP:
battery_min_temp = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case PID_TEMP_POINT_1:
datalayer_extended.meb.temp_points[0] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_2:
datalayer_extended.meb.temp_points[1] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_3:
datalayer_extended.meb.temp_points[2] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_4:
datalayer_extended.meb.temp_points[3] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_5:
datalayer_extended.meb.temp_points[4] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_6:
datalayer_extended.meb.temp_points[5] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_7:
datalayer_extended.meb.temp_points[6] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_8:
datalayer_extended.meb.temp_points[7] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_9:
datalayer_extended.meb.temp_points[8] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_10:
datalayer_extended.meb.temp_points[9] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_11:
datalayer_extended.meb.temp_points[10] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_12:
datalayer_extended.meb.temp_points[11] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_13:
datalayer_extended.meb.temp_points[12] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_14:
datalayer_extended.meb.temp_points[13] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_15:
datalayer_extended.meb.temp_points[14] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_16:
datalayer_extended.meb.temp_points[15] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_17:
datalayer_extended.meb.temp_points[16] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_TEMP_POINT_18:
datalayer_extended.meb.temp_points[17] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8.f) - 40;
break;
case PID_MAX_CHARGE_VOLTAGE:
battery_max_charge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
@ -1535,7 +1574,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void transmit_can_battery() {
unsigned long currentMillis = millis();
// Send 10ms CAN Message
if (datalayer.system.settings.equipment_stop_active || currentMillis > last_can_msg_timestamp + 500) {
if (currentMillis > last_can_msg_timestamp + 500) {
#ifdef DEBUG_LOG
if (first_can_msg)
logging.printf("MEB: No CAN msg received for 500ms\n");
@ -1615,31 +1654,63 @@ void transmit_can_battery() {
//HV request and DC/DC control lies in 0x503
if (datalayer.battery.status.real_bms_status != BMS_FAULT &&
(datalayer.battery.status.real_bms_status == BMS_STANDBY ||
datalayer.battery.status.real_bms_status == BMS_ACTIVE) &&
(labs(((int32_t)datalayer.battery.status.voltage_dV) -
((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200)) {
if ((!datalayer.system.settings.equipment_stop_active) && datalayer.battery.status.real_bms_status != BMS_FAULT &&
(datalayer.battery.status.real_bms_status == BMS_ACTIVE ||
(datalayer.battery.status.real_bms_status == BMS_STANDBY &&
(hv_requested ||
(datalayer.battery.status.voltage_dV > 200 && datalayer_extended.meb.BMS_voltage_intermediate_dV > 0 &&
labs(((int32_t)datalayer.battery.status.voltage_dV) -
((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200))))) {
hv_requested = true;
datalayer.system.settings.start_precharging = false;
#ifdef DEBUG_LOG
if (MEB_503.data.u8[3] == BMS_TARGET_HV_OFF) {
logging.printf("MEB Requesting HV\n");
logging.printf("MEB: Requesting HV\n");
}
if ((MEB_503.data.u8[1] & 0x80) !=
(datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00)) {
if (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING) {
logging.printf("MEB: Precharge bit set to active\n");
} else {
logging.printf("MEB: Precharge bit set to inactive\n");
}
}
#endif
MEB_503.data.u8[1] =
0x30 |
(datalayer.battery.status.real_bms_status == BMS_ACTIVE ? 0x00 : 0x80); // Disable precharing if ACTIVE
MEB_503.data.u8[3] = BMS_TARGET_HV_ON; //TODO, should we try AC_2 or DC charging?
0x30 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00);
MEB_503.data.u8[3] = BMS_TARGET_AC_CHARGING;
MEB_503.data.u8[5] = 0x82; // Bordnetz Active
MEB_503.data.u8[6] = 0xE0; // Request emergency shutdown HV system == 0, false
} else if (first_can_msg > 0 && currentMillis > first_can_msg + 2000 && BMS_mode != 0 &&
BMS_mode != 7) { //FAULT STATE, open contactors
MEB_503.data.u8[1] = 0x90;
} else if ((first_can_msg > 0 && currentMillis > first_can_msg + 1000 && BMS_mode != 7) ||
datalayer.system.settings.equipment_stop_active) { //FAULT STATE, open contactors
if (datalayer.battery.status.bms_status != FAULT && datalayer.battery.status.real_bms_status == BMS_STANDBY &&
!datalayer.system.settings.equipment_stop_active) {
datalayer.system.settings.start_precharging = true;
}
#ifdef DEBUG_LOG
if (MEB_503.data.u8[3] != BMS_TARGET_HV_OFF) {
logging.printf("MEB: Requesting HV_OFF\n");
}
if ((MEB_503.data.u8[1] & 0x80) !=
(datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00)) {
if (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING) {
logging.printf("MEB: Precharge bit set to active\n");
} else {
logging.printf("MEB: Precharge bit set to inactive\n");
}
}
#endif
MEB_503.data.u8[1] =
0x10 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00);
MEB_503.data.u8[3] = BMS_TARGET_HV_OFF;
MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive
MEB_503.data.u8[6] =
0xE3; // Request emergency shutdown HV system == init (3) (not sure if we dare activate this, this is done with 0xE1)
} else {
MEB_503.data.u8[3] = 0;
MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive
}
MEB_503.data.u8[1] = ((MEB_503.data.u8[1] & 0xF0) | counter_100ms);
MEB_503.data.u8[0] = vw_crc_calc(MEB_503.data.u8, MEB_503.DLC, MEB_503.ID);
@ -1673,9 +1744,9 @@ void transmit_can_battery() {
if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) {
previousMillis200ms = currentMillis;
//TODO: 153 does not seem to need CRC even though it has it? Empty in some logs and still works
// MEB_153 does not need CRC even though it has it. Empty in some logs as well.
//TODO: MEB_1B0000B9 & MEB_1B000010 & MEB_1B000046 has CAN sleep commands, static OK?
//TODO: MEB_1B0000B9 & MEB_1B000010 & MEB_1B000046 has CAN sleep commands. May be removed?
transmit_can_frame(&MEB_5E1, can_config.battery);
transmit_can_frame(&MEB_153, can_config.battery);
@ -1707,6 +1778,32 @@ void transmit_can_battery() {
case PID_MIN_TEMP:
MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_TEMP >> 8);
MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_TEMP;
poll_pid = PID_TEMP_POINT_1;
break;
case PID_TEMP_POINT_1:
case PID_TEMP_POINT_2:
case PID_TEMP_POINT_3:
case PID_TEMP_POINT_4:
case PID_TEMP_POINT_5:
case PID_TEMP_POINT_6:
case PID_TEMP_POINT_7:
case PID_TEMP_POINT_8:
case PID_TEMP_POINT_9:
case PID_TEMP_POINT_10:
case PID_TEMP_POINT_11:
case PID_TEMP_POINT_12:
case PID_TEMP_POINT_13:
case PID_TEMP_POINT_14:
case PID_TEMP_POINT_15:
case PID_TEMP_POINT_16:
case PID_TEMP_POINT_17:
MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(poll_pid >> 8);
MEB_POLLING_FRAME.data.u8[3] = (uint8_t)poll_pid;
poll_pid = poll_pid + 1;
break;
case PID_TEMP_POINT_18:
MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(poll_pid >> 8);
MEB_POLLING_FRAME.data.u8[3] = (uint8_t)poll_pid;
poll_pid = PID_MAX_CHARGE_VOLTAGE;
break;
case PID_MAX_CHARGE_VOLTAGE:

View file

@ -131,6 +131,24 @@
#define PID_CELLVOLTAGE_CELL_106 0x1EA9
#define PID_CELLVOLTAGE_CELL_107 0x1EAA
#define PID_CELLVOLTAGE_CELL_108 0x1EAB
#define PID_TEMP_POINT_1 0x1EAE
#define PID_TEMP_POINT_2 0x1EAF
#define PID_TEMP_POINT_3 0x1EB0
#define PID_TEMP_POINT_4 0x1EB1
#define PID_TEMP_POINT_5 0x1EB2
#define PID_TEMP_POINT_6 0x1EB3
#define PID_TEMP_POINT_7 0x1EB4
#define PID_TEMP_POINT_8 0x1EB5
#define PID_TEMP_POINT_9 0x1EB6
#define PID_TEMP_POINT_10 0x1EB7
#define PID_TEMP_POINT_11 0x1EB8
#define PID_TEMP_POINT_12 0x1EB9
#define PID_TEMP_POINT_13 0x1EBA
#define PID_TEMP_POINT_14 0x1EBB
#define PID_TEMP_POINT_15 0x1EBC
#define PID_TEMP_POINT_16 0x1EBD
#define PID_TEMP_POINT_17 0x1EBE
#define PID_TEMP_POINT_18 0x1EBF
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -6,8 +6,6 @@
// Parameters
#ifdef PRECHARGE_CONTROL
enum State { PRECHARGE_IDLE, START_PRECHARGE, PRECHARGE, PRECHARGE_OFF, COMPLETED };
State prechargeStatus = PRECHARGE_IDLE;
#define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled
@ -32,6 +30,7 @@ void init_precharge_control() {
#endif
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
}
@ -44,22 +43,28 @@ void handle_precharge_control() {
#endif
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (prechargeStatus) {
case PRECHARGE_IDLE:
switch (datalayer.system.status.precharge_status) {
case AUTO_PRECHARGE_IDLE:
#if 0
if (datalayer.battery.status.bms_status != FAULT && datalayer.battery.status.real_bms_status == BMS_STANDBY &&
datalayer.system.status.inverter_allows_contactor_closing &&
/*datalayer.system.status.inverter_allows_contactor_closing &&*/
!datalayer.system.settings.equipment_stop_active) {
prechargeStatus = START_PRECHARGE;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
#else
if (datalayer.system.settings.start_precharging) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
#endif
break;
case START_PRECHARGE:
case AUTO_PRECHARGE_START:
freq = Precharge_default_PWM_Freq;
ledcAttachChannel(PRECHARGE_PIN, freq, PWM_Res, PWM_Precharge_Channel);
ledcWriteTone(PRECHARGE_PIN, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentTime;
prechargeStatus = PRECHARGE;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING;
#ifdef DEBUG_LOG
logging.printf("Precharge: Starting sequence\n");
#endif
@ -67,9 +72,9 @@ void handle_precharge_control() {
break;
case PRECHARGE:
case AUTO_PRECHARGE_PRECHARGING:
// Check if external voltage measurement changed, for instance with the MEB batteries, the external voltage is only updated every 100ms.
if (prev_external_voltage != external_voltage) {
if (prev_external_voltage != external_voltage && external_voltage != 0) {
prev_external_voltage = external_voltage;
if (labs(target_voltage - external_voltage) > 150) {
@ -101,17 +106,18 @@ void handle_precharge_control() {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
prechargeStatus = PRECHARGE_IDLE;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n");
#endif
} else if (currentTime - prechargeStartTime >= MAX_PRECHARGE_TIME_MS) {
} else if (currentTime - prechargeStartTime >= MAX_PRECHARGE_TIME_MS ||
datalayer.battery.status.real_bms_status == BMS_FAULT) {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
prechargeStatus = PRECHARGE_OFF;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (timeout reached) -> PRECHARGE_OFF\n");
logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n");
#endif
set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0);
@ -120,27 +126,27 @@ void handle_precharge_control() {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
prechargeStatus = COMPLETED;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n");
#endif
}
break;
case COMPLETED:
case AUTO_PRECHARGE_COMPLETED:
if (datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != ACTIVE) {
prechargeStatus = PRECHARGE_IDLE;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
#ifdef DEBUG_LOG
logging.printf("Precharge: equipment stop activated -> IDLE\n");
#endif
}
break;
case PRECHARGE_OFF:
case AUTO_PRECHARGE_OFF:
if (!datalayer.system.status.battery_allows_contactor_closing ||
!datalayer.system.status.inverter_allows_contactor_closing ||
datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
prechargeStatus = PRECHARGE_IDLE;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
#ifdef DEBUG_LOG
@ -153,4 +159,4 @@ void handle_precharge_control() {
break;
}
}
#endif // PRECHARGE_CONTROL
#endif // AUTO_PRECHARGE_CONTROL

View file

@ -280,10 +280,17 @@ typedef struct {
#endif
/** True if the BMS is being reset, by cutting power towards it */
bool BMS_reset_in_progress = false;
#ifdef PRECHARGE_CONTROL
/** State of automatic precharge sequence */
PrechargeState precharge_status = AUTO_PRECHARGE_IDLE;
#endif
} DATALAYER_SYSTEM_STATUS_TYPE;
typedef struct {
bool equipment_stop_active = false;
#ifdef PRECHARGE_CONTROL
bool start_precharging = false;
#endif
} DATALAYER_SYSTEM_SETTINGS_TYPE;
typedef struct {

View file

@ -603,6 +603,12 @@ typedef struct {
bool BMS_warning_lamp_req = 0;
int32_t BMS_voltage_intermediate_dV = 0;
int32_t BMS_voltage_dV = 0;
uint8_t balancing_active = 0;
bool balancing_request = 0;
bool charging_active = 0;
float temp_points[18] = {0};
uint16_t celltemperature_dC[56] = {0};
uint16_t battery_temperature_dC = 0;
} DATALAYER_INFO_MEB;
typedef struct {

View file

@ -354,14 +354,14 @@ void emulator_pause_state_transmit_can_battery() {
if (previous_allowed_to_send_CAN && !allowed_to_send_CAN) {
#ifdef DEBUG_LOG
logging.printf("Safety: Pausing CAN sending");
logging.printf("Safety: Pausing CAN sending\n");
#endif
//completely force stop the CAN communication
ESP32Can.CANStop();
} else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) {
//resume CAN communication
#ifdef DEBUG_LOG
logging.printf("Safety: Resuming CAN sending");
logging.printf("Safety: Resuming CAN sending\n");
#endif
ESP32Can.CANInit();
}

View file

@ -7,6 +7,13 @@ enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAU
enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
enum battery_chemistry_enum { NCA, NMC, LFP };
enum led_color { GREEN, YELLOW, RED, BLUE };
enum PrechargeState {
AUTO_PRECHARGE_IDLE,
AUTO_PRECHARGE_START,
AUTO_PRECHARGE_PRECHARGING,
AUTO_PRECHARGE_OFF,
AUTO_PRECHARGE_COMPLETED
};
#define DISCHARGING 1
#define CHARGING 2

View file

@ -998,6 +998,23 @@ String advanced_battery_processor(const String& var) {
default:
content += String("?");
}
content += String("</h4><h4>Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active");
content += String("</h4><h4>Balancing: ");
switch (datalayer_extended.meb.balancing_active) {
case 0:
content += String("init");
break;
case 1:
content += String("active");
break;
case 2:
content += String("inactive");
break;
default:
content += String("?");
}
content +=
String("</h4><h4>Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested");
content += "</h4><h4>Diagnostic: ";
switch (datalayer_extended.meb.battery_diagnostic) {
case 0:
@ -1127,6 +1144,27 @@ String advanced_battery_processor(const String& var) {
content += "<h4>Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance & 0x03]) + "</h4>";
content +=
"<h4>Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized & 0x03]) + "</h4>";
content +=
"<h4>Battery temperature: " + String(datalayer_extended.meb.battery_temperature_dC / 10.f, 1) + " &deg;C</h4>";
for (int i = 0; i < 3; i++) {
content += "<h4>Temperature points " + String(i * 6 + 1) + "-" + String(i * 6 + 6) + " :";
for (int j = 0; j < 6; j++)
content += " &nbsp;" + String(datalayer_extended.meb.temp_points[i * 6 + j], 1);
content += " &deg;C</h4>";
}
bool temps_done = false;
for (int i = 0; i < 7 && !temps_done; i++) {
content += "<h4>Cell temperatures " + String(i * 8 + 1) + "-" + String(i * 8 + 8) + " :";
for (int j = 0; j < 8; j++) {
if (datalayer_extended.meb.celltemperature_dC[i * 8 + j] == 865) {
temps_done = true;
break;
} else {
content += " &nbsp;" + String(datalayer_extended.meb.celltemperature_dC[i * 8 + j] / 10.f, 1);
}
}
content += " &deg;C</h4>";
}
#endif //MEB_BATTERY
#ifdef RENAULT_ZOE_GEN2_BATTERY

View file

@ -896,8 +896,8 @@ String processor(const String& var) {
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " &deg;C</h4>";
content += "<h4>System status: ";
switch (datalayer.battery.status.bms_status) {
@ -1074,8 +1074,8 @@ String processor(const String& var) {
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " &deg;C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
content += "<h4>System status: OK </h4>";
} else if (datalayer.battery.status.bms_status == UPDATING) {