From e82fb9f1605da8fcf4a5b120e3367f2a23534060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 14 Mar 2025 21:56:16 +0200 Subject: [PATCH 01/37] Add skeleton for CMFA-EV battery --- Software/USER_SETTINGS.h | 3 +- Software/src/battery/BATTERIES.h | 4 + Software/src/battery/CMFA-EV-BATTERY.cpp | 108 +++++++++++++++++++++++ Software/src/battery/CMFA-EV-BATTERY.h | 15 ++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 Software/src/battery/CMFA-EV-BATTERY.cpp create mode 100644 Software/src/battery/CMFA-EV-BATTERY.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index a7167890..1d290c82 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -17,6 +17,7 @@ //#define FOXESS_BATTERY //#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below +#define CMFA_EV_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY //#define KIA_E_GMP_BATTERY @@ -66,7 +67,7 @@ //#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" over CAN bus /* Select hardware used for Battery-Emulator */ -//#define HW_LILYGO +#define HW_LILYGO //#define HW_STARK //#define HW_3LB //#define HW_DEVKIT diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index e50f3633..1bf91d0f 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -38,6 +38,10 @@ void setup_can_shunt(); #include "CHADEMO-SHUNTS.h" #endif +#ifdef CMFA_EV_BATTERY +#include "CMFA-EV-BATTERY.h" +#endif + #ifdef FOXESS_BATTERY #include "FOXESS-BATTERY.h" #endif diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp new file mode 100644 index 00000000..5acb621a --- /dev/null +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -0,0 +1,108 @@ +#include "../include.h" +#ifdef CMFA_EV_BATTERY +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "CMFA-EV-BATTERY.h" + +/* +TODO: + +/* + +/* Do not change code below unless you are sure what you are doing */ + +CAN_frame ZOE_423 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x423, + .data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}}; + +static unsigned long previousMillis100 = 0; + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.real_soc; + + datalayer.battery.status.current_dA; + + datalayer.battery.status.voltage_dV; + + //Calculate the remaining Wh amount from SOC% and max Wh value. + datalayer.battery.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + + datalayer.battery.status.max_discharge_power_W; + + datalayer.battery.status.max_charge_power_W; + + datalayer.battery.status.temperature_min_dC; + + datalayer.battery.status.temperature_max_dC; + + datalayer.battery.status.cell_min_voltage_mV; + + datalayer.battery.status.cell_max_voltage_mV; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + switch (rx_frame.ID) { //These frames are transmitted by the battery + case 0x127: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x3D6: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x3D7: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x3D8: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x43C: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x431: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5A9: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5AB: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5C8: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5E1: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100)); + } + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "CMFA platform 26.8/27.4kWh", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.system.status.battery_allows_contactor_closing = true; + datalayer.battery.info.number_of_cells = 96; + 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; +} + +#endif //CMFA_EV_BATTERY diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h new file mode 100644 index 00000000..76c1be67 --- /dev/null +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -0,0 +1,15 @@ +#ifndef CMFA_EV_BATTERY_H +#define CMFA_EV_BATTERY_H +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_PACK_VOLTAGE_DV 4200 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 3000 +#define MAX_CELL_DEVIATION_MV 150 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif From 50b9db1a233cada3c57da8674edeab978904e732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 14 Mar 2025 22:25:04 +0200 Subject: [PATCH 02/37] Add CAN sending and TODOs --- Software/src/battery/CMFA-EV-BATTERY.cpp | 70 ++++++++++++++++++++---- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 5acb621a..4c79a2d4 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -6,18 +6,50 @@ /* TODO: - +- Find the following data points in the transmitted CAN data: + - SOC% + - SOH% (optional) + - Current + - Pack voltage + - Max discharge power + - Max charge power + - Temperature min + - Temperature max + - Cellvoltage min + - Cellvoltage max + - Alternatively all these values can be taken from OBD2 PID polling +- Figure out which messages need to be sent towards the battery to keep it satisfied and staying alive + - TODO: Test that the current amount of messages sent is enough to keep it alive /* /* Do not change code below unless you are sure what you are doing */ -CAN_frame ZOE_423 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x423, - .data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}}; +CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}}; +CAN_frame CMFA_125 = {.FD = false, + .ext_ID = false, + .DLC = 7, + .ID = 0x125, + .data = {0x7D, 0x7D, 0x7D, 0x07, 0x82, 0x6A, 0x8A}}; +CAN_frame CMFA_134 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x134, + .data = {0x90, 0x8A, 0x7E, 0x3E, 0xB2, 0x4C, 0x80, 0x00}}; +CAN_frame CMFA_135 = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x135, .data = {0xD5, 0x85, 0x38, 0x80, 0x01}}; +CAN_frame CMFA_3D3 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x3D3, + .data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}}; +CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}}; -static unsigned long previousMillis100 = 0; +static uint8_t counter_10ms = 0; +static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A, + 0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02}; +static uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5, + 0x05, 0x55, 0xA5, 0xF5, 0x45, 0x95, 0xE5, 0x35}; +static unsigned long previousMillis100ms = 0; +static unsigned long previousMillis10ms = 0; void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer.battery.status.soh_pptt; @@ -84,12 +116,28 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void transmit_can_battery() { unsigned long currentMillis = millis(); - // Send 100ms CAN Message - if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + // Send 10ms CAN Message + if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100)); + if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms)); } + previousMillis10ms = currentMillis; + transmit_can_frame(&CMFA_1EA, can_config.battery); + transmit_can_frame(&CMFA_135, can_config.battery); + transmit_can_frame(&CMFA_134, can_config.battery); + transmit_can_frame(&CMFA_125, can_config.battery); + + CMFA_135.data.u8[1] = content_135[counter_10ms]; + CMFA_125.data.u8[3] = content_125[counter_10ms]; + counter_10ms = (counter_10ms + 1) % 16; // counter_10ms cycles between 0-1-2-3..15-0-1... + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + transmit_can_frame(&CMFA_59B, can_config.battery); + transmit_can_frame(&CMFA_3D3, can_config.battery); } } From 8b2aa2cdbab0a8728601d352aacc508039701546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 14:28:39 +0200 Subject: [PATCH 03/37] Update voltage limits for 72S --- Software/src/battery/CMFA-EV-BATTERY.cpp | 4 +++- Software/src/battery/CMFA-EV-BATTERY.h | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 4c79a2d4..e8faf9de 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -81,6 +81,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { //These frames are transmitted by the battery case 0x127: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //value1 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); + //value2 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); break; case 0x3D6: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; @@ -145,7 +147,7 @@ void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "CMFA platform 26.8/27.4kWh", 63); datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; - datalayer.battery.info.number_of_cells = 96; + datalayer.battery.info.number_of_cells = 72; 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/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index 76c1be67..b5fc2db8 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -3,9 +3,9 @@ #include "../include.h" #define BATTERY_SELECTED -#define MAX_PACK_VOLTAGE_DV 4200 //5000 = 500.0V -#define MIN_PACK_VOLTAGE_DV 3000 -#define MAX_CELL_DEVIATION_MV 150 +#define MAX_PACK_VOLTAGE_DV 3040 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 2150 +#define MAX_CELL_DEVIATION_MV 100 #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value From e285bdd492d15a3318a07d2cb458713075c373db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 14:55:37 +0200 Subject: [PATCH 04/37] Add some best guess CAN mappings --- Software/src/battery/CMFA-EV-BATTERY.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index e8faf9de..0d00d199 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -51,10 +51,14 @@ static uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5 static unsigned long previousMillis100ms = 0; static unsigned long previousMillis10ms = 0; +static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame +static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame +static uint8_t SOC = 0; + void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer.battery.status.soh_pptt; - datalayer.battery.status.real_soc; + datalayer.battery.status.real_soc = SOC * 100; // Add two decimals datalayer.battery.status.current_dA; @@ -79,25 +83,32 @@ void update_values_battery() { //This function maps all the values fetched via void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { //These frames are transmitted by the battery - case 0x127: + case 0x127: //10ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //value1 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); //value2 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); + SOC = rx_frame.data.u8[7]; break; case 0x3D6: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + heartbeat = rx_frame.data.u8[6]; //Alternates between 0x55 and 0xAA every 5th frame break; case 0x3D7: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x3D8: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //counter_3D8 = rx_frame.data.u8[3]; //? + //CRC_3D8 = rx_frame.data.u8[4]; //? break; case 0x43C: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame break; case 0x431: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //byte0 9C always + //byte1 40 always break; case 0x5A9: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; From 2c9ad0ee8311975658419325d03fe17eb2eae1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 16:03:09 +0200 Subject: [PATCH 05/37] Add more CAN findings --- Software/src/battery/CMFA-EV-BATTERY.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 0d00d199..bc6d35fd 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -54,6 +54,7 @@ static unsigned long previousMillis10ms = 0; static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame static uint8_t SOC = 0; +static uint16_t pack_voltage = 2700; void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer.battery.status.soh_pptt; @@ -62,7 +63,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.current_dA; - datalayer.battery.status.voltage_dV; + datalayer.battery.status.voltage_dV = pack_voltage; //Calculate the remaining Wh amount from SOC% and max Wh value. datalayer.battery.status.remaining_capacity_Wh = static_cast( @@ -95,6 +96,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { break; case 0x3D7: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F))); break; case 0x3D8: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; From 7cf3c687a94f5ad358c9d581a8b052dff4065ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 18:13:25 +0200 Subject: [PATCH 06/37] Translated 0x3D6 message completely --- Software/src/battery/CMFA-EV-BATTERY.cpp | 28 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index bc6d35fd..7afb27ef 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -53,13 +53,18 @@ static unsigned long previousMillis10ms = 0; static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame -static uint8_t SOC = 0; +static uint16_t SOC = 0; +static uint16_t SOH = 99; static uint16_t pack_voltage = 2700; +static int16_t highest_cell_temperature = 0; +static uint16_t lowest_cell_temperature = 0; +static uint32_t discharge_power_w = 0; +static uint32_t charge_power_w = 0; void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus - datalayer.battery.status.soh_pptt; + datalayer.battery.status.soh_pptt = SOH * 100; - datalayer.battery.status.real_soc = SOC * 100; // Add two decimals + datalayer.battery.status.real_soc = SOC * 100; datalayer.battery.status.current_dA; @@ -69,13 +74,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); - datalayer.battery.status.max_discharge_power_W; + datalayer.battery.status.max_discharge_power_W = discharge_power_w; - datalayer.battery.status.max_charge_power_W; + datalayer.battery.status.max_charge_power_W = charge_power_w; - datalayer.battery.status.temperature_min_dC; + datalayer.battery.status.temperature_min_dC = (lowest_cell_temperature * 10); - datalayer.battery.status.temperature_max_dC; + datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); datalayer.battery.status.cell_min_voltage_mV; @@ -90,9 +95,14 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { //value2 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); SOC = rx_frame.data.u8[7]; break; - case 0x3D6: + case 0x3D6: // Same structure as old Zoe 0x424 message! datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - heartbeat = rx_frame.data.u8[6]; //Alternates between 0x55 and 0xAA every 5th frame + charge_power_w = rx_frame.data.u8[2] * 500; + discharge_power_w = rx_frame.data.u8[3] * 500; + lowest_cell_temperature = (rx_frame.data.u8[4] - 40); + SOH = rx_frame.data.u8[5]; + heartbeat = rx_frame.data.u8[6]; + highest_cell_temperature = (rx_frame.data.u8[6] - 40); break; case 0x3D7: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; From 8be8c5bc3ba73a8cfb1fbbbdd6548e5bc66d850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 22:20:05 +0200 Subject: [PATCH 07/37] Add Current and SOC reading --- Software/src/battery/CMFA-EV-BATTERY.cpp | 47 ++++++++++-------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 7afb27ef..806adf50 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -4,26 +4,17 @@ #include "../devboard/utils/events.h" #include "CMFA-EV-BATTERY.h" -/* -TODO: +/* TODO: +Before this integration can be considered stable, we need to: - Find the following data points in the transmitted CAN data: - - SOC% - - SOH% (optional) - - Current - - Pack voltage - - Max discharge power - - Max charge power - - Temperature min - - Temperature max + - Pack voltage (Current implementation might be wrong) - Cellvoltage min - Cellvoltage max - Alternatively all these values can be taken from OBD2 PID polling -- Figure out which messages need to be sent towards the battery to keep it satisfied and staying alive - - TODO: Test that the current amount of messages sent is enough to keep it alive /* -/* Do not change code below unless you are sure what you are doing */ +/* Do not change code below unless you are sure what you are doing */ CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}}; CAN_frame CMFA_125 = {.FD = false, .ext_ID = false, @@ -53,8 +44,9 @@ static unsigned long previousMillis10ms = 0; static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame -static uint16_t SOC = 0; +static uint32_t SOC = 0; static uint16_t SOH = 99; +static int16_t current = 0; static uint16_t pack_voltage = 2700; static int16_t highest_cell_temperature = 0; static uint16_t lowest_cell_temperature = 0; @@ -62,14 +54,16 @@ static uint32_t discharge_power_w = 0; static uint32_t charge_power_w = 0; void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus - datalayer.battery.status.soh_pptt = SOH * 100; + datalayer.battery.status.soh_pptt = (SOH * 100); - datalayer.battery.status.real_soc = SOC * 100; + datalayer.battery.status.real_soc = (SOC * 0.25); - datalayer.battery.status.current_dA; + datalayer.battery.status.current_dA = current * 10; datalayer.battery.status.voltage_dV = pack_voltage; + datalayer.battery.info.total_capacity_Wh = 27000; + //Calculate the remaining Wh amount from SOC% and max Wh value. datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); @@ -89,13 +83,12 @@ void update_values_battery() { //This function maps all the values fetched via void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { //These frames are transmitted by the battery - case 0x127: //10ms + case 0x127: //10ms , Same structure as old Zoe 0x155 message! datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - //value1 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); - //value2 = ((rx_frame.data.u8[0] << 2 | (rx_frame.data.u8[1] & 0xC0) >> 6)); - SOC = rx_frame.data.u8[7]; + current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500); + SOC = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; - case 0x3D6: // Same structure as old Zoe 0x424 message! + case 0x3D6: //100ms, Same structure as old Zoe 0x424 message! datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; charge_power_w = rx_frame.data.u8[2] * 500; discharge_power_w = rx_frame.data.u8[3] * 500; @@ -104,20 +97,20 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { heartbeat = rx_frame.data.u8[6]; highest_cell_temperature = (rx_frame.data.u8[6] - 40); break; - case 0x3D7: + case 0x3D7: //100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F))); break; - case 0x3D8: + case 0x3D8: //100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //counter_3D8 = rx_frame.data.u8[3]; //? //CRC_3D8 = rx_frame.data.u8[4]; //? break; - case 0x43C: + case 0x43C: //100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame break; - case 0x431: + case 0x431: //100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //byte0 9C always //byte1 40 always @@ -167,7 +160,7 @@ void transmit_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "CMFA platform 26.8/27.4kWh", 63); + strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63); datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 72; From 5a874f1201b326eb6601985af66cb805f114d257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 22:41:58 +0200 Subject: [PATCH 08/37] Fix lowest typecast --- Software/src/battery/CMFA-EV-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 806adf50..e7870a10 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -49,7 +49,7 @@ static uint16_t SOH = 99; static int16_t current = 0; static uint16_t pack_voltage = 2700; static int16_t highest_cell_temperature = 0; -static uint16_t lowest_cell_temperature = 0; +static int16_t lowest_cell_temperature = 0; static uint32_t discharge_power_w = 0; static uint32_t charge_power_w = 0; From 208625abe2334a3496956edca65e7653b5b08913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 15 Mar 2025 22:59:18 +0200 Subject: [PATCH 09/37] Fix mapping of high temp location --- Software/src/battery/CMFA-EV-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index e7870a10..7900ee1f 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -95,7 +95,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { lowest_cell_temperature = (rx_frame.data.u8[4] - 40); SOH = rx_frame.data.u8[5]; heartbeat = rx_frame.data.u8[6]; - highest_cell_temperature = (rx_frame.data.u8[6] - 40); + highest_cell_temperature = (rx_frame.data.u8[7] - 40); break; case 0x3D7: //100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; From dcda24c4125a37a7aac27d7793d829164c5e2f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 25 Mar 2025 16:17:58 +0200 Subject: [PATCH 10/37] Add CMFA to build system --- .github/workflows/compile-all-batteries.yml | 1 + .../compile-all-combinations-part1-batteries-A-to-M.yml | 1 + Software/USER_SETTINGS.h | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 50ffc6e9..0bcbd6d1 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -55,6 +55,7 @@ jobs: - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY + - CMFA_EV_BATTERY - FOXESS_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY 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 4791150b..efdbb667 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 @@ -60,6 +60,7 @@ jobs: - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY + - CMFA_EV_BATTERY - FOXESS_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 1d290c82..cf26f407 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -17,7 +17,7 @@ //#define FOXESS_BATTERY //#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below -#define CMFA_EV_BATTERY +//#define CMFA_EV_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY //#define KIA_E_GMP_BATTERY @@ -67,7 +67,7 @@ //#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" over CAN bus /* Select hardware used for Battery-Emulator */ -#define HW_LILYGO +//#define HW_LILYGO //#define HW_STARK //#define HW_3LB //#define HW_DEVKIT From 95aca19120cb9b35ec443c66d901766bc8102c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 29 Mar 2025 21:51:48 +0200 Subject: [PATCH 11/37] Add reverse engineered PID polls --- Software/src/battery/CMFA-EV-BATTERY.cpp | 115 ++++++++++++++++++++++- Software/src/battery/CMFA-EV-BATTERY.h | 59 ++++++++++++ 2 files changed, 171 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 7900ee1f..51d048a3 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -34,11 +34,32 @@ CAN_frame CMFA_3D3 = {.FD = false, .data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}}; CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}}; +CAN_frame CMFA_ACK = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x79B, + .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame CMFA_POLLING_FRAME = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x79B, + .data = {0x30, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}}; + +static uint32_t average_voltage_of_cells = 270000; +static uint16_t highest_cell_voltage_mv = 3700; +static uint16_t leadAcidBatteryVoltage = 12000; +static uint16_t highest_cell_voltage_number = 0; +static uint16_t lowest_cell_voltage_number = 0; +static uint64_t cumulative_energy_when_discharging = 0; // Wh +static uint16_t pack_voltage_polled = 2700; +static uint32_t poll_pid = PID_POLL_PACKVOLTAGE; +static uint16_t pid_reply = 0; static uint8_t counter_10ms = 0; static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A, 0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02}; static uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5, 0x05, 0x55, 0xA5, 0xF5, 0x45, 0x95, 0xE5, 0x35}; +static unsigned long previousMillis200ms = 0; static unsigned long previousMillis100ms = 0; static unsigned long previousMillis10ms = 0; @@ -60,7 +81,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.current_dA = current * 10; - datalayer.battery.status.voltage_dV = pack_voltage; + datalayer.battery.status.voltage_dV = average_voltage_of_cells / 100; datalayer.battery.info.total_capacity_Wh = 27000; @@ -76,9 +97,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); - datalayer.battery.status.cell_min_voltage_mV; + datalayer.battery.status.cell_min_voltage_mV = highest_cell_voltage_mv; //Placeholder until we can find minimum - datalayer.battery.status.cell_max_voltage_mV; + datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; + + if (leadAcidBatteryVoltage < 11000) { //11.000V + set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); + } } void handle_incoming_can_frame_battery(CAN_frame rx_frame) { @@ -126,6 +151,43 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { break; case 0x5E1: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x7BB: // Reply from battery + if (rx_frame.data.u8[0] == 0x10) { //PID header + transmit_can_frame(&CMFA_ACK, can_config.battery); + } + + pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; + + switch (pid_reply) { + case PID_POLL_PACKVOLTAGE: + pack_voltage_polled = + (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); //Hmm, not same LSB as others + break; + case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: + average_voltage_of_cells = + (uint16_t)((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + case PID_POLL_HIGHEST_CELL_VOLTAGE: + highest_cell_voltage_mv = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: + highest_cell_voltage_number = rx_frame.data.u8[4]; + break; + case PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE: + lowest_cell_voltage_number = rx_frame.data.u8[4]; + break; + case PID_POLL_12V_BATTERY: + leadAcidBatteryVoltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: + cumulative_energy_when_discharging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + default: + break; + } + break; default: break; @@ -157,6 +219,53 @@ void transmit_can_battery() { transmit_can_frame(&CMFA_59B, can_config.battery); transmit_can_frame(&CMFA_3D3, can_config.battery); } + //Send 200ms message + if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { + previousMillis200ms = currentMillis; + + switch (poll_pid) { + case PID_POLL_PACKVOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_PACKVOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_PACKVOLTAGE; + poll_pid = PID_POLL_AVERAGE_VOLTAGE_OF_CELLS; + break; + case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_AVERAGE_VOLTAGE_OF_CELLS >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_AVERAGE_VOLTAGE_OF_CELLS; + poll_pid = PID_POLL_HIGHEST_CELL_VOLTAGE; + break; + case PID_POLL_HIGHEST_CELL_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_HIGHEST_CELL_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_HIGHEST_CELL_VOLTAGE; + poll_pid = PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE; + break; + case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE; + poll_pid = PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE; + break; + case PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE; + poll_pid = PID_POLL_12V_BATTERY; + break; + case PID_POLL_12V_BATTERY: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_12V_BATTERY >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_12V_BATTERY; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; + poll_pid = PID_POLL_PACKVOLTAGE; + break; + default: + poll_pid = PID_POLL_PACKVOLTAGE; + break; + } + + transmit_can_frame(&CMFA_POLLING_FRAME, can_config.battery); + } } void setup_battery(void) { // Performs one time setup at startup diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index b5fc2db8..6e99e428 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -9,6 +9,65 @@ #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value +// OBD2 PID polls +/* +#define PID_POLL_UNKNOWN1 0x9001 //122 in log +#define PID_POLL_UNKNOWNX 0x9002 //5531 (Possible SOC candidate) +*/ +#define PID_POLL_PACKVOLTAGE 0x9003 //Best guess, not confirmed +#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 //Guaranteed pack voltage +#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 //Best guess, not confirmed +#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008 // Cell number with highest voltage +#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A // Cell number with the lowest voltage +//#define PID_POLL_UNKNOWNX 0x900D +#define PID_POLL_12V_BATTERY 0x9011 //Confirmed 12v lead acid battery +/* +#define PID_POLL_UNKNOWNX 0x9012 //749 in log +#define PID_POLL_UNKNOWN3 0x9013 //736 in log +#define PID_POLL_UNKNOWN4 0x9014 //752 in log +#define PID_POLL_UNKNOWN4 0x901B // Multi frame message, lots of values ranging between 0x30 - 0x52 +#define PID_POLL_UNKNOWNX 0x912F // Multi frame message, empty +#define PID_POLL_UNKNOWNX 0x9129 +#define PID_POLL_UNKNOWNX 0x9131 +#define PID_POLL_UNKNOWNX 0x9132 +#define PID_POLL_UNKNOWNX 0x9133 +#define PID_POLL_UNKNOWNX 0x9134 +#define PID_POLL_UNKNOWNX 0x9135 +#define PID_POLL_UNKNOWNX 0x9136 +#define PID_POLL_UNKNOWNX 0x9137 +#define PID_POLL_UNKNOWNX 0x9138 +#define PID_POLL_UNKNOWNX 0x9139 +#define PID_POLL_UNKNOWNX 0x913A +#define PID_POLL_UNKNOWNX 0x913B +#define PID_POLL_UNKNOWNX 0x913C +#define PID_POLL_UNKNOWN5 0x912F +#define PID_POLL_UNKNOWNX 0x91B7 +#define PID_POLL_UNKNOWNX 0x91C1 +#define PID_POLL_UNKNOWNX 0x91CD +#define PID_POLL_UNKNOWNX 0x91CF +#define PID_POLL_UNKNOWNX 0x91F6 +#define PID_POLL_UNKNOWNX 0x91F7 +#define PID_POLL_UNKNOWNX 0x920F +#define PID_POLL_UNKNOWNx 0x9242 +#define PID_POLL_UNKNOWNx 0x9243 +*/ +#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245 //OK +/* +#define PID_POLL_UNKNOWNx 0x9256 +#define PID_POLL_UNKNOWNx 0x9261 +#define PID_POLL_UNKNOWN7 0x9284 +#define PID_POLL_UNKNOWNx 0xF012 +#define PID_POLL_UNKNOWNx 0xF1A0 +#define PID_POLL_UNKNOWNx 0xF182 +#define PID_POLL_UNKNOWNx 0xF187 +#define PID_POLL_UNKNOWNx 0xF188 +#define PID_POLL_UNKNOWNx 0xF18A +#define PID_POLL_UNKNOWNx 0xF18C +#define PID_POLL_UNKNOWNx 0xF191 +#define PID_POLL_UNKNOWNx 0xF194 +#define PID_POLL_UNKNOWNx 0xF195 +*/ + void setup_battery(void); void transmit_can_frame(CAN_frame* tx_frame, int interface); From f3b3bb92cfcc8f821305da5e5002bf7f83dd94b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 30 Mar 2025 21:49:25 +0300 Subject: [PATCH 12/37] Fix PID polling message frame --- Software/src/battery/CMFA-EV-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 51d048a3..c5815aa7 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -43,7 +43,7 @@ CAN_frame CMFA_POLLING_FRAME = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x79B, - .data = {0x30, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}}; + .data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}}; static uint32_t average_voltage_of_cells = 270000; static uint16_t highest_cell_voltage_mv = 3700; From be7a79686a1dc17253f08ffa3253d1fd85178786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 30 Mar 2025 22:11:51 +0300 Subject: [PATCH 13/37] Fix datatype casting on pack voltage --- Software/src/battery/CMFA-EV-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index c5815aa7..a1c7364c 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -166,7 +166,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { break; case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: average_voltage_of_cells = - (uint16_t)((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + (uint32_t)((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); break; case PID_POLL_HIGHEST_CELL_VOLTAGE: highest_cell_voltage_mv = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); From 9b77b135b659853b2517f8fb2cb192d90e07d852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 30 Mar 2025 22:25:34 +0300 Subject: [PATCH 14/37] Update TODO comments --- Software/src/battery/CMFA-EV-BATTERY.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index a1c7364c..0190e8a0 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -5,15 +5,11 @@ #include "CMFA-EV-BATTERY.h" /* TODO: -Before this integration can be considered stable, we need to: -- Find the following data points in the transmitted CAN data: - - Pack voltage (Current implementation might be wrong) - - Cellvoltage min - - Cellvoltage max - - Alternatively all these values can be taken from OBD2 PID polling +Integration considered stable! Following points can still be improved: +- Cellvoltage Min missing. Value now mapped to same as Cellvoltage Max +- All individual cellvoltages can not yet be viewed in the cellmonitor /* - /* Do not change code below unless you are sure what you are doing */ CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}}; CAN_frame CMFA_125 = {.FD = false, @@ -33,7 +29,6 @@ CAN_frame CMFA_3D3 = {.FD = false, .ID = 0x3D3, .data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}}; CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}}; - CAN_frame CMFA_ACK = {.FD = false, .ext_ID = false, .DLC = 8, From 9626c04233ed2409d7b4f84ab79214458e868b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 31 Mar 2025 15:18:53 +0300 Subject: [PATCH 15/37] Add more battery info page for CMFA --- Software/src/battery/CMFA-EV-BATTERY.cpp | 63 ++++++++++++++----- Software/src/battery/CMFA-EV-BATTERY.h | 9 ++- Software/src/datalayer/datalayer_extended.h | 11 ++++ .../webserver/advanced_battery_html.cpp | 15 ++++- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 0190e8a0..837592ff 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -1,6 +1,7 @@ #include "../include.h" #ifdef CMFA_EV_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" #include "../devboard/utils/events.h" #include "CMFA-EV-BATTERY.h" @@ -42,12 +43,14 @@ CAN_frame CMFA_POLLING_FRAME = {.FD = false, static uint32_t average_voltage_of_cells = 270000; static uint16_t highest_cell_voltage_mv = 3700; -static uint16_t leadAcidBatteryVoltage = 12000; -static uint16_t highest_cell_voltage_number = 0; -static uint16_t lowest_cell_voltage_number = 0; -static uint64_t cumulative_energy_when_discharging = 0; // Wh -static uint16_t pack_voltage_polled = 2700; -static uint32_t poll_pid = PID_POLL_PACKVOLTAGE; +static uint16_t lead_acid_voltage = 12000; +static uint8_t highest_cell_voltage_number = 0; +static uint8_t lowest_cell_voltage_number = 0; +static uint64_t cumulative_energy_when_discharging = 0; +static uint64_t cumulative_energy_when_charging = 0; +static uint64_t cumulative_energy_in_regen = 0; +static uint16_t soh_average = 10000; +static uint32_t poll_pid = PID_POLL_SOH_AVERAGE; static uint16_t pid_reply = 0; static uint8_t counter_10ms = 0; static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A, @@ -96,9 +99,18 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; - if (leadAcidBatteryVoltage < 11000) { //11.000V - set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); + if (lead_acid_voltage < 11000) { //11.000V + set_event(EVENT_12V_LOW, lead_acid_voltage); } + + // Update webserver datalayer + datalayer_extended.CMFAEV.lead_acid_voltage = lead_acid_voltage; + datalayer_extended.CMFAEV.highest_cell_voltage_number = highest_cell_voltage_number; + datalayer_extended.CMFAEV.lowest_cell_voltage_number = lowest_cell_voltage_number; + datalayer_extended.CMFAEV.cumulative_energy_when_discharging = cumulative_energy_when_discharging; + datalayer_extended.CMFAEV.cumulative_energy_when_charging = cumulative_energy_when_charging; + datalayer_extended.CMFAEV.cumulative_energy_in_regen = cumulative_energy_in_regen; + datalayer_extended.CMFAEV.soh_average = soh_average; } void handle_incoming_can_frame_battery(CAN_frame rx_frame) { @@ -155,9 +167,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; switch (pid_reply) { - case PID_POLL_PACKVOLTAGE: - pack_voltage_polled = - (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); //Hmm, not same LSB as others + case PID_POLL_SOH_AVERAGE: + soh_average = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: average_voltage_of_cells = @@ -173,12 +184,20 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { lowest_cell_voltage_number = rx_frame.data.u8[4]; break; case PID_POLL_12V_BATTERY: - leadAcidBatteryVoltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + lead_acid_voltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: cumulative_energy_when_discharging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING: + cumulative_energy_when_charging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; + case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: + cumulative_energy_in_regen = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + break; default: break; } @@ -219,9 +238,9 @@ void transmit_can_battery() { previousMillis200ms = currentMillis; switch (poll_pid) { - case PID_POLL_PACKVOLTAGE: - CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_PACKVOLTAGE >> 8); - CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_PACKVOLTAGE; + case PID_POLL_SOH_AVERAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_SOH_AVERAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_SOH_AVERAGE; poll_pid = PID_POLL_AVERAGE_VOLTAGE_OF_CELLS; break; case PID_POLL_AVERAGE_VOLTAGE_OF_CELLS: @@ -247,15 +266,25 @@ void transmit_can_battery() { case PID_POLL_12V_BATTERY: CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_12V_BATTERY >> 8); CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_12V_BATTERY; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING; + break; + case PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING; poll_pid = PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; break; case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING >> 8); CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING; - poll_pid = PID_POLL_PACKVOLTAGE; + poll_pid = PID_POLL_CUMULATIVE_ENERGY_IN_REGEN; + break; + case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_IN_REGEN >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_IN_REGEN; + poll_pid = PID_POLL_SOH_AVERAGE; break; default: - poll_pid = PID_POLL_PACKVOLTAGE; + poll_pid = PID_POLL_SOH_AVERAGE; break; } diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index 6e99e428..ec4c2268 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -14,7 +14,7 @@ #define PID_POLL_UNKNOWN1 0x9001 //122 in log #define PID_POLL_UNKNOWNX 0x9002 //5531 (Possible SOC candidate) */ -#define PID_POLL_PACKVOLTAGE 0x9003 //Best guess, not confirmed +#define PID_POLL_SOH_AVERAGE 0x9003 #define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 //Guaranteed pack voltage #define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 //Best guess, not confirmed #define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008 // Cell number with highest voltage @@ -42,6 +42,8 @@ #define PID_POLL_UNKNOWNX 0x913C #define PID_POLL_UNKNOWN5 0x912F #define PID_POLL_UNKNOWNX 0x91B7 +#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100% +#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100% #define PID_POLL_UNKNOWNX 0x91C1 #define PID_POLL_UNKNOWNX 0x91CD #define PID_POLL_UNKNOWNX 0x91CF @@ -49,9 +51,10 @@ #define PID_POLL_UNKNOWNX 0x91F7 #define PID_POLL_UNKNOWNX 0x920F #define PID_POLL_UNKNOWNx 0x9242 -#define PID_POLL_UNKNOWNx 0x9243 */ -#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245 //OK +#define PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING 0x9243 +#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245 +#define PID_POLL_CUMULATIVE_ENERGY_IN_REGEN 0x9247 /* #define PID_POLL_UNKNOWNx 0x9256 #define PID_POLL_UNKNOWNx 0x9261 diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 5578c8b0..d832acb4 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -252,6 +252,16 @@ typedef struct { bool warning_Charger_not_responding = false; } DATALAYER_INFO_CELLPOWER; +typedef struct { + uint16_t lead_acid_voltage = 0; + uint8_t highest_cell_voltage_number = 0; + uint8_t lowest_cell_voltage_number = 0; + uint64_t cumulative_energy_when_discharging = 0; + uint64_t cumulative_energy_when_charging = 0; + uint64_t cumulative_energy_in_regen = 0; + uint16_t soh_average = 0; +} DATALAYER_INFO_CMFAEV; + typedef struct { uint8_t total_cell_count = 0; int16_t battery_12V = 0; @@ -733,6 +743,7 @@ class DataLayerExtended { DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; + DATALAYER_INFO_CMFAEV CMFAEV; DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 5d85380e..796dfab4 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -432,6 +432,19 @@ String advanced_battery_processor(const String& var) { String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + ""; #endif //CELLPOWER_BMS +#ifdef CMFA_EV_BATTERY + content += "

SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "

"; + content += "

12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "

"; + content += "

Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "

"; + content += "

Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "

"; + content += + "

Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) + + "

"; + content += + "

Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) + "

"; + content += "

Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "

"; +#endif //CMFA_EV_BATTERY + #ifdef KIA_HYUNDAI_64_BATTERY content += "

Cells: " + String(datalayer_extended.KiaHyundai64.total_cell_count) + "S

"; content += "

12V voltage: " + String(datalayer_extended.KiaHyundai64.battery_12V / 10.0, 1) + "

"; @@ -1430,7 +1443,7 @@ String advanced_battery_processor(const String& var) { !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(VOLVO_SPA_HYBRID_BATTERY) && \ - !defined(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info + !defined(KIA_HYUNDAI_64_BATTERY) && !defined(CMFA_EV_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From 35f88a8fe281a5f9627a91a623ff62bde603147b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 31 Mar 2025 20:54:40 +0300 Subject: [PATCH 16/37] Add more PID polls --- Software/src/battery/CMFA-EV-BATTERY.cpp | 501 ++++++++++++++++++++++- Software/src/battery/CMFA-EV-BATTERY.h | 115 +++++- 2 files changed, 596 insertions(+), 20 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 837592ff..344da7f8 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -40,9 +40,21 @@ CAN_frame CMFA_POLLING_FRAME = {.FD = false, .DLC = 8, .ID = 0x79B, .data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}}; - +static bool end_of_charge = false; +static bool interlock_flag = false; +static uint16_t soc_z = 0; +static uint16_t soc_u = 0; +static uint16_t max_regen_power = 0; +static uint16_t max_discharge_power = 0; +static uint16_t average_temperature = 0; +static uint16_t minimum_temperature = 0; +static uint16_t maximum_temperature = 0; +static uint16_t maximum_charge_power = 0; +static uint16_t SOH_available_power = 0; +static uint16_t SOH_generated_power = 0; static uint32_t average_voltage_of_cells = 270000; static uint16_t highest_cell_voltage_mv = 3700; +static uint16_t lowest_cell_voltage_mv = 3700; static uint16_t lead_acid_voltage = 12000; static uint8_t highest_cell_voltage_number = 0; static uint8_t lowest_cell_voltage_number = 0; @@ -50,8 +62,10 @@ static uint64_t cumulative_energy_when_discharging = 0; static uint64_t cumulative_energy_when_charging = 0; static uint64_t cumulative_energy_in_regen = 0; static uint16_t soh_average = 10000; +static uint16_t cellvoltages_mv[72]; static uint32_t poll_pid = PID_POLL_SOH_AVERAGE; static uint16_t pid_reply = 0; + static uint8_t counter_10ms = 0; static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A, 0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02}; @@ -95,10 +109,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); - datalayer.battery.status.cell_min_voltage_mV = highest_cell_voltage_mv; //Placeholder until we can find minimum + datalayer.battery.status.cell_min_voltage_mV = lowest_cell_voltage_mv; datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t)); + if (lead_acid_voltage < 11000) { //11.000V set_event(EVENT_12V_LOW, lead_acid_voltage); } @@ -167,6 +184,12 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; switch (pid_reply) { + case PID_POLL_SOCZ: + soc_z = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_USOC: + soc_u = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; case PID_POLL_SOH_AVERAGE: soh_average = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; @@ -180,12 +203,51 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: highest_cell_voltage_number = rx_frame.data.u8[4]; break; + case PID_POLL_LOWEST_CELL_VOLTAGE: + lowest_cell_voltage_mv = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; case PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE: lowest_cell_voltage_number = rx_frame.data.u8[4]; break; + case PID_POLL_CURRENT_OFFSET: + //current_offset = + break; + case PID_POLL_INSTANT_CURRENT: + //instant_offset = + break; + case PID_POLL_MAX_REGEN: + max_regen_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_MAX_DISCHARGE_POWER: + max_discharge_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; case PID_POLL_12V_BATTERY: lead_acid_voltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; + case PID_POLL_AVERAGE_TEMPERATURE: + average_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_MIN_TEMPERATURE: + minimum_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_MAX_TEMPERATURE: + maximum_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_MAX_CHARGE_POWER: + maximum_charge_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_END_OF_CHARGE_FLAG: + end_of_charge = rx_frame.data.u8[4]; + break; + case PID_POLL_INTERLOCK_FLAG: + interlock_flag = rx_frame.data.u8[4]; + break; + case PID_POLL_SOH_AVAILABLE_POWER_CALCULATION: + SOH_available_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_SOH_GENERATED_POWER_CALCULATION: + SOH_generated_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; case PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING: cumulative_energy_when_discharging = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); @@ -197,6 +259,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: cumulative_energy_in_regen = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])); + case PID_POLL_CELL_1: + cellvoltages_mv[0] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_2: + cellvoltages_mv[1] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_3: + cellvoltages_mv[2] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; break; default: break; @@ -251,6 +322,11 @@ void transmit_can_battery() { case PID_POLL_HIGHEST_CELL_VOLTAGE: CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_HIGHEST_CELL_VOLTAGE >> 8); CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_HIGHEST_CELL_VOLTAGE; + poll_pid = PID_POLL_LOWEST_CELL_VOLTAGE; + break; + case PID_POLL_LOWEST_CELL_VOLTAGE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_LOWEST_CELL_VOLTAGE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_LOWEST_CELL_VOLTAGE; poll_pid = PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE; break; case PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE: @@ -281,9 +357,428 @@ void transmit_can_battery() { case PID_POLL_CUMULATIVE_ENERGY_IN_REGEN: CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CUMULATIVE_ENERGY_IN_REGEN >> 8); CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CUMULATIVE_ENERGY_IN_REGEN; + poll_pid = PID_POLL_SOCZ; + break; + case PID_POLL_SOCZ: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_SOCZ >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_SOCZ; + poll_pid = PID_POLL_USOC; + break; + case PID_POLL_USOC: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_USOC >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_USOC; + poll_pid = PID_POLL_CURRENT_OFFSET; + break; + case PID_POLL_CURRENT_OFFSET: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CURRENT_OFFSET >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CURRENT_OFFSET; + poll_pid = PID_POLL_INSTANT_CURRENT; + break; + case PID_POLL_INSTANT_CURRENT: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_INSTANT_CURRENT >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_INSTANT_CURRENT; + poll_pid = PID_POLL_MAX_REGEN; + break; + case PID_POLL_MAX_REGEN: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_REGEN >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_REGEN; + poll_pid = PID_POLL_MAX_DISCHARGE_POWER; + break; + case PID_POLL_MAX_DISCHARGE_POWER: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_DISCHARGE_POWER >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_DISCHARGE_POWER; + poll_pid = PID_POLL_MAX_CHARGE_POWER; + break; + case PID_POLL_MAX_CHARGE_POWER: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_CHARGE_POWER >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_CHARGE_POWER; + poll_pid = PID_POLL_AVERAGE_TEMPERATURE; + break; + case PID_POLL_AVERAGE_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_AVERAGE_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_AVERAGE_TEMPERATURE; + poll_pid = PID_POLL_MIN_TEMPERATURE; + break; + case PID_POLL_MIN_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MIN_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MIN_TEMPERATURE; + poll_pid = PID_POLL_MAX_TEMPERATURE; + break; + case PID_POLL_MAX_TEMPERATURE: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_MAX_TEMPERATURE >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_MAX_TEMPERATURE; + poll_pid = PID_POLL_END_OF_CHARGE_FLAG; + break; + case PID_POLL_END_OF_CHARGE_FLAG: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_END_OF_CHARGE_FLAG >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_END_OF_CHARGE_FLAG; + poll_pid = PID_POLL_INTERLOCK_FLAG; + break; + case PID_POLL_INTERLOCK_FLAG: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_INTERLOCK_FLAG >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_INTERLOCK_FLAG; + poll_pid = PID_POLL_CELL_1; + break; + case PID_POLL_CELL_1: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_1 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_1; + poll_pid = PID_POLL_CELL_2; + break; + case PID_POLL_CELL_2: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_2 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_2; + poll_pid = PID_POLL_CELL_3; + break; + case PID_POLL_CELL_3: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_3 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_3; + poll_pid = PID_POLL_CELL_4; + break; + case PID_POLL_CELL_4: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_4 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_4; + poll_pid = PID_POLL_CELL_5; + break; + case PID_POLL_CELL_5: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_5 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_5; + poll_pid = PID_POLL_CELL_6; + break; + case PID_POLL_CELL_6: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_6 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_6; + poll_pid = PID_POLL_CELL_7; + break; + case PID_POLL_CELL_7: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_7 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_7; + poll_pid = PID_POLL_CELL_8; + break; + case PID_POLL_CELL_8: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_8 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_8; + poll_pid = PID_POLL_CELL_9; + break; + case PID_POLL_CELL_9: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_9 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_9; + poll_pid = PID_POLL_CELL_10; + break; + case PID_POLL_CELL_10: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_10 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_10; + poll_pid = PID_POLL_CELL_11; + break; + case PID_POLL_CELL_11: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_11 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_11; + poll_pid = PID_POLL_CELL_12; + break; + case PID_POLL_CELL_12: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_12 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_12; + poll_pid = PID_POLL_CELL_13; + break; + case PID_POLL_CELL_13: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_13 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_13; + poll_pid = PID_POLL_CELL_14; + break; + case PID_POLL_CELL_14: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_14 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_14; + poll_pid = PID_POLL_CELL_15; + break; + case PID_POLL_CELL_15: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_15 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_15; + poll_pid = PID_POLL_CELL_16; + break; + case PID_POLL_CELL_16: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_16 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_16; + poll_pid = PID_POLL_CELL_17; + break; + case PID_POLL_CELL_17: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_17 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_17; + poll_pid = PID_POLL_CELL_18; + break; + case PID_POLL_CELL_18: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_18 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_18; + poll_pid = PID_POLL_CELL_19; + break; + case PID_POLL_CELL_19: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_19 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_19; + poll_pid = PID_POLL_CELL_20; + break; + case PID_POLL_CELL_20: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_20 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_20; + poll_pid = PID_POLL_CELL_21; + break; + case PID_POLL_CELL_21: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_21 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_21; + poll_pid = PID_POLL_CELL_22; + break; + case PID_POLL_CELL_22: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_22 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_22; + poll_pid = PID_POLL_CELL_23; + break; + case PID_POLL_CELL_23: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_23 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_23; + poll_pid = PID_POLL_CELL_24; + break; + case PID_POLL_CELL_24: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_24 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_24; + poll_pid = PID_POLL_CELL_25; + break; + case PID_POLL_CELL_25: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_25 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_25; + poll_pid = PID_POLL_CELL_26; + break; + case PID_POLL_CELL_26: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_26 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_26; + poll_pid = PID_POLL_CELL_27; + break; + case PID_POLL_CELL_27: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_27 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_27; + poll_pid = PID_POLL_CELL_28; + break; + case PID_POLL_CELL_28: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_28 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_28; + poll_pid = PID_POLL_CELL_29; + break; + case PID_POLL_CELL_29: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_29 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_29; + poll_pid = PID_POLL_CELL_30; + break; + case PID_POLL_CELL_30: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_30 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_30; + poll_pid = PID_POLL_CELL_31; + break; + case PID_POLL_CELL_31: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_31 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_31; + poll_pid = PID_POLL_CELL_32; + break; + case PID_POLL_CELL_32: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_32 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_32; + poll_pid = PID_POLL_CELL_33; + break; + case PID_POLL_CELL_33: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_33 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_33; + poll_pid = PID_POLL_CELL_34; + break; + case PID_POLL_CELL_34: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_34 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_34; + poll_pid = PID_POLL_CELL_35; + break; + case PID_POLL_CELL_35: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_35 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_35; + poll_pid = PID_POLL_CELL_36; + break; + case PID_POLL_CELL_36: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_36 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_36; + poll_pid = PID_POLL_CELL_37; + break; + case PID_POLL_CELL_37: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_37 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_37; + poll_pid = PID_POLL_CELL_38; + break; + case PID_POLL_CELL_38: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_38 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_38; + poll_pid = PID_POLL_CELL_39; + break; + case PID_POLL_CELL_39: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_39 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_39; + poll_pid = PID_POLL_CELL_40; + break; + case PID_POLL_CELL_40: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_40 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_40; + poll_pid = PID_POLL_CELL_41; + break; + case PID_POLL_CELL_41: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_41 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_41; + poll_pid = PID_POLL_CELL_42; + break; + case PID_POLL_CELL_42: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_42 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_42; + poll_pid = PID_POLL_CELL_43; + break; + case PID_POLL_CELL_43: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_43 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_43; + poll_pid = PID_POLL_CELL_44; + break; + case PID_POLL_CELL_44: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_44 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_44; + poll_pid = PID_POLL_CELL_45; + break; + case PID_POLL_CELL_45: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_45 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_45; + poll_pid = PID_POLL_CELL_46; + break; + case PID_POLL_CELL_46: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_46 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_46; + poll_pid = PID_POLL_CELL_47; + break; + case PID_POLL_CELL_47: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_47 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_47; + poll_pid = PID_POLL_CELL_48; + break; + case PID_POLL_CELL_48: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_48 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_48; + poll_pid = PID_POLL_CELL_49; + break; + case PID_POLL_CELL_49: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_49 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_49; + poll_pid = PID_POLL_CELL_50; + break; + case PID_POLL_CELL_50: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_50 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_50; + poll_pid = PID_POLL_CELL_51; + break; + case PID_POLL_CELL_51: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_51 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_51; + poll_pid = PID_POLL_CELL_52; + break; + case PID_POLL_CELL_52: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_52 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_52; + poll_pid = PID_POLL_CELL_53; + break; + case PID_POLL_CELL_53: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_53 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_53; + poll_pid = PID_POLL_CELL_54; + break; + case PID_POLL_CELL_54: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_54 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_54; + poll_pid = PID_POLL_CELL_55; + break; + case PID_POLL_CELL_55: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_55 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_55; + poll_pid = PID_POLL_CELL_56; + break; + case PID_POLL_CELL_56: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_56 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_56; + poll_pid = PID_POLL_CELL_57; + break; + case PID_POLL_CELL_57: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_57 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_57; + poll_pid = PID_POLL_CELL_58; + break; + case PID_POLL_CELL_58: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_58 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_58; + poll_pid = PID_POLL_CELL_59; + break; + case PID_POLL_CELL_59: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_59 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_59; + poll_pid = PID_POLL_CELL_60; + break; + case PID_POLL_CELL_60: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_60 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_60; + poll_pid = PID_POLL_CELL_61; + break; + case PID_POLL_CELL_61: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_61 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_61; + poll_pid = PID_POLL_CELL_62; + break; + case PID_POLL_CELL_62: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_62 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_62; + poll_pid = PID_POLL_CELL_63; + break; + case PID_POLL_CELL_63: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_63 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_63; + poll_pid = PID_POLL_CELL_64; + break; + case PID_POLL_CELL_64: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_64 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_64; + poll_pid = PID_POLL_CELL_65; + break; + case PID_POLL_CELL_65: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_65 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_65; + poll_pid = PID_POLL_CELL_66; + break; + case PID_POLL_CELL_66: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_66 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_66; + poll_pid = PID_POLL_CELL_67; + break; + case PID_POLL_CELL_67: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_67 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_67; + poll_pid = PID_POLL_CELL_68; + break; + case PID_POLL_CELL_68: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_68 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_68; + poll_pid = PID_POLL_CELL_69; + break; + case PID_POLL_CELL_69: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_69 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_69; + poll_pid = PID_POLL_CELL_70; + break; + case PID_POLL_CELL_70: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_70 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_70; + poll_pid = PID_POLL_CELL_71; + break; + case PID_POLL_CELL_71: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_71 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_71; + poll_pid = PID_POLL_CELL_72; + break; + case PID_POLL_CELL_72: + CMFA_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_POLL_CELL_72 >> 8); + CMFA_POLLING_FRAME.data.u8[3] = (uint8_t)PID_POLL_CELL_72; poll_pid = PID_POLL_SOH_AVERAGE; break; - default: poll_pid = PID_POLL_SOH_AVERAGE; break; } diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index ec4c2268..7a084ae7 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -9,23 +9,102 @@ #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value -// OBD2 PID polls -/* -#define PID_POLL_UNKNOWN1 0x9001 //122 in log -#define PID_POLL_UNKNOWNX 0x9002 //5531 (Possible SOC candidate) -*/ +// OBD2 PID polls. Some of these have been reverse engineered, but there are many unknown values still +#define PID_POLL_SOCZ 0x9001 //122 in log +#define PID_POLL_USOC 0x9002 //5531 (Possible SOC candidate) #define PID_POLL_SOH_AVERAGE 0x9003 -#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 //Guaranteed pack voltage -#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 //Best guess, not confirmed -#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008 // Cell number with highest voltage -#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A // Cell number with the lowest voltage -//#define PID_POLL_UNKNOWNX 0x900D -#define PID_POLL_12V_BATTERY 0x9011 //Confirmed 12v lead acid battery +#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 +#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 +#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008 +#define PID_POLL_LOWEST_CELL_VOLTAGE 0x9009 +#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A +#define PID_POLL_CURRENT_OFFSET 0x900C +#define PID_POLL_INSTANT_CURRENT 0x900D +#define PID_POLL_MAX_REGEN 0x900E +#define PID_POLL_MAX_DISCHARGE_POWER 0x900F +#define PID_POLL_12V_BATTERY 0x9011 +#define PID_POLL_AVERAGE_TEMPERATURE 0x9012 //749 in log +#define PID_POLL_MIN_TEMPERATURE 0x9013 //736 in log +#define PID_POLL_MAX_TEMPERATURE 0x9014 //752 in log +#define PID_POLL_MAX_CHARGE_POWER 0x9018 +#define PID_POLL_END_OF_CHARGE_FLAG 0x9019 +#define PID_POLL_INTERLOCK_FLAG 0x901A +#define PID_POLL_BATTERY_IDENTIFICATION 0x901B // Multi frame message +#define PID_POLL_CELL_1 0x9021 +#define PID_POLL_CELL_2 0x9022 +#define PID_POLL_CELL_3 0x9023 +#define PID_POLL_CELL_4 0x9024 +#define PID_POLL_CELL_5 0x9025 +#define PID_POLL_CELL_6 0x9026 +#define PID_POLL_CELL_7 0x9027 +#define PID_POLL_CELL_8 0x9028 +#define PID_POLL_CELL_9 0x9029 +#define PID_POLL_CELL_10 0x902A +#define PID_POLL_CELL_11 0x902B +#define PID_POLL_CELL_12 0x902C +#define PID_POLL_CELL_13 0x902D +#define PID_POLL_CELL_14 0x902E +#define PID_POLL_CELL_15 0x902F +#define PID_POLL_CELL_16 0x9030 +#define PID_POLL_CELL_17 0x9031 +#define PID_POLL_CELL_18 0x9032 +#define PID_POLL_CELL_19 0x9033 +#define PID_POLL_CELL_20 0x9034 +#define PID_POLL_CELL_21 0x9035 +#define PID_POLL_CELL_22 0x9036 +#define PID_POLL_CELL_23 0x9037 +#define PID_POLL_CELL_24 0x9038 +#define PID_POLL_CELL_25 0x9039 +#define PID_POLL_CELL_26 0x903A +#define PID_POLL_CELL_27 0x903B +#define PID_POLL_CELL_28 0x903C +#define PID_POLL_CELL_29 0x903D +#define PID_POLL_CELL_30 0x903E +#define PID_POLL_CELL_31 0x903F +#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060 0x9040 +#define PID_POLL_CELL_32 0x9041 +#define PID_POLL_CELL_33 0x9042 +#define PID_POLL_CELL_34 0x9043 +#define PID_POLL_CELL_35 0x9044 +#define PID_POLL_CELL_36 0x9045 +#define PID_POLL_CELL_37 0x9046 +#define PID_POLL_CELL_38 0x9047 +#define PID_POLL_CELL_39 0x9048 +#define PID_POLL_CELL_40 0x9049 +#define PID_POLL_CELL_41 0x904A +#define PID_POLL_CELL_42 0x904B +#define PID_POLL_CELL_43 0x904C +#define PID_POLL_CELL_44 0x904D +#define PID_POLL_CELL_45 0x904E +#define PID_POLL_CELL_46 0x904F +#define PID_POLL_CELL_47 0x9050 +#define PID_POLL_CELL_48 0x9051 +#define PID_POLL_CELL_49 0x9052 +#define PID_POLL_CELL_50 0x9053 +#define PID_POLL_CELL_51 0x9054 +#define PID_POLL_CELL_52 0x9055 +#define PID_POLL_CELL_53 0x9056 +#define PID_POLL_CELL_54 0x9057 +#define PID_POLL_CELL_55 0x9058 +#define PID_POLL_CELL_56 0x9059 +#define PID_POLL_CELL_57 0x905A +#define PID_POLL_CELL_58 0x905B +#define PID_POLL_CELL_59 0x905C +#define PID_POLL_CELL_60 0x905D +#define PID_POLL_CELL_61 0x905E +#define PID_POLL_CELL_62 0x905F +#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9061_9080 0x9060 +#define PID_POLL_CELL_63 0x9061 +#define PID_POLL_CELL_64 0x9062 +#define PID_POLL_CELL_65 0x9063 +#define PID_POLL_CELL_66 0x9064 +#define PID_POLL_CELL_67 0x9065 +#define PID_POLL_CELL_68 0x9066 +#define PID_POLL_CELL_69 0x9067 +#define PID_POLL_CELL_70 0x9068 +#define PID_POLL_CELL_71 0x9069 +#define PID_POLL_CELL_72 0x906A /* -#define PID_POLL_UNKNOWNX 0x9012 //749 in log -#define PID_POLL_UNKNOWN3 0x9013 //736 in log -#define PID_POLL_UNKNOWN4 0x9014 //752 in log -#define PID_POLL_UNKNOWN4 0x901B // Multi frame message, lots of values ranging between 0x30 - 0x52 #define PID_POLL_UNKNOWNX 0x912F // Multi frame message, empty #define PID_POLL_UNKNOWNX 0x9129 #define PID_POLL_UNKNOWNX 0x9131 @@ -42,8 +121,10 @@ #define PID_POLL_UNKNOWNX 0x913C #define PID_POLL_UNKNOWN5 0x912F #define PID_POLL_UNKNOWNX 0x91B7 -#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100% -#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100% +*/ +#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100% +#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100% +/* #define PID_POLL_UNKNOWNX 0x91C1 #define PID_POLL_UNKNOWNX 0x91CD #define PID_POLL_UNKNOWNX 0x91CF From 94cd005f848ee8b4b012d3b1e5ab53daae2434c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 31 Mar 2025 21:09:27 +0300 Subject: [PATCH 17/37] Add more to datalayer --- Software/src/battery/CMFA-EV-BATTERY.cpp | 22 ++++++++++++++----- Software/src/datalayer/datalayer_extended.h | 12 +++++++++- .../webserver/advanced_battery_html.cpp | 21 +++++++++++++----- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 344da7f8..216f829e 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -46,9 +46,9 @@ static uint16_t soc_z = 0; static uint16_t soc_u = 0; static uint16_t max_regen_power = 0; static uint16_t max_discharge_power = 0; -static uint16_t average_temperature = 0; -static uint16_t minimum_temperature = 0; -static uint16_t maximum_temperature = 0; +static int16_t average_temperature = 0; +static int16_t minimum_temperature = 0; +static int16_t maximum_temperature = 0; static uint16_t maximum_charge_power = 0; static uint16_t SOH_available_power = 0; static uint16_t SOH_generated_power = 0; @@ -121,9 +121,19 @@ void update_values_battery() { //This function maps all the values fetched via } // Update webserver datalayer + datalayer_extended.CMFAEV.soc_u = soc_u; + datalayer_extended.CMFAEV.soc_z = soc_z; datalayer_extended.CMFAEV.lead_acid_voltage = lead_acid_voltage; datalayer_extended.CMFAEV.highest_cell_voltage_number = highest_cell_voltage_number; datalayer_extended.CMFAEV.lowest_cell_voltage_number = lowest_cell_voltage_number; + datalayer_extended.CMFAEV.max_regen_power = max_regen_power; + datalayer_extended.CMFAEV.max_discharge_power = max_discharge_power; + datalayer_extended.CMFAEV.average_temperature = average_temperature; + datalayer_extended.CMFAEV.minimum_temperature = minimum_temperature; + datalayer_extended.CMFAEV.maximum_temperature = maximum_temperature; + datalayer_extended.CMFAEV.maximum_charge_power = maximum_charge_power; + datalayer_extended.CMFAEV.SOH_available_power = SOH_available_power; + datalayer_extended.CMFAEV.SOH_generated_power = SOH_generated_power; datalayer_extended.CMFAEV.cumulative_energy_when_discharging = cumulative_energy_when_discharging; datalayer_extended.CMFAEV.cumulative_energy_when_charging = cumulative_energy_when_charging; datalayer_extended.CMFAEV.cumulative_energy_in_regen = cumulative_energy_in_regen; @@ -225,13 +235,13 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { lead_acid_voltage = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; case PID_POLL_AVERAGE_TEMPERATURE: - average_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + average_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); break; case PID_POLL_MIN_TEMPERATURE: - minimum_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + minimum_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); break; case PID_POLL_MAX_TEMPERATURE: - maximum_temperature = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + maximum_temperature = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) - 400) / 2); break; case PID_POLL_MAX_CHARGE_POWER: maximum_charge_power = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index d832acb4..93c24652 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -253,13 +253,23 @@ typedef struct { } DATALAYER_INFO_CELLPOWER; typedef struct { + uint16_t soc_z = 0; + uint16_t soc_u = 0; + uint16_t soh_average = 0; + uint16_t max_regen_power = 0; + uint16_t max_discharge_power = 0; + int16_t average_temperature = 0; + int16_t minimum_temperature = 0; + int16_t maximum_temperature = 0; + uint16_t maximum_charge_power = 0; + uint16_t SOH_available_power = 0; + uint16_t SOH_generated_power = 0; uint16_t lead_acid_voltage = 0; uint8_t highest_cell_voltage_number = 0; uint8_t lowest_cell_voltage_number = 0; uint64_t cumulative_energy_when_discharging = 0; uint64_t cumulative_energy_when_charging = 0; uint64_t cumulative_energy_in_regen = 0; - uint16_t soh_average = 0; } DATALAYER_INFO_CMFAEV; typedef struct { diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 796dfab4..140a95f6 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -433,16 +433,27 @@ String advanced_battery_processor(const String& var) { #endif //CELLPOWER_BMS #ifdef CMFA_EV_BATTERY - content += "

SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "

"; - content += "

12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "

"; + content += "

SOC U: " + String(datalayer_extended.CMFAEV.soc_u) + "percent

"; + content += "

SOC Z: " + String(datalayer_extended.CMFAEV.soc_z) + "percent

"; + content += "

SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "pptt

"; + content += "

12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV

"; content += "

Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "

"; content += "

Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "

"; + content += "

Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + "

"; + content += "

Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + "

"; + content += "

Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + "

"; + content += "

SOH available power: " + String(datalayer_extended.CMFAEV.SOH_available_power) + "

"; + content += "

SOH generated power: " + String(datalayer_extended.CMFAEV.SOH_generated_power) + "

"; + content += "

Average temperature: " + String(datalayer_extended.CMFAEV.average_temperature) + "dC

"; + content += "

Maximum temperature: " + String(datalayer_extended.CMFAEV.maximum_temperature) + "dC

"; + content += "

Minimum temperature: " + String(datalayer_extended.CMFAEV.minimum_temperature) + "dC

"; content += "

Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) + - "

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

Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) + + "Wh

"; content += - "

Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) + "

"; - content += "

Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "

"; + "

Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh

"; #endif //CMFA_EV_BATTERY #ifdef KIA_HYUNDAI_64_BATTERY From f4a7de639ffe6bb5ba4d4990caea6e3c543d90e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 31 Mar 2025 21:16:20 +0300 Subject: [PATCH 18/37] Add all cellvoltage PID replies --- Software/src/battery/CMFA-EV-BATTERY.cpp | 207 +++++++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index 216f829e..671bbee6 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -278,6 +278,213 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { case PID_POLL_CELL_3: cellvoltages_mv[2] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; + case PID_POLL_CELL_4: + cellvoltages_mv[3] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_5: + cellvoltages_mv[4] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_6: + cellvoltages_mv[5] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_7: + cellvoltages_mv[6] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_8: + cellvoltages_mv[7] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_9: + cellvoltages_mv[8] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_10: + cellvoltages_mv[9] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_11: + cellvoltages_mv[10] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_12: + cellvoltages_mv[11] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_13: + cellvoltages_mv[12] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_14: + cellvoltages_mv[13] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_15: + cellvoltages_mv[14] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_16: + cellvoltages_mv[15] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_17: + cellvoltages_mv[16] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_18: + cellvoltages_mv[17] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_19: + cellvoltages_mv[18] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_20: + cellvoltages_mv[19] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_21: + cellvoltages_mv[20] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_22: + cellvoltages_mv[21] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_23: + cellvoltages_mv[22] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_24: + cellvoltages_mv[23] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_25: + cellvoltages_mv[24] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_26: + cellvoltages_mv[25] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_27: + cellvoltages_mv[26] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_28: + cellvoltages_mv[27] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_29: + cellvoltages_mv[28] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_30: + cellvoltages_mv[29] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_31: + cellvoltages_mv[30] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_32: + cellvoltages_mv[31] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_33: + cellvoltages_mv[32] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_34: + cellvoltages_mv[33] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_35: + cellvoltages_mv[34] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_36: + cellvoltages_mv[35] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_37: + cellvoltages_mv[36] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_38: + cellvoltages_mv[37] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_39: + cellvoltages_mv[38] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_40: + cellvoltages_mv[39] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_41: + cellvoltages_mv[40] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_42: + cellvoltages_mv[41] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_43: + cellvoltages_mv[42] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_44: + cellvoltages_mv[43] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_45: + cellvoltages_mv[44] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_46: + cellvoltages_mv[45] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_47: + cellvoltages_mv[46] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_48: + cellvoltages_mv[47] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_49: + cellvoltages_mv[48] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_50: + cellvoltages_mv[49] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_51: + cellvoltages_mv[50] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_52: + cellvoltages_mv[51] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_53: + cellvoltages_mv[52] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_54: + cellvoltages_mv[53] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_55: + cellvoltages_mv[54] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_56: + cellvoltages_mv[55] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_57: + cellvoltages_mv[56] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_58: + cellvoltages_mv[57] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_59: + cellvoltages_mv[58] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_60: + cellvoltages_mv[59] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_61: + cellvoltages_mv[60] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_62: + cellvoltages_mv[61] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_63: + cellvoltages_mv[62] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_64: + cellvoltages_mv[63] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_65: + cellvoltages_mv[64] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_66: + cellvoltages_mv[65] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_67: + cellvoltages_mv[66] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_68: + cellvoltages_mv[67] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_69: + cellvoltages_mv[68] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_70: + cellvoltages_mv[69] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_71: + cellvoltages_mv[70] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_POLL_CELL_72: + cellvoltages_mv[71] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; break; default: break; From 2c3ae3d79d846bfa75225a75e43e9c709ab09b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 1 Apr 2025 14:47:41 +0300 Subject: [PATCH 19/37] Add support for 50kWh standard range --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 126 +++++++++++++++----- Software/src/battery/BYD-ATTO-3-BATTERY.h | 8 +- 2 files changed, 104 insertions(+), 30 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index e78d08c7..43583781 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -12,6 +12,10 @@ */ /* Do not change code below unless you are sure what you are doing */ +#define NOT_DETERMINED_YET 0 +#define STANDARD_RANGE 1 +#define EXTENDED_RANGE 2 +static uint8_t battery_type = NOT_DETERMINED_YET; static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send @@ -37,8 +41,7 @@ static int16_t BMS_average_cell_temperature = 0; static uint16_t BMS_lowest_cell_voltage_mV = 3300; static uint16_t BMS_highest_cell_voltage_mV = 3300; static uint8_t battery_frame_index = 0; -#define NOF_CELLS 126 -static uint16_t battery_cellvoltages[NOF_CELLS] = {0}; +static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0}; #ifdef DOUBLE_BATTERY static int16_t battery2_temperature_ambient = 0; static int16_t battery2_daughterboard_temperatures[10]; @@ -56,7 +59,7 @@ static int16_t BMS2_average_cell_temperature = 0; static uint16_t BMS2_lowest_cell_voltage_mV = 3300; static uint16_t BMS2_highest_cell_voltage_mV = 3300; static uint8_t battery2_frame_index = 0; -static uint16_t battery2_cellvoltages[NOF_CELLS] = {0}; +static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0}; #endif //DOUBLE_BATTERY #define POLL_FOR_BATTERY_SOC 0x05 #define POLL_FOR_BATTERY_VOLTAGE 0x08 @@ -90,20 +93,39 @@ CAN_frame ATTO_3_7E7_POLL = {.FD = false, // Define the data points for %SOC depending on pack voltage const uint8_t numPoints = 14; const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0}; -const uint16_t voltage[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130, - 4121, 4119, 4100, 4070, 4030, 3950, 3800}; +const uint16_t voltage_extended[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130, + 4121, 4119, 4100, 4070, 4030, 3950, 3800}; +const uint16_t voltage_standard[numPoints] = {3620, 3485, 3443, 3435, 3433, 3425, 3400, + 3392, 3390, 3375, 3350, 3315, 3250, 3140}; -uint16_t estimateSOC(uint16_t packVoltage) { // Linear interpolation function - if (packVoltage >= voltage[0]) { +uint16_t estimateSOCextended(uint16_t packVoltage) { // Linear interpolation function + if (packVoltage >= voltage_extended[0]) { return SOC[0]; } - if (packVoltage <= voltage[numPoints - 1]) { + if (packVoltage <= voltage_extended[numPoints - 1]) { return SOC[numPoints - 1]; } for (int i = 1; i < numPoints; ++i) { - if (packVoltage >= voltage[i]) { - double t = (packVoltage - voltage[i]) / (voltage[i - 1] - voltage[i]); + if (packVoltage >= voltage_extended[i]) { + double t = (packVoltage - voltage_extended[i]) / (voltage_extended[i - 1] - voltage_extended[i]); + return SOC[i] + t * (SOC[i - 1] - SOC[i]); + } + } + return 0; // Default return for safety, should never reach here +} + +uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation function + if (packVoltage >= voltage_standard[0]) { + return SOC[0]; + } + if (packVoltage <= voltage_standard[numPoints - 1]) { + return SOC[numPoints - 1]; + } + + for (int i = 1; i < numPoints; ++i) { + if (packVoltage >= voltage_standard[i]) { + double t = (packVoltage - voltage_standard[i]) / (voltage_standard[i - 1] - voltage_standard[i]); return SOC[i] + t * (SOC[i - 1] - SOC[i]); } } @@ -120,7 +142,12 @@ void update_values_battery() { //This function maps all the values fetched via // When the battery is crashed hard, it locks itself and SOC becomes unavailable. // We instead estimate the SOC% based on the battery voltage. // This is a bad solution, you wont be able to use 100% of the battery - datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); + if (battery_type == EXTENDED_RANGE) { + datalayer.battery.status.real_soc = estimateSOCextended(datalayer.battery.status.voltage_dV); + } + if (battery_type == STANDARD_RANGE) { + datalayer.battery.status.real_soc = estimateSOCstandard(datalayer.battery.status.voltage_dV); + } SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC datalayer.battery.status.real_soc = battery_highprecision_SOC * 10; @@ -141,7 +168,41 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; //Map all cell voltages to the global array - memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, NOF_CELLS * sizeof(uint16_t)); + memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); + + // Check if we are on Standard range or Extended range battery. + // We use a variety of checks to ensure we catch a potential Standard range battery + if ((battery_cellvoltages[125] > 0) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = EXTENDED_RANGE; + } + if ((battery_cellvoltages[104] == 4095) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //This cell reading is always 4095 on Standard range + } + if ((battery_daughterboard_temperatures[9] == 215) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //Sensor 10 is missing on Standard range + } + if ((battery_daughterboard_temperatures[8] == 215) && (battery_type == NOT_DETERMINED_YET)) { + battery_type = STANDARD_RANGE; //Sensor 9 is missing on Standard range + } + + switch (battery_type) { + case STANDARD_RANGE: + datalayer.battery.info.total_capacity_Wh = 50000; + datalayer.battery.info.number_of_cells = CELLCOUNT_STANDARD; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; + break; + case EXTENDED_RANGE: + datalayer.battery.info.total_capacity_Wh = 60000; + datalayer.battery.info.number_of_cells = CELLCOUNT_EXTENDED; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV; + break; + case NOT_DETERMINED_YET: + default: + //Do nothing + break; + } #ifdef SKIP_TEMPERATURE_SENSOR_NUMBER // Initialize min and max variables for temperature calculation @@ -263,7 +324,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery_frame_index = rx_frame.data.u8[0]; - if (battery_frame_index < (NOF_CELLS / 3)) { + if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) { uint8_t base_index = battery_frame_index * 3; for (uint8_t i = 0; i < 3; i++) { battery_cellvoltages[base_index + i] = @@ -447,23 +508,19 @@ void transmit_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63); datalayer.system.info.battery_protocol[63] = '\0'; - datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; - 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_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range 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; - //Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V - //We now init the value to 380.1V to avoid false positive events. - datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1; #ifdef DOUBLE_BATTERY - datalayer.battery2.info.number_of_cells = 126; + datalayer.battery2.info.number_of_cells = CELLCOUNT_STANDARD; datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP; - datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; - datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; - datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; - datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; + datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; + datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV; + datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV; datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; #endif //DOUBLE_BATTERY } @@ -478,7 +535,12 @@ void update_values_battery2() { //This function maps all the values fetched via // We instead estimate the SOC% based on the battery2 voltage // This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that! - datalayer.battery2.status.real_soc = estimateSOC(datalayer.battery2.status.voltage_dV); + if (battery_type == EXTENDED_RANGE) { + datalayer.battery2.status.real_soc = estimateSOCextended(datalayer.battery2.status.voltage_dV); + } + if (battery_type == STANDARD_RANGE) { + datalayer.battery2.status.real_soc = estimateSOCstandard(datalayer.battery2.status.voltage_dV); + } datalayer.battery2.status.current_dA = -BMS2_current; @@ -498,7 +560,12 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10; //Map all cell voltages to the global array - memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, NOF_CELLS * sizeof(uint16_t)); + memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); + + datalayer.battery2.info.total_capacity_Wh = datalayer.battery.info.total_capacity_Wh; + datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells; + datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; + datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; } void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { @@ -515,7 +582,10 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { case 0x286: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; - case 0x334 datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x338: + case 0x334: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x338: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x344: @@ -568,7 +638,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { case 0x43D: datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery2_frame_index = rx_frame.data.u8[0]; - if (battery2_frame_index < (NOF_CELLS / 3)) { + if (battery2_frame_index < (CELLCOUNT_EXTENDED / 3)) { uint8_t base2_index = battery2_frame_index * 3; for (uint8_t i = 0; i < 3; i++) { battery2_cellvoltages[base2_index + i] = diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index f726ebc6..21454223 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -14,8 +14,12 @@ /* Do not modify the rows below */ #define BATTERY_SELECTED -#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V -#define MIN_PACK_VOLTAGE_DV 3800 +#define CELLCOUNT_EXTENDED 126 +#define CELLCOUNT_STANDARD 104 +#define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range +#define MIN_PACK_VOLTAGE_EXTENDED_DV 3800 //Extended range +#define MAX_PACK_VOLTAGE_STANDARD_DV 3640 //Standard range +#define MIN_PACK_VOLTAGE_STANDARD_DV 3136 //Standard range #define MAX_CELL_DEVIATION_MV 150 #define MAX_CELL_VOLTAGE_MV 3800 //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 From 41d8d5cf272c3d7f97f97e1c8c0fcbee8fb0c446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 1 Apr 2025 23:48:13 +0300 Subject: [PATCH 20/37] Improve wording --- Software/src/battery/BYD-ATTO-3-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index 21454223..3060eb64 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -4,7 +4,7 @@ #include "../include.h" #define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ - // Uncomment this only if you know your BMS is unlocked and able to send SOC% + // Comment out this only if you know your BMS is unlocked and able to send SOC% #define MAXPOWER_CHARGE_W 10000 #define MAXPOWER_DISCHARGE_W 10000 From efabac4b8ec9e1b56b2616f6d78f98ddc8416dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 6 Apr 2025 21:26:04 +0300 Subject: [PATCH 21/37] Add negative SOC scaling --- Software/Software.ino | 6 ++++++ Software/src/datalayer/datalayer.h | 2 +- Software/src/devboard/webserver/settings_html.cpp | 7 ++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index f779ed5d..4666a031 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -427,6 +427,12 @@ void update_calculated_values() { calc_soc = 10000 * (calc_soc - datalayer.battery.settings.min_percentage); calc_soc = calc_soc / (datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage); datalayer.battery.status.reported_soc = calc_soc; + //Extra safety since we allow scaling negatively, if real% is < 1.00%, zero it out + if (datalayer.battery.status.real_soc < 100) { + datalayer.battery.status.reported_soc = 0; + } else { + datalayer.battery.status.reported_soc = calc_soc; + } // Calculate the scaled remaining capacity in Wh if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index e9fc9867..9ce97676 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -113,7 +113,7 @@ typedef struct { /** Minimum percentage setting. Set this value to the lowest real SOC * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 0% */ - uint16_t min_percentage = BATTERY_MINPERCENTAGE; + int16_t min_percentage = BATTERY_MINPERCENTAGE; /** Maximum percentage setting. Set this value to the highest real SOC * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 100% */ diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 0fc0dae9..2735a362 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -209,10 +209,11 @@ String settings_processor(const String& var) { "100.0');}}}"; content += "function editSocMin(){var value=prompt('Inverter will see completely discharged (0pct)SOC when this value is " - "reached. Enter new minimum SOC value that battery will discharge to " - "(0-50.0):');if(value!==null){if(value>=0&&value<=50){var xhr=new " + "reached. Advanced users can set to negative values. Enter new minimum SOC value that battery will discharge " + "to " + "(-10.0to50.0):');if(value!==null){if(value>=-10&&value<=50){var xhr=new " "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" - "updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 and " + "updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between -10 and " "50.0');}}}"; content += "function editMaxChargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new " From a5c99488dd90f2d027978eb8fa6233e4488217d2 Mon Sep 17 00:00:00 2001 From: James Brookes Date: Sun, 6 Apr 2025 19:40:57 +0100 Subject: [PATCH 22/37] Feature: Add Tesla BMS ECU reset, basic UDS response logging --- Software/src/battery/TESLA-BATTERY.cpp | 85 +++++++++++++++++-- Software/src/datalayer/datalayer.h | 1 + .../webserver/advanced_battery_html.cpp | 15 +++- Software/src/devboard/webserver/webserver.cpp | 9 ++ 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 86fbf16c..da3337f9 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -8,9 +8,11 @@ /* Do not change code below unless you are sure what you are doing */ /* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */ -static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was send -static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send -static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was sent +static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent static bool alternate243 = false; //0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms CAN_frame TESLA_221_1 = { @@ -50,12 +52,14 @@ CAN_frame TESLA_129 = {.FD = false, .DLC = 8, .ID = 0x129, .data = {0x21, 0x24, 0x36, 0x5F, 0x00, 0x20, 0xFF, 0x3F}}; +//0x612 UDS diagnostic requests - on demand CAN_frame TESLA_602 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x602, - .data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Diagnostic request + .data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; static uint8_t stateMachineClearIsolationFault = 0xFF; +static uint8_t stateMachineBMSReset = 0xFF; static uint16_t sendContactorClosingMessagesStill = 300; static uint16_t battery_cell_max_v = 3300; static uint16_t battery_cell_min_v = 3300; @@ -867,9 +871,18 @@ void update_values_battery() { //This function maps all the values fetched via // Check if user requests some action if (datalayer.battery.settings.user_requests_isolation_clear) { - stateMachineClearIsolationFault = 0; //Start the statemachine + stateMachineClearIsolationFault = 0; //Start the isolation fault statemachine datalayer.battery.settings.user_requests_isolation_clear = false; } + if (datalayer.battery.settings.user_requests_bms_ecu_reset) { + if (battery_contactor == 1 && battery_BMS_a180_SW_ECU_reset_blocked == false) { + stateMachineBMSReset = + 0; //Start the BMS ECU reset statemachine, only if contactors are OPEN and BMS ECU allows it + datalayer.battery.settings.user_requests_bms_ecu_reset = false; + } else { + logging.println("ERROR: BMS ECU reset failed due to contactors not being open, or BMS ECU not allowing it"); + } + } // Update webserver datalayer //0x20A @@ -1801,6 +1814,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) { BMS_SerialNumber[13] = rx_frame.data.u8[7]; } break; + case 0x612: // CAN UDS responses for BMS ECU reset + if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU unlocked"); + } else if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU reset request successful but ECU busy, response pending"); + } else if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) { + logging.println("CAN UDS response: ECU reset positive response, 1 second downtime"); + } + break; default: break; } @@ -1949,6 +1971,7 @@ the first, for a few cycles, then stop all messages which causes the contactor case 4: TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message indicating ECU unlocked stateMachineClearIsolationFault = 5; break; case 5: @@ -1961,6 +1984,58 @@ the first, for a few cycles, then stop all messages which causes the contactor stateMachineClearIsolationFault = 0xFF; break; } + if (stateMachineBMSReset != 0xFF) { + //This implementation should be rewritten to actually replying to the UDS replied sent by the BMS + //While this may work, it is not the correct way to implement this clearing logic + switch (stateMachineBMSReset) { + case 0: + TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 1; + break; + case 1: + TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 2; + break; + case 2: + TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 3; + break; + case 3: + TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 4; + break; + case 4: + TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message indicating ECU unlocked + stateMachineBMSReset = 5; + break; + case 5: + TESLA_602.data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 6; + break; + case 6: + TESLA_602.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineBMSReset = 7; + break; + case 7: + TESLA_602.data = {0x02, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + //Should generate a CAN UDS log message(s) indicating ECU has reset + stateMachineBMSReset = 0xFF; + break; + default: + //Something went wrong. Reset all and cancel + stateMachineBMSReset = 0xFF; + break; + } + } } } } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index e9fc9867..8a0d1cb8 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -142,6 +142,7 @@ typedef struct { /* Bool for specifying if user has requested manual function */ bool user_requests_balancing = false; bool user_requests_isolation_clear = false; + bool user_requests_bms_ecu_reset = false; /* Forced balancing max time & start timestamp */ uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms) uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index c7da7f07..c862cc5c 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -657,6 +657,7 @@ String advanced_battery_processor(const String& var) { //Buttons for user action content += ""; + content += ""; //0x20A 522 HVP_contatorState content += "

Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "

"; content += "

HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "

"; @@ -683,7 +684,7 @@ String advanced_battery_processor(const String& var) { sizeof(datalayer_extended.tesla.BMS_SerialNumber)); readableSerialNumber[14] = '\0'; // Null terminate the string content += "

BMS Serial number: " + String(readableSerialNumber) + "

"; - // Comment what data you would like to dislay, order can be changed. + // Comment what data you would like to display, order can be changed. //0x352 850 BMS_energyStatus if (datalayer_extended.tesla.BMS352_mux == false) { content += "

BMS 0x352 w/o mux

"; //if using older BMS <2021 and comment 0x352 without MUX @@ -1464,6 +1465,18 @@ String advanced_battery_processor(const String& var) { content += "function goToMainPage() { window.location.href = '/'; }"; content += ""; content += ""; + content += ""; content += "