Merge pull request #224 from dalathegreat/feature/bmwi3

New battery: BMW i3 🔋
This commit is contained in:
Daniel Öster 2024-03-16 21:24:50 +02:00 committed by GitHub
commit d4ecbfddbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 631 additions and 105 deletions

View file

@ -5,16 +5,57 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "BMW-I3-BATTERY.h"
//TODO: before using
// Map the final values in update_values_battery, set some to static values if not available (current, discharge max , charge max)
// Check if I3 battery stays alive with only 10B and 512. If not, add 12F. If that doesn't help, add more from CAN log (ask Dala)
/* 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 previousMillis600 = 0; // will store last time a 600ms 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
static const int interval20 = 20; // interval (ms) at which send CAN Messages
static const int interval600 = 600; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static const int interval200 = 200; // interval (ms) at which send CAN Messages
static const int interval500 = 500; // interval (ms) at which send CAN Messages
static const int interval640 = 640; // interval (ms) at which send CAN Messages
static const int interval1000 = 1000; // interval (ms) at which send CAN Messages
static const int interval5000 = 5000; // interval (ms) at which send CAN Messages
static const int interval10000 = 10000; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
#define MAX_CAN_FAILURES 500 // Amount of malformed CAN messages to allow before raising a warning
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
static const uint16_t WUPonDuration = 477; // in milliseconds how long WUP should be ON after poweron
static const uint16_t WUPoffDuration = 105; // in milliseconds how long WUP should be OFF after on pulse
unsigned long lastChangeTime; // Variables to store timestamps
unsigned long turnOnTime; // Variables to store timestamps
enum State { POWERON, STATE_ON, STATE_OFF };
static State WUPState = POWERON;
const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0,
0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23,
0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40,
0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B,
0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B,
0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8,
0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D,
0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC,
0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B,
0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C,
0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB,
0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47,
0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
0xE3, 0xFE, 0xD9, 0xC4};
/* 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_t BMW_10B = {.FIR = {.B =
{
@ -22,71 +63,336 @@ CAN_frame_t BMW_10B = {.FIR = {.B =
.FF = CAN_frame_std,
}},
.MsgID = 0x10B,
.data = {0xCD, 0x01, 0xFC}};
CAN_frame_t BMW_512 = {
.FIR = {.B =
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
CAN_frame_t BMW_12F = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; //0x512 Network management edme VCU
//CAN_frame_t BMW_12F //Might be needed, Wake up ,VCU 100ms
//These CAN messages need to be sent towards the battery to keep it alive
.MsgID = 0x12F,
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
CAN_frame_t BMW_13E = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame_t BMW_192 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x192,
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
CAN_frame_t BMW_19B = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x19B,
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
CAN_frame_t BMW_1D0 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x1D0,
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame_t BMW_2CA = {.FIR = {.B =
{
.DLC = 2,
.FF = CAN_frame_std,
}},
.MsgID = 0x2CA,
.data = {0x57, 0x57}};
CAN_frame_t BMW_2E2 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x2E2,
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
CAN_frame_t BMW_30B = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x30B,
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
CAN_frame_t BMW_328 = {.FIR = {.B =
{
.DLC = 6,
.FF = CAN_frame_std,
}},
.MsgID = 0x328,
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame_t BMW_37B = {.FIR = {.B =
{
.DLC = 6,
.FF = CAN_frame_std,
}},
.MsgID = 0x37B,
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame_t BMW_380 = {.FIR = {.B =
{
.DLC = 7,
.FF = CAN_frame_std,
}},
.MsgID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame_t BMW_3A0 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x3A0,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
CAN_frame_t BMW_3A7 = {.FIR = {.B =
{
.DLC = 7,
.FF = CAN_frame_std,
}},
.MsgID = 0x3A7,
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
CAN_frame_t BMW_3C5 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame_t BMW_3CA = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x3CA,
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
CAN_frame_t BMW_3D0 = {.FIR = {.B =
{
.DLC = 2,
.FF = CAN_frame_std,
}},
.MsgID = 0x3D0,
.data = {0xFD, 0xFF}};
CAN_frame_t BMW_3E4 = {.FIR = {.B =
{
.DLC = 6,
.FF = CAN_frame_std,
}},
.MsgID = 0x3E4,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame_t BMW_3E5 = {.FIR = {.B =
{
.DLC = 3,
.FF = CAN_frame_std,
}},
.MsgID = 0x3E5,
.data = {0xFC, 0xFF, 0xFF}};
CAN_frame_t BMW_3E8 = {.FIR = {.B =
{
.DLC = 2,
.FF = CAN_frame_std,
}},
.MsgID = 0x3E8,
.data = {0xF0, 0xFF}}; //1000ms OBD reset
CAN_frame_t BMW_3EC = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x3EC,
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
CAN_frame_t BMW_3F9 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame_t BMW_3FB = {.FIR = {.B =
{
.DLC = 6,
.FF = CAN_frame_std,
}},
.MsgID = 0x3FB,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
CAN_frame_t BMW_3FC = {.FIR = {.B =
{
.DLC = 3,
.FF = CAN_frame_std,
}},
.MsgID = 0x3FC,
.data = {0xC0, 0xF9, 0x0F}};
CAN_frame_t BMW_418 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x418,
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
CAN_frame_t BMW_41D = {.FIR = {.B =
{
.DLC = 4,
.FF = CAN_frame_std,
}},
.MsgID = 0x41D,
.data = {0xFF, 0xF7, 0x7F, 0xFF}};
CAN_frame_t BMW_433 = {.FIR = {.B =
{
.DLC = 4,
.FF = CAN_frame_std,
}},
.MsgID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame_t BMW_512 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x512, // Required to keep BMS active
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame_t BMW_592_0 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame_t BMW_592_1 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame_t BMW_5F8 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
static const uint8_t BMW_10B_0[15] = {0xCD, 0x19, 0x94, 0x6D, 0xE0, 0x34, 0x78, 0xDB,
0x97, 0x43, 0x0F, 0xF6, 0xBA, 0x6E, 0x81};
static const uint8_t BMW_10B_1[15] = {0x01, 0x02, 0x33, 0x34, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x00};
static uint8_t BMW_10B_counter = 0;
//The above CAN messages need to be sent towards the battery to keep it alive
static int16_t Battery_Current = 0;
static uint16_t Battery_Capacity_kWh = 0;
static uint16_t Voltage_Setpoint = 0;
static uint16_t Low_SOC = 0;
static uint16_t High_SOC = 0;
static uint16_t Display_SOC = 0;
static uint16_t Calculated_SOC = 0;
static uint16_t Battery_Volts = 0;
static uint16_t HVBatt_SOC = 0;
static uint16_t Battery_Status = 0;
static uint16_t DC_link = 0;
static int16_t Battery_Power = 0;
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_counter = 0;
static bool battery_awake = false;
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_kWh = 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 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_power = 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_cellvoltage_mux = 0;
static uint8_t calculateCRC(CAN_frame_t rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
crc = crc8_table[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
}
return crc;
}
static uint8_t increment_alive_counter(uint8_t counter) {
counter++;
if (counter > ALIVE_MAX_VALUE) {
counter = 0;
}
return counter;
}
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
//Calculate the SOC% value to send to inverter
system_real_SOC_pptt = (Display_SOC * 100); //increase Display_SOC range from 0-100 -> 100.00
system_battery_voltage_dV = Battery_Volts; //Unit V+1 (5000 = 500.0V)
system_real_SOC_pptt = (battery_display_SOC * 100); //increase Display_SOC range from 0-100 -> 100.00
system_battery_current_dA = Battery_Current;
system_battery_voltage_dV = battery_volts; //Unit V+1 (5000 = 500.0V)
system_battery_current_dA = battery_current;
system_capacity_Wh = BATTERY_WH_MAX;
system_remaining_capacity_Wh = (Battery_Capacity_kWh * 1000);
system_remaining_capacity_Wh = (battery_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
if (system_scaled_SOC_pptt > 9900) //If Soc is over 99%, stop charging
{
system_max_charge_power_W = 0;
} else {
system_max_charge_power_W = 5000; //Hardcoded value for testing. TODO: read real value from battery when discovered
}
system_max_charge_power_W = (battery_max_charge_amperage * system_battery_voltage_dV);
if (system_scaled_SOC_pptt < 500) //If Soc is under 5%, stop dicharging
{
system_max_discharge_power_W = 0;
} else {
system_max_discharge_power_W =
5000; //Hardcoded value for testing. TODO: read real value from battery when discovered
}
system_max_discharge_power_W = (battery_max_discharge_amperage * system_battery_voltage_dV);
Battery_Power = (Battery_Current * (Battery_Volts / 10));
battery_power = (system_battery_current_dA * (system_battery_voltage_dV / 10));
system_active_power_W = Battery_Power; //TODO:, is mapping OK?
system_active_power_W = battery_power;
system_temperature_min_dC; //hardcoded to 5*C in startup, TODO:, find from battery CAN later
system_temperature_min_dC = battery_temperature_min * 10; // Add a decimal
system_temperature_max_dC; //hardcoded to 6*C in startup, TODO:, find from battery CAN later
system_temperature_max_dC = battery_temperature_max * 10; // Add a decimal
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
@ -95,64 +401,156 @@ void update_values_battery() { //This function maps all the values fetched via
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
// Check if we have encountered any malformed CAN messages
if (CANerror > MAX_CAN_FAILURES) {
set_event(EVENT_CAN_RX_WARNING, 0);
}
#ifdef DEBUG_VIA_USB
Serial.print("SOC% battery: ");
Serial.print(Display_SOC);
Serial.print(" SOC% sent to inverter: ");
Serial.print(system_scaled_SOC_pptt);
Serial.println(" ");
Serial.print("Values sent to inverter: ");
Serial.print("Real SOC%: ");
Serial.print(system_real_SOC_pptt * 0.01);
Serial.print(" Battery voltage: ");
Serial.print(system_battery_voltage_dV);
Serial.print(system_battery_voltage_dV * 0.1);
Serial.print(" Battery current: ");
Serial.print(system_battery_current_dA * 0.1);
Serial.print(" Wh when full: ");
Serial.print(system_capacity_Wh);
Serial.print(" Remaining Wh: ");
Serial.print(system_remaining_capacity_Wh);
Serial.print(" Max charge power: ");
Serial.print(system_max_charge_power_W);
Serial.print(" Max discharge power: ");
Serial.print(system_max_discharge_power_W);
Serial.print(" Active power: ");
Serial.print(system_active_power_W);
Serial.print(" Min temp: ");
Serial.print(system_temperature_min_dC * 0.1);
Serial.print(" Max temp: ");
Serial.print(system_temperature_max_dC * 0.1);
#endif
}
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive = 12;
switch (rx_frame.MsgID) {
case 0x431: //Battery capacity [200ms]
Battery_Capacity_kWh = (((rx_frame.data.u8[1] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
battery_awake = true;
CANstillAlive = 12; //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]) / 10) - 819; //Amps
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
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);
battery_request_open_contactors_fast = (rx_frame.data.u8[6] & 0x0C) >> 2;
battery_charging_condition_delta = (rx_frame.data.u8[6] & 0xF0) >> 4;
battery_DC_link_voltage = rx_frame.data.u8[7];
break;
case 0x432: //SOC% charged [200ms]
Voltage_Setpoint = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10;
Low_SOC = (rx_frame.data.u8[2] / 2);
High_SOC = (rx_frame.data.u8[3] / 2);
Display_SOC = (rx_frame.data.u8[4] / 2);
case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1
battery_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03);
battery_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2;
battery_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4;
battery_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6;
battery_status_error_locking = (rx_frame.data.u8[1] & 0x03);
battery_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2;
battery_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4;
battery_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6;
battery_request_service = (rx_frame.data.u8[2] & 0x03);
battery_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2;
battery_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4;
battery_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6;
battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F);
battery_temperature_HV = (rx_frame.data.u8[4] - 50);
battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50);
battery_temperature_max = (rx_frame.data.u8[6] - 50);
battery_temperature_min = (rx_frame.data.u8[7] - 50);
break;
case 0x112: //BMS status [10ms]
CANstillAlive = 12;
Battery_Current = ((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) / 10) - 819; //Amps
Battery_Volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 4 | rx_frame.data.u8[4]) / 10;
Battery_Status = (rx_frame.data.u8[6] & 0x0F);
DC_link = rx_frame.data.u8[7];
case 0x239: //BMS [200ms]
battery_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh
battery_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh
break;
case 0x430:
case 0x2BD: //BMS [100ms] Status diagnosis high voltage - 1
battery_awake = true;
if (calculateCRC(rx_frame, 3, 0x15) != rx_frame.data.u8[0]) {
//If calculated CRC does not match transmitted CRC, increase CANerror counter
CANerror++;
break;
case 0x1FA:
}
battery_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
break;
case 0x40D:
case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations
battery_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
battery_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2);
battery_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
battery_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2);
break;
case 0x2FF:
case 0x2FF: //BMS [100ms] Status Heating High-Voltage Battery
battery_awake = true;
battery_actual_value_power_heating = (rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4);
break;
case 0x239:
case 0x363: //BMS [1s] Identification High-Voltage Battery
battery_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 0x2BD:
case 0x3C2: //BMS (94AH exclusive) - Status diagnostics OBD 2 powertrain
battery_status_diagnosis_powertrain_maximum_multiplexer =
((rx_frame.data.u8[1] & 0x03) << 4 | rx_frame.data.u8[0] >> 4);
battery_status_diagnosis_powertrain_immediate_multiplexer = (rx_frame.data.u8[0] & 0xFC) >> 2;
break;
case 0x2F5:
case 0x3EB: //BMS [1s] Status of charging high-voltage storage - 3
battery_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
battery_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
battery_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
battery_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
break;
case 0x3EB:
case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1
battery_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
battery_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
battery_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
battery_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
break;
case 0x363:
case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2
battery_status_cooling_HV = (rx_frame.data.u8[1] & 0x03);
break;
case 0x507:
case 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command?
battery_cellvoltage_mux = rx_frame.data.u8[0];
if (battery_cellvoltage_mux == 0) {
system_cellvoltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800);
system_cellvoltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800);
system_cellvoltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800);
system_cellvoltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800);
system_cellvoltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800);
system_cellvoltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800);
system_cellvoltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800);
}
break;
case 0x41C:
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
battery_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
battery_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
battery_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
battery_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x431: //BMS [200ms] Data High-Voltage Battery Unit
battery_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F);
battery_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2;
battery_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4;
battery_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
battery_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
battery_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
break;
case 0x432: //BMS [200ms] SOC% info
battery_request_operating_mode = (rx_frame.data.u8[0] & 0x03);
battery_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10;
battery_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2);
battery_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2);
battery_display_SOC = (rx_frame.data.u8[4] / 2);
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
battery_ID2 = rx_frame.data.u8[0];
break;
case 0x607: //BMS - No use for this message
break;
default:
break;
@ -160,32 +558,156 @@ void receive_can_battery(CAN_frame_t rx_frame) {
}
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 600ms CAN Message
if (currentMillis - previousMillis600 >= interval600) {
previousMillis600 = currentMillis;
ESP32Can.CANWriteFrame(&BMW_512);
}
if (battery_awake) {
//Send 20ms message
if (currentMillis - previousMillis20 >= interval20) {
previousMillis20 = currentMillis;
BMW_10B.data.u8[0] = BMW_10B_0[BMW_10B_counter];
BMW_10B.data.u8[1] = BMW_10B_1[BMW_10B_counter];
BMW_10B_counter++;
if (BMW_10B_counter > 14) {
BMW_10B_counter = 0;
if (startup_counter_contactor < 160) {
startup_counter_contactor++;
} else { //After 160 messages, turn on the request
BMW_10B.data.u8[1] = 0x10; // Close contactors
}
if (system_bms_status == FAULT) {
BMW_10B.data.u8[1] = 0x00; // Open contactors (TODO: test if this works)
}
BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms);
BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F);
alive_counter_20ms = increment_alive_counter(alive_counter_20ms);
BMW_13E_counter++;
BMW_13E.data.u8[4] = BMW_13E_counter;
ESP32Can.CANWriteFrame(&BMW_10B);
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
previousMillis100 = currentMillis;
BMW_12F.data.u8[1] = ((BMW_12F.data.u8[1] & 0xF0) + alive_counter_100ms);
BMW_12F.data.u8[0] = calculateCRC(BMW_12F, 8, 0x60);
alive_counter_100ms = increment_alive_counter(alive_counter_100ms);
ESP32Can.CANWriteFrame(&BMW_12F);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= interval200) {
previousMillis200 = currentMillis;
BMW_19B.data.u8[1] = ((BMW_19B.data.u8[1] & 0xF0) + alive_counter_200ms);
BMW_19B.data.u8[0] = calculateCRC(BMW_19B, 8, 0x6C);
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
ESP32Can.CANWriteFrame(&BMW_19B);
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= interval500) {
previousMillis500 = currentMillis;
BMW_30B.data.u8[1] = ((BMW_30B.data.u8[1] & 0xF0) + alive_counter_500ms);
BMW_30B.data.u8[0] = calculateCRC(BMW_30B, 8, 0xBE);
alive_counter_500ms = increment_alive_counter(alive_counter_500ms);
ESP32Can.CANWriteFrame(&BMW_30B);
}
// Send 640ms CAN Message
if (currentMillis - previousMillis640 >= interval640) {
previousMillis640 = currentMillis;
ESP32Can.CANWriteFrame(&BMW_512); // Keep BMS alive
ESP32Can.CANWriteFrame(&BMW_5F8);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= interval1000) {
previousMillis1000 = currentMillis;
BMW_328_counter++; // Used to increment seconds
BMW_328.data.u8[0] = BMW_328_counter;
BMW_328.data.u8[1] = BMW_328_counter << 8;
BMW_328.data.u8[2] = BMW_328_counter << 16;
BMW_328.data.u8[3] = BMW_328_counter << 24;
BMW_1D0.data.u8[1] = ((BMW_1D0.data.u8[1] & 0xF0) + alive_counter_1000ms);
BMW_1D0.data.u8[0] = calculateCRC(BMW_1D0, 8, 0xF9);
BMW_3F9.data.u8[1] = ((BMW_3F9.data.u8[1] & 0xF0) + alive_counter_1000ms);
BMW_3F9.data.u8[0] = calculateCRC(BMW_3F9, 8, 0x38);
BMW_3EC.data.u8[1] = ((BMW_3EC.data.u8[1] & 0xF0) + alive_counter_1000ms);
BMW_3EC.data.u8[0] = calculateCRC(BMW_3EC, 8, 0x53);
BMW_3A7.data.u8[1] = ((BMW_3A7.data.u8[1] & 0xF0) + alive_counter_1000ms);
BMW_3A7.data.u8[0] = calculateCRC(BMW_3A7, 8, 0x05);
alive_counter_1000ms = increment_alive_counter(alive_counter_1000ms);
ESP32Can.CANWriteFrame(&BMW_3E8); //Order comes from CAN logs
ESP32Can.CANWriteFrame(&BMW_328);
ESP32Can.CANWriteFrame(&BMW_3F9);
ESP32Can.CANWriteFrame(&BMW_2E2);
ESP32Can.CANWriteFrame(&BMW_41D);
ESP32Can.CANWriteFrame(&BMW_3D0);
ESP32Can.CANWriteFrame(&BMW_3CA);
ESP32Can.CANWriteFrame(&BMW_3A7);
ESP32Can.CANWriteFrame(&BMW_2CA);
ESP32Can.CANWriteFrame(&BMW_3FB);
ESP32Can.CANWriteFrame(&BMW_418);
ESP32Can.CANWriteFrame(&BMW_1D0);
ESP32Can.CANWriteFrame(&BMW_3EC);
ESP32Can.CANWriteFrame(&BMW_192);
ESP32Can.CANWriteFrame(&BMW_13E);
ESP32Can.CANWriteFrame(&BMW_433);
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
}
// Send 5000ms CAN Message
if (currentMillis - previousMillis5000 >= interval5000) {
previousMillis5000 = 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);
ESP32Can.CANWriteFrame(&BMW_3FC); //Order comes from CAN logs
ESP32Can.CANWriteFrame(&BMW_3C5);
ESP32Can.CANWriteFrame(&BMW_3A0);
ESP32Can.CANWriteFrame(&BMW_592_0);
ESP32Can.CANWriteFrame(&BMW_592_1);
alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms);
if (BMW_380_counter < 3) {
ESP32Can.CANWriteFrame(&BMW_380); // This message stops after 3 times on startup
BMW_380_counter++;
}
}
// Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= interval10000) {
previousMillis10000 = currentMillis;
ESP32Can.CANWriteFrame(&BMW_3E5); //Order comes from CAN logs
ESP32Can.CANWriteFrame(&BMW_3E4);
ESP32Can.CANWriteFrame(&BMW_37B);
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
}
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("BMW i3 battery selected");
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
system_min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled
digitalWrite(WUP_PIN, HIGH); // Wake up the battery
}
#endif

View file

@ -26,6 +26,10 @@
#define PRECHARGE_PIN 25
#endif
#ifdef BMW_I3_BATTERY
#define WUP_PIN 25
#endif
#define SD_MISO_PIN 2
#define SD_MOSI_PIN 15
#define SD_SCLK_PIN 14