mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 02:39:57 +02:00
Merge branch 'main' into kostal-class
This commit is contained in:
commit
cdc4558c7f
10 changed files with 1060 additions and 928 deletions
|
@ -25,9 +25,15 @@ void setup_battery() {
|
|||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
if (battery2 == nullptr) {
|
||||
#ifdef BMW_I3_BATTERY
|
||||
battery2 =
|
||||
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
|
||||
can_config.battery_double, WUP_PIN2);
|
||||
#else
|
||||
battery2 =
|
||||
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
|
||||
nullptr, can_config.battery_double);
|
||||
#endif
|
||||
}
|
||||
battery2->setup();
|
||||
#endif
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
#include "../include.h"
|
||||
#ifdef BMW_I3_BATTERY
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../datalayer/datalayer_extended.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "BMW-I3-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
|
||||
static BatterySize detectedBattery = BATTERY_60AH;
|
||||
static BatterySize detectedBattery2 = BATTERY_60AH; // For double battery setups
|
||||
|
||||
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
|
||||
|
||||
static CmdState cmdState = SOC;
|
||||
|
||||
const unsigned char crc8_table[256] =
|
||||
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
|
||||
|
@ -43,312 +26,6 @@ const unsigned char crc8_table[256] =
|
|||
0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
|
||||
0xE3, 0xFE, 0xD9, 0xC4};
|
||||
|
||||
/* CAN messages from PT-CAN2 not needed to operate the battery
|
||||
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
|
||||
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
|
||||
*/
|
||||
|
||||
CAN_frame BMW_10B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 3,
|
||||
.ID = 0x10B,
|
||||
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
|
||||
CAN_frame BMW_12F = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x12F,
|
||||
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
|
||||
CAN_frame BMW_13E = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x13E,
|
||||
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
|
||||
CAN_frame BMW_192 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x192,
|
||||
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_19B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x19B,
|
||||
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_1D0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x1D0,
|
||||
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
|
||||
CAN_frame BMW_2E2 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x2E2,
|
||||
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
|
||||
CAN_frame BMW_30B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x30B,
|
||||
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
|
||||
CAN_frame BMW_328 = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x328, .data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
|
||||
CAN_frame BMW_37B = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x37B, .data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
|
||||
CAN_frame BMW_380 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x380,
|
||||
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
|
||||
CAN_frame BMW_3A0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3A0,
|
||||
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
|
||||
CAN_frame BMW_3A7 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x3A7,
|
||||
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
|
||||
CAN_frame BMW_3C5 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3C5,
|
||||
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
|
||||
CAN_frame BMW_3CA = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3CA,
|
||||
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
|
||||
CAN_frame BMW_3E4 = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x3E4, .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
|
||||
CAN_frame BMW_3EC = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3EC,
|
||||
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
|
||||
CAN_frame BMW_3F9 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3F9,
|
||||
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
|
||||
CAN_frame BMW_3FB = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x3FB, .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
|
||||
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
|
||||
CAN_frame BMW_418 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x418,
|
||||
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}};
|
||||
CAN_frame BMW_433 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x433,
|
||||
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
|
||||
CAN_frame BMW_512 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x512,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
|
||||
CAN_frame BMW_592_0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x592,
|
||||
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
|
||||
CAN_frame BMW_592_1 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x592,
|
||||
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame BMW_5F8 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x5F8,
|
||||
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
|
||||
CAN_frame BMW_6F1_CELL = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
|
||||
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}};
|
||||
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
|
||||
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F1,
|
||||
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
|
||||
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}};
|
||||
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
|
||||
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
|
||||
|
||||
//The above CAN messages need to be sent towards the battery to keep it alive
|
||||
|
||||
static uint8_t startup_counter_contactor = 0;
|
||||
static uint8_t alive_counter_20ms = 0;
|
||||
static uint8_t alive_counter_100ms = 0;
|
||||
static uint8_t alive_counter_200ms = 0;
|
||||
static uint8_t alive_counter_500ms = 0;
|
||||
static uint8_t alive_counter_1000ms = 0;
|
||||
static uint8_t alive_counter_5000ms = 0;
|
||||
static uint8_t BMW_1D0_counter = 0;
|
||||
static uint8_t BMW_13E_counter = 0;
|
||||
static uint8_t BMW_380_counter = 0;
|
||||
static uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
|
||||
static uint16_t BMW_328_days =
|
||||
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
|
||||
static uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
|
||||
|
||||
static bool battery_awake = false;
|
||||
static bool battery2_awake = false;
|
||||
static bool battery_info_available = false;
|
||||
static bool battery2_info_available = false;
|
||||
static bool skipCRCCheck = false;
|
||||
static bool CRCCheckPassedPreviously = false;
|
||||
static bool skipCRCCheck_battery2 = false;
|
||||
static bool CRCCheckPassedPreviously_battery2 = false;
|
||||
|
||||
static uint16_t cellvoltage_temp_mV = 0;
|
||||
static uint32_t battery_serial_number = 0;
|
||||
static uint32_t battery_available_power_shortterm_charge = 0;
|
||||
static uint32_t battery_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery_available_power_longterm_charge = 0;
|
||||
static uint32_t battery_available_power_longterm_discharge = 0;
|
||||
static uint32_t battery_BEV_available_power_shortterm_charge = 0;
|
||||
static uint32_t battery_BEV_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery_BEV_available_power_longterm_charge = 0;
|
||||
static uint32_t battery_BEV_available_power_longterm_discharge = 0;
|
||||
static uint16_t battery_energy_content_maximum_Wh = 0;
|
||||
static uint16_t battery_display_SOC = 0;
|
||||
static uint16_t battery_volts = 0;
|
||||
static uint16_t battery_HVBatt_SOC = 0;
|
||||
static uint16_t battery_DC_link_voltage = 0;
|
||||
static uint16_t battery_max_charge_voltage = 0;
|
||||
static uint16_t battery_min_discharge_voltage = 0;
|
||||
static uint16_t battery_predicted_energy_charge_condition = 0;
|
||||
static uint16_t battery_predicted_energy_charging_target = 0;
|
||||
static uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
|
||||
static uint16_t battery_prediction_voltage_shortterm_charge = 0;
|
||||
static uint16_t battery_prediction_voltage_shortterm_discharge = 0;
|
||||
static uint16_t battery_prediction_voltage_longterm_charge = 0;
|
||||
static uint16_t battery_prediction_voltage_longterm_discharge = 0;
|
||||
static uint16_t battery_prediction_duration_charging_minutes = 0;
|
||||
static uint16_t battery_target_voltage_in_CV_mode = 0;
|
||||
static uint16_t battery_soc = 0;
|
||||
static uint16_t battery_soc_hvmax = 0;
|
||||
static uint16_t battery_soc_hvmin = 0;
|
||||
static uint16_t battery_capacity_cah = 0;
|
||||
static int16_t battery_temperature_HV = 0;
|
||||
static int16_t battery_temperature_heat_exchanger = 0;
|
||||
static int16_t battery_temperature_max = 0;
|
||||
static int16_t battery_temperature_min = 0;
|
||||
static int16_t battery_max_charge_amperage = 0;
|
||||
static int16_t battery_max_discharge_amperage = 0;
|
||||
static int16_t battery_current = 0;
|
||||
static uint8_t battery_status_error_isolation_external_Bordnetz = 0;
|
||||
static uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
|
||||
static uint8_t battery_request_cooling = 0;
|
||||
static uint8_t battery_status_valve_cooling = 0;
|
||||
static uint8_t battery_status_error_locking = 0;
|
||||
static uint8_t battery_status_precharge_locked = 0;
|
||||
static uint8_t battery_status_disconnecting_switch = 0;
|
||||
static uint8_t battery_status_emergency_mode = 0;
|
||||
static uint8_t battery_request_service = 0;
|
||||
static uint8_t battery_error_emergency_mode = 0;
|
||||
static uint8_t battery_status_error_disconnecting_switch = 0;
|
||||
static uint8_t battery_status_warning_isolation = 0;
|
||||
static uint8_t battery_status_cold_shutoff_valve = 0;
|
||||
static uint8_t battery_request_open_contactors = 0;
|
||||
static uint8_t battery_request_open_contactors_instantly = 0;
|
||||
static uint8_t battery_request_open_contactors_fast = 0;
|
||||
static uint8_t battery_charging_condition_delta = 0;
|
||||
static uint8_t battery_status_service_disconnection_plug = 0;
|
||||
static uint8_t battery_status_measurement_isolation = 0;
|
||||
static uint8_t battery_request_abort_charging = 0;
|
||||
static uint8_t battery_prediction_time_end_of_charging_minutes = 0;
|
||||
static uint8_t battery_request_operating_mode = 0;
|
||||
static uint8_t battery_request_charging_condition_minimum = 0;
|
||||
static uint8_t battery_request_charging_condition_maximum = 0;
|
||||
static uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
|
||||
static uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
|
||||
static uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
|
||||
static uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
|
||||
static uint8_t battery_ID2 = 0;
|
||||
static uint8_t battery_soh = 99;
|
||||
|
||||
static uint16_t cellvoltage2_temp_mV = 0;
|
||||
static uint32_t battery2_serial_number = 0;
|
||||
static uint32_t battery2_available_power_shortterm_charge = 0;
|
||||
static uint32_t battery2_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery2_available_power_longterm_charge = 0;
|
||||
static uint32_t battery2_available_power_longterm_discharge = 0;
|
||||
static uint32_t battery2_BEV_available_power_shortterm_charge = 0;
|
||||
static uint32_t battery2_BEV_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery2_BEV_available_power_longterm_charge = 0;
|
||||
static uint32_t battery2_BEV_available_power_longterm_discharge = 0;
|
||||
static uint16_t battery2_energy_content_maximum_Wh = 0;
|
||||
static uint16_t battery2_display_SOC = 0;
|
||||
static uint16_t battery2_volts = 0;
|
||||
static uint16_t battery2_HVBatt_SOC = 0;
|
||||
static uint16_t battery2_DC_link_voltage = 0;
|
||||
static uint16_t battery2_max_charge_voltage = 0;
|
||||
static uint16_t battery2_min_discharge_voltage = 0;
|
||||
static uint16_t battery2_predicted_energy_charge_condition = 0;
|
||||
static uint16_t battery2_predicted_energy_charging_target = 0;
|
||||
static uint16_t battery2_actual_value_power_heating = 0; //0 - 4094 W
|
||||
static uint16_t battery2_prediction_voltage_shortterm_charge = 0;
|
||||
static uint16_t battery2_prediction_voltage_shortterm_discharge = 0;
|
||||
static uint16_t battery2_prediction_voltage_longterm_charge = 0;
|
||||
static uint16_t battery2_prediction_voltage_longterm_discharge = 0;
|
||||
static uint16_t battery2_prediction_duration_charging_minutes = 0;
|
||||
static uint16_t battery2_target_voltage_in_CV_mode = 0;
|
||||
static uint16_t battery2_soc = 0;
|
||||
static uint16_t battery2_soc_hvmax = 0;
|
||||
static uint16_t battery2_soc_hvmin = 0;
|
||||
static uint16_t battery2_capacity_cah = 0;
|
||||
static int16_t battery2_temperature_HV = 0;
|
||||
static int16_t battery2_temperature_heat_exchanger = 0;
|
||||
static int16_t battery2_temperature_max = 0;
|
||||
static int16_t battery2_temperature_min = 0;
|
||||
static int16_t battery2_max_charge_amperage = 0;
|
||||
static int16_t battery2_max_discharge_amperage = 0;
|
||||
static int16_t battery2_current = 0;
|
||||
static uint8_t battery2_status_error_isolation_external_Bordnetz = 0;
|
||||
static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0;
|
||||
static uint8_t battery2_request_cooling = 0;
|
||||
static uint8_t battery2_status_valve_cooling = 0;
|
||||
static uint8_t battery2_status_error_locking = 0;
|
||||
static uint8_t battery2_status_precharge_locked = 0;
|
||||
static uint8_t battery2_status_disconnecting_switch = 0;
|
||||
static uint8_t battery2_status_emergency_mode = 0;
|
||||
static uint8_t battery2_request_service = 0;
|
||||
static uint8_t battery2_error_emergency_mode = 0;
|
||||
static uint8_t battery2_status_error_disconnecting_switch = 0;
|
||||
static uint8_t battery2_status_warning_isolation = 0;
|
||||
static uint8_t battery2_status_cold_shutoff_valve = 0;
|
||||
static uint8_t battery2_request_open_contactors = 0;
|
||||
static uint8_t battery2_request_open_contactors_instantly = 0;
|
||||
static uint8_t battery2_request_open_contactors_fast = 0;
|
||||
static uint8_t battery2_charging_condition_delta = 0;
|
||||
static uint8_t battery2_status_service_disconnection_plug = 0;
|
||||
static uint8_t battery2_status_measurement_isolation = 0;
|
||||
static uint8_t battery2_request_abort_charging = 0;
|
||||
static uint8_t battery2_prediction_time_end_of_charging_minutes = 0;
|
||||
static uint8_t battery2_request_operating_mode = 0;
|
||||
static uint8_t battery2_request_charging_condition_minimum = 0;
|
||||
static uint8_t battery2_request_charging_condition_maximum = 0;
|
||||
static uint8_t battery2_status_cooling_HV = 0; //1 works, 2 does not start
|
||||
static uint8_t battery2_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
|
||||
static uint8_t battery2_status_diagnosis_powertrain_maximum_multiplexer = 0;
|
||||
static uint8_t battery2_status_diagnosis_powertrain_immediate_multiplexer = 0;
|
||||
static uint8_t battery2_ID2 = 0;
|
||||
static uint8_t battery2_soh = 99;
|
||||
|
||||
static uint8_t message_data[50];
|
||||
static uint8_t next_data = 0;
|
||||
static uint8_t current_cell_polled = 0;
|
||||
|
||||
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
|
||||
uint8_t crc = initial_value;
|
||||
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
|
||||
|
@ -365,118 +42,54 @@ static uint8_t increment_alive_counter(uint8_t counter) {
|
|||
return counter;
|
||||
}
|
||||
|
||||
void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer
|
||||
if (!battery2_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery2.status.real_soc = (battery2_display_SOC * 50);
|
||||
|
||||
datalayer.battery2.status.voltage_dV = battery2_volts; //Unit V+1 (5000 = 500.0V)
|
||||
|
||||
datalayer.battery2.status.current_dA = battery2_current;
|
||||
|
||||
datalayer.battery2.info.total_capacity_Wh = battery2_energy_content_maximum_Wh;
|
||||
|
||||
datalayer.battery2.status.remaining_capacity_Wh = battery2_predicted_energy_charge_condition;
|
||||
|
||||
datalayer.battery2.status.soh_pptt = battery2_soh * 100;
|
||||
|
||||
datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge;
|
||||
|
||||
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
|
||||
|
||||
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
|
||||
|
||||
datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal
|
||||
|
||||
if (battery2_info_available) {
|
||||
// Start checking safeties. First up, cellvoltages!
|
||||
if (detectedBattery2 == BATTERY_60AH) {
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
|
||||
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
|
||||
} else if (detectedBattery2 == BATTERY_94AH) {
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
|
||||
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
|
||||
} else { // BATTERY_120AH
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
|
||||
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform other safety checks
|
||||
if (battery2_status_error_locking == 2) { // HVIL seated?
|
||||
set_event(EVENT_HVIL_FAILURE, 2);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
if (battery2_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
|
||||
set_event(EVENT_CONTACTOR_WELDED, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CONTACTOR_WELDED);
|
||||
}
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||
void BmwI3Battery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||
if (datalayer.system.settings.equipment_stop_active == true) {
|
||||
digitalWrite(WUP_PIN1, LOW); // Turn off WUP_PIN1
|
||||
#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY)
|
||||
digitalWrite(WUP_PIN2, LOW); // Turn off WUP_PIN2
|
||||
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
|
||||
digitalWrite(wakeup_pin, LOW); // Turn off wakeup pin
|
||||
} else {
|
||||
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery
|
||||
#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY)
|
||||
digitalWrite(WUP_PIN2, HIGH); // Wake up the battery2
|
||||
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
|
||||
digitalWrite(wakeup_pin, HIGH); // Wake up the battery
|
||||
}
|
||||
|
||||
if (!battery_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery.status.real_soc = (battery_display_SOC * 50);
|
||||
datalayer_battery->status.real_soc = (battery_display_SOC * 50);
|
||||
|
||||
datalayer.battery.status.voltage_dV = battery_volts; //Unit V+1 (5000 = 500.0V)
|
||||
datalayer_battery->status.voltage_dV = battery_volts; //Unit V+1 (5000 = 500.0V)
|
||||
|
||||
datalayer.battery.status.current_dA = battery_current;
|
||||
datalayer_battery->status.current_dA = battery_current;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = battery_energy_content_maximum_Wh;
|
||||
datalayer_battery->info.total_capacity_Wh = battery_energy_content_maximum_Wh;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition;
|
||||
datalayer_battery->status.remaining_capacity_Wh = battery_predicted_energy_charge_condition;
|
||||
|
||||
datalayer.battery.status.soh_pptt = battery_soh * 100;
|
||||
datalayer_battery->status.soh_pptt = battery_soh * 100;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
|
||||
datalayer_battery->status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
datalayer_battery->status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
|
||||
datalayer_battery->status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
|
||||
datalayer_battery->status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
|
||||
|
||||
if (battery_info_available) {
|
||||
// Start checking safeties. First up, cellvoltages!
|
||||
if (detectedBattery == BATTERY_60AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
|
||||
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
|
||||
} else if (detectedBattery == BATTERY_94AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
|
||||
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
|
||||
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
|
||||
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
|
||||
} else { // BATTERY_120AH
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
|
||||
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
|
||||
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
|
||||
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -508,15 +121,15 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer_extended.bmwi3.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve;
|
||||
}
|
||||
|
||||
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||
void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery_awake = true;
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
datalayer_battery->status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery.status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
|
||||
datalayer_battery->status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
|
||||
battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
|
||||
battery_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6;
|
||||
battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
|
||||
|
@ -552,7 +165,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
if (!skipCRCCheck) {
|
||||
if (calculateCRC(rx_frame, rx_frame.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
// If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
datalayer_battery->status.CAN_error_counter++;
|
||||
|
||||
// If the CRC check has never passed before, set the flag to skip future checks. Some SMEs have differing CRC checks.
|
||||
if (!CRCCheckPassedPreviously) {
|
||||
|
@ -638,10 +251,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
case 0x607: //BMS - responses to message requests on 0x615
|
||||
if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) {
|
||||
if (rx_frame.DLC == 6) {
|
||||
transmit_can_frame(&BMW_6F4_CELL_CONTINUE, can_config.battery); // tell battery to send the cellvoltage
|
||||
transmit_can_frame(&BMW_6F4_CELL_CONTINUE, can_interface); // tell battery to send the cellvoltage
|
||||
}
|
||||
if (rx_frame.DLC == 8) { // We have the full value, map it
|
||||
datalayer.battery.status.cell_voltages_mV[current_cell_polled - 1] =
|
||||
datalayer_battery->status.cell_voltages_mV[current_cell_polled - 1] =
|
||||
(rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
}
|
||||
}
|
||||
|
@ -651,7 +264,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
while (count < rx_frame.DLC && next_data < 49) {
|
||||
message_data[next_data++] = rx_frame.data.u8[count++];
|
||||
}
|
||||
transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery); // tell battery to send additional messages
|
||||
transmit_can_frame(&BMW_6F1_CONTINUE, can_interface); // tell battery to send additional messages
|
||||
|
||||
} else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 &&
|
||||
((rx_frame.data.u8[1] & 0xF0) == 0x20)) {
|
||||
|
@ -665,11 +278,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
if (next_data >= 4) {
|
||||
cellvoltage_temp_mV = (message_data[0] << 8 | message_data[1]);
|
||||
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
|
||||
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_temp_mV;
|
||||
datalayer_battery->status.cell_min_voltage_mV = cellvoltage_temp_mV;
|
||||
}
|
||||
cellvoltage_temp_mV = (message_data[2] << 8 | message_data[3]);
|
||||
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
|
||||
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_temp_mV;
|
||||
datalayer_battery->status.cell_max_voltage_mV = cellvoltage_temp_mV;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -693,194 +306,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery2_awake = true;
|
||||
datalayer.battery2.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery2.status.voltage_dV =
|
||||
battery2_volts; // Update the datalayer as soon as possible with this info, needed for contactor control
|
||||
battery2_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
|
||||
battery2_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6;
|
||||
battery2_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
|
||||
battery2_request_open_contactors_fast = (rx_frame.data.u8[6] & 0x0C) >> 2;
|
||||
battery2_charging_condition_delta = (rx_frame.data.u8[6] & 0xF0) >> 4;
|
||||
battery2_DC_link_voltage = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1
|
||||
battery2_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03);
|
||||
battery2_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2;
|
||||
battery2_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
battery2_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6;
|
||||
battery2_status_error_locking = (rx_frame.data.u8[1] & 0x03);
|
||||
battery2_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2;
|
||||
battery2_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4;
|
||||
battery2_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
||||
battery2_request_service = (rx_frame.data.u8[2] & 0x03);
|
||||
battery2_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2;
|
||||
battery2_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4;
|
||||
battery2_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6;
|
||||
battery2_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F);
|
||||
battery2_temperature_HV = (rx_frame.data.u8[4] - 50);
|
||||
battery2_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50);
|
||||
battery2_temperature_min = (rx_frame.data.u8[6] - 50);
|
||||
battery2_temperature_max = (rx_frame.data.u8[7] - 50);
|
||||
break;
|
||||
case 0x239: //BMS [200ms]
|
||||
battery2_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh
|
||||
battery2_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh
|
||||
break;
|
||||
case 0x2BD: //BMS [100ms] Status diagnosis high voltage - 1
|
||||
battery2_awake = true;
|
||||
if (!skipCRCCheck_battery2) {
|
||||
if (calculateCRC(rx_frame, rx_frame.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
// If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
datalayer.battery2.status.CAN_error_counter++;
|
||||
|
||||
// If the CRC check has never passed before, set the flag to skip future checks. Some SMEs have differing CRC checks.
|
||||
if (!CRCCheckPassedPreviously_battery2) {
|
||||
skipCRCCheck_battery2 = true;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// If CRC check passes, update the flag
|
||||
CRCCheckPassedPreviously_battery2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the data since CRC check is either passed or skipped
|
||||
battery2_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
break;
|
||||
case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations
|
||||
battery2_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
|
||||
battery2_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2);
|
||||
battery2_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
|
||||
battery2_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2);
|
||||
break;
|
||||
case 0x2FF: //BMS [100ms] Status Heating High-Voltage Battery
|
||||
battery2_awake = true;
|
||||
battery2_actual_value_power_heating = (rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4);
|
||||
break;
|
||||
case 0x363: //BMS [1s] Identification High-Voltage Battery
|
||||
battery2_serial_number =
|
||||
(rx_frame.data.u8[3] << 24 | rx_frame.data.u8[2] << 16 | rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
|
||||
break;
|
||||
case 0x3C2: //BMS (94AH exclusive) - Status diagnostics OBD 2 powertrain
|
||||
battery2_status_diagnosis_powertrain_maximum_multiplexer =
|
||||
((rx_frame.data.u8[1] & 0x03) << 4 | rx_frame.data.u8[0] >> 4);
|
||||
battery2_status_diagnosis_powertrain_immediate_multiplexer = (rx_frame.data.u8[0] & 0xFC) >> 2;
|
||||
break;
|
||||
case 0x3EB: //BMS [1s] Status of charging high-voltage storage - 3
|
||||
battery2_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
|
||||
battery2_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
|
||||
battery2_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
|
||||
battery2_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
|
||||
break;
|
||||
case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1
|
||||
battery2_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
|
||||
battery2_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
|
||||
battery2_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
|
||||
battery2_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
|
||||
break;
|
||||
case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2
|
||||
battery2_status_cooling_HV = (rx_frame.data.u8[1] & 0x03);
|
||||
break;
|
||||
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
|
||||
battery2_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
|
||||
battery2_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
battery2_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
|
||||
battery2_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
|
||||
break;
|
||||
case 0x431: //BMS [200ms] Data High-Voltage Battery Unit
|
||||
battery2_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F);
|
||||
battery2_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2;
|
||||
battery2_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
battery2_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
battery2_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
|
||||
battery2_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
|
||||
if (battery2_energy_content_maximum_Wh > 33000) {
|
||||
detectedBattery2 = BATTERY_120AH;
|
||||
} else if (battery2_energy_content_maximum_Wh > 20000) {
|
||||
detectedBattery2 = BATTERY_94AH;
|
||||
} else {
|
||||
detectedBattery2 = BATTERY_60AH;
|
||||
}
|
||||
break;
|
||||
case 0x432: //BMS [200ms] SOC% info
|
||||
battery2_request_operating_mode = (rx_frame.data.u8[0] & 0x03);
|
||||
battery2_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10;
|
||||
battery2_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2);
|
||||
battery2_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2);
|
||||
battery2_display_SOC = rx_frame.data.u8[4];
|
||||
break;
|
||||
case 0x507: //BMS [640ms] Network Management - 2 - This message is sent on the bus for sleep coordination purposes
|
||||
break;
|
||||
case 0x587: //BMS [5s] Services
|
||||
battery2_ID2 = rx_frame.data.u8[0];
|
||||
break;
|
||||
case 0x607: //BMS - responses to message requests on 0x615
|
||||
if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) {
|
||||
if (rx_frame.DLC == 6) {
|
||||
transmit_can_frame(&BMW_6F4_CELL_CONTINUE,
|
||||
can_config.battery_double); // tell battery to send the cellvoltage
|
||||
}
|
||||
if (rx_frame.DLC == 8) { // We have the full value, map it
|
||||
datalayer.battery2.status.cell_voltages_mV[current_cell_polled - 1] =
|
||||
(rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.DLC > 6 && next_data == 0 && rx_frame.data.u8[0] == 0xf1) {
|
||||
uint8_t count2 = 6;
|
||||
while (count2 < rx_frame.DLC && next_data < 49) {
|
||||
message_data[next_data++] = rx_frame.data.u8[count2++];
|
||||
}
|
||||
transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery_double);
|
||||
|
||||
} else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 &&
|
||||
((rx_frame.data.u8[1] & 0xF0) == 0x20)) {
|
||||
uint8_t count2 = 2;
|
||||
while (count2 < rx_frame.DLC && next_data < 49) {
|
||||
message_data[next_data++] = rx_frame.data.u8[count2++];
|
||||
}
|
||||
|
||||
switch (cmdState) {
|
||||
case CELL_VOLTAGE_MINMAX:
|
||||
if (next_data >= 4) {
|
||||
cellvoltage2_temp_mV = (message_data[0] << 8 | message_data[1]);
|
||||
if (cellvoltage2_temp_mV < 4500) { // Prevents garbage data from being read on bootup
|
||||
datalayer.battery2.status.cell_min_voltage_mV = cellvoltage2_temp_mV;
|
||||
}
|
||||
cellvoltage2_temp_mV = (message_data[2] << 8 | message_data[3]);
|
||||
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
|
||||
datalayer.battery2.status.cell_max_voltage_mV = cellvoltage2_temp_mV;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SOH:
|
||||
if (next_data >= 4) {
|
||||
battery2_soh = message_data[3];
|
||||
battery2_info_available = true;
|
||||
}
|
||||
break;
|
||||
case SOC:
|
||||
if (next_data >= 6) {
|
||||
battery2_soc = (message_data[0] << 8 | message_data[1]);
|
||||
battery2_soc_hvmax = (message_data[2] << 8 | message_data[3]);
|
||||
battery2_soc_hvmin = (message_data[4] << 8 | message_data[5]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void transmit_can_battery(unsigned long currentMillis) {
|
||||
void BmwI3Battery::transmit_can(unsigned long currentMillis) {
|
||||
|
||||
if (battery_awake) {
|
||||
//Send 20ms message
|
||||
|
@ -901,17 +328,11 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
BMW_13E_counter++;
|
||||
BMW_13E.data.u8[4] = BMW_13E_counter;
|
||||
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
if (datalayer_battery->status.bms_status == FAULT) {
|
||||
} //If battery is not in Fault mode, allow contactor to close by sending 10B
|
||||
else {
|
||||
transmit_can_frame(&BMW_10B, can_config.battery);
|
||||
else if (*allows_contactor_closing == true) {
|
||||
transmit_can_frame(&BMW_10B, can_interface);
|
||||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY //If second battery is allowed to join in, also send 10B
|
||||
if (datalayer.system.status.battery2_allows_contactor_closing == true) {
|
||||
transmit_can_frame(&BMW_10B, can_config.battery_double);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
|
@ -922,10 +343,7 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
|
||||
alive_counter_100ms = increment_alive_counter(alive_counter_100ms);
|
||||
|
||||
transmit_can_frame(&BMW_12F, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_12F, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_12F, can_interface);
|
||||
}
|
||||
// Send 200ms CAN Message
|
||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||
|
@ -936,10 +354,7 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
|
||||
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
|
||||
|
||||
transmit_can_frame(&BMW_19B, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_19B, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_19B, can_interface);
|
||||
}
|
||||
// Send 500ms CAN Message
|
||||
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
|
||||
|
@ -950,21 +365,14 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
|
||||
alive_counter_500ms = increment_alive_counter(alive_counter_500ms);
|
||||
|
||||
transmit_can_frame(&BMW_30B, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_30B, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_30B, can_interface);
|
||||
}
|
||||
// Send 640ms CAN Message
|
||||
if (currentMillis - previousMillis640 >= INTERVAL_640_MS) {
|
||||
previousMillis640 = currentMillis;
|
||||
|
||||
transmit_can_frame(&BMW_512, can_config.battery); // Keep BMS alive
|
||||
transmit_can_frame(&BMW_5F8, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_512, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_5F8, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_512, can_interface); // Keep BMS alive
|
||||
transmit_can_frame(&BMW_5F8, can_interface);
|
||||
}
|
||||
// Send 1000ms CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
|
@ -1001,40 +409,22 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
|
||||
alive_counter_1000ms = increment_alive_counter(alive_counter_1000ms);
|
||||
|
||||
transmit_can_frame(&BMW_3E8, can_config.battery); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_328, can_config.battery);
|
||||
transmit_can_frame(&BMW_3F9, can_config.battery);
|
||||
transmit_can_frame(&BMW_2E2, can_config.battery);
|
||||
transmit_can_frame(&BMW_41D, can_config.battery);
|
||||
transmit_can_frame(&BMW_3D0, can_config.battery);
|
||||
transmit_can_frame(&BMW_3CA, can_config.battery);
|
||||
transmit_can_frame(&BMW_3A7, can_config.battery);
|
||||
transmit_can_frame(&BMW_2CA, can_config.battery);
|
||||
transmit_can_frame(&BMW_3FB, can_config.battery);
|
||||
transmit_can_frame(&BMW_418, can_config.battery);
|
||||
transmit_can_frame(&BMW_1D0, can_config.battery);
|
||||
transmit_can_frame(&BMW_3EC, can_config.battery);
|
||||
transmit_can_frame(&BMW_192, can_config.battery);
|
||||
transmit_can_frame(&BMW_13E, can_config.battery);
|
||||
transmit_can_frame(&BMW_433, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_3E8, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_328, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3F9, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_2E2, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_41D, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3D0, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3CA, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3A7, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_2CA, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3FB, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_418, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_1D0, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3EC, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_192, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_13E, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_433, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_3E8, can_interface); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_328, can_interface);
|
||||
transmit_can_frame(&BMW_3F9, can_interface);
|
||||
transmit_can_frame(&BMW_2E2, can_interface);
|
||||
transmit_can_frame(&BMW_41D, can_interface);
|
||||
transmit_can_frame(&BMW_3D0, can_interface);
|
||||
transmit_can_frame(&BMW_3CA, can_interface);
|
||||
transmit_can_frame(&BMW_3A7, can_interface);
|
||||
transmit_can_frame(&BMW_2CA, can_interface);
|
||||
transmit_can_frame(&BMW_3FB, can_interface);
|
||||
transmit_can_frame(&BMW_418, can_interface);
|
||||
transmit_can_frame(&BMW_1D0, can_interface);
|
||||
transmit_can_frame(&BMW_3EC, can_interface);
|
||||
transmit_can_frame(&BMW_192, can_interface);
|
||||
transmit_can_frame(&BMW_13E, can_interface);
|
||||
transmit_can_frame(&BMW_433, can_interface);
|
||||
|
||||
BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this
|
||||
BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this
|
||||
|
@ -1042,24 +432,15 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
next_data = 0;
|
||||
switch (cmdState) {
|
||||
case SOC:
|
||||
transmit_can_frame(&BMW_6F1_CELL, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_6F1_CELL, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_6F1_CELL, can_interface);
|
||||
cmdState = CELL_VOLTAGE_MINMAX;
|
||||
break;
|
||||
case CELL_VOLTAGE_MINMAX:
|
||||
transmit_can_frame(&BMW_6F1_SOH, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_6F1_SOH, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_6F1_SOH, can_interface);
|
||||
cmdState = SOH;
|
||||
break;
|
||||
case SOH:
|
||||
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_interface);
|
||||
cmdState = CELL_VOLTAGE_CELLNO;
|
||||
current_cell_polled = 0;
|
||||
|
||||
|
@ -1072,17 +453,11 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
cmdState = CELL_VOLTAGE_CELLNO;
|
||||
|
||||
BMW_6F4_CELL_VOLTAGE_CELLNO.data.u8[6] = current_cell_polled;
|
||||
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_interface);
|
||||
}
|
||||
break;
|
||||
case CELL_VOLTAGE_CELLNO_LAST:
|
||||
transmit_can_frame(&BMW_6F1_SOC, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_6F1_SOC, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_6F1_SOC, can_interface);
|
||||
cmdState = SOC;
|
||||
break;
|
||||
}
|
||||
|
@ -1094,26 +469,16 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
BMW_3FC.data.u8[1] = ((BMW_3FC.data.u8[1] & 0xF0) + alive_counter_5000ms);
|
||||
BMW_3C5.data.u8[0] = ((BMW_3C5.data.u8[0] & 0xF0) + alive_counter_5000ms);
|
||||
|
||||
transmit_can_frame(&BMW_3FC, can_config.battery); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_3C5, can_config.battery);
|
||||
transmit_can_frame(&BMW_3A0, can_config.battery);
|
||||
transmit_can_frame(&BMW_592_0, can_config.battery);
|
||||
transmit_can_frame(&BMW_592_1, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_3FC, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3C5, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3A0, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_592_0, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_592_1, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_3FC, can_interface); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_3C5, can_interface);
|
||||
transmit_can_frame(&BMW_3A0, can_interface);
|
||||
transmit_can_frame(&BMW_592_0, can_interface);
|
||||
transmit_can_frame(&BMW_592_1, can_interface);
|
||||
|
||||
alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms);
|
||||
|
||||
if (BMW_380_counter < 3) {
|
||||
transmit_can_frame(&BMW_380, can_config.battery); // This message stops after 3 times on startup
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_380, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_380, can_interface); // This message stops after 3 times on startup
|
||||
BMW_380_counter++;
|
||||
}
|
||||
}
|
||||
|
@ -1121,14 +486,9 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
|
||||
previousMillis10000 = currentMillis;
|
||||
|
||||
transmit_can_frame(&BMW_3E5, can_config.battery); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_3E4, can_config.battery);
|
||||
transmit_can_frame(&BMW_37B, can_config.battery);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
transmit_can_frame(&BMW_3E5, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_3E4, can_config.battery_double);
|
||||
transmit_can_frame(&BMW_37B, can_config.battery_double);
|
||||
#endif
|
||||
transmit_can_frame(&BMW_3E5, can_interface); //Order comes from CAN logs
|
||||
transmit_can_frame(&BMW_3E4, can_interface);
|
||||
transmit_can_frame(&BMW_37B, can_interface);
|
||||
|
||||
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
|
||||
}
|
||||
|
@ -1144,30 +504,19 @@ void transmit_can_battery(unsigned long currentMillis) {
|
|||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
void BmwI3Battery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 60AH values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
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_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
|
||||
datalayer.battery2.status.voltage_dV =
|
||||
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
|
||||
datalayer.battery2.info.number_of_cells = NUMBER_OF_CELLS;
|
||||
#endif
|
||||
pinMode(WUP_PIN1, OUTPUT);
|
||||
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery
|
||||
#if defined(DOUBLE_BATTERY) && defined(WUP_PIN2)
|
||||
pinMode(WUP_PIN2, OUTPUT);
|
||||
digitalWrite(WUP_PIN2, HIGH); // Wake up the battery
|
||||
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
|
||||
datalayer_battery->info.number_of_cells = NUMBER_OF_CELLS;
|
||||
|
||||
pinMode(wakeup_pin, OUTPUT);
|
||||
digitalWrite(wakeup_pin, HIGH); // Wake up the battery
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,25 +1,334 @@
|
|||
#ifndef BMW_I3_BATTERY_H
|
||||
#define BMW_I3_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../datalayer/datalayer_extended.h"
|
||||
#include "../include.h"
|
||||
#include "CanBattery.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define SELECTED_BATTERY_CLASS BmwI3Battery
|
||||
|
||||
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
|
||||
#define NUMBER_OF_CELLS 96
|
||||
void setup_battery(void);
|
||||
void transmit_can_frame(CAN_frame* tx_frame, int interface);
|
||||
class BmwI3Battery : public CanBattery {
|
||||
public:
|
||||
// Use this constructor for the second battery.
|
||||
BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* allows_contactor_closing_ptr, int targetCan, int wakeup) {
|
||||
datalayer_battery = datalayer_ptr;
|
||||
allows_contactor_closing = allows_contactor_closing_ptr;
|
||||
can_interface = targetCan;
|
||||
wakeup_pin = wakeup;
|
||||
*allows_contactor_closing = true;
|
||||
|
||||
//Init voltage to 0 to allow contactor check to operate without fear of default values colliding
|
||||
battery_volts = 0;
|
||||
}
|
||||
|
||||
// Use the default constructor to create the first or single battery.
|
||||
BmwI3Battery() {
|
||||
datalayer_battery = &datalayer.battery;
|
||||
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
|
||||
can_interface = can_config.battery;
|
||||
wakeup_pin = WUP_PIN1;
|
||||
}
|
||||
|
||||
virtual void setup(void);
|
||||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
|
||||
private:
|
||||
const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value
|
||||
const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value
|
||||
const int MAX_CELL_VOLTAGE_94AH = 4140; // Battery is put into emergency stop if one cell goes over this value
|
||||
const int MIN_CELL_VOLTAGE_94AH = 2700; // Battery is put into emergency stop if one cell goes below this value
|
||||
const int MAX_CELL_VOLTAGE_120AH = 4190; // Battery is put into emergency stop if one cell goes over this value
|
||||
const int MIN_CELL_VOLTAGE_120AH = 2790; // Battery is put into emergency stop if one cell goes below this value
|
||||
const int MAX_CELL_DEVIATION_MV = 250; // LED turns yellow on the board if mv delta exceeds this value
|
||||
const int MAX_PACK_VOLTAGE_60AH = 3950; // Charge stops if pack voltage exceeds this value
|
||||
const int MIN_PACK_VOLTAGE_60AH = 2590; // Discharge stops if pack voltage exceeds this value
|
||||
const int MAX_PACK_VOLTAGE_94AH = 3980; // Charge stops if pack voltage exceeds this value
|
||||
const int MIN_PACK_VOLTAGE_94AH = 2590; // Discharge stops if pack voltage exceeds this value
|
||||
const int MAX_PACK_VOLTAGE_120AH = 4030; // Charge stops if pack voltage exceeds this value
|
||||
const int MIN_PACK_VOLTAGE_120AH = 2680; // Discharge stops if pack voltage exceeds this value
|
||||
const int NUMBER_OF_CELLS = 96;
|
||||
|
||||
DATALAYER_BATTERY_TYPE* datalayer_battery;
|
||||
bool* allows_contactor_closing;
|
||||
int wakeup_pin;
|
||||
|
||||
int can_interface;
|
||||
|
||||
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
|
||||
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
|
||||
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
|
||||
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
|
||||
BatterySize detectedBattery = BATTERY_60AH;
|
||||
|
||||
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
|
||||
|
||||
CmdState cmdState = SOC;
|
||||
|
||||
/* CAN messages from PT-CAN2 not needed to operate the battery
|
||||
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
|
||||
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
|
||||
*/
|
||||
|
||||
CAN_frame BMW_10B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 3,
|
||||
.ID = 0x10B,
|
||||
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
|
||||
CAN_frame BMW_12F = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x12F,
|
||||
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
|
||||
CAN_frame BMW_13E = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x13E,
|
||||
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
|
||||
CAN_frame BMW_192 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x192,
|
||||
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_19B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x19B,
|
||||
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_1D0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x1D0,
|
||||
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
|
||||
CAN_frame BMW_2E2 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x2E2,
|
||||
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
|
||||
CAN_frame BMW_30B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x30B,
|
||||
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
|
||||
CAN_frame BMW_328 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x328,
|
||||
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
|
||||
CAN_frame BMW_37B = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x37B,
|
||||
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
|
||||
CAN_frame BMW_380 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x380,
|
||||
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
|
||||
CAN_frame BMW_3A0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3A0,
|
||||
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
|
||||
CAN_frame BMW_3A7 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x3A7,
|
||||
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
|
||||
CAN_frame BMW_3C5 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3C5,
|
||||
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
|
||||
CAN_frame BMW_3CA = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3CA,
|
||||
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
|
||||
CAN_frame BMW_3E4 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x3E4,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
|
||||
CAN_frame BMW_3EC = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3EC,
|
||||
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
|
||||
CAN_frame BMW_3F9 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x3F9,
|
||||
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
|
||||
CAN_frame BMW_3FB = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x3FB,
|
||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
|
||||
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
|
||||
CAN_frame BMW_418 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x418,
|
||||
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
|
||||
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}};
|
||||
CAN_frame BMW_433 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x433,
|
||||
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
|
||||
CAN_frame BMW_512 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x512,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
|
||||
CAN_frame BMW_592_0 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x592,
|
||||
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
|
||||
CAN_frame BMW_592_1 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x592,
|
||||
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame BMW_5F8 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x5F8,
|
||||
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
|
||||
CAN_frame BMW_6F1_CELL = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F1,
|
||||
.data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
|
||||
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}};
|
||||
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
|
||||
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F1,
|
||||
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
|
||||
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}};
|
||||
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 7,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
|
||||
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
|
||||
|
||||
//The above CAN messages need to be sent towards the battery to keep it alive
|
||||
|
||||
uint8_t startup_counter_contactor = 0;
|
||||
uint8_t alive_counter_20ms = 0;
|
||||
uint8_t alive_counter_100ms = 0;
|
||||
uint8_t alive_counter_200ms = 0;
|
||||
uint8_t alive_counter_500ms = 0;
|
||||
uint8_t alive_counter_1000ms = 0;
|
||||
uint8_t alive_counter_5000ms = 0;
|
||||
uint8_t BMW_1D0_counter = 0;
|
||||
uint8_t BMW_13E_counter = 0;
|
||||
uint8_t BMW_380_counter = 0;
|
||||
uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
|
||||
uint16_t BMW_328_days =
|
||||
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
|
||||
uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
|
||||
|
||||
bool battery_awake = false;
|
||||
bool battery_info_available = false;
|
||||
bool skipCRCCheck = false;
|
||||
bool CRCCheckPassedPreviously = false;
|
||||
|
||||
uint16_t cellvoltage_temp_mV = 0;
|
||||
uint32_t battery_serial_number = 0;
|
||||
uint32_t battery_available_power_shortterm_charge = 0;
|
||||
uint32_t battery_available_power_shortterm_discharge = 0;
|
||||
uint32_t battery_available_power_longterm_charge = 0;
|
||||
uint32_t battery_available_power_longterm_discharge = 0;
|
||||
uint32_t battery_BEV_available_power_shortterm_charge = 0;
|
||||
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
|
||||
uint32_t battery_BEV_available_power_longterm_charge = 0;
|
||||
uint32_t battery_BEV_available_power_longterm_discharge = 0;
|
||||
uint16_t battery_energy_content_maximum_Wh = 0;
|
||||
uint16_t battery_display_SOC = 0;
|
||||
uint16_t battery_volts = 0;
|
||||
uint16_t battery_HVBatt_SOC = 0;
|
||||
uint16_t battery_DC_link_voltage = 0;
|
||||
uint16_t battery_max_charge_voltage = 0;
|
||||
uint16_t battery_min_discharge_voltage = 0;
|
||||
uint16_t battery_predicted_energy_charge_condition = 0;
|
||||
uint16_t battery_predicted_energy_charging_target = 0;
|
||||
uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
|
||||
uint16_t battery_prediction_voltage_shortterm_charge = 0;
|
||||
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
|
||||
uint16_t battery_prediction_voltage_longterm_charge = 0;
|
||||
uint16_t battery_prediction_voltage_longterm_discharge = 0;
|
||||
uint16_t battery_prediction_duration_charging_minutes = 0;
|
||||
uint16_t battery_target_voltage_in_CV_mode = 0;
|
||||
uint16_t battery_soc = 0;
|
||||
uint16_t battery_soc_hvmax = 0;
|
||||
uint16_t battery_soc_hvmin = 0;
|
||||
uint16_t battery_capacity_cah = 0;
|
||||
int16_t battery_temperature_HV = 0;
|
||||
int16_t battery_temperature_heat_exchanger = 0;
|
||||
int16_t battery_temperature_max = 0;
|
||||
int16_t battery_temperature_min = 0;
|
||||
int16_t battery_max_charge_amperage = 0;
|
||||
int16_t battery_max_discharge_amperage = 0;
|
||||
int16_t battery_current = 0;
|
||||
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
|
||||
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
|
||||
uint8_t battery_request_cooling = 0;
|
||||
uint8_t battery_status_valve_cooling = 0;
|
||||
uint8_t battery_status_error_locking = 0;
|
||||
uint8_t battery_status_precharge_locked = 0;
|
||||
uint8_t battery_status_disconnecting_switch = 0;
|
||||
uint8_t battery_status_emergency_mode = 0;
|
||||
uint8_t battery_request_service = 0;
|
||||
uint8_t battery_error_emergency_mode = 0;
|
||||
uint8_t battery_status_error_disconnecting_switch = 0;
|
||||
uint8_t battery_status_warning_isolation = 0;
|
||||
uint8_t battery_status_cold_shutoff_valve = 0;
|
||||
uint8_t battery_request_open_contactors = 0;
|
||||
uint8_t battery_request_open_contactors_instantly = 0;
|
||||
uint8_t battery_request_open_contactors_fast = 0;
|
||||
uint8_t battery_charging_condition_delta = 0;
|
||||
uint8_t battery_status_service_disconnection_plug = 0;
|
||||
uint8_t battery_status_measurement_isolation = 0;
|
||||
uint8_t battery_request_abort_charging = 0;
|
||||
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
|
||||
uint8_t battery_request_operating_mode = 0;
|
||||
uint8_t battery_request_charging_condition_minimum = 0;
|
||||
uint8_t battery_request_charging_condition_maximum = 0;
|
||||
uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
|
||||
uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
|
||||
uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
|
||||
uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
|
||||
uint8_t battery_ID2 = 0;
|
||||
uint8_t battery_soh = 99;
|
||||
|
||||
uint8_t message_data[50];
|
||||
uint8_t next_data = 0;
|
||||
uint8_t current_cell_polled = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
#include "BMW-IX-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 20ms CAN Message was send
|
||||
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 600ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
@ -19,6 +21,27 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE
|
|||
|
||||
static CmdState cmdState = SOC;
|
||||
|
||||
static bool battery_awake = false;
|
||||
|
||||
bool contactorCloseReq = false;
|
||||
|
||||
struct ContactorCloseRequestStruct {
|
||||
bool previous;
|
||||
bool present;
|
||||
} ContactorCloseRequest = {false, false};
|
||||
|
||||
struct ContactorStateStruct {
|
||||
bool closed;
|
||||
bool open;
|
||||
};
|
||||
ContactorStateStruct ContactorState = {false, true};
|
||||
|
||||
struct InverterContactorCloseRequestStruct {
|
||||
bool previous;
|
||||
bool present;
|
||||
};
|
||||
InverterContactorCloseRequestStruct InverterContactorCloseRequest = {false, false};
|
||||
|
||||
/*
|
||||
SME output:
|
||||
0x12B8D087 5000ms - Extended ID
|
||||
|
@ -89,13 +112,17 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
|
|||
.data = {0xFC, 0xFF}}; // 5000ms SME output - Static values
|
||||
*/
|
||||
|
||||
CAN_frame BMWiX_16E = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x16E,
|
||||
// .data = {TODO:, TODO:, TODO: 0xC8 or 0xC9, 0xFF, TODO:, 0xC9, TODO:, TODO:, }
|
||||
}; // CCU output
|
||||
CAN_frame BMWiX_16E = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x16E,
|
||||
.data = {0x00, // Almost any possible number in 0x00 and 0xFF
|
||||
0xA0, // Almost any possible number in 0xA0 and 0xAF
|
||||
0xC9, 0xFF,
|
||||
0x60, // FIXME: find out what this value represents
|
||||
0xC9,
|
||||
0x3A, // 0x3A to close contactors, 0x33 to open contactors
|
||||
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
|
||||
|
||||
CAN_frame BMWiX_188 = {.FD = true,
|
||||
.ext_ID = false,
|
||||
|
@ -127,12 +154,12 @@ CAN_frame BMWiX_21D = {
|
|||
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
|
||||
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
|
||||
|
||||
CAN_frame BMWiX_276 = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x276,
|
||||
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}}; // 5000ms BDC output - vehicle condition
|
||||
CAN_frame BMWiX_276 = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x276,
|
||||
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFD}}; // BDC output - vehicle condition. Used for contactor closing
|
||||
|
||||
CAN_frame BMWiX_2ED = {
|
||||
.FD = true,
|
||||
|
@ -228,8 +255,13 @@ CAN_frame BMWiX_510 = {
|
|||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x510,
|
||||
.data = {0x40, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common
|
||||
.data = {
|
||||
0x40, 0x10,
|
||||
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
|
||||
0x00, 0x00,
|
||||
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
|
||||
0x01,
|
||||
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
|
||||
|
||||
CAN_frame BMWiX_6D = {
|
||||
.FD = true,
|
||||
|
@ -420,8 +452,6 @@ CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
|
|||
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
|
||||
//Request Data CAN End
|
||||
|
||||
static bool battery_awake = false;
|
||||
|
||||
//Setup UDS values to poll for
|
||||
CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
|
||||
&BMWiX_6F4_REQUEST_SOC,
|
||||
|
@ -495,6 +525,9 @@ const unsigned long STALE_PERIOD =
|
|||
|
||||
static uint8_t current_cell_polled = 0;
|
||||
|
||||
static uint16_t counter_10ms = 0; // max 65535 --> 655.35 seconds
|
||||
static uint8_t counter_100ms = 0; // max 255 --> 25.5 seconds
|
||||
|
||||
// Function to check if a value has gone stale over a specified time period
|
||||
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
|
||||
unsigned long currentTime = millis();
|
||||
|
@ -629,13 +662,53 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||
battery_awake = true;
|
||||
switch (rx_frame.ID) {
|
||||
case 0x112:
|
||||
case 0x12B8D087:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x1D2:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x20B:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x2E2:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x31F:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x3EA:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x453:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x486:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x49C:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x4A1:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x4BB:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x4D0:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x507:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x587:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x607: //SME responds to UDS requests on 0x607
|
||||
|
||||
if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
|
||||
rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
|
||||
//First of multi frame data - Parse the first frame
|
||||
|
@ -785,7 +858,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
|
||||
#endif
|
||||
#endif // DEBUG_LOG
|
||||
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
|
||||
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
|
||||
} else { //Only ingest values if they are not the 10V Error state
|
||||
|
@ -832,38 +905,94 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
battery_serial_number = strtoul(numberString, NULL, 10);
|
||||
}
|
||||
break;
|
||||
case 0x7AB:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0x8F:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
case 0xD0D087:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void transmit_can_battery(unsigned long currentMillis) {
|
||||
// We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
previousMillis10 = currentMillis;
|
||||
ContactorCloseRequest.present = contactorCloseReq;
|
||||
// Detect edge
|
||||
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
|
||||
// Rising edge detected
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Rising edge detected. Resetting 10ms counter.");
|
||||
#endif // DEBUG_LOG
|
||||
counter_10ms = 0; // reset counter
|
||||
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
|
||||
// Dropping edge detected
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Dropping edge detected. Resetting 10ms counter.");
|
||||
#endif // DEBUG_LOG
|
||||
counter_10ms = 0; // reset counter
|
||||
}
|
||||
ContactorCloseRequest.previous = ContactorCloseRequest.present;
|
||||
HandleBmwIxCloseContactorsRequest(counter_10ms);
|
||||
HandleBmwIxOpenContactorsRequest(counter_10ms);
|
||||
counter_10ms++;
|
||||
|
||||
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
// prevent counter overflow: 2^16-1 = 65535
|
||||
if (counter_10ms == 65535) {
|
||||
counter_10ms = 1; // set to 1, to differentiate the counter being set to 0 by the functions above
|
||||
}
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
HandleIncomingInverterRequest();
|
||||
|
||||
//Loop through and send a different UDS request each cycle
|
||||
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
|
||||
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery);
|
||||
//Loop through and send a different UDS request once the contactors are closed
|
||||
if (contactorCloseReq == true &&
|
||||
ContactorState.closed ==
|
||||
true) { // Do not send unless the contactors are requested to be closed and are closed, as sending these does not allow the contactors to close
|
||||
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
|
||||
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter],
|
||||
can_config.battery); // FIXME: sending these does not allow the contactors to close
|
||||
} else { // FIXME: hotfix: If contactors are not requested to be closed, ensure the battery is reported as alive, even if no CAN messages are received
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
}
|
||||
|
||||
// Keep contactors closed if needed
|
||||
BmwIxKeepContactorsClosed(counter_100ms);
|
||||
counter_100ms++;
|
||||
if (counter_100ms == 140) {
|
||||
counter_100ms = 0; // reset counter every 14 seconds
|
||||
}
|
||||
|
||||
//Send SME Keep alive values 100ms
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
//transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
}
|
||||
// Send 200ms CAN Message
|
||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||
previousMillis200 = currentMillis;
|
||||
|
||||
//Send SME Keep alive values 200ms
|
||||
BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
|
||||
transmit_can_frame(&BMWiX_C0, can_config.battery);
|
||||
//BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
|
||||
//transmit_can_frame(&BMWiX_C0, can_config.battery);
|
||||
}
|
||||
// Send 1000ms CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
HandleIncomingUserRequest();
|
||||
}
|
||||
// Send 10000ms CAN Message
|
||||
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
|
||||
previousMillis10000 = currentMillis;
|
||||
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
|
||||
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
|
||||
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
|
||||
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -872,7 +1001,7 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Reset Battery at bootup
|
||||
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
|
||||
//transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 108S values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
@ -883,4 +1012,211 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
void HandleIncomingUserRequest(void) {
|
||||
// Debug user request to open or close the contactors
|
||||
#ifdef DEBUG_LOG
|
||||
logging.print("User request: contactor close: ");
|
||||
logging.print(datalayer_extended.bmwix.UserRequestContactorClose);
|
||||
logging.print(" User request: contactor open: ");
|
||||
logging.println(datalayer_extended.bmwix.UserRequestContactorOpen);
|
||||
#endif // DEBUG_LOG
|
||||
if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
|
||||
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
|
||||
// do nothing
|
||||
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
|
||||
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
|
||||
BmwIxCloseContactors();
|
||||
// set user request to false
|
||||
datalayer_extended.bmwix.UserRequestContactorClose = false;
|
||||
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
|
||||
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
|
||||
BmwIxOpenContactors();
|
||||
// set user request to false
|
||||
datalayer_extended.bmwix.UserRequestContactorOpen = false;
|
||||
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
|
||||
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
|
||||
// these flasgs should not be true at the same time, therefore open contactors, as that is the safest state
|
||||
BmwIxOpenContactors();
|
||||
// set user request to false
|
||||
datalayer_extended.bmwix.UserRequestContactorClose = false;
|
||||
datalayer_extended.bmwix.UserRequestContactorOpen = false;
|
||||
// print error, as both these flags shall not be true at the same time
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println(
|
||||
"Error: user requested contactors to close and open at the same time. Contactors have been opened.");
|
||||
#endif // DEBUG_LOG
|
||||
}
|
||||
}
|
||||
|
||||
void HandleIncomingInverterRequest(void) {
|
||||
InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing;
|
||||
// Detect edge
|
||||
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
|
||||
// Rising edge detected
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Inverter requests to close contactors");
|
||||
#endif // DEBUG_LOG
|
||||
BmwIxCloseContactors();
|
||||
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
|
||||
// Falling edge detected
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Inverter requests to open contactors");
|
||||
#endif // DEBUG_LOG
|
||||
BmwIxOpenContactors();
|
||||
} // else: do nothing
|
||||
|
||||
// Update state
|
||||
InverterContactorCloseRequest.previous = InverterContactorCloseRequest.present;
|
||||
}
|
||||
|
||||
void BmwIxCloseContactors(void) {
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Closing contactors");
|
||||
#endif // DEBUG_LOG
|
||||
contactorCloseReq = true;
|
||||
}
|
||||
|
||||
void BmwIxOpenContactors(void) {
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Opening contactors");
|
||||
#endif // DEBUG_LOG
|
||||
contactorCloseReq = false;
|
||||
counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning
|
||||
}
|
||||
|
||||
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
|
||||
if (contactorCloseReq == true) { // Only when contactor close request is set to true
|
||||
if (ContactorState.closed == false &&
|
||||
ContactorState.open ==
|
||||
true) { // Only when the following commands have not been completed yet, because it shall not be run when commands have already been run, AND only when contactor open commands have finished
|
||||
// Initially 0x510[2] needs to be 0x02, and 0x510[5] needs to be 0x00
|
||||
BMWiX_510.data = {0x40, 0x10,
|
||||
0x02, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
|
||||
0x00, 0x00,
|
||||
0x00, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
|
||||
0x01, // 0x01 at contactor closing
|
||||
0x00}; // Explicit declaration, to prevent modification by other functions
|
||||
BMWiX_16E.data = {
|
||||
0x00, // Almost any possible number in 0x00 and 0xFF
|
||||
0xA0, // Almost any possible number in 0xA0 and 0xAF
|
||||
0xC9, 0xFF, 0x60,
|
||||
0xC9, 0x3A, 0xF7}; // Explicit declaration of default values, to prevent modification by other functions
|
||||
|
||||
if (counter_10ms == 0) {
|
||||
// @0 ms
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x510 - 1/6");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_10ms == 5) {
|
||||
// @50 ms
|
||||
transmit_can_frame(&BMWiX_276, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x276 - 2/6");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_10ms == 10) {
|
||||
// @100 ms
|
||||
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x510 - 3/6");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_10ms == 20) {
|
||||
// @200 ms
|
||||
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
|
||||
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x510 - 4/6");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_10ms == 30) {
|
||||
// @300 ms
|
||||
BMWiX_16E.data.u8[0] = 0x6A;
|
||||
BMWiX_16E.data.u8[1] = 0xAD;
|
||||
transmit_can_frame(&BMWiX_16E, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x16E - 5/6");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_10ms == 50) {
|
||||
// @500 ms
|
||||
BMWiX_16E.data.u8[0] = 0x03;
|
||||
BMWiX_16E.data.u8[1] = 0xA9;
|
||||
transmit_can_frame(&BMWiX_16E, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Transmitted 0x16E - 6/6");
|
||||
#endif // DEBUG_LOG
|
||||
ContactorState.closed = true;
|
||||
ContactorState.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
|
||||
if ((ContactorState.closed == true) && (ContactorState.open == false)) {
|
||||
BMWiX_510.data = {0x40, 0x10,
|
||||
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
|
||||
0x00, 0x00,
|
||||
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
|
||||
0x01, // 0x01 at contactor closing
|
||||
0x00}; // Explicit declaration, to prevent modification by other functions
|
||||
BMWiX_16E.data = {0x00, // Almost any possible number in 0x00 and 0xFF
|
||||
0xA0, // Almost any possible number in 0xA0 and 0xAF
|
||||
0xC9, 0xFF, 0x60,
|
||||
0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions
|
||||
|
||||
if (counter_100ms == 0) {
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Sending keep contactors closed messages started");
|
||||
#endif // DEBUG_LOG
|
||||
// @0 ms
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
} else if (counter_100ms == 7) {
|
||||
// @ 730 ms
|
||||
BMWiX_16E.data.u8[0] = 0x8C;
|
||||
BMWiX_16E.data.u8[1] = 0xA0;
|
||||
transmit_can_frame(&BMWiX_16E, can_config.battery);
|
||||
} else if (counter_100ms == 24) {
|
||||
// @2380 ms
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
} else if (counter_100ms == 29) {
|
||||
// @ 2900 ms
|
||||
BMWiX_16E.data.u8[0] = 0x02;
|
||||
BMWiX_16E.data.u8[1] = 0xA7;
|
||||
transmit_can_frame(&BMWiX_16E, can_config.battery);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Sending keep contactors closed messages finished");
|
||||
#endif // DEBUG_LOG
|
||||
} else if (counter_100ms == 140) {
|
||||
// @14000 ms
|
||||
// reset counter (outside of this function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms) {
|
||||
if (contactorCloseReq == false) { // if contactors are not requested to be closed, they are requested to be opened
|
||||
if (ContactorState.open == false) { // only if contactors are not open yet
|
||||
// message content to quickly open contactors
|
||||
if (counter_10ms == 0) {
|
||||
// @0 ms (0.00) RX0 510 [8] 40 10 00 00 00 80 00 00
|
||||
BMWiX_510.data = {0x40, 0x10, 0x00, 0x00,
|
||||
0x00, 0x80, 0x00, 0x00}; // Explicit declaration, to prevent modification by other functions
|
||||
transmit_can_frame(&BMWiX_510, can_config.battery);
|
||||
// set back to default values
|
||||
BMWiX_510.data = {0x40, 0x10, 0x04, 0x00, 0x00, 0x80, 0x01, 0x00}; // default values
|
||||
} else if (counter_10ms == 6) {
|
||||
// @60 ms (0.06) RX0 16E [8] E6 A4 C8 FF 60 C9 33 F0
|
||||
BMWiX_16E.data = {0xE6, 0xA4, 0xC8, 0xFF,
|
||||
0x60, 0xC9, 0x33, 0xF0}; // Explicit declaration, to prevent modification by other functions
|
||||
transmit_can_frame(&BMWiX_16E, can_config.battery);
|
||||
// set back to default values
|
||||
BMWiX_16E.data = {0x00, 0xA0, 0xC9, 0xFF, 0x60, 0xC9, 0x3A, 0xF7}; // default values
|
||||
ContactorState.closed = false;
|
||||
ContactorState.open = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BMW_IX_BATTERY
|
||||
|
|
|
@ -19,4 +19,69 @@
|
|||
void setup_battery(void);
|
||||
void transmit_can_frame(CAN_frame* tx_frame, int interface);
|
||||
|
||||
/**
|
||||
* @brief Handle incoming user request to close or open contactors
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void HandleIncomingUserRequest(void);
|
||||
|
||||
/**
|
||||
* @brief Handle incoming inverter request to close or open contactors.alignas
|
||||
*
|
||||
* This function uses the "inverter_allows_contactor_closing" flag from the datalayer, to determine if CAN messages shall be sent to the battery to close or open the contactors.
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void HandleIncomingInverterRequest(void);
|
||||
|
||||
/**
|
||||
* @brief Close contactors of the BMW iX battery
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void BmwIxCloseContactors(void);
|
||||
|
||||
/**
|
||||
* @brief Handle close contactors requests for the BMW iX battery
|
||||
*
|
||||
* @param[in] counter_10ms Counter that increments by 1, every 10ms
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms);
|
||||
|
||||
/**
|
||||
* @brief Keep contactors of the BMW iX battery closed
|
||||
*
|
||||
* @param[in] counter_100ms Counter that increments by 1, every 100 ms
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void BmwIxKeepContactorsClosed(uint8_t counter_100ms);
|
||||
|
||||
/**
|
||||
* @brief Open contactors of the BMW iX battery
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void BmwIxOpenContactors(void);
|
||||
|
||||
/**
|
||||
* @brief Handle open contactors requests for the BMW iX battery
|
||||
*
|
||||
* @param[in] counter_10ms Counter that increments by 1, every 10ms
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -41,6 +41,9 @@ typedef struct {
|
|||
} DATALAYER_INFO_BOLTAMPERA;
|
||||
|
||||
typedef struct {
|
||||
/** User requesting contactor open or close via WebUI*/
|
||||
bool UserRequestContactorClose = false;
|
||||
bool UserRequestContactorOpen = false;
|
||||
/** uint16_t */
|
||||
/** Terminal 30 - 12V SME Supply Voltage */
|
||||
uint16_t T30_Voltage = 0;
|
||||
|
|
|
@ -58,6 +58,8 @@ String advanced_battery_processor(const String& var) {
|
|||
#endif //BOLT_AMPERA_BATTERY
|
||||
|
||||
#ifdef BMW_IX_BATTERY
|
||||
content += "<button onclick='askContactorClose()'>Close Contactors</button>";
|
||||
content += "<button onclick='askContactorOpen()'>Open Contactors</button>";
|
||||
content +=
|
||||
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
|
||||
" dV</h4>";
|
||||
|
@ -1492,6 +1494,7 @@ String advanced_battery_processor(const String& var) {
|
|||
#endif
|
||||
|
||||
content += "</div>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
|
||||
|
@ -1504,6 +1507,7 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the "
|
||||
|
@ -1516,6 +1520,7 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askResetCrash() { if (window.confirm('Are you sure you want to reset crash data? "
|
||||
|
@ -1540,6 +1545,33 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askContactorClose() { if (window.confirm('Are you sure you want to tirgger "
|
||||
"a contactor close request?')) { "
|
||||
"bmwIxCloseContactorRequest(); } }";
|
||||
content += "function bmwIxCloseContactorRequest() {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.open('GET', '/bmwIxCloseContactorRequest', true);";
|
||||
content += " xhr.send();";
|
||||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askContactorOpen() { if (window.confirm('Are you sure you want to tirgger "
|
||||
"a contactor open request?')) { "
|
||||
"bmwIxOpenContactorRequest(); } }";
|
||||
content += "function bmwIxOpenContactorRequest() {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.open('GET', '/bmwIxOpenContactorRequest', true);";
|
||||
content += " xhr.send();";
|
||||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
|
||||
|
@ -1552,6 +1584,7 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function Volvo_askEraseDTC() { if (window.confirm('Are you sure you want to erase DTCs?')) { "
|
||||
|
@ -1585,6 +1618,7 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
|
||||
// Additial functions added
|
||||
content += "<script>";
|
||||
content += "function exportLog() { window.location.href = '/export_log'; }";
|
||||
|
|
|
@ -599,6 +599,24 @@ void init_webserver() {
|
|||
request->send(200, "text/plain", "Updated successfully");
|
||||
});
|
||||
|
||||
// Route for closing BMW iX Contactors
|
||||
server.on("/bmwIxCloseContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
datalayer_extended.bmwix.UserRequestContactorClose = true;
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
});
|
||||
|
||||
// Route for opening BMW iX Contactors
|
||||
server.on("/bmwIxOpenContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
datalayer_extended.bmwix.UserRequestContactorOpen = true;
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
});
|
||||
|
||||
// Route for resetting SOH on Nissan LEAF batteries
|
||||
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||
|
@ -1207,15 +1225,14 @@ String processor(const String& var) {
|
|||
}
|
||||
}
|
||||
|
||||
content += "<h4>Automatic contactor closing allowed:</h4>";
|
||||
content += "<h4>Battery: ";
|
||||
content += "<h4>Battery allows contactor closing: ";
|
||||
if (datalayer.system.status.battery_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Inverter: ";
|
||||
content += " Inverter allows contactor closing: ";
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span></h4>";
|
||||
} else {
|
||||
|
|
|
@ -1,143 +1,11 @@
|
|||
#include "../include.h"
|
||||
#ifdef FERROAMP_CAN
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "FERROAMP-CAN.h"
|
||||
|
||||
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
|
||||
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
|
||||
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
|
||||
//useful for some inverters like Sofar that report the voltages incorrect otherwise
|
||||
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
|
||||
|
||||
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
|
||||
Change the following only if your inverter is generating fault codes about voltage range */
|
||||
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
|
||||
#define MODULES_IN_SERIES 4
|
||||
#define CELLS_PER_MODULE 30
|
||||
#define VOLTAGE_LEVEL 384
|
||||
#define AH_CAPACITY 37
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
//Actual content messages
|
||||
CAN_frame PYLON_7310 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7310,
|
||||
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame PYLON_7311 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7311,
|
||||
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame PYLON_7320 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7320,
|
||||
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
|
||||
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
|
||||
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
|
||||
CAN_frame PYLON_7321 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7321,
|
||||
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
|
||||
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
|
||||
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
|
||||
CAN_frame PYLON_4210 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4210,
|
||||
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame PYLON_4220 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4220,
|
||||
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame PYLON_4230 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4230,
|
||||
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame PYLON_4240 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4240,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame PYLON_4250 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4250,
|
||||
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4260 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4260,
|
||||
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame PYLON_4270 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4270,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame PYLON_4280 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4280,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4290 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4290,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4211 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4211,
|
||||
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame PYLON_4221 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4221,
|
||||
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame PYLON_4231 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4231,
|
||||
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame PYLON_4241 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4241,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame PYLON_4251 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4251,
|
||||
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4261 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4261,
|
||||
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame PYLON_4271 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4271,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame PYLON_4281 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4281,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4291 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4291,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
static uint16_t cell_tweaked_max_voltage_mV = 3300;
|
||||
static uint16_t cell_tweaked_min_voltage_mV = 3300;
|
||||
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void FerroampCanInverter::
|
||||
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//There are more mappings that could be added, but this should be enough to use as a starting point
|
||||
// Note we map both 0 and 1 messages
|
||||
|
||||
|
@ -437,7 +305,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
|
|||
}
|
||||
}
|
||||
|
||||
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
|
||||
void FerroampCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
|
||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
|
@ -453,11 +321,11 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void transmit_can_inverter(unsigned long currentMillis) {
|
||||
void FerroampCanInverter::transmit_can(unsigned long currentMillis) {
|
||||
// No periodic sending, we only react on received can messages
|
||||
}
|
||||
|
||||
void send_setup_info() { //Ensemble information
|
||||
void FerroampCanInverter::send_setup_info() { //Ensemble information
|
||||
#ifdef SEND_0
|
||||
transmit_can_frame(&PYLON_7310, can_config.inverter);
|
||||
transmit_can_frame(&PYLON_7320, can_config.inverter);
|
||||
|
@ -468,7 +336,7 @@ void send_setup_info() { //Ensemble information
|
|||
#endif
|
||||
}
|
||||
|
||||
void send_system_data() { //System equipment information
|
||||
void FerroampCanInverter::send_system_data() { //System equipment information
|
||||
#ifdef SEND_0
|
||||
transmit_can_frame(&PYLON_4210, can_config.inverter);
|
||||
transmit_can_frame(&PYLON_4220, can_config.inverter);
|
||||
|
@ -492,7 +360,8 @@ void send_system_data() { //System equipment information
|
|||
transmit_can_frame(&PYLON_4291, can_config.inverter);
|
||||
#endif
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
|
||||
void FerroampCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Ferroamp Pylon battery over CAN bus", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
|
|
|
@ -3,10 +3,154 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define CAN_INVERTER_SELECTED
|
||||
#define SELECTED_INVERTER_CLASS FerroampCanInverter
|
||||
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
void transmit_can_frame(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
class FerroampCanInverter : public CanInverterProtocol {
|
||||
public:
|
||||
void setup();
|
||||
|
||||
void update_values();
|
||||
|
||||
void transmit_can(unsigned long currentMillis);
|
||||
|
||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||
|
||||
private:
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
|
||||
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
|
||||
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
|
||||
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
|
||||
//useful for some inverters like Sofar that report the voltages incorrect otherwise
|
||||
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
|
||||
|
||||
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
|
||||
Change the following only if your inverter is generating fault codes about voltage range */
|
||||
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
|
||||
#define MODULES_IN_SERIES 4
|
||||
#define CELLS_PER_MODULE 30
|
||||
#define VOLTAGE_LEVEL 384
|
||||
#define AH_CAPACITY 37
|
||||
|
||||
//Actual content messages
|
||||
CAN_frame PYLON_7310 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7310,
|
||||
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame PYLON_7311 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7311,
|
||||
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame PYLON_7320 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7320,
|
||||
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
|
||||
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
|
||||
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
|
||||
CAN_frame PYLON_7321 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x7321,
|
||||
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
|
||||
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
|
||||
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
|
||||
CAN_frame PYLON_4210 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4210,
|
||||
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame PYLON_4220 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4220,
|
||||
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame PYLON_4230 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4230,
|
||||
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame PYLON_4240 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4240,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame PYLON_4250 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4250,
|
||||
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4260 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4260,
|
||||
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame PYLON_4270 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4270,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame PYLON_4280 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4280,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4290 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4290,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4211 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4211,
|
||||
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame PYLON_4221 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4221,
|
||||
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame PYLON_4231 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4231,
|
||||
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame PYLON_4241 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4241,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame PYLON_4251 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4251,
|
||||
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4261 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4261,
|
||||
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame PYLON_4271 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4271,
|
||||
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame PYLON_4281 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4281,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame PYLON_4291 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x4291,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
uint16_t cell_tweaked_max_voltage_mV = 3300;
|
||||
uint16_t cell_tweaked_min_voltage_mV = 3300;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue