From a9c37d15a9888df534a2be8b94de8a857c0186f1 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:48:35 +0000 Subject: [PATCH 01/31] Initial PHEV Demo Implements basic functionality Supported: -Pre/Post contactor voltage -Min/ Cell Temp -SOC --- Software/src/battery/BATTERIES.h | 4 + Software/src/battery/BMW-PHEV-BATTERY.cpp | 550 ++++++++++++++++++ Software/src/battery/BMW-PHEV-BATTERY.h | 22 + Software/src/datalayer/datalayer_extended.h | 26 + .../webserver/advanced_battery_html.cpp | 48 +- 5 files changed, 646 insertions(+), 4 deletions(-) create mode 100644 Software/src/battery/BMW-PHEV-BATTERY.cpp create mode 100644 Software/src/battery/BMW-PHEV-BATTERY.h diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 93a96f9b0..92306cc66 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -17,6 +17,10 @@ void setup_can_shunt(); #include "BMW-IX-BATTERY.h" #endif +#ifdef BMW_PHEV_BATTERY +#include "BMW-PHEV-BATTERY.h" +#endif + #ifdef BOLT_AMPERA_BATTERY #include "BOLT-AMPERA-BATTERY.h" #endif diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp new file mode 100644 index 000000000..b402faa26 --- /dev/null +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -0,0 +1,550 @@ +#include "../include.h" +#ifdef BMW_PHEV_BATTERY +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "../devboard/utils/events.h" +#include "BMW-PHEV-BATTERY.h" + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send + +#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 + +enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST }; + +static CmdState cmdState = SOC; + +/* +INFO + +V0.1 very basic implementation reading Gen3/4 BMW PHEV SME. +Supported: +-Pre/Post contactor voltage +-Min/ Cell Temp +-SOC + +UDS MAP +22 D6 CF - CSC Temps +22 DD C0 - Min Max temps +*/ + +//Vehicle CAN START + +CAN_frame BMWiX_0C0 = { + .FD = false, + .ext_ID = false, + .DLC = 2, + .ID = 0x0C0, + .data = { + 0xF0, + 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE + +//Vehicle CAN END + +//Request Data CAN START + +CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0xC4}}; // SOC% + +CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0xB4}}; //Main Battery Voltage (Pre Contactor) + +CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x66}}; //Main Battery Voltage (After Contactor) + +CAN_frame BMWPHEV_6F1_REQUEST_MINMAXCELLV = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xDF, + 0xA0}}; //Min and max cell voltage 6.55V = Qualifier Invalid? Multi return frame - might be all cell voltages + +CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xDD, + 0xC0}}; // UDS Request Cell Temperatures min max avg. Has continue frame min in first, then max + avg in second frame + +CAN_frame BMW_6F4_CELL_TEMP_CONTINUE = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x30, 0x03, 0x00, 0x00}}; + +CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x01, 0x00, 0x00}}; // Request Contactors Close - Unconfirmed +CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_OPEN = { + .FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x00, 0x00, 0x00}}; // Request Contactors Open - Unconfirmed + +//UNTESTED/UNCHANGED FROM IX BEYOND HERE + +CAN_frame BMWPHEV_6F1 = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value +CAN_frame BMWPHEV_6F1_REQUEST_SLEEPMODE = { + .FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F1, + .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode +CAN_frame BMWPHEV_6F1_REQUEST_HARD_RESET = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F1, + .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME + +CAN_frame BMWPHEV_6F1_REQUEST_CAPACITY = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias + +CAN_frame BMWPHEV_6F1_REQUEST_BATTERYCURRENT = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge +CAN_frame BMWPHEV_6F1_REQUEST_CELL_VOLTAGE = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages +CAN_frame BMWPHEV_6F1_REQUEST_T30VOLTAGE = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) +CAN_frame BMWPHEV_6F1_REQUEST_EOL_ISO = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO +CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request +CAN_frame BMWPHEV_6F1_REQUEST_DATASUMMARY = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xE5, + 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate +CAN_frame BMWPHEV_6F1_REQUEST_PYRO = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status +CAN_frame BMWPHEV_6F1_REQUEST_UPTIME = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status +CAN_frame BMWPHEV_6F1_REQUEST_HVIL = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State +CAN_frame BMWPHEV_6F1_REQUEST_BALANCINGSTATUS = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data +CAN_frame BMWPHEV_6F1_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps +CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_QUALIFIER_CHECK = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = { + .FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F1, + .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? +CAN_frame BMWPHEV_6F1_REQUEST_PACK_VOLTAGE_LIMITS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits + +CAN_frame BMWPHEV_6F1_CONTINUE_DATA = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F1, + .data = {0x07, 0x30, 0x00, 0x02}}; + +//Action Requests: +CAN_frame BMW_10B = {.FD = false, + .ext_ID = false, + .DLC = 3, + .ID = 0x10B, + .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command? + +CAN_frame BMWPHEV_6F1_CELL_SOC = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; +CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; +//Request Data CAN End + +static bool battery_awake = false; + +//Setup UDS values to poll for +CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_CELL_TEMP}; +int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array + +//PHEV intermediate vars + +static uint16_t battery_max_charge_voltage = 0; +static int16_t battery_max_charge_amperage = 0; +static uint16_t battery_min_discharge_voltage = 0; +static int16_t battery_max_discharge_amperage = 0; + +//iX Intermediate vars +static bool battery_info_available = false; +static uint32_t battery_serial_number = 0; +static int32_t battery_current = 0; +static int16_t battery_voltage = 370; +static int16_t terminal30_12v_voltage = 0; +static int16_t battery_voltage_after_contactor = 0; +static int16_t min_soc_state = 50; +static int16_t avg_soc_state = 50; +static int16_t max_soc_state = 50; +static int16_t min_soh_state = 99; // Uses E5 45, also available in 78 73 +static int16_t avg_soh_state = 99; // Uses E5 45, also available in 78 73 +static int16_t max_soh_state = 99; // Uses E5 45, also available in 78 73 +static uint16_t max_design_voltage = 0; +static uint16_t min_design_voltage = 0; +static int32_t remaining_capacity = 0; +static int32_t max_capacity = 0; +static int16_t min_battery_temperature = 0; +static int16_t avg_battery_temperature = 0; +static int16_t max_battery_temperature = 0; +static int16_t main_contactor_temperature = 0; +static int16_t min_cell_voltage = 0; +static int16_t max_cell_voltage = 0; +static unsigned long min_cell_voltage_lastchanged = 0; +static unsigned long max_cell_voltage_lastchanged = 0; +static unsigned min_cell_voltage_lastreceived = 0; +static unsigned max_cell_voltage_lastreceived = 0; +static uint32_t sme_uptime = 0; //Uses E4 C0 +static int16_t allowable_charge_amps = 0; //E5 62 +static int16_t allowable_discharge_amps = 0; //E5 62 +static int32_t iso_safety_positive = 0; //Uses A8 60 +static int32_t iso_safety_negative = 0; //Uses A8 60 +static int32_t iso_safety_parallel = 0; //Uses A8 60 +static int16_t count_full_charges = 0; //TODO 42 +static int16_t count_charges = 0; //TODO 42 +static int16_t hvil_status = 0; +static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid +static int16_t balancing_status = 0; //4 = not active +static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 +static uint8_t contactor_status_precharge = 0; //TODO E5 BF +static uint8_t contactor_status_negative = 0; //TODO E5 BF +static uint8_t contactor_status_positive = 0; //TODO E5 BF +static uint8_t pyro_status_pss1 = 0; //Using AC 93 +static uint8_t pyro_status_pss4 = 0; //Using AC 93 +static uint8_t pyro_status_pss6 = 0; //Using AC 93 +static uint8_t uds_req_id_counter = 0; +static uint8_t detected_number_of_cells = 108; +const unsigned long STALE_PERIOD = + STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) + +static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 + +//End iX Intermediate vars + +static uint8_t current_cell_polled = 0; + +// Function to check if a value has gone stale over a specified time period +bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) { + unsigned long currentTime = millis(); + + // Check if the value has changed + if (currentValue != lastValue) { + // Update the last change time and value + lastChangeTime = currentTime; + lastValue = currentValue; + return false; // Value is fresh because it has changed + } + + // Check if the value has stayed the same for the specified staleness period + return (currentTime - lastChangeTime >= STALE_PERIOD); +} + +static uint8_t increment_uds_req_id_counter(uint8_t index) { + index++; + if (index >= numUDSreqs) { + index = 0; + } + return index; +} + +static uint8_t increment_alive_counter(uint8_t counter) { + counter++; + if (counter > ALIVE_MAX_VALUE) { + counter = 0; + } + return counter; +} + +static byte increment_0C0_counter(byte counter) { + counter++; + // Reset to 0xF0 if it exceeds 0xFE + if (counter > 0xFE) { + counter = 0xF0; + } + return counter; +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer + + datalayer.battery.status.real_soc = avg_soc_state; + + datalayer.battery.status.voltage_dV = battery_voltage; + + datalayer.battery.status.current_dA = battery_current; + + datalayer.battery.info.total_capacity_Wh = max_capacity; + + datalayer.battery.status.remaining_capacity_Wh = remaining_capacity; + + datalayer.battery.status.soh_pptt = min_soh_state; + + datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W; + + //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping + + // Charge power is set in .h file + if (datalayer.battery.status.real_soc > 9900) { + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W; + } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) { + // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 + datalayer.battery.status.max_charge_power_W = + MAX_CHARGE_POWER_ALLOWED_W * + (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC)); + } else { // No limits, max charging power allowed + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; + } + + datalayer.battery.status.temperature_min_dC = min_battery_temperature; + + datalayer.battery.status.temperature_max_dC = max_battery_temperature; + + //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. + bool isMinCellVoltageStale = + isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged); + bool isMaxCellVoltageStale = + isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged); + + if (isMinCellVoltageStale && isMaxCellVoltageStale) { + datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop + datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE, 0); + } else { + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive + } + + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + + datalayer.battery.info.number_of_cells = detected_number_of_cells; + + datalayer_extended.bmwphev.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); + + datalayer_extended.bmwphev.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged); + + datalayer_extended.bmwphev.T30_Voltage = terminal30_12v_voltage; + + datalayer_extended.bmwphev.hvil_status = hvil_status; + + datalayer_extended.bmwphev.bms_uptime = sme_uptime; + + datalayer_extended.bmwphev.pyro_status_pss1 = pyro_status_pss1; + + datalayer_extended.bmwphev.pyro_status_pss4 = pyro_status_pss4; + + datalayer_extended.bmwphev.pyro_status_pss6 = pyro_status_pss6; + + datalayer_extended.bmwphev.iso_safety_positive = iso_safety_positive; + + datalayer_extended.bmwphev.iso_safety_negative = iso_safety_negative; + + datalayer_extended.bmwphev.iso_safety_parallel = iso_safety_parallel; + + datalayer_extended.bmwphev.allowable_charge_amps = allowable_charge_amps; + + datalayer_extended.bmwphev.allowable_discharge_amps = allowable_discharge_amps; + + datalayer_extended.bmwphev.balancing_status = balancing_status; + + datalayer_extended.bmwphev.battery_voltage_after_contactor = battery_voltage_after_contactor; + + if (battery_info_available) { + // If we have data from battery - override the defaults to suit + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + } +} +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + battery_awake = true; + switch (rx_frame.ID) { + case 0x112: + break; + case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations + battery_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2); + battery_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2); + break; + case 0x607: //SME responds to UDS requests on 0x607 + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% + avg_soc_state = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + } + + if (rx_frame.DLC = + 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) + battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) + battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 8 && rx_frame.data.u8[4] == 0xDD && + rx_frame.data.u8[5] == 0xC0) { //Cell Temp Min - continue frame follows + min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; + } + + // if (rx_frame.DLC = + // 7 && + // rx_frame.data.u8[1] == + // 0x21) { //Cell Temp Max/Avg - is continue frame - fingerprinting needs improving as 0xF1 0x21 is used by other continues + // max_battery_temperature = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]) / 10; + // avg_battery_temperature = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) / 10; + // } + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + + //if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; + } + // Send 200ms CAN Message + if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { + previousMillis200 = currentMillis; + } + // Send 1000ms CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; + + //Loop through and send a different UDS request each cycle + uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); + transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); + } + // Send 5000ms CAN Message + if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { + previousMillis5000 = currentMillis; + transmit_can_frame(&BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE, + can_config.battery); // Attempt contactor close - experimental + } + // Send 10000ms CAN Message + if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { + previousMillis10000 = currentMillis; + } +} +//We can always send CAN as the iX BMS will wake up on vehicle comms +// else { +// previousMillis20 = currentMillis; +// previousMillis100 = currentMillis; +// previousMillis200 = currentMillis; +// previousMillis500 = currentMillis; +// previousMillis640 = currentMillis; +// previousMillis1000 = currentMillis; +// previousMillis5000 = currentMillis; +// previousMillis10000 = currentMillis; +// } +//} //We can always send CAN as the iX BMS will wake up on vehicle comms + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + + //Before we have started up and detected which battery is in use, use 108S values + 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_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; + datalayer.system.status.battery_allows_contactor_closing = true; +} + +#endif diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h new file mode 100644 index 000000000..f066f66fa --- /dev/null +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -0,0 +1,22 @@ +#ifndef BMW_PHEV_BATTERY_H +#define BMW_PHEV_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V +#define MIN_PACK_VOLTAGE_DV 3000 +#define MAX_CELL_DEVIATION_MV 250 +#define MAX_CELL_VOLTAGE_MV 4300 //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 +#define MAX_DISCHARGE_POWER_ALLOWED_W 10000 +#define MAX_CHARGE_POWER_ALLOWED_W 10000 +#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 +#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% +#define STALE_PERIOD_CONFIG \ + 300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 3bf20f218..70fef6e57 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -64,6 +64,31 @@ typedef struct { } DATALAYER_INFO_BMWIX; +typedef struct { + /** uint16_t */ + /** Terminal 30 - 12V SME Supply Voltage */ + uint16_t T30_Voltage = 0; + /** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/ + uint8_t hvil_status = 0; + /** Min/Max Cell SOH*/ + uint16_t min_soh_state = 0; + uint16_t max_soh_state = 0; + uint32_t bms_uptime = 0; + uint8_t pyro_status_pss1 = 0; + uint8_t pyro_status_pss4 = 0; + uint8_t pyro_status_pss6 = 0; + int32_t iso_safety_positive = 0; + int32_t iso_safety_negative = 0; + int32_t iso_safety_parallel = 0; + int32_t allowable_charge_amps = 0; + int32_t allowable_discharge_amps = 0; + int16_t balancing_status = 0; + int16_t battery_voltage_after_contactor = 0; + unsigned long min_cell_voltage_data_age = 0; + unsigned long max_cell_voltage_data_age = 0; + +} DATALAYER_INFO_BMWPHEV; + typedef struct { /** uint16_t */ /** SOC% raw battery value. Might not always reach 100% */ @@ -614,6 +639,7 @@ class DataLayerExtended { public: DATALAYER_INFO_BOLTAMPERA boltampera; DATALAYER_INFO_BMWIX bmwix; + DATALAYER_INFO_BMWIX bmwphev; DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index b999e6e39..007452ad1 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -97,6 +97,45 @@ String advanced_battery_processor(const String& var) { content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; #endif //BMW_IX_BATTERY +#ifdef BMW_PHEV_BATTERY + content += + "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwphev.battery_voltage_after_contactor) + + " dV

"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; + content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; + content += + "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; + content += + "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; + content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; + content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", + "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", + "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", + "4 No balancing mode active, qualifier invalid"}; + content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; + static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; + content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; + content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; + content += + "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += "
"; + content += "

HV Isolation (2147483647kOhm = maximum/invalid)

"; + content += "

Isolation Positive: " + String(datalayer_extended.bmwphev.iso_safety_positive) + " kOhm

"; + content += "

Isolation Negative: " + String(datalayer_extended.bmwphev.iso_safety_negative) + " kOhm

"; + content += "

Isolation Parallel: " + String(datalayer_extended.bmwphev.iso_safety_parallel) + " kOhm

"; + static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", + "3 Not Activated - Pyro Intact", "4 Unknown"}; + content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss1])) + "

"; + content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss4])) + "

"; + content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss6])) + "

"; +#endif //BMW_PHEV_BATTERY + #ifdef BMW_I3_BATTERY content += "

SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "

"; content += "

SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "

"; @@ -1140,10 +1179,11 @@ String advanced_battery_processor(const String& var) { content += ""; #endif // VOLVO_SPA_BATTERY -#if !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && !defined(TESLA_BATTERY) && \ - !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && !defined(BYD_ATTO_3_BATTERY) && \ - !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && !defined(MEB_BATTERY) && \ - !defined(VOLVO_SPA_BATTERY) && !defined(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info +#if !defined(BMW_PHEV_BATTERY) && !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && \ + !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ + !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \ + !defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && \ + !defined(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From 9b25921cab19274833f4c57724a1a4036e2143f1 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:52:48 +0000 Subject: [PATCH 02/31] add user setting for phev --- Software/USER_SETTINGS.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index b4d027fb9..816230e67 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -11,6 +11,7 @@ /* Select battery used */ //#define BMW_I3_BATTERY //#define BMW_IX_BATTERY +//#define BMW_PHEV_BATTERY //#define BOLT_AMPERA_BATTERY //#define BYD_ATTO_3_BATTERY //#define CELLPOWER_BMS From 5e54ee2f099a398f8e2c368c4c94f5091b415028 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:17:13 +0000 Subject: [PATCH 03/31] balancing status support Add balancing status support (in advanced info ) --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 43 +++++++++++++------ .../webserver/advanced_battery_html.cpp | 6 +-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index b402faa26..f0a3eef6d 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -107,6 +107,28 @@ CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_OPEN = { .ID = 0x6F1, .data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x00, 0x00, 0x00}}; // Request Contactors Open - Unconfirmed +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STATUS = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6B, 0x00, + 0x00}}; // Balancing status. Response 7DLC F1 05 71 03 AD 6B 01 (01 = active) (03 not active) + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing start request + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STOP = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request + //UNTESTED/UNCHANGED FROM IX BEYOND HERE CAN_frame BMWPHEV_6F1 = { @@ -185,11 +207,7 @@ CAN_frame BMWPHEV_6F1_REQUEST_HVIL = {.FD = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State -CAN_frame BMWPHEV_6F1_REQUEST_BALANCINGSTATUS = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data + CAN_frame BMWPHEV_6F1_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = { .FD = false, .ext_ID = false, @@ -203,12 +221,6 @@ CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_QUALIFIER_CHECK = { .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier -CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = { - .FD = false, - .ext_ID = false, - .DLC = 6, - .ID = 0x6F1, - .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? CAN_frame BMWPHEV_6F1_REQUEST_PACK_VOLTAGE_LIMITS = { .FD = false, .ext_ID = false, @@ -245,7 +257,8 @@ static bool battery_awake = false; //Setup UDS values to poll for CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, - &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_CELL_TEMP}; + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_CELL_TEMP, + &BMWPHEV_6F1_REQUEST_BALANCING_STATUS}; int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //PHEV intermediate vars @@ -472,6 +485,12 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; } + if (rx_frame.DLC = + 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && rx_frame.data.u8[3] == 0x03 && + rx_frame.data.u8[4] == 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 + balancing_status = (rx_frame.data.u8[6]); + } + if (rx_frame.DLC = 8 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Cell Temp Min - continue frame follows min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 007452ad1..b82426a86 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -113,10 +113,8 @@ String advanced_battery_processor(const String& var) { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", - "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", - "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", - "4 No balancing mode active, qualifier invalid"}; + static const char* balanceText[5] = {"0 Unknown", "1 Balancing Active", "2 Unknown", "3 Balancing Inactive", + "4 Unknown"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; From aa0a92e68d167ed94ad71493e56201faacd211a7 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 18:20:47 +0000 Subject: [PATCH 04/31] Enable balancing Now enables balancing every 10secs --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index f0a3eef6d..80a71cf8b 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -538,6 +538,8 @@ void transmit_can_battery() { // Send 10000ms CAN Message if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { previousMillis10000 = currentMillis; + transmit_can_frame(&BMWPHEV_6F1_REQUEST_BALANCING_START, + can_config.battery); // Enable Balancing } } //We can always send CAN as the iX BMS will wake up on vehicle comms From 33c0705a5fb54de767348c1de8532d535fd66172 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:05:12 +0000 Subject: [PATCH 05/31] Cell rest break status added handle balancing status 2 --- Software/src/devboard/webserver/advanced_battery_html.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index b82426a86..436684254 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -113,7 +113,7 @@ String advanced_battery_processor(const String& var) { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 Unknown", "1 Balancing Active", "2 Unknown", "3 Balancing Inactive", + static const char* balanceText[5] = {"0 Unknown", "1 Balancing Active", "2 Balancing Inactive - Cells not in rest break wait 10mins", "3 Balancing Inactive", "4 Unknown"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; From 9301648835f9369d15949752eebd381a387e8094 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:19:46 +0000 Subject: [PATCH 06/31] extra balancing status --- Software/src/devboard/webserver/advanced_battery_html.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 436684254..1dac1871d 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -113,7 +113,7 @@ String advanced_battery_processor(const String& var) { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 Unknown", "1 Balancing Active", "2 Balancing Inactive - Cells not in rest break wait 10mins", "3 Balancing Inactive", + static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", "2 Balancing Inactive - Cells not in rest break wait 10mins", "3 Balancing Inactive", "4 Unknown"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; From 2ebc42ef29d604b9acfc23b52cae1e82f53180a6 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:20:29 +0000 Subject: [PATCH 07/31] formatting fix --- Software/src/devboard/webserver/advanced_battery_html.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 1dac1871d..30503c16b 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -113,8 +113,9 @@ String advanced_battery_processor(const String& var) { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", "2 Balancing Inactive - Cells not in rest break wait 10mins", "3 Balancing Inactive", - "4 Unknown"}; + static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", + "2 Balancing Inactive - Cells not in rest break wait 10mins", + "3 Balancing Inactive", "4 Unknown"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; From d2e8e51770a52128cfe0cbb0065dd63d96957850 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:35:01 +0000 Subject: [PATCH 08/31] Various improvements Try i3 contactor close method, cleanup and more messages parsed --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 156 +++++++++++++++--- Software/src/datalayer/datalayer_extended.h | 35 +++- .../webserver/advanced_battery_html.cpp | 80 ++++++++- 3 files changed, 242 insertions(+), 29 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 80a71cf8b..df583b253 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -21,6 +21,24 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE static CmdState cmdState = SOC; +const unsigned char crc8_table[256] = + { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies + 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, + 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, + 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, + 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, + 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, + 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, + 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, + 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, + 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, + 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, + 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, + 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, + 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, + 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0, + 0xE3, 0xFE, 0xD9, 0xC4}; + /* INFO @@ -33,6 +51,17 @@ V0.1 very basic implementation reading Gen3/4 BMW PHEV SME. UDS MAP 22 D6 CF - CSC Temps 22 DD C0 - Min Max temps +22 DF A5 - All Cell voltages +22 E5 EA - Alternate all cell voltages +22 DE 7E - Voltage limits. 62 DD 73 9D 5A 69 26 = 269.18V - 402.82V +22 DD 7D - Current limits 62 DD 7D 08 20 0B EA = 305A max discharge 208A max charge +22 E5 E9 DD 7D - Individual cell SOC +22 DD 69 - Current in Amps 62 DD 69 00 00 00 00 = 0 Amps +22 DD 7B - SOH 62 DD 7B 62 = 98% +22 DD 62 - HVIL Status 62 DD 64 01 = OK/Closed +22 DD 6A - Isolation values 62 DD 6A 07 D0 07 D0 07 D0 01 01 01 = in operation plausible/2000kOhm, in follow up plausible/2000kohm, internal iso open contactors (measured on request) pluasible/2000kohm +31 03 AD 61 - Isolation measurement status 71 03 AD 61 00 FF = Nmeasurement status - not successful / fault satate - not defined +22 DF A0 - Cell voltage and temps summary including min/max/average, Ah, */ //Vehicle CAN START @@ -46,6 +75,12 @@ CAN_frame BMWiX_0C0 = { 0xF0, 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE +CAN_frame BMW_13E = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x13E, + .data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}}; + //Vehicle CAN END //Request Data CAN START @@ -70,14 +105,14 @@ CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR = { .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x66}}; //Main Battery Voltage (After Contactor) -CAN_frame BMWPHEV_6F1_REQUEST_MINMAXCELLV = { +CAN_frame BMWPHEV_6F1_REQUEST_CELLSUMMARY = { .FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = { 0x07, 0x03, 0x22, 0xDF, - 0xA0}}; //Min and max cell voltage 6.55V = Qualifier Invalid? Multi return frame - might be all cell voltages + 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid? Multi return frame - might be all cell voltages CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = { .FD = false, @@ -268,6 +303,28 @@ static int16_t battery_max_charge_amperage = 0; static uint16_t battery_min_discharge_voltage = 0; static int16_t battery_max_discharge_amperage = 0; +static uint8_t startup_counter_contactor = 0; +static uint8_t alive_counter_20ms = 0; +static uint8_t BMW_13E_counter = 0; + +static uint8_t battery_status_error_isolation_external_Bordnetz = 0; +static uint8_t battery_status_error_isolation_internal_Bordnetz = 0; +static uint8_t battery_request_cooling = 0; +static uint8_t battery_status_valve_cooling = 0; +static uint8_t battery_status_error_locking = 0; +static uint8_t battery_status_precharge_locked = 0; +static uint8_t battery_status_disconnecting_switch = 0; +static uint8_t battery_status_emergency_mode = 0; +static uint8_t battery_request_service = 0; +static uint8_t battery_error_emergency_mode = 0; +static uint8_t battery_status_error_disconnecting_switch = 0; +static uint8_t battery_status_warning_isolation = 0; +static uint8_t battery_status_cold_shutoff_valve = 0; +static int16_t battery_temperature_HV = 0; +static int16_t battery_temperature_heat_exchanger = 0; +static int16_t battery_temperature_max = 0; +static int16_t battery_temperature_min = 0; + //iX Intermediate vars static bool battery_info_available = false; static uint32_t battery_serial_number = 0; @@ -285,9 +342,7 @@ static uint16_t max_design_voltage = 0; static uint16_t min_design_voltage = 0; static int32_t remaining_capacity = 0; static int32_t max_capacity = 0; -static int16_t min_battery_temperature = 0; -static int16_t avg_battery_temperature = 0; -static int16_t max_battery_temperature = 0; + static int16_t main_contactor_temperature = 0; static int16_t min_cell_voltage = 0; static int16_t max_cell_voltage = 0; @@ -310,9 +365,6 @@ static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 static uint8_t contactor_status_precharge = 0; //TODO E5 BF static uint8_t contactor_status_negative = 0; //TODO E5 BF static uint8_t contactor_status_positive = 0; //TODO E5 BF -static uint8_t pyro_status_pss1 = 0; //Using AC 93 -static uint8_t pyro_status_pss4 = 0; //Using AC 93 -static uint8_t pyro_status_pss6 = 0; //Using AC 93 static uint8_t uds_req_id_counter = 0; static uint8_t detected_number_of_cells = 108; const unsigned long STALE_PERIOD = @@ -340,6 +392,14 @@ bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChang return (currentTime - lastChangeTime >= STALE_PERIOD); } +static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) { + uint8_t crc = initial_value; + for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC + crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256]; + } + return crc; +} + static uint8_t increment_uds_req_id_counter(uint8_t index) { index++; if (index >= numUDSreqs) { @@ -395,9 +455,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; } - datalayer.battery.status.temperature_min_dC = min_battery_temperature; + datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal - datalayer.battery.status.temperature_max_dC = max_battery_temperature; + datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. bool isMinCellVoltageStale = @@ -430,12 +490,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.bms_uptime = sme_uptime; - datalayer_extended.bmwphev.pyro_status_pss1 = pyro_status_pss1; - - datalayer_extended.bmwphev.pyro_status_pss4 = pyro_status_pss4; - - datalayer_extended.bmwphev.pyro_status_pss6 = pyro_status_pss6; - datalayer_extended.bmwphev.iso_safety_positive = iso_safety_positive; datalayer_extended.bmwphev.iso_safety_negative = iso_safety_negative; @@ -450,6 +504,19 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.battery_voltage_after_contactor = battery_voltage_after_contactor; + // Update webserver datalayer + + datalayer_extended.bmwphev.ST_iso_ext = battery_status_error_isolation_external_Bordnetz; + datalayer_extended.bmwphev.ST_iso_int = battery_status_error_isolation_internal_Bordnetz; + datalayer_extended.bmwphev.ST_valve_cooling = battery_status_valve_cooling; + datalayer_extended.bmwphev.ST_interlock = battery_status_error_locking; + datalayer_extended.bmwphev.ST_precharge = battery_status_precharge_locked; + datalayer_extended.bmwphev.ST_DCSW = battery_status_disconnecting_switch; + datalayer_extended.bmwphev.ST_EMG = battery_status_emergency_mode; + datalayer_extended.bmwphev.ST_WELD = battery_status_error_disconnecting_switch; + datalayer_extended.bmwphev.ST_isolation = battery_status_warning_isolation; + datalayer_extended.bmwphev.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; + if (battery_info_available) { // If we have data from battery - override the defaults to suit datalayer.battery.info.max_design_voltage_dV = max_design_voltage; @@ -491,10 +558,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { balancing_status = (rx_frame.data.u8[6]); } - if (rx_frame.DLC = 8 && rx_frame.data.u8[4] == 0xDD && - rx_frame.data.u8[5] == 0xC0) { //Cell Temp Min - continue frame follows - min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; - } + //moved away from UDS - using SME default sent message + //if (rx_frame.DLC = 8 && rx_frame.data.u8[4] == 0xDD && + // rx_frame.data.u8[5] == 0xC0) { //Cell Temp Min - continue frame follows + // min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; + //} // if (rx_frame.DLC = // 7 && @@ -504,6 +572,25 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // avg_battery_temperature = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) / 10; // } break; + case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1 + battery_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03); + battery_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4; + battery_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6; + battery_status_error_locking = (rx_frame.data.u8[1] & 0x03); + battery_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2; + battery_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4; + battery_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6; + battery_request_service = (rx_frame.data.u8[2] & 0x03); + battery_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2; + battery_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4; + battery_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6; + battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); + battery_temperature_HV = (rx_frame.data.u8[4] - 50); + battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); + battery_temperature_min = (rx_frame.data.u8[6] - 50); + battery_temperature_max = (rx_frame.data.u8[7] - 50); + break; default: break; } @@ -513,6 +600,31 @@ void transmit_can_battery() { unsigned long currentMillis = millis(); //if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms + + if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { + previousMillis20 = currentMillis; + + if (startup_counter_contactor < 160) { + startup_counter_contactor++; + } else { //After 160 messages, turn on the request + BMW_10B.data.u8[1] = 0x10; // Close contactors + } + + BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms); + BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F); + + alive_counter_20ms = increment_alive_counter(alive_counter_20ms); + + BMW_13E_counter++; + BMW_13E.data.u8[4] = BMW_13E_counter; + + //if (datalayer.battery.status.bms_status == FAULT) { //ALLOW ANY TIME - TEST ONLY + //} //If battery is not in Fault mode, allow contactor to close by sending 10B + //else { + transmit_can_frame(&BMW_10B, can_config.battery); + //} + } + // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; @@ -532,8 +644,8 @@ void transmit_can_battery() { // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { previousMillis5000 = currentMillis; - transmit_can_frame(&BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE, - can_config.battery); // Attempt contactor close - experimental + // transmit_can_frame(&BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE, + // can_config.battery); // Attempt contactor close - experimental } // Send 10000ms CAN Message if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 70fef6e57..8752883cd 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -65,6 +65,36 @@ typedef struct { } DATALAYER_INFO_BMWIX; typedef struct { + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_ext = 0; + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_int = 0; + /** uint8_t */ + /** Status cooling valve error, 0 not evaluated, 1 OK valve closed, 2 error active valve open, 3 Invalid signal*/ + uint8_t ST_valve_cooling = 0; + /** uint8_t */ + /** Status interlock error, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_interlock = 0; + /** uint8_t */ + /** Status precharge, 0 no statement, 1 Not active closing not blocked, 2 error precharge blocked, 3 Invalid signal*/ + uint8_t ST_precharge = 0; + /** uint8_t */ + /** Status DC switch, 0 contactors open, 1 precharge ongoing, 2 contactors engaged, 3 Invalid signal*/ + uint8_t ST_DCSW = 0; + /** uint8_t */ + /** Status emergency, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_EMG = 0; + /** uint8_t */ + /** Status welding detection, 0 Contactors OK, 1 One contactor welded, 2 Two contactors welded, 3 Invalid signal*/ + uint8_t ST_WELD = 0; + /** uint8_t */ + /** Status isolation, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_isolation = 0; + /** uint8_t */ + /** Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal*/ + uint8_t ST_cold_shutoff_valve = 0; /** uint16_t */ /** Terminal 30 - 12V SME Supply Voltage */ uint16_t T30_Voltage = 0; @@ -74,9 +104,6 @@ typedef struct { uint16_t min_soh_state = 0; uint16_t max_soh_state = 0; uint32_t bms_uptime = 0; - uint8_t pyro_status_pss1 = 0; - uint8_t pyro_status_pss4 = 0; - uint8_t pyro_status_pss6 = 0; int32_t iso_safety_positive = 0; int32_t iso_safety_negative = 0; int32_t iso_safety_parallel = 0; @@ -639,7 +666,7 @@ class DataLayerExtended { public: DATALAYER_INFO_BOLTAMPERA boltampera; DATALAYER_INFO_BMWIX bmwix; - DATALAYER_INFO_BMWIX bmwphev; + DATALAYER_INFO_BMWPHEV bmwphev; DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 30503c16b..e12d00b6c 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -130,9 +130,83 @@ String advanced_battery_processor(const String& var) { content += "

Isolation Parallel: " + String(datalayer_extended.bmwphev.iso_safety_parallel) + " kOhm

"; static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", "3 Not Activated - Pyro Intact", "4 Unknown"}; - content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss1])) + "

"; - content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss4])) + "

"; - content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwphev.pyro_status_pss6])) + "

"; + static const char* statusText[16] = { + "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; + content += "

Interlock: " + String(statusText[datalayer_extended.bmwi3.ST_interlock]) + "

"; + content += "

Isolation external: " + String(statusText[datalayer_extended.bmwi3.ST_iso_ext]) + "

"; + content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwi3.ST_iso_int]) + "

"; + content += "

Isolation: " + String(statusText[datalayer_extended.bmwi3.ST_isolation]) + "

"; + content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwi3.ST_valve_cooling]) + "

"; + content += "

Emergency: " + String(statusText[datalayer_extended.bmwi3.ST_EMG]) + "

"; + static const char* prechargeText[16] = {"Not evaluated", + "Not active, closing not blocked", + "Error precharge blocked", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwi3.ST_precharge]) + + "

"; //Still unclear of enum + static const char* DCSWText[16] = {"Contactors open", + "Precharge ongoing", + "Contactors engaged", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwi3.ST_DCSW]) + "

"; + static const char* contText[16] = {"Contactors OK", + "One contactor welded!", + "Two contactors welded!", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor weld: " + String(contText[datalayer_extended.bmwi3.ST_WELD]) + "

"; + static const char* valveText[16] = {"OK", + "Short circuit to GND", + "Short circuit to 12V", + "Line break", + "", + "", + "Driver error", + "", + "", + "", + "", + "", + "Stuck", + "Stuck", + "", + "Invalid Signal"}; + content += "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "

"; #endif //BMW_PHEV_BATTERY #ifdef BMW_I3_BATTERY From 484fa629c54fa0dcbd901522e8d42d2ec8135ec2 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:42:46 +0000 Subject: [PATCH 09/31] fixes support battery charge/discharge limits --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 26 ++++++++++++++++--- .../webserver/advanced_battery_html.cpp | 5 +--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index df583b253..093c0b4ae 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -307,6 +307,14 @@ static uint8_t startup_counter_contactor = 0; static uint8_t alive_counter_20ms = 0; static uint8_t BMW_13E_counter = 0; +static uint32_t battery_BEV_available_power_shortterm_charge = 0; +static uint32_t battery_BEV_available_power_shortterm_discharge = 0; +static uint32_t battery_BEV_available_power_longterm_charge = 0; +static uint32_t battery_BEV_available_power_longterm_discharge = 0; + +static uint16_t battery_predicted_energy_charge_condition = 0; +static uint16_t battery_predicted_energy_charging_target = 0; + static uint8_t battery_status_error_isolation_external_Bordnetz = 0; static uint8_t battery_status_error_isolation_internal_Bordnetz = 0; static uint8_t battery_request_cooling = 0; @@ -435,11 +443,11 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.total_capacity_Wh = max_capacity; - datalayer.battery.status.remaining_capacity_Wh = remaining_capacity; + datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition; datalayer.battery.status.soh_pptt = min_soh_state; - datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W; + datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge; //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping @@ -449,10 +457,10 @@ void update_values_battery() { //This function maps all the values fetched via } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery.status.max_charge_power_W = - MAX_CHARGE_POWER_ALLOWED_W * + battery_BEV_available_power_longterm_charge * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC)); } else { // No limits, max charging power allowed - datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; + datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge; } datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal @@ -536,6 +544,16 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); battery_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2); break; + case 0x239: //BMS [200ms] + battery_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh + battery_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh + break; + case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1 + battery_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3; + battery_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3; + battery_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3; + battery_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3; + break; case 0x607: //SME responds to UDS requests on 0x607 if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index e12d00b6c..4777861ce 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -124,10 +124,7 @@ String advanced_battery_processor(const String& var) { content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; content += "
"; - content += "

HV Isolation (2147483647kOhm = maximum/invalid)

"; - content += "

Isolation Positive: " + String(datalayer_extended.bmwphev.iso_safety_positive) + " kOhm

"; - content += "

Isolation Negative: " + String(datalayer_extended.bmwphev.iso_safety_negative) + " kOhm

"; - content += "

Isolation Parallel: " + String(datalayer_extended.bmwphev.iso_safety_parallel) + " kOhm

"; + static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", "3 Not Activated - Pyro Intact", "4 Unknown"}; static const char* statusText[16] = { From 253b38346176d92cc1b6ce2c6e675b2f0552a403 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:54:08 +0000 Subject: [PATCH 10/31] fixes/cleanup --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 6 --- Software/src/datalayer/datalayer_extended.h | 3 -- .../webserver/advanced_battery_html.cpp | 38 ++++++++++--------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 093c0b4ae..0e83f803b 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -498,12 +498,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.bms_uptime = sme_uptime; - datalayer_extended.bmwphev.iso_safety_positive = iso_safety_positive; - - datalayer_extended.bmwphev.iso_safety_negative = iso_safety_negative; - - datalayer_extended.bmwphev.iso_safety_parallel = iso_safety_parallel; - datalayer_extended.bmwphev.allowable_charge_amps = allowable_charge_amps; datalayer_extended.bmwphev.allowable_discharge_amps = allowable_discharge_amps; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 8752883cd..9e51afae4 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -104,9 +104,6 @@ typedef struct { uint16_t min_soh_state = 0; uint16_t max_soh_state = 0; uint32_t bms_uptime = 0; - int32_t iso_safety_positive = 0; - int32_t iso_safety_negative = 0; - int32_t iso_safety_parallel = 0; int32_t allowable_charge_amps = 0; int32_t allowable_discharge_amps = 0; int16_t balancing_status = 0; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 4777861ce..034a26257 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -101,30 +101,12 @@ String advanced_battery_processor(const String& var) { content += "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwphev.battery_voltage_after_contactor) + " dV

"; - content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; - content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; - content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; - content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; - content += - "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; - content += - "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; - content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; - content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", "2 Balancing Inactive - Cells not in rest break wait 10mins", "3 Balancing Inactive", "4 Unknown"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; - static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; - content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; - content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; - content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; - content += - "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; - content += "
"; - static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", "3 Not Activated - Pyro Intact", "4 Unknown"}; static const char* statusText[16] = { @@ -204,6 +186,26 @@ String advanced_battery_processor(const String& var) { "", "Invalid Signal"}; content += "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "

"; + content += "
"; + content += "

Todo"; + content += "
"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; + content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; + content += + "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; + content += + "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; + static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; + content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; + content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; + content += + "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + content += "
"; #endif //BMW_PHEV_BATTERY #ifdef BMW_I3_BATTERY From 01f11c3bbab5f3bd67e43e6e2ecc3c7ca744c0ac Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:02:06 +0000 Subject: [PATCH 11/31] add more values Include max capacity --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 0e83f803b..28248b3a7 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -315,6 +315,24 @@ static uint32_t battery_BEV_available_power_longterm_discharge = 0; static uint16_t battery_predicted_energy_charge_condition = 0; static uint16_t battery_predicted_energy_charging_target = 0; +static uint16_t battery_prediction_voltage_shortterm_charge = 0; +static uint16_t battery_prediction_voltage_shortterm_discharge = 0; +static uint16_t battery_prediction_voltage_longterm_charge = 0; +static uint16_t battery_prediction_voltage_longterm_discharge = 0; + +static uint8_t battery_status_service_disconnection_plug = 0; +static uint8_t battery_status_measurement_isolation = 0; +static uint8_t battery_request_abort_charging = 0; +static uint16_t battery_prediction_duration_charging_minutes = 0; +static uint8_t battery_prediction_time_end_of_charging_minutes = 0; +static uint16_t battery_energy_content_maximum_kWh = 0; + +static uint8_t battery_request_operating_mode = 0; +static uint16_t battery_target_voltage_in_CV_mode = 0; +static uint8_t battery_request_charging_condition_minimum = 0; +static uint8_t battery_request_charging_condition_maximum = 0; +static uint16_t battery_display_SOC = 0; + static uint8_t battery_status_error_isolation_external_Bordnetz = 0; static uint8_t battery_status_error_isolation_internal_Bordnetz = 0; static uint8_t battery_request_cooling = 0; @@ -441,7 +459,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.current_dA = battery_current; - datalayer.battery.info.total_capacity_Wh = max_capacity; + datalayer.battery.info.total_capacity_Wh = (battery_energy_content_maximum_kWh * 1000); // Convert kWh to Wh datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition; @@ -548,6 +566,27 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3; battery_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3; break; + case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2 + battery_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x431: //BMS [200ms] Data High-Voltage Battery Unit + battery_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F); + battery_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4; + battery_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4]; + battery_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50; + break; + case 0x432: //BMS [200ms] SOC% info + battery_request_operating_mode = (rx_frame.data.u8[0] & 0x03); + battery_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10; + battery_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2); + battery_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2); + battery_display_SOC = rx_frame.data.u8[4]; + break; case 0x607: //SME responds to UDS requests on 0x607 if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% From 5d3362ceedf981fb516f3043908f88f20961ef81 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:34:30 +0000 Subject: [PATCH 12/31] advanced values fix fix mapping --- .../webserver/advanced_battery_html.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 034a26257..0e38b93cb 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -111,12 +111,12 @@ String advanced_battery_processor(const String& var) { "3 Not Activated - Pyro Intact", "4 Unknown"}; static const char* statusText[16] = { "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; - content += "

Interlock: " + String(statusText[datalayer_extended.bmwi3.ST_interlock]) + "

"; - content += "

Isolation external: " + String(statusText[datalayer_extended.bmwi3.ST_iso_ext]) + "

"; - content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwi3.ST_iso_int]) + "

"; - content += "

Isolation: " + String(statusText[datalayer_extended.bmwi3.ST_isolation]) + "

"; - content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwi3.ST_valve_cooling]) + "

"; - content += "

Emergency: " + String(statusText[datalayer_extended.bmwi3.ST_EMG]) + "

"; + content += "

Interlock: " + String(statusText[datalayer_extended.bmwphev.ST_interlock]) + "

"; + content += "

Isolation external: " + String(statusText[datalayer_extended.bmwphev.ST_iso_ext]) + "

"; + content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwphev.ST_iso_int]) + "

"; + content += "

Isolation: " + String(statusText[datalayer_extended.bmwphev.ST_isolation]) + "

"; + content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwphev.ST_valve_cooling]) + "

"; + content += "

Emergency: " + String(statusText[datalayer_extended.bmwphev.ST_EMG]) + "

"; static const char* prechargeText[16] = {"Not evaluated", "Not active, closing not blocked", "Error precharge blocked", @@ -133,7 +133,7 @@ String advanced_battery_processor(const String& var) { "", "", ""}; - content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwi3.ST_precharge]) + + content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwphev.ST_precharge]) + "

"; //Still unclear of enum static const char* DCSWText[16] = {"Contactors open", "Precharge ongoing", @@ -151,7 +151,7 @@ String advanced_battery_processor(const String& var) { "", "", ""}; - content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwi3.ST_DCSW]) + "

"; + content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwphev.ST_DCSW]) + "

"; static const char* contText[16] = {"Contactors OK", "One contactor welded!", "Two contactors welded!", @@ -168,7 +168,7 @@ String advanced_battery_processor(const String& var) { "", "", ""}; - content += "

Contactor weld: " + String(contText[datalayer_extended.bmwi3.ST_WELD]) + "

"; + content += "

Contactor weld: " + String(contText[datalayer_extended.bmwphev.ST_WELD]) + "

"; static const char* valveText[16] = {"OK", "Short circuit to GND", "Short circuit to 12V", @@ -185,7 +185,8 @@ String advanced_battery_processor(const String& var) { "Stuck", "", "Invalid Signal"}; - content += "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "

"; + content += + "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; content += "
"; content += "

Todo"; content += "
"; From ba1845e1b05b19a1292fa69d6f99005240233112 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:14:38 +0000 Subject: [PATCH 13/31] Add UDS Multi-frame, cell voltages, min max and SOH --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 433 +++++++++++++++------- 1 file changed, 300 insertions(+), 133 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 28248b3a7..c94bc5b8b 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -21,6 +21,20 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE static CmdState cmdState = SOC; +// A structure to keep track of the ongoing multi-frame UDS response +typedef struct { + bool UDS_inProgress; // Are we currently receiving a multi-frame message? + uint16_t UDS_expectedLength; // Expected total payload length + uint16_t UDS_bytesReceived; // How many bytes have been stored so far + uint8_t UDS_moduleID; // The "module" indicated by the first frame + uint8_t receivedInBatch; // Number of CFs received in the current batch + uint8_t UDS_buffer[256]; // Buffer for the reassembled data + unsigned long UDS_lastFrameMillis; // Timestamp of last frame (for timeouts, if desired) +} UDS_RxContext; + +// A single global UDS context, since only one module can respond at a time +static UDS_RxContext gUDSContext; + const unsigned char crc8_table[256] = { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, @@ -59,9 +73,10 @@ UDS MAP 22 DD 69 - Current in Amps 62 DD 69 00 00 00 00 = 0 Amps 22 DD 7B - SOH 62 DD 7B 62 = 98% 22 DD 62 - HVIL Status 62 DD 64 01 = OK/Closed +22 DD BF - Cell min max alt - f18 ZELLSPANNUNGEN_MIN_MAX < doesn't work 22 DD 6A - Isolation values 62 DD 6A 07 D0 07 D0 07 D0 01 01 01 = in operation plausible/2000kOhm, in follow up plausible/2000kohm, internal iso open contactors (measured on request) pluasible/2000kohm 31 03 AD 61 - Isolation measurement status 71 03 AD 61 00 FF = Nmeasurement status - not successful / fault satate - not defined -22 DF A0 - Cell voltage and temps summary including min/max/average, Ah, +22 DF A0 - Cell voltage and temps summary including min/max/average, Ah, (ZUSTAND_SPEICHER) */ //Vehicle CAN START @@ -91,6 +106,12 @@ CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xC4}}; // SOC% +CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH% + CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = { .FD = false, .ext_ID = false, @@ -110,9 +131,14 @@ CAN_frame BMWPHEV_6F1_REQUEST_CELLSUMMARY = { .ext_ID = false, .DLC = 5, .ID = 0x6F1, - .data = { - 0x07, 0x03, 0x22, 0xDF, - 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid? Multi return frame - might be all cell voltages + .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid? + +CAN_frame BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0xA5}}; //All individual cell voltages CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = { .FD = false, @@ -123,11 +149,20 @@ CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = { 0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures min max avg. Has continue frame min in first, then max + avg in second frame -CAN_frame BMW_6F4_CELL_TEMP_CONTINUE = {.FD = false, +CAN_frame BMW_6F1_REQUEST_CONTINUE_MULTIFRAME = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = { + 0x07, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00}}; //Request continued frames from UDS Multiframe request byte[2] is the request messages to return per continue. default 0x03, all is 0x00 + +CAN_frame BMW_6F1_REQUEST_HARD_RESET = {.FD = false, .ext_ID = false, - .DLC = 5, + .DLC = 4, .ID = 0x6F1, - .data = {0x07, 0x30, 0x03, 0x00, 0x00}}; + .data = {0x07, 0x03, 0x11, 0x01}}; // Reset BMS - TBC CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE = { .FD = false, @@ -164,111 +199,6 @@ CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STOP = { .ID = 0x6F1, .data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request -//UNTESTED/UNCHANGED FROM IX BEYOND HERE - -CAN_frame BMWPHEV_6F1 = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value -CAN_frame BMWPHEV_6F1_REQUEST_SLEEPMODE = { - .FD = false, - .ext_ID = false, - .DLC = 4, - .ID = 0x6F1, - .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode -CAN_frame BMWPHEV_6F1_REQUEST_HARD_RESET = {.FD = false, - .ext_ID = false, - .DLC = 4, - .ID = 0x6F1, - .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME - -CAN_frame BMWPHEV_6F1_REQUEST_CAPACITY = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias - -CAN_frame BMWPHEV_6F1_REQUEST_BATTERYCURRENT = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge -CAN_frame BMWPHEV_6F1_REQUEST_CELL_VOLTAGE = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages -CAN_frame BMWPHEV_6F1_REQUEST_T30VOLTAGE = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) -CAN_frame BMWPHEV_6F1_REQUEST_EOL_ISO = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO -CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request -CAN_frame BMWPHEV_6F1_REQUEST_DATASUMMARY = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = { - 0x07, 0x03, 0x22, 0xE5, - 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate -CAN_frame BMWPHEV_6F1_REQUEST_PYRO = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status -CAN_frame BMWPHEV_6F1_REQUEST_UPTIME = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status -CAN_frame BMWPHEV_6F1_REQUEST_HVIL = {.FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State - -CAN_frame BMWPHEV_6F1_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps -CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_QUALIFIER_CHECK = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier - -CAN_frame BMWPHEV_6F1_REQUEST_PACK_VOLTAGE_LIMITS = { - .FD = false, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits - -CAN_frame BMWPHEV_6F1_CONTINUE_DATA = {.FD = false, - .ext_ID = false, - .DLC = 4, - .ID = 0x6F1, - .data = {0x07, 0x30, 0x00, 0x02}}; - //Action Requests: CAN_frame BMW_10B = {.FD = false, .ext_ID = false, @@ -291,9 +221,15 @@ CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false, static bool battery_awake = false; //Setup UDS values to poll for -CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, - &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_CELL_TEMP, - &BMWPHEV_6F1_REQUEST_BALANCING_STATUS}; +CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, + &BMWPHEV_6F1_REQUEST_SOH, + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, + &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, + &BMWPHEV_6F1_REQUEST_CELLSUMMARY, + &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, + &BMWPHEV_6F1_REQUEST_CELL_TEMP, + &BMWPHEV_6F1_REQUEST_CELLSUMMARY}; int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //PHEV intermediate vars @@ -355,7 +291,7 @@ static int16_t battery_temperature_min = 0; static bool battery_info_available = false; static uint32_t battery_serial_number = 0; static int32_t battery_current = 0; -static int16_t battery_voltage = 370; +static int16_t battery_voltage = 0; static int16_t terminal30_12v_voltage = 0; static int16_t battery_voltage_after_contactor = 0; static int16_t min_soc_state = 50; @@ -434,6 +370,46 @@ static uint8_t increment_uds_req_id_counter(uint8_t index) { return index; } +/* -------------------------------------------------------------------------- + UDS Multi-Frame Helpers + -------------------------------------------------------------------------- */ + +void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) { + gUDSContext.UDS_inProgress = true; + gUDSContext.UDS_expectedLength = totalLength; + gUDSContext.UDS_bytesReceived = 0; + gUDSContext.UDS_moduleID = moduleID; + memset(gUDSContext.UDS_buffer, 0, sizeof(gUDSContext.UDS_buffer)); + gUDSContext.UDS_lastFrameMillis = millis(); // if you want to track timeouts +} + +bool storeUDSPayload(const uint8_t* payload, uint8_t length) { + if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) { + // Overflow => abort + gUDSContext.UDS_inProgress = false; +#ifdef DEBUG_LOG + logging.println("UDS Payload Overflow"); +#endif // DEBUG_LOG + return false; + } + memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length); + gUDSContext.UDS_bytesReceived += length; + gUDSContext.UDS_lastFrameMillis = millis(); + + // If we’ve reached or exceeded the expected length, mark complete + if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) { + gUDSContext.UDS_inProgress = false; +#ifdef DEBUG_LOG + logging.println("Recived all expected UDS bytes"); +#endif // DEBUG_LOG + } + return true; +} + +bool isUDSMessageComplete() { + return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0); +} + static uint8_t increment_alive_counter(uint8_t counter) { counter++; if (counter > ALIVE_MAX_VALUE) { @@ -451,6 +427,27 @@ static byte increment_0C0_counter(byte counter) { return counter; } +void processCellVoltages() { + const int startByte = 3; // Start reading at byte 3 + const int numVoltages = 96; // Number of cell voltage values to process + int voltage_index = 0; // Starting index for the destination array + + // Loop through 96 voltage values + for (int i = 0; i < numVoltages; i++) { + // Calculate the index of the first and second bytes in the input array + int byteIndex = startByte + (i * 2); + + // Combine two bytes to form a 16-bit value + uint16_t voltageRaw = (gUDSContext.UDS_buffer[byteIndex] << 8) | gUDSContext.UDS_buffer[byteIndex + 1]; + + // Store the result in the destination array + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltageRaw; + + // Increment the destination array index + voltage_index++; + } +} + void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer datalayer.battery.status.real_soc = avg_soc_state; @@ -546,6 +543,7 @@ void update_values_battery() { //This function maps all the values fetched via } } void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + battery_awake = true; switch (rx_frame.ID) { case 0x112: @@ -588,25 +586,193 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_display_SOC = rx_frame.data.u8[4]; break; case 0x607: //SME responds to UDS requests on 0x607 - - if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% - avg_soc_state = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + { + // UDS Multi Frame vars - Top nibble indicates Frame Type: SF (0), FF (1), CF (2), FC (3) + // Extended addressing => data[0] is ext address, data[1] is PCI + uint8_t extAddr = rx_frame.data.u8[0]; // e.g., 0xF1 + uint8_t pciByte = rx_frame.data.u8[1]; // e.g., 0x10, 0x21, etc. + uint8_t pciType = pciByte >> 4; // top nibble => 0=SF,1=FF,2=CF,3=FC + uint8_t pciLower = pciByte & 0x0F; // bottom nibble => length nibble or sequence + + switch (pciType) { + case 0x0: { + // Single Frame reponse + // SF payload length is in pciLower + uint8_t sfLength = pciLower; + uint8_t moduleID = rx_frame.data.u8[5]; + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% + avg_soc_state = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0x7B) { // SOH% + min_soh_state = (rx_frame.data.u8[5]) * 100; + } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) + battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) + battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && + rx_frame.data.u8[3] == 0x03 && + rx_frame.data.u8[4] == + 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 + balancing_status = (rx_frame.data.u8[6]); +#ifdef DEBUG_LOG + + logging.println("Balancing Status received"); + +#endif // DEBUG_LOG + } + + break; + } + case 0x1: { + // total length = (pciLower << 8) + data[2] + uint16_t totalLength = ((uint16_t)pciLower << 8) | rx_frame.data.u8[2]; + uint8_t moduleID = rx_frame.data.u8[5]; +#ifdef DEBUG_LOG + logging.print("FF arrived! moduleID=0x"); + logging.print(moduleID, HEX); + logging.print(", totalLength="); + logging.println(totalLength); +#endif // DEBUG_LOG + + // Start the multi-frame + startUDSMultiFrameReception(totalLength, moduleID); + gUDSContext.receivedInBatch = 0; // Reset batch count + // The FF payload is at data[3..7] (5 bytes) for an 8-byte CAN frame in extended addressing + const uint8_t* ffPayload = &rx_frame.data.u8[3]; + uint8_t ffPayloadSize = 5; + storeUDSPayload(ffPayload, ffPayloadSize); + +#ifdef DEBUG_LOG + logging.print("After FF, UDS_bytesReceived="); + logging.println(gUDSContext.UDS_bytesReceived); +#endif // DEBUG_LOG +#ifdef DEBUG_LOG + logging.println("Requesting continue frame..."); +#endif // DEBUG_LOG + transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); + break; + } + + case 0x2: { + // The sequence number is in (data[0] & 0x0F), but we often don’t need it if frames are in order. + // Make sure we *are* in progress + if (!gUDSContext.UDS_inProgress) { +// Unexpected CF. Possibly ignore or reset. +#ifdef DEBUG_LOG + uint8_t seq = pciByte & 0x0F; + logging.print("Unexpected CF --- seq=0x"); + logging.print(seq, HEX); + logging.print(" for moduleID=0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); +#endif // DEBUG_LOG + return; + } +#ifdef DEBUG_LOG + uint8_t seq = pciByte & 0x0F; + logging.print("CF seq=0x"); + logging.print(seq, HEX); + logging.print("CF pcibyte=0x"); + logging.print(pciByte, HEX); + logging.print(" for moduleID=0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); +#endif // DEBUG_LOG + + storeUDSPayload(&rx_frame.data.u8[2], 6); + // Increment batch counter + gUDSContext.receivedInBatch++; +#ifdef DEBUG_LOG + logging.print("After CF seq=0x"); + logging.print(seq, HEX); + logging.print(", moduleID=0x"); + logging.print(gUDSContext.UDS_moduleID, HEX); + logging.print(", UDS_bytesReceived="); + logging.println(gUDSContext.UDS_bytesReceived); +#endif // DEBUG_LOG + + // Check if the batch is complete + if (gUDSContext.receivedInBatch >= 3) { //BMW PHEV Using batch size of 3 in continue message + // Send the next Flow Control +#ifdef DEBUG_LOG + logging.println("Batch Complete - Requesting continue frame..."); +#endif // DEBUG_LOG + transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); + gUDSContext.receivedInBatch = 0; // Reset batch count + Serial.println("Sent FC for next batch of 3 frames."); + } + + break; + } + + case 0x3: { + // Flow Control Frame from ECU -> Tester (rare in a typical request/response flow) + // Typically we only *send* FC. If the ECU sends one, parse or ignore here. + break; + } } - if (rx_frame.DLC = - 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) - battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; - } - - if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && - rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) - battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; - } - - if (rx_frame.DLC = - 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && rx_frame.data.u8[3] == 0x03 && - rx_frame.data.u8[4] == 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 - balancing_status = (rx_frame.data.u8[6]); + // Optionally, check if message is complete + if (isUDSMessageComplete()) { + // We have a complete UDS/ISO-TP response in gUDSContext.UDS_buffer + // Do something with the data (e.g., parse it). ***************** + // + //set dummy values to see if we got a complete message + +#ifdef DEBUG_LOG + + logging.print("UDS message complete for module ID 0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); + + logging.print("Total bytes: "); + logging.println(gUDSContext.UDS_bytesReceived); + + logging.print("Received data: "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end + +#endif // DEBUG_LOG + + if (gUDSContext.UDS_moduleID == 0xA5) { //We have a complete set of cell voltages - pass to data layer + processCellVoltages(); +#ifdef DEBUG_LOG + logging.println("Parsing Cell Voltages..."); +#endif // DEBUG_LOG + } + + if (gUDSContext.UDS_moduleID == 0xA0 && gUDSContext.UDS_buffer[9] != 0xFF && + gUDSContext.UDS_buffer[9] != 0xFF && gUDSContext.UDS_buffer[11] != 0xFF && + gUDSContext.UDS_buffer[12] != 0xFF && gUDSContext.UDS_buffer[9] != 0x00 && + gUDSContext.UDS_buffer[9] != 0x00 && gUDSContext.UDS_buffer[11] != 0x00 && + gUDSContext.UDS_buffer[12] != + 0x00) { //We have a complete frame for cell min max - pass to data layer UNCONFIRMED IF THESE ARE CORRECT BYTES + min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10; + max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10; + +#ifdef DEBUG_LOG + logging.println("Parsing Cell Min Max..."); +#endif // DEBUG_LOG + } else { +#ifdef DEBUG_LOG + logging.println("Cell Min Max Invalid 65535 or 0..."); +#endif // DEBUG_LOG + } + + // Example: + // parseBatteryData(gUDSContext.UDS_buffer, gUDSContext.UDS_bytesReceived); } //moved away from UDS - using SME default sent message @@ -623,6 +789,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // avg_battery_temperature = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) / 10; // } break; + } case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1 battery_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03); battery_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2; From 53479097906ba4d84c3f740d7a3e2dd8824ff2b4 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:53:27 +0000 Subject: [PATCH 14/31] add battery design voltage --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 40 ++++++++++++------- .../webserver/advanced_battery_html.cpp | 21 +++++----- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index c94bc5b8b..46adb0e04 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -112,6 +112,20 @@ CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH% +CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame + +CAN_frame BMWPHEV_6F1_REQUEST_CURRENT_LIMITS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7D}}; // Pack Current Limits Multi Frame + CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = { .FD = false, .ext_ID = false, @@ -223,6 +237,8 @@ static bool battery_awake = false; //Setup UDS values to poll for CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_SOH, + &BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, + &BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, @@ -607,6 +623,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0x7B) { // SOH% min_soh_state = (rx_frame.data.u8[5]) * 100; } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; @@ -771,23 +788,16 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { #endif // DEBUG_LOG } - // Example: - // parseBatteryData(gUDSContext.UDS_buffer, gUDSContext.UDS_bytesReceived); + if (gUDSContext.UDS_moduleID == 0x7E) { // Voltage Limits + max_design_voltage = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; + min_design_voltage = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; + } + if (gUDSContext.UDS_moduleID == 0x7D) { // Current Limits + allowable_charge_amps = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; + allowable_discharge_amps = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; + } } - //moved away from UDS - using SME default sent message - //if (rx_frame.DLC = 8 && rx_frame.data.u8[4] == 0xDD && - // rx_frame.data.u8[5] == 0xC0) { //Cell Temp Min - continue frame follows - // min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; - //} - - // if (rx_frame.DLC = - // 7 && - // rx_frame.data.u8[1] == - // 0x21) { //Cell Temp Max/Avg - is continue frame - fingerprinting needs improving as 0xF1 0x21 is used by other continues - // max_battery_temperature = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]) / 10; - // avg_battery_temperature = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) / 10; - // } break; } case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1 diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 0e38b93cb..db70bf793 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -187,23 +187,26 @@ String advanced_battery_processor(const String& var) { "Invalid Signal"}; content += "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; + content += + "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; + content += + "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; + content += + "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwphev.allowable_discharge_amps) + " A

"; content += "
"; content += "

Todo"; content += "
"; - content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; - content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; - content += - "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; - content += - "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; + static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; - content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; - content += - "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; content += "
"; From 5a65b227059a7e6ab2d97ca70aca3e8effff8355 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:01:24 +0000 Subject: [PATCH 15/31] add Can Alive messages --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 46adb0e04..9c10c82cf 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -563,6 +563,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_awake = true; switch (rx_frame.ID) { case 0x112: + battery_awake = true; + datalayer.battery.status.CAN_battery_still_alive = + CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V break; case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations battery_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); From 8998dc48240ff4b0108465ea8a5de68f2088ef77 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:07:10 +0000 Subject: [PATCH 16/31] Publish Cell Delta Value for Battery 1 + 2 Adds a calculated cell delta voltage for monitoring via MQTT --- Software/src/devboard/mqtt/mqtt.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index e0aef8935..15e973315 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -59,6 +59,7 @@ SensorConfig sensorConfigs[] = { {"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"}, {"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, {"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"cell_voltage_delta", "Cell Voltage Delta", "{{ value_json.cell_voltage_delta }}", "mV", "voltage"}, {"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, {"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, {"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", @@ -79,6 +80,7 @@ SensorConfig sensorConfigs[] = { {"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"}, {"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"}, {"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"}, + {"cell_voltage_delta_2", "Cell Voltage Delta 2", "{{ value_json.cell_voltage_delta_2 }}", "mV", "voltage"}, {"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"}, {"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"}, {"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh", @@ -174,6 +176,8 @@ static void publish_common_info(void) { datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) { doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0; doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0; + doc["cell_voltage_delta"] = ((float)datalayer.battery.status.cell_max_voltage_mV) - + ((float)datalayer.battery.status.cell_min_voltage_mV); } doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh); doc["remaining_capacity_real"] = ((float)datalayer.battery.status.remaining_capacity_Wh); @@ -197,6 +201,8 @@ static void publish_common_info(void) { datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0; doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0; + doc["cell_voltage_delta_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) - + ((float)datalayer.battery2.status.cell_min_voltage_mV); } doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh); doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh); From 533b13f17f6f42d75d0f18a19f682550e27ca6af Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:13:22 +0000 Subject: [PATCH 17/31] 96 Cell default for now --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 9c10c82cf..7d9862152 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -344,7 +344,7 @@ static uint8_t contactor_status_precharge = 0; //TODO E5 BF static uint8_t contactor_status_negative = 0; //TODO E5 BF static uint8_t contactor_status_positive = 0; //TODO E5 BF static uint8_t uds_req_id_counter = 0; -static uint8_t detected_number_of_cells = 108; +static uint8_t detected_number_of_cells = 96; const unsigned long STALE_PERIOD = STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) From 1751798295a2ee898283d7d29478fed4e98a810b Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:58:06 +0000 Subject: [PATCH 18/31] Cleanup & Fix Increased stale allowance Polling rate upped to 200ms Current reading added Various cleanups --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 145 +++++++++++------- Software/src/battery/BMW-PHEV-BATTERY.h | 2 +- .../webserver/advanced_battery_html.cpp | 5 +- 3 files changed, 93 insertions(+), 59 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 7d9862152..a049eec9a 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -62,6 +62,19 @@ V0.1 very basic implementation reading Gen3/4 BMW PHEV SME. -Min/ Cell Temp -SOC +BROADCAST MAP +0x112 20ms Status Of High-Voltage Battery 2 +0x1F1 1000ms Status Of High-Voltage Battery 1 +0x239 200ms predicted charge condition and predicted target +0x295 1000ms ? [1] Alive Counter 50-5F? +0x2A5 200ms ? +0x2F5 100ms High-Voltage Battery Charge/Discharge Limitations +0x33e 5000ms? +0x40D 1000ms ? +0x430 1000ms Charging status of high-voltage battery - 2 +0x431 200ms Data High-Voltage Battery Unit +0x432 200ms SOC% info + UDS MAP 22 D6 CF - CSC Temps 22 DD C0 - Min Max temps @@ -112,6 +125,12 @@ CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH% +CAN_frame BMWPHEV_6F1_REQUEST_CURRENT = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x69}}; // SOH% + CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { .FD = false, .ext_ID = false, @@ -237,6 +256,7 @@ static bool battery_awake = false; //Setup UDS values to poll for CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_SOH, + &BMWPHEV_6F1_REQUEST_CURRENT, &BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, &BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, @@ -249,7 +269,7 @@ CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //PHEV intermediate vars - +//#define UDS_LOG //Useful for logging multiframe handling static uint16_t battery_max_charge_voltage = 0; static int16_t battery_max_charge_amperage = 0; static uint16_t battery_min_discharge_voltage = 0; @@ -302,9 +322,11 @@ static int16_t battery_temperature_HV = 0; static int16_t battery_temperature_heat_exchanger = 0; static int16_t battery_temperature_max = 0; static int16_t battery_temperature_min = 0; +static bool pack_limit_info_available = false; +static bool cell_limit_info_available = false; //iX Intermediate vars -static bool battery_info_available = false; + static uint32_t battery_serial_number = 0; static int32_t battery_current = 0; static int16_t battery_voltage = 0; @@ -415,9 +437,9 @@ bool storeUDSPayload(const uint8_t* payload, uint8_t length) { // If we’ve reached or exceeded the expected length, mark complete if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) { gUDSContext.UDS_inProgress = false; -#ifdef DEBUG_LOG - logging.println("Recived all expected UDS bytes"); -#endif // DEBUG_LOG + // #ifdef DEBUG_LOG + // logging.println("Recived all expected UDS bytes"); + // #endif // DEBUG_LOG } return true; } @@ -508,6 +530,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); +#ifdef DEBUG_LOG + logging.println("Stale Min/Max voltage values detected sending - 9999mV..."); +#endif // DEBUG_LOG } else { datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive @@ -550,10 +575,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.ST_isolation = battery_status_warning_isolation; datalayer_extended.bmwphev.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; - if (battery_info_available) { - // If we have data from battery - override the defaults to suit + if (pack_limit_info_available) { + // If we have pack limit data from battery - override the defaults to suit datalayer.battery.info.max_design_voltage_dV = max_design_voltage; datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + } + if (cell_limit_info_available) { + // If we have cell limit data from battery - override the defaults to suit datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; } @@ -637,16 +665,22 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0x69) { //Current (32bit mA? negative = discharge) + battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) | + (rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) * + 0.01; + } if (rx_frame.DLC = 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && rx_frame.data.u8[3] == 0x03 && rx_frame.data.u8[4] == 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 balancing_status = (rx_frame.data.u8[6]); -#ifdef DEBUG_LOG + // #ifdef DEBUG_LOG - logging.println("Balancing Status received"); + // logging.println("Balancing Status received"); -#endif // DEBUG_LOG + // #endif // DEBUG_LOG } break; @@ -655,12 +689,12 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // total length = (pciLower << 8) + data[2] uint16_t totalLength = ((uint16_t)pciLower << 8) | rx_frame.data.u8[2]; uint8_t moduleID = rx_frame.data.u8[5]; -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.print("FF arrived! moduleID=0x"); logging.print(moduleID, HEX); logging.print(", totalLength="); logging.println(totalLength); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG // Start the multi-frame startUDSMultiFrameReception(totalLength, moduleID); @@ -670,13 +704,13 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { uint8_t ffPayloadSize = 5; storeUDSPayload(ffPayload, ffPayloadSize); -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.print("After FF, UDS_bytesReceived="); logging.println(gUDSContext.UDS_bytesReceived); -#endif // DEBUG_LOG -#ifdef DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.println("Requesting continue frame..."); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); break; } @@ -686,16 +720,16 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // Make sure we *are* in progress if (!gUDSContext.UDS_inProgress) { // Unexpected CF. Possibly ignore or reset. -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) uint8_t seq = pciByte & 0x0F; logging.print("Unexpected CF --- seq=0x"); logging.print(seq, HEX); logging.print(" for moduleID=0x"); logging.println(gUDSContext.UDS_moduleID, HEX); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG return; } -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) uint8_t seq = pciByte & 0x0F; logging.print("CF seq=0x"); logging.print(seq, HEX); @@ -703,26 +737,26 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { logging.print(pciByte, HEX); logging.print(" for moduleID=0x"); logging.println(gUDSContext.UDS_moduleID, HEX); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG storeUDSPayload(&rx_frame.data.u8[2], 6); // Increment batch counter gUDSContext.receivedInBatch++; -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.print("After CF seq=0x"); logging.print(seq, HEX); logging.print(", moduleID=0x"); logging.print(gUDSContext.UDS_moduleID, HEX); logging.print(", UDS_bytesReceived="); logging.println(gUDSContext.UDS_bytesReceived); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG // Check if the batch is complete if (gUDSContext.receivedInBatch >= 3) { //BMW PHEV Using batch size of 3 in continue message // Send the next Flow Control -#ifdef DEBUG_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.println("Batch Complete - Requesting continue frame..."); -#endif // DEBUG_LOG +#endif // DEBUG_LOG && UDS_LOG transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); gUDSContext.receivedInBatch = 0; // Reset batch count Serial.println("Sent FC for next batch of 3 frames."); @@ -741,12 +775,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // Optionally, check if message is complete if (isUDSMessageComplete()) { // We have a complete UDS/ISO-TP response in gUDSContext.UDS_buffer - // Do something with the data (e.g., parse it). ***************** - // - //set dummy values to see if we got a complete message - -#ifdef DEBUG_LOG - +#if defined(DEBUG_LOG) && defined(UDS_LOG) logging.print("UDS message complete for module ID 0x"); logging.println(gUDSContext.UDS_moduleID, HEX); @@ -763,37 +792,44 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { logging.print(" "); } logging.println(); // new line at the end +#endif // DEBUG_LOG -#endif // DEBUG_LOG - + //Cell Voltages if (gUDSContext.UDS_moduleID == 0xA5) { //We have a complete set of cell voltages - pass to data layer processCellVoltages(); -#ifdef DEBUG_LOG - logging.println("Parsing Cell Voltages..."); -#endif // DEBUG_LOG } - - if (gUDSContext.UDS_moduleID == 0xA0 && gUDSContext.UDS_buffer[9] != 0xFF && - gUDSContext.UDS_buffer[9] != 0xFF && gUDSContext.UDS_buffer[11] != 0xFF && - gUDSContext.UDS_buffer[12] != 0xFF && gUDSContext.UDS_buffer[9] != 0x00 && - gUDSContext.UDS_buffer[9] != 0x00 && gUDSContext.UDS_buffer[11] != 0x00 && - gUDSContext.UDS_buffer[12] != - 0x00) { //We have a complete frame for cell min max - pass to data layer UNCONFIRMED IF THESE ARE CORRECT BYTES - min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10; - max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10; - + //Cell Min/Max + if (gUDSContext.UDS_moduleID == + 0xA0) { //We have a complete frame for cell min max - pass to data layer UNCONFIRMED IF THESE ARE CORRECT BYTES + + //Check values are valid + if (gUDSContext.UDS_buffer[9] != 0xFF && gUDSContext.UDS_buffer[10] != 0xFF && + gUDSContext.UDS_buffer[11] != 0xFF && gUDSContext.UDS_buffer[12] != 0xFF && + gUDSContext.UDS_buffer[9] != 0x00 && gUDSContext.UDS_buffer[10] != 0x00 && + gUDSContext.UDS_buffer[11] != 0x00 && gUDSContext.UDS_buffer[12] != 0x00) { + min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10; + max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10; + } else { #ifdef DEBUG_LOG - logging.println("Parsing Cell Min Max..."); -#endif // DEBUG_LOG - } else { -#ifdef DEBUG_LOG - logging.println("Cell Min Max Invalid 65535 or 0..."); -#endif // DEBUG_LOG + logging.println("Cell Min Max Invalid 65535 or 0..."); + logging.print("Received data: "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG + } } if (gUDSContext.UDS_moduleID == 0x7E) { // Voltage Limits max_design_voltage = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; min_design_voltage = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; + pack_limit_info_available = true; } if (gUDSContext.UDS_moduleID == 0x7D) { // Current Limits allowable_charge_amps = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; @@ -863,14 +899,13 @@ void transmit_can_battery() { // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { previousMillis200 = currentMillis; + //Loop through and send a different UDS request each cycle + uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); + transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); } // Send 1000ms CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; - - //Loop through and send a different UDS request each cycle - uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); - transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); } // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h index f066f66fa..4e9ea9966 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.h +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -15,7 +15,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds + 1800000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 1800 seconds / 30mins void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index db70bf793..f446bf8a9 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -196,6 +196,7 @@ String advanced_battery_processor(const String& var) { content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwphev.allowable_discharge_amps) + " A

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; content += "
"; content += "

Todo"; content += "
"; @@ -203,12 +204,10 @@ String advanced_battery_processor(const String& var) { content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; - static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; - content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwphev.hvil_status]) + "

"; content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; - content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + content += "
"; #endif //BMW_PHEV_BATTERY From bafe5cba744d2c5299284137749ea190a709b17a Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:00:40 +0000 Subject: [PATCH 19/31] Stale allowance change Stale allowance can be tightened up after increased polling rate --- Software/src/battery/BMW-PHEV-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h index 4e9ea9966..232a19317 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.h +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -15,7 +15,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 1800000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 1800 seconds / 30mins + 900000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 900 seconds / 15mins void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); From 18596c95ec9702e9bedcea5bb65f02ebd11c1b99 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:20:26 +0000 Subject: [PATCH 20/31] Valve state text fix valve state was referencing wrong lookup for description --- Software/src/devboard/webserver/advanced_battery_html.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index f446bf8a9..d9a096fb0 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -186,7 +186,7 @@ String advanced_battery_processor(const String& var) { "", "Invalid Signal"}; content += - "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; + "

Cold shutoff valve: " + String(valveText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; content += "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; content += From b57ab8b40604acb2fb34b9a89b42e51e17893569 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:54:07 +0000 Subject: [PATCH 21/31] Add sanity checks to cell temps. cell temps given as temp + 50. Check value is not 0 (-50) --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 17 +++++++++++++++-- Software/src/battery/BMW-PHEV-BATTERY.h | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index a049eec9a..e7c471207 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -855,8 +855,21 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); battery_temperature_HV = (rx_frame.data.u8[4] - 50); battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); - battery_temperature_min = (rx_frame.data.u8[6] - 50); - battery_temperature_max = (rx_frame.data.u8[7] - 50); + if (rx_frame.data.u8[6] > 0) { + battery_temperature_min = (rx_frame.data.u8[6] - 50); + } else { +#ifdef DEBUG_LOG + logging.println("Pre parsed Cell Temp Min (0) is Invalid "); +#endif + } + if (rx_frame.data.u8[7] > 0) { + battery_temperature_max = (rx_frame.data.u8[7] - 50); + } else { +#ifdef DEBUG_LOG + logging.println("Pre parsed Cell Temp Max (0) is Invalid "); +#endif + } + break; default: break; diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h index 232a19317..14a82fa0c 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.h +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -15,7 +15,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 900000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 900 seconds / 15mins + 1800000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 1800 seconds / 30mins void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); From 3d0d576b4ba5782c654a7f6af0585bf4fda0102d Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:08:41 +0000 Subject: [PATCH 22/31] Add battery wakeup support (Native CAN only) Support for battery wakeup. You MUST be using the native can bus for this to work for now. --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 115 ++++++++++++++++++++-- 1 file changed, 108 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index e7c471207..9c268a83d 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -90,6 +90,13 @@ UDS MAP 22 DD 6A - Isolation values 62 DD 6A 07 D0 07 D0 07 D0 01 01 01 = in operation plausible/2000kOhm, in follow up plausible/2000kohm, internal iso open contactors (measured on request) pluasible/2000kohm 31 03 AD 61 - Isolation measurement status 71 03 AD 61 00 FF = Nmeasurement status - not successful / fault satate - not defined 22 DF A0 - Cell voltage and temps summary including min/max/average, Ah, (ZUSTAND_SPEICHER) + + +TODO: + BMWPHEV_6F1_REQUEST_LAST_ISO_READING - add results to advanced + BMWPHEV_6F1_REQUEST_PACK_INFO - add cell count reading + Find current measurement reading + */ //Vehicle CAN START @@ -113,6 +120,15 @@ CAN_frame BMW_13E = {.FD = false, //Request Data CAN START +CAN_frame BMW_PHEV_BUS_WAKEUP_REQUEST = { + .FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x554, + .data = { + 0x5A, 0xA5, 0x5A, + 0xA5}}; // Won't work at 500kbps! Ideally sent at 50kbps - but can also achieve wakeup at 100kbps (helps with library support but might not be as reliable). Might need to be sent twice + clear buffer + CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false, .ext_ID = false, .DLC = 5, @@ -138,6 +154,29 @@ CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame +CAN_frame BMWPHEV_6F1_REQUEST_ISO_MEASUREMENTS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xDD, + 0x6A}}; // 62 D6 D9 07 FF 13 reading during contactors closed, plausible, reading during open contactors (only on request via steurn_isolation) ( request = 31 01 AD 61) + +CAN_frame BMWPHEV_6F1_REQUEST_LAST_ISO_READING = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xD6, 0xD9}}; // 62 D6 D9 07 FF 13 (2047kohm) quality of reading 0-21 (19) + +CAN_frame BMWPHEV_6F1_REQUEST_PACK_INFO = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0x71}}; // 62 DF 71 00 60 1C 25 1C? Cell Count, Module Count + CAN_frame BMWPHEV_6F1_REQUEST_CURRENT_LIMITS = { .FD = false, .ext_ID = false, @@ -486,6 +525,24 @@ void processCellVoltages() { } } +void wake_battery_via_canbus() { + //TJA1055 transceiver remote wake requires pulses on the bus of + // Dominant for at least ~7 µs (min) and at most ~38 µs (max) + // Followed by a Recessive interval of at least ~3 µs (min) and at most ~10 µs (max) + // Then a second dominant pulse of similar timing. + + CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings + ESP32Can.CANInit(); // ReInit CAN Module? + transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); + transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); + CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed + ESP32Can.CANInit(); // ReInit CAN Module? + +#ifdef DEBUG_LOG + logging.println("Send magic wakeup packet to SME at 100kbps..."); +#endif + +} void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer datalayer.battery.status.real_soc = avg_soc_state; @@ -658,19 +715,41 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; +#ifdef DEBUG_LOG + logging.print("Received pre contactor measurement data: "); + logging.print(battery_voltage); + logging.print(" - "); + for (uint16_t i = 0; i < 8; i++) { + // Optional leading zero for single-digit hex + if (rx_frame.data.u8[i] < 0x10) { + logging.print("0"); + } + logging.print(rx_frame.data.u8[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG } if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; +#ifdef DEBUG_LOG + logging.print("Received post contactor measurement data: "); + logging.print(battery_voltage_after_contactor); + logging.print(" - "); + for (uint16_t i = 0; i < 8; i++) { + // Optional leading zero for single-digit hex + if (rx_frame.data.u8[i] < 0x10) { + logging.print("0"); + } + logging.print(rx_frame.data.u8[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG } - if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && - rx_frame.data.u8[4] == 0x69) { //Current (32bit mA? negative = discharge) - battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) | - (rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) * - 0.01; - } if (rx_frame.DLC = 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && rx_frame.data.u8[3] == 0x03 && rx_frame.data.u8[4] == @@ -798,6 +877,27 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { if (gUDSContext.UDS_moduleID == 0xA5) { //We have a complete set of cell voltages - pass to data layer processCellVoltages(); } + //Current measurement + if (gUDSContext.UDS_moduleID == 0x69) { //Current (32bit mA? negative = discharge) + battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) | + (gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) * + 0.1; +#ifdef DEBUG_LOG + logging.print("Received current/amps measurement data: "); + logging.print(battery_current); + logging.print(" - "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG + } + //Cell Min/Max if (gUDSContext.UDS_moduleID == 0xA0) { //We have a complete frame for cell min max - pass to data layer UNCONFIRMED IF THESE ARE CORRECT BYTES @@ -949,7 +1049,8 @@ void transmit_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); datalayer.system.info.battery_protocol[63] = '\0'; - + //Wakeup the SME + wake_battery_via_canbus(); //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; From 5de4b1e57559560931e77ae9532b9dcf9e7ebe28 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:09:15 +0000 Subject: [PATCH 23/31] Formatting fixes for wakeup support --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 9c268a83d..e2795bd31 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -532,16 +532,15 @@ void wake_battery_via_canbus() { // Then a second dominant pulse of similar timing. CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings - ESP32Can.CANInit(); // ReInit CAN Module? + ESP32Can.CANInit(); // ReInit CAN Module? transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); - CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed - ESP32Can.CANInit(); // ReInit CAN Module? - -#ifdef DEBUG_LOG - logging.println("Send magic wakeup packet to SME at 100kbps..."); -#endif + CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed + ESP32Can.CANInit(); // ReInit CAN Module? +#ifdef DEBUG_LOG + logging.println("Send magic wakeup packet to SME at 100kbps..."); +#endif } void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer From 551e1afb0badebe85b21fec1451cc0e369d58b12 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sat, 11 Jan 2025 20:49:23 +0000 Subject: [PATCH 24/31] Ignore invalid cell temps SME sends a 255 sometimes --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index e2795bd31..b6a284a7a 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -532,14 +532,14 @@ void wake_battery_via_canbus() { // Then a second dominant pulse of similar timing. CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings - ESP32Can.CANInit(); // ReInit CAN Module? + ESP32Can.CANInit(); // ReInit native CAN module at new speed transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed - ESP32Can.CANInit(); // ReInit CAN Module? + ESP32Can.CANInit(); // ReInit native CAN module at new speed #ifdef DEBUG_LOG - logging.println("Send magic wakeup packet to SME at 100kbps..."); + logging.println("Sent magic wakeup packet to SME at 100kbps..."); #endif } void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer @@ -954,18 +954,18 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); battery_temperature_HV = (rx_frame.data.u8[4] - 50); battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); - if (rx_frame.data.u8[6] > 0) { + if (rx_frame.data.u8[6] > 0 || rx_frame.data.u8[6] < 255) { battery_temperature_min = (rx_frame.data.u8[6] - 50); } else { #ifdef DEBUG_LOG - logging.println("Pre parsed Cell Temp Min (0) is Invalid "); + logging.println("Pre parsed Cell Temp Min is Invalid "); #endif } - if (rx_frame.data.u8[7] > 0) { + if (rx_frame.data.u8[7] > 0 || rx_frame.data.u8[7] < 255) { battery_temperature_max = (rx_frame.data.u8[7] - 50); } else { #ifdef DEBUG_LOG - logging.println("Pre parsed Cell Temp Max (0) is Invalid "); + logging.println("Pre parsed Cell Temp Max is Invalid "); #endif } From 7d08e4267f65f623ed22fdb5674e45b89aa0399a Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:28:07 +0000 Subject: [PATCH 25/31] Small fixes and cleanup --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 80 ++++++++--------------- Software/src/battery/BMW-PHEV-BATTERY.h | 2 +- 2 files changed, 28 insertions(+), 54 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index b6a284a7a..1590c08ab 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -62,6 +62,9 @@ V0.1 very basic implementation reading Gen3/4 BMW PHEV SME. -Min/ Cell Temp -SOC +VEHICLE CANBUS +i3 messages don't affect contact close block except 0x380 which gives an error on PHEV (Possibly VIN number) 0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34 + BROADCAST MAP 0x112 20ms Status Of High-Voltage Battery 2 0x1F1 1000ms Status Of High-Voltage Battery 1 @@ -293,7 +296,8 @@ CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false, static bool battery_awake = false; //Setup UDS values to poll for -CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, +CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, + &BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_SOH, &BMWPHEV_6F1_REQUEST_CURRENT, &BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, @@ -301,10 +305,8 @@ CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_SOC, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, - &BMWPHEV_6F1_REQUEST_CELLSUMMARY, &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, - &BMWPHEV_6F1_REQUEST_CELL_TEMP, - &BMWPHEV_6F1_REQUEST_CELLSUMMARY}; + &BMWPHEV_6F1_REQUEST_CELL_TEMP}; int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //PHEV intermediate vars @@ -371,12 +373,12 @@ static int32_t battery_current = 0; static int16_t battery_voltage = 0; static int16_t terminal30_12v_voltage = 0; static int16_t battery_voltage_after_contactor = 0; -static int16_t min_soc_state = 50; -static int16_t avg_soc_state = 50; -static int16_t max_soc_state = 50; -static int16_t min_soh_state = 99; // Uses E5 45, also available in 78 73 -static int16_t avg_soh_state = 99; // Uses E5 45, also available in 78 73 -static int16_t max_soh_state = 99; // Uses E5 45, also available in 78 73 +static int16_t min_soc_state = 5000; +static int16_t avg_soc_state = 5000; +static int16_t max_soc_state = 5000; +static int16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73 +static int16_t avg_soh_state = 9999; // Uses E5 45, also available in 78 73 +static int16_t max_soh_state = 9999; // Uses E5 45, also available in 78 73 static uint16_t max_design_voltage = 0; static uint16_t min_design_voltage = 0; static int32_t remaining_capacity = 0; @@ -714,39 +716,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; -#ifdef DEBUG_LOG - logging.print("Received pre contactor measurement data: "); - logging.print(battery_voltage); - logging.print(" - "); - for (uint16_t i = 0; i < 8; i++) { - // Optional leading zero for single-digit hex - if (rx_frame.data.u8[i] < 0x10) { - logging.print("0"); - } - logging.print(rx_frame.data.u8[i], HEX); - logging.print(" "); - } - logging.println(); // new line at the end -#endif // DEBUG_LOG } if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; -#ifdef DEBUG_LOG - logging.print("Received post contactor measurement data: "); - logging.print(battery_voltage_after_contactor); - logging.print(" - "); - for (uint16_t i = 0; i < 8; i++) { - // Optional leading zero for single-digit hex - if (rx_frame.data.u8[i] < 0x10) { - logging.print("0"); - } - logging.print(rx_frame.data.u8[i], HEX); - logging.print(" "); - } - logging.println(); // new line at the end -#endif // DEBUG_LOG } if (rx_frame.DLC = 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && @@ -881,20 +855,20 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) | (gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) * 0.1; -#ifdef DEBUG_LOG - logging.print("Received current/amps measurement data: "); - logging.print(battery_current); - logging.print(" - "); - for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { - // Optional leading zero for single-digit hex - if (gUDSContext.UDS_buffer[i] < 0x10) { - logging.print("0"); - } - logging.print(gUDSContext.UDS_buffer[i], HEX); - logging.print(" "); - } - logging.println(); // new line at the end -#endif // DEBUG_LOG + // #ifdef DEBUG_LOG + // logging.print("Received current/amps measurement data: "); + // logging.print(battery_current); + // logging.print(" - "); + // for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // // Optional leading zero for single-digit hex + // if (gUDSContext.UDS_buffer[i] < 0x10) { + // logging.print("0"); + // } + // logging.print(gUDSContext.UDS_buffer[i], HEX); + // logging.print(" "); + // } + // logging.println(); // new line at the end + // #endif // DEBUG_LOG } //Cell Min/Max @@ -1050,7 +1024,7 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.system.info.battery_protocol[63] = '\0'; //Wakeup the SME wake_battery_via_canbus(); - //Before we have started up and detected which battery is in use, use 108S values + 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_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h index 14a82fa0c..7aaccb7d1 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.h +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -15,7 +15,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 1800000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 1800 seconds / 30mins + 3600000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 3600 seconds / 60mins void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); From b7e42a0d1b9b50c1a584b202c8a70adee5a64e7c Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:01:49 +0000 Subject: [PATCH 26/31] Fix errors on startup Battery should now startup without any errors --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 1590c08ab..96d112872 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -370,7 +370,7 @@ static bool cell_limit_info_available = false; static uint32_t battery_serial_number = 0; static int32_t battery_current = 0; -static int16_t battery_voltage = 0; +static int16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future static int16_t terminal30_12v_voltage = 0; static int16_t battery_voltage_after_contactor = 0; static int16_t min_soc_state = 5000; @@ -385,8 +385,8 @@ static int32_t remaining_capacity = 0; static int32_t max_capacity = 0; static int16_t main_contactor_temperature = 0; -static int16_t min_cell_voltage = 0; -static int16_t max_cell_voltage = 0; +static int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future +static int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future static unsigned long min_cell_voltage_lastchanged = 0; static unsigned long max_cell_voltage_lastchanged = 0; static unsigned min_cell_voltage_lastreceived = 0; @@ -592,6 +592,7 @@ void update_values_battery() { //This function maps all the values fetched via logging.println("Stale Min/Max voltage values detected sending - 9999mV..."); #endif // DEBUG_LOG } else { + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive } From b348fc32e4eb1a9d08457c90c96685afb995a67a Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:18:36 +0000 Subject: [PATCH 27/31] Fixes for invalid values and stale tracker --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 96d112872..6931efc17 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -584,12 +584,13 @@ void update_values_battery() { //This function maps all the values fetched via bool isMaxCellVoltageStale = isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged); - if (isMinCellVoltageStale && isMaxCellVoltageStale) { + if (isMinCellVoltageStale && isMaxCellVoltageStale && + battery_current != 0) { //Ignore stale values if there is no current flowing datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); #ifdef DEBUG_LOG - logging.println("Stale Min/Max voltage values detected sending - 9999mV..."); + logging.println("Stale Min/Max voltage values detected during charge/discharge sending - 9999mV..."); #endif // DEBUG_LOG } else { @@ -929,14 +930,14 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); battery_temperature_HV = (rx_frame.data.u8[4] - 50); battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); - if (rx_frame.data.u8[6] > 0 || rx_frame.data.u8[6] < 255) { + if (rx_frame.data.u8[6] > 0 && rx_frame.data.u8[6] < 255) { battery_temperature_min = (rx_frame.data.u8[6] - 50); } else { #ifdef DEBUG_LOG logging.println("Pre parsed Cell Temp Min is Invalid "); #endif } - if (rx_frame.data.u8[7] > 0 || rx_frame.data.u8[7] < 255) { + if (rx_frame.data.u8[7] > 0 && rx_frame.data.u8[7] < 255) { battery_temperature_max = (rx_frame.data.u8[7] - 50); } else { #ifdef DEBUG_LOG From 991ee02805e8cd9cc126c1feef0f449332737ea4 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:46:57 +0000 Subject: [PATCH 28/31] Add isolation Test Isolation test is called at startup - and results viewable in advanced --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 68 +++++++++++++++---- Software/src/datalayer/datalayer_extended.h | 9 ++- .../webserver/advanced_battery_html.cpp | 13 ++-- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 6931efc17..0e49d146d 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -157,21 +157,22 @@ CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame -CAN_frame BMWPHEV_6F1_REQUEST_ISO_MEASUREMENTS = { +CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING1 = { .FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = { 0x07, 0x03, 0x22, 0xDD, - 0x6A}}; // 62 D6 D9 07 FF 13 reading during contactors closed, plausible, reading during open contactors (only on request via steurn_isolation) ( request = 31 01 AD 61) + 0x6A}}; // MULTI FRAME ISOLATIONSWIDERSTAND 62 DD 6A [07 D0] [07 D0] [07 D0] [01] [01] [01] 00 00 00 00 00 [EXT Reading] [INT reading] [ EXT - 0 not plausible, 1 plausible] -CAN_frame BMWPHEV_6F1_REQUEST_LAST_ISO_READING = { +CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING2 = { .FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, - .data = {0x07, 0x03, 0x22, 0xD6, 0xD9}}; // 62 D6 D9 07 FF 13 (2047kohm) quality of reading 0-21 (19) + .data = {0x07, 0x03, 0x22, 0xD6, + 0xD9}}; // R_ISO_ROH 62 D6 D9 [07 FF] [13] (2047kohm) quality of reading 0-21 (19) CAN_frame BMWPHEV_6F1_REQUEST_PACK_INFO = { .FD = false, @@ -260,6 +261,13 @@ CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STATUS = { .data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing status. Response 7DLC F1 05 71 03 AD 6B 01 (01 = active) (03 not active) +CAN_frame BMWPHEV_6F1_REQUEST_ISOLATION_TEST = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x61, 0x00, 0x00}}; // Start Isolation Test + CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = { .FD = false, .ext_ID = false, @@ -306,7 +314,9 @@ CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, - &BMWPHEV_6F1_REQUEST_CELL_TEMP}; + &BMWPHEV_6F1_REQUEST_CELL_TEMP, + &BMWPHEV_6F1_REQUEST_ISO_READING1, + &BMWPHEV_6F1_REQUEST_ISO_READING2}; int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //PHEV intermediate vars @@ -391,14 +401,20 @@ static unsigned long min_cell_voltage_lastchanged = 0; static unsigned long max_cell_voltage_lastchanged = 0; static unsigned min_cell_voltage_lastreceived = 0; static unsigned max_cell_voltage_lastreceived = 0; -static uint32_t sme_uptime = 0; //Uses E4 C0 static int16_t allowable_charge_amps = 0; //E5 62 static int16_t allowable_discharge_amps = 0; //E5 62 -static int32_t iso_safety_positive = 0; //Uses A8 60 -static int32_t iso_safety_negative = 0; //Uses A8 60 -static int32_t iso_safety_parallel = 0; //Uses A8 60 -static int16_t count_full_charges = 0; //TODO 42 -static int16_t count_charges = 0; //TODO 42 + +static int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT +static int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT +static int32_t iso_safety_trg_kohm = 0; +static int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS +static int32_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT +static int32_t iso_safety_trg_plausible = 0; +static int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT +static int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) + +static int16_t count_full_charges = 0; //TODO 42 +static int16_t count_charges = 0; //TODO 42 static int16_t hvil_status = 0; static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid static int16_t balancing_status = 0; //4 = not active @@ -612,8 +628,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.hvil_status = hvil_status; - datalayer_extended.bmwphev.bms_uptime = sme_uptime; - datalayer_extended.bmwphev.allowable_charge_amps = allowable_charge_amps; datalayer_extended.bmwphev.allowable_discharge_amps = allowable_discharge_amps; @@ -634,6 +648,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwphev.ST_WELD = battery_status_error_disconnecting_switch; datalayer_extended.bmwphev.ST_isolation = battery_status_warning_isolation; datalayer_extended.bmwphev.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; + datalayer_extended.bmwphev.iso_safety_int_kohm = iso_safety_int_kohm; + datalayer_extended.bmwphev.iso_safety_ext_kohm = iso_safety_ext_kohm; + datalayer_extended.bmwphev.iso_safety_trg_kohm = iso_safety_trg_kohm; + datalayer_extended.bmwphev.iso_safety_ext_plausible = iso_safety_ext_plausible; + datalayer_extended.bmwphev.iso_safety_int_plausible = iso_safety_int_plausible; + datalayer_extended.bmwphev.iso_safety_kohm = iso_safety_kohm; + datalayer_extended.bmwphev.iso_safety_kohm_quality = iso_safety_kohm_quality; if (pack_limit_info_available) { // If we have pack limit data from battery - override the defaults to suit @@ -715,6 +736,12 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { min_soh_state = (rx_frame.data.u8[5]) * 100; } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xD6 && rx_frame.data.u8[4] == 0xD9) { // Isolation Reading 2 + iso_safety_kohm = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); //STAT_R_ISO_ROH_01_WERT + iso_safety_kohm_quality = + (rx_frame.data.u8[7]); //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) + } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; @@ -906,10 +933,22 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { min_design_voltage = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; pack_limit_info_available = true; } + if (gUDSContext.UDS_moduleID == 0x7D) { // Current Limits allowable_charge_amps = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; allowable_discharge_amps = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; } + + if (gUDSContext.UDS_moduleID == 0x6A) { // Iso Reading 1 + iso_safety_int_kohm = + (gUDSContext.UDS_buffer[7] << 8 | gUDSContext.UDS_buffer[8]); //STAT_ISOWIDERSTAND_INT_WERT + iso_safety_ext_kohm = + (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]); //STAT_ISOWIDERSTAND_EXT_STD_WERT + iso_safety_trg_kohm = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]); + iso_safety_ext_plausible = gUDSContext.UDS_buffer[9]; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS + iso_safety_trg_plausible = gUDSContext.UDS_buffer[10]; //STAT_ISOWIDERSTAND_EXT_TRG_WERT + iso_safety_int_plausible = gUDSContext.UDS_buffer[11]; //STAT_ISOWIDERSTAND_EXT_TRG_WERT + } } break; @@ -1027,6 +1066,9 @@ void setup_battery(void) { // Performs one time setup at startup //Wakeup the SME wake_battery_via_canbus(); + transmit_can_frame(&BMWPHEV_6F1_REQUEST_ISOLATION_TEST, + can_config.battery); // Run Internal Isolation Test at startup + 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_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 9e51afae4..1a97906dc 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -103,13 +103,20 @@ typedef struct { /** Min/Max Cell SOH*/ uint16_t min_soh_state = 0; uint16_t max_soh_state = 0; - uint32_t bms_uptime = 0; int32_t allowable_charge_amps = 0; int32_t allowable_discharge_amps = 0; int16_t balancing_status = 0; int16_t battery_voltage_after_contactor = 0; unsigned long min_cell_voltage_data_age = 0; unsigned long max_cell_voltage_data_age = 0; + int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT + int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT + int32_t iso_safety_trg_kohm = 0; + int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS + int32_t iso_safety_int_plausible = 0; + int32_t iso_safety_trg_plausible = 0; + int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT + int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) } DATALAYER_INFO_BMWPHEV; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index f45743a55..c83f09be3 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -197,17 +197,20 @@ String advanced_battery_processor(const String& var) { content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwphev.allowable_discharge_amps) + " A

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + content += "

iso_safety_int_kohm: " + String(datalayer_extended.bmwphev.iso_safety_int_kohm) + "

"; + content += "

iso_safety_ext_kohm: " + String(datalayer_extended.bmwphev.iso_safety_ext_kohm) + "

"; + content += "

iso_safety_trg_kohm: " + String(datalayer_extended.bmwphev.iso_safety_trg_kohm) + "

"; + content += "

iso_safety_ext_plausible: " + String(datalayer_extended.bmwphev.iso_safety_ext_plausible) + "

"; + content += "

iso_safety_int_plausible: " + String(datalayer_extended.bmwphev.iso_safety_int_plausible) + "

"; + content += "

iso_safety_trg_plausible: " + String(datalayer_extended.bmwphev.iso_safety_trg_plausible) + "

"; + content += "

iso_safety_kohm: " + String(datalayer_extended.bmwphev.iso_safety_kohm) + "

"; + content += "

iso_safety_kohm_quality: " + String(datalayer_extended.bmwphev.iso_safety_kohm_quality) + "

"; content += "
"; content += "

Todo"; content += "
"; - content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; - - content += "

BMS Uptime: " + String(datalayer_extended.bmwphev.bms_uptime) + " seconds

"; - content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; - content += "
"; #endif //BMW_PHEV_BATTERY From 28591aea0f2cb97d904a9a62d2a5ffb9a3bccf1e Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 15 Jan 2025 20:28:18 +0000 Subject: [PATCH 29/31] Only fast poll values that change quickly Introduce a slow update loop for slow changing values. --- Software/src/battery/BMW-PHEV-BATTERY.cpp | 94 ++++++++++++----------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index 0e49d146d..0f04d0ba6 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -157,6 +157,12 @@ CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame +CAN_frame BMWPHEV_6F1_REQUEST_PAIRED_VIN = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xF1, 0x90}}; // SME Paired VIN + CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING1 = { .FD = false, .ext_ID = false, @@ -303,21 +309,21 @@ CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false, static bool battery_awake = false; -//Setup UDS values to poll for -CAN_frame* UDS_REQUESTS100MS[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, +//Setup Fast UDS values to poll for +CAN_frame* UDS_REQUESTS_FAST[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, &BMWPHEV_6F1_REQUEST_SOC, - &BMWPHEV_6F1_REQUEST_SOH, &BMWPHEV_6F1_REQUEST_CURRENT, &BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, - &BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, - &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR, - &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, - &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, - &BMWPHEV_6F1_REQUEST_CELL_TEMP, - &BMWPHEV_6F1_REQUEST_ISO_READING1, - &BMWPHEV_6F1_REQUEST_ISO_READING2}; -int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR}; +int numFastUDSreqs = sizeof(UDS_REQUESTS_FAST) / sizeof(UDS_REQUESTS_FAST[0]); //Store Number of elements in the array + +//Setup Slow UDS values to poll for +CAN_frame* UDS_REQUESTS_SLOW[] = {&BMWPHEV_6F1_REQUEST_ISO_READING1, &BMWPHEV_6F1_REQUEST_ISO_READING2, + &BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_SOH, + &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, &BMWPHEV_6F1_REQUEST_CELL_TEMP, + &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, &BMWPHEV_6F1_REQUEST_PAIRED_VIN}; +int numSlowUDSreqs = sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Store Number of elements in the array //PHEV intermediate vars //#define UDS_LOG //Useful for logging multiframe handling @@ -413,6 +419,9 @@ static int32_t iso_safety_trg_plausible = 0; static int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT static int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) +static uint8_t paired_vin[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN + static int16_t count_full_charges = 0; //TODO 42 static int16_t count_charges = 0; //TODO 42 static int16_t hvil_status = 0; @@ -422,7 +431,8 @@ static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 static uint8_t contactor_status_precharge = 0; //TODO E5 BF static uint8_t contactor_status_negative = 0; //TODO E5 BF static uint8_t contactor_status_positive = 0; //TODO E5 BF -static uint8_t uds_req_id_counter = 0; +static uint8_t uds_fast_req_id_counter = 0; +static uint8_t uds_slow_req_id_counter = 0; static uint8_t detected_number_of_cells = 96; const unsigned long STALE_PERIOD = STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) @@ -457,9 +467,9 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_ return crc; } -static uint8_t increment_uds_req_id_counter(uint8_t index) { +static uint8_t increment_uds_req_id_counter(uint8_t index, int numReqs) { index++; - if (index >= numUDSreqs) { + if (index >= numReqs) { index = 0; } return index; @@ -884,20 +894,20 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) | (gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) * 0.1; - // #ifdef DEBUG_LOG - // logging.print("Received current/amps measurement data: "); - // logging.print(battery_current); - // logging.print(" - "); - // for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { - // // Optional leading zero for single-digit hex - // if (gUDSContext.UDS_buffer[i] < 0x10) { - // logging.print("0"); - // } - // logging.print(gUDSContext.UDS_buffer[i], HEX); - // logging.print(" "); - // } - // logging.println(); // new line at the end - // #endif // DEBUG_LOG +#ifdef DEBUG_LOG + logging.print("Received current/amps measurement data: "); + logging.print(battery_current); + logging.print(" - "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG } //Cell Min/Max @@ -939,6 +949,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { allowable_discharge_amps = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; } + if (gUDSContext.UDS_moduleID == 0x90) { // Paired VIN + for (int i = 0; i < 17; i++) { + paired_vin[i] = gUDSContext.UDS_buffer[4 + i]; + } + } if (gUDSContext.UDS_moduleID == 0x6A) { // Iso Reading 1 iso_safety_int_kohm = (gUDSContext.UDS_buffer[7] << 8 | gUDSContext.UDS_buffer[8]); //STAT_ISOWIDERSTAND_INT_WERT @@ -1026,17 +1041,22 @@ void transmit_can_battery() { // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { previousMillis200 = currentMillis; - //Loop through and send a different UDS request each cycle - uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); - transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); + uds_fast_req_id_counter = increment_uds_req_id_counter( + uds_fast_req_id_counter, numFastUDSreqs); //Loop through and send a different UDS request each cycle + transmit_can_frame(UDS_REQUESTS_FAST[uds_fast_req_id_counter], can_config.battery); } // Send 1000ms CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; + + uds_slow_req_id_counter = increment_uds_req_id_counter( + uds_slow_req_id_counter, numSlowUDSreqs); //Loop through and send a different UDS request each cycle + transmit_can_frame(UDS_REQUESTS_SLOW[uds_slow_req_id_counter], can_config.battery); } // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { previousMillis5000 = currentMillis; + // transmit_can_frame(&BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE, // can_config.battery); // Attempt contactor close - experimental } @@ -1047,18 +1067,6 @@ void transmit_can_battery() { can_config.battery); // Enable Balancing } } -//We can always send CAN as the iX BMS will wake up on vehicle comms -// else { -// previousMillis20 = currentMillis; -// previousMillis100 = currentMillis; -// previousMillis200 = currentMillis; -// previousMillis500 = currentMillis; -// previousMillis640 = currentMillis; -// previousMillis1000 = currentMillis; -// previousMillis5000 = currentMillis; -// previousMillis10000 = currentMillis; -// } -//} //We can always send CAN as the iX BMS will wake up on vehicle comms void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); From 3ec9de9f8a14afb88e8bb09f0c5eb70cdf7db9f3 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:12:44 +0000 Subject: [PATCH 30/31] Add BMW-PHEV to the build system Adds a test compilation for BMW_PHEV --- .github/workflows/compile-all-batteries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index ae6bafb6b..a3e2d7df7 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -51,6 +51,7 @@ jobs: battery: - BMW_I3_BATTERY - BMW_IX_BATTERY + - BMW_PHEV_BATTERY - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY From 8c2e299f00577e436f6970820d352e7e147a69a8 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:55:42 +0000 Subject: [PATCH 31/31] Add PHEV to all combination workflow --- .../compile-all-combinations-part1-batteries-A-to-M.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml index 14381ea37..2aed0464a 100644 --- a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml +++ b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml @@ -56,6 +56,7 @@ jobs: battery: - BMW_I3_BATTERY - BMW_IX_BATTERY + - BMW_PHEV_BATTERY - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY