BYD Atto 3 battery converted to use the base class

This commit is contained in:
Jaakko Haakana 2025-05-13 15:55:11 +03:00
parent 75427cdcd6
commit 09e719690f
2 changed files with 224 additions and 395 deletions

View file

@ -1,5 +1,6 @@
#include "../include.h"
#ifdef BYD_ATTO_3_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
@ -11,76 +12,6 @@ If you have a crash-locked pack, See the Wiki for more info on how to attempt an
After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the BYD-ATTO-3-BATTERY.h file
*/
/* Do not change code below unless you are sure what you are doing */
#define NOT_DETERMINED_YET 0
#define STANDARD_RANGE 1
#define EXTENDED_RANGE 2
#define NOT_RUNNING 0xFF
#define STARTED 0
#define RUNNING_STEP_1 1
#define RUNNING_STEP_2 2
static uint8_t battery_type = NOT_DETERMINED_YET;
static uint8_t stateMachineClearCrash = NOT_RUNNING;
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static bool SOC_method = false;
static uint8_t counter_50ms = 0;
static uint8_t counter_100ms = 0;
static uint8_t frame6_counter = 0xB;
static uint8_t frame7_counter = 0x5;
static uint16_t battery_voltage = 0;
static int16_t battery_temperature_ambient = 0;
static int16_t battery_daughterboard_temperatures[10];
static int16_t battery_lowest_temperature = 0;
static int16_t battery_highest_temperature = 0;
static int16_t battery_calc_min_temperature = 0;
static int16_t battery_calc_max_temperature = 0;
static uint16_t battery_highprecision_SOC = 0;
static uint16_t BMS_SOC = 0;
static uint16_t BMS_voltage = 0;
static int16_t BMS_current = 0;
static int16_t BMS_lowest_cell_temperature = 0;
static int16_t BMS_highest_cell_temperature = 0;
static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
static uint32_t BMS_unknown0 = 0;
static uint32_t BMS_unknown1 = 0;
static uint16_t BMS_allowed_charge_power = 0;
static uint16_t BMS_unknown3 = 0;
static uint16_t BMS_unknown4 = 0;
static uint16_t BMS_unknown5 = 0;
static uint16_t BMS_unknown6 = 0;
static uint16_t BMS_unknown7 = 0;
static uint16_t BMS_unknown8 = 0;
static uint16_t BMS_unknown9 = 0;
static uint8_t BMS_unknown10 = 0;
static uint8_t BMS_unknown11 = 0;
static uint8_t BMS_unknown12 = 0;
static uint8_t BMS_unknown13 = 0;
static uint8_t battery_frame_index = 0;
static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#ifdef DOUBLE_BATTERY
static int16_t battery2_temperature_ambient = 0;
static int16_t battery2_daughterboard_temperatures[10];
static int16_t battery2_lowest_temperature = 0;
static int16_t battery2_highest_temperature = 0;
static int16_t battery2_calc_min_temperature = 0;
static int16_t battery2_calc_max_temperature = 0;
static uint16_t battery2_highprecision_SOC = 0;
static uint16_t BMS2_SOC = 0;
static uint16_t BMS2_voltage = 0;
static int16_t BMS2_current = 0;
static int16_t BMS2_lowest_cell_temperature = 0;
static int16_t BMS2_highest_cell_temperature = 0;
static int16_t BMS2_average_cell_temperature = 0;
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
static uint8_t battery2_frame_index = 0;
static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x0005
#define POLL_FOR_BATTERY_VOLTAGE 0x0008
#define POLL_FOR_BATTERY_CURRENT 0x0009
#define POLL_FOR_LOWEST_TEMP_CELL 0x002f
@ -165,34 +96,6 @@ static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#define ESTIMATED 0
#define MEASURED 1
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
static uint16_t pid_reply = 0;
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12D,
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
CAN_frame ATTO_3_441 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x441,
.data = {0x98, 0x3A, 0x88, 0x13, 0x07, 0x00, 0xFF, 0x8C}};
CAN_frame ATTO_3_7E7_POLL = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //ACK frame for long PIDs
.data = {0x30, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_CLEAR_CRASH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
// Define the data points for %SOC depending on pack voltage
const uint8_t numPoints = 28;
@ -241,10 +144,11 @@ uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation fu
return 0; // Default return for safety, should never reach here
}
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
void BydAttoBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS_voltage > 0) {
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
datalayer_battery->status.voltage_dV = BMS_voltage * 10;
}
#ifdef USE_ESTIMATED_SOC
@ -252,32 +156,32 @@ void update_values_battery() { //This function maps all the values fetched via
// We instead estimate the SOC% based on the battery voltage.
// This is a bad solution, you wont be able to use 100% of the battery
if (battery_type == EXTENDED_RANGE) {
datalayer.battery.status.real_soc = estimateSOCextended(datalayer.battery.status.voltage_dV);
datalayer_battery->status.real_soc = estimateSOCextended(datalayer_battery->status.voltage_dV);
}
if (battery_type == STANDARD_RANGE) {
datalayer.battery.status.real_soc = estimateSOCstandard(datalayer.battery.status.voltage_dV);
datalayer_battery->status.real_soc = estimateSOCstandard(datalayer_battery->status.voltage_dV);
}
SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10;
datalayer_battery->status.real_soc = battery_highprecision_SOC * 10;
SOC_method = MEASURED;
#endif
datalayer.battery.status.current_dA = -BMS_current;
datalayer_battery->status.current_dA = -BMS_current;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer_battery->status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess
datalayer_battery->status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
datalayer_battery->status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
memcpy(datalayer_battery->status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
// Check if we are on Standard range or Extended range battery.
// We use a variety of checks to ensure we catch a potential Standard range battery
@ -296,16 +200,16 @@ void update_values_battery() { //This function maps all the values fetched via
switch (battery_type) {
case STANDARD_RANGE:
datalayer.battery.info.total_capacity_Wh = 50000;
datalayer.battery.info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV;
datalayer_battery->info.total_capacity_Wh = 50000;
datalayer_battery->info.number_of_cells = CELLCOUNT_STANDARD;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV;
break;
case EXTENDED_RANGE:
datalayer.battery.info.total_capacity_Wh = 60000;
datalayer.battery.info.number_of_cells = CELLCOUNT_EXTENDED;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV;
datalayer_battery->info.total_capacity_Wh = 60000;
datalayer_battery->info.number_of_cells = CELLCOUNT_EXTENDED;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV;
break;
case NOT_DETERMINED_YET:
default:
@ -338,106 +242,108 @@ void update_values_battery() { //This function maps all the values fetched via
//Write the result to datalayer
if ((battery_calc_min_temperature != 0) && (battery_calc_max_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
datalayer_battery->status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer_battery->status.temperature_max_dC = battery_calc_max_temperature * 10;
}
#else //User does not need filtering out a broken sensor, just use the min-max the BMS sends
if ((BMS_lowest_cell_temperature != 0) && (BMS_highest_cell_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = BMS_highest_cell_temperature * 10;
datalayer_battery->status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer_battery->status.temperature_max_dC = BMS_highest_cell_temperature * 10;
}
#endif //!SKIP_TEMPERATURE_SENSOR_NUMBER
// Update webserver datalayer
datalayer_extended.bydAtto3.SOC_method = SOC_method;
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
if (datalayer_bydatto) {
datalayer_bydatto->SOC_method = SOC_method;
datalayer_bydatto->SOC_estimated = datalayer_battery->status.real_soc;
//Once we implement switching logic, remember to change from where the estimated is taken
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC;
datalayer_extended.bydAtto3.SOC_polled = BMS_SOC;
datalayer_extended.bydAtto3.voltage_periodic = battery_voltage;
datalayer_extended.bydAtto3.voltage_polled = BMS_voltage;
datalayer_extended.bydAtto3.battery_temperatures[0] = battery_daughterboard_temperatures[0];
datalayer_extended.bydAtto3.battery_temperatures[1] = battery_daughterboard_temperatures[1];
datalayer_extended.bydAtto3.battery_temperatures[2] = battery_daughterboard_temperatures[2];
datalayer_extended.bydAtto3.battery_temperatures[3] = battery_daughterboard_temperatures[3];
datalayer_extended.bydAtto3.battery_temperatures[4] = battery_daughterboard_temperatures[4];
datalayer_extended.bydAtto3.battery_temperatures[5] = battery_daughterboard_temperatures[5];
datalayer_extended.bydAtto3.battery_temperatures[6] = battery_daughterboard_temperatures[6];
datalayer_extended.bydAtto3.battery_temperatures[7] = battery_daughterboard_temperatures[7];
datalayer_extended.bydAtto3.battery_temperatures[8] = battery_daughterboard_temperatures[8];
datalayer_extended.bydAtto3.battery_temperatures[9] = battery_daughterboard_temperatures[9];
datalayer_extended.bydAtto3.unknown0 = BMS_unknown0;
datalayer_extended.bydAtto3.unknown1 = BMS_unknown1;
datalayer_extended.bydAtto3.chargePower = BMS_allowed_charge_power;
datalayer_extended.bydAtto3.unknown3 = BMS_unknown3;
datalayer_extended.bydAtto3.unknown4 = BMS_unknown4;
datalayer_extended.bydAtto3.unknown5 = BMS_unknown5;
datalayer_extended.bydAtto3.unknown6 = BMS_unknown6;
datalayer_extended.bydAtto3.unknown7 = BMS_unknown7;
datalayer_extended.bydAtto3.unknown8 = BMS_unknown8;
datalayer_extended.bydAtto3.unknown9 = BMS_unknown9;
datalayer_extended.bydAtto3.unknown10 = BMS_unknown10;
datalayer_extended.bydAtto3.unknown11 = BMS_unknown11;
datalayer_extended.bydAtto3.unknown12 = BMS_unknown12;
datalayer_extended.bydAtto3.unknown13 = BMS_unknown13;
datalayer_bydatto->SOC_highprec = battery_highprecision_SOC;
datalayer_bydatto->SOC_polled = BMS_SOC;
datalayer_bydatto->voltage_periodic = battery_voltage;
datalayer_bydatto->voltage_polled = BMS_voltage;
datalayer_bydatto->battery_temperatures[0] = battery_daughterboard_temperatures[0];
datalayer_bydatto->battery_temperatures[1] = battery_daughterboard_temperatures[1];
datalayer_bydatto->battery_temperatures[2] = battery_daughterboard_temperatures[2];
datalayer_bydatto->battery_temperatures[3] = battery_daughterboard_temperatures[3];
datalayer_bydatto->battery_temperatures[4] = battery_daughterboard_temperatures[4];
datalayer_bydatto->battery_temperatures[5] = battery_daughterboard_temperatures[5];
datalayer_bydatto->battery_temperatures[6] = battery_daughterboard_temperatures[6];
datalayer_bydatto->battery_temperatures[7] = battery_daughterboard_temperatures[7];
datalayer_bydatto->battery_temperatures[8] = battery_daughterboard_temperatures[8];
datalayer_bydatto->battery_temperatures[9] = battery_daughterboard_temperatures[9];
datalayer_bydatto->unknown0 = BMS_unknown0;
datalayer_bydatto->unknown1 = BMS_unknown1;
datalayer_bydatto->chargePower = BMS_allowed_charge_power;
datalayer_bydatto->unknown3 = BMS_unknown3;
datalayer_bydatto->unknown4 = BMS_unknown4;
datalayer_bydatto->unknown5 = BMS_unknown5;
datalayer_bydatto->unknown6 = BMS_unknown6;
datalayer_bydatto->unknown7 = BMS_unknown7;
datalayer_bydatto->unknown8 = BMS_unknown8;
datalayer_bydatto->unknown9 = BMS_unknown9;
datalayer_bydatto->unknown10 = BMS_unknown10;
datalayer_bydatto->unknown11 = BMS_unknown11;
datalayer_bydatto->unknown12 = BMS_unknown12;
datalayer_bydatto->unknown13 = BMS_unknown13;
// Update requests from webserver datalayer
if (datalayer_extended.bydAtto3.UserRequestCrashReset && stateMachineClearCrash == NOT_RUNNING) {
if (datalayer_bydatto->UserRequestCrashReset && stateMachineClearCrash == NOT_RUNNING) {
stateMachineClearCrash = STARTED;
datalayer_extended.bydAtto3.UserRequestCrashReset = false;
datalayer_bydatto->UserRequestCrashReset = false;
}
}
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x244:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x245:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x01) {
battery_temperature_ambient = (rx_frame.data.u8[4] - 40);
}
break;
case 0x286:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x334:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x338:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x344:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x345:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x347:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x34A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x35E:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x360:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x36C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x438:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43C:
if (rx_frame.data.u8[0] == 0x00) {
@ -456,7 +362,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
break;
case 0x43D:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_frame_index = rx_frame.data.u8[0];
if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) {
@ -468,31 +374,31 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
break;
case 0x444:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
break;
case 0x445:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x446:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x447:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break;
case 0x47B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x524:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EF: //OBD2 PID reply from battery
if (rx_frame.data.u8[0] == 0x10) {
transmit_can_frame(&ATTO_3_7E7_ACK, can_config.battery); //Send next line request
transmit_can_frame(&ATTO_3_7E7_ACK, can_interface); //Send next line request
}
pid_reply = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
switch (pid_reply) {
@ -572,13 +478,14 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery(unsigned long currentMillis) {
void BydAttoBattery::transmit_can(unsigned long currentMillis) {
//Send 50ms message
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
previousMillis50 = currentMillis;
// Set close contactors to allowed (Useful for crashed packs, started via contactor control thru GPIO)
if (datalayer.battery.status.bms_status == ACTIVE) {
if (datalayer_battery->status.bms_status == ACTIVE) {
datalayer.system.status.battery_allows_contactor_closing = true;
} else { // Fault state, open contactors!
datalayer.system.status.battery_allows_contactor_closing = false;
@ -606,10 +513,7 @@ void transmit_can_battery(unsigned long currentMillis) {
ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4));
ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4));
transmit_can_frame(&ATTO_3_12D, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_12D, can_config.battery_double);
#endif //DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_12D, can_interface);
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -626,24 +530,21 @@ void transmit_can_battery(unsigned long currentMillis) {
ATTO_3_441.data.u8[7] = 0xF5;
}
transmit_can_frame(&ATTO_3_441, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_441, can_config.battery_double);
#endif //DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_441, can_interface);
switch (stateMachineClearCrash) {
case STARTED:
ATTO_3_7E7_CLEAR_CRASH.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = RUNNING_STEP_1;
break;
case RUNNING_STEP_1:
ATTO_3_7E7_CLEAR_CRASH.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = RUNNING_STEP_2;
break;
case RUNNING_STEP_2:
ATTO_3_7E7_CLEAR_CRASH.data = {0x03, 0x19, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = NOT_RUNNING;
break;
case NOT_RUNNING:
@ -773,210 +674,21 @@ void transmit_can_battery(unsigned long currentMillis) {
}
if (stateMachineClearCrash == NOT_RUNNING) { //Don't poll battery for data if clear crash running
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery_double);
#endif //DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_7E7_POLL, can_interface);
}
}
}
void setup_battery(void) { // Performs one time setup at startup
void BydAttoBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#endif //DOUBLE_BATTERY
datalayer_battery->info.number_of_cells = CELLCOUNT_STANDARD;
datalayer_battery->info.chemistry = battery_chemistry_enum::LFP;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS2_voltage > 0) {
datalayer.battery2.status.voltage_dV = BMS2_voltage * 10;
}
// We instead estimate the SOC% based on the battery2 voltage
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
if (battery_type == EXTENDED_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCextended(datalayer.battery2.status.voltage_dV);
}
if (battery_type == STANDARD_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCstandard(datalayer.battery2.status.voltage_dV);
}
datalayer.battery2.status.current_dA = -BMS2_current;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.cell_max_voltage_mV = BMS2_highest_cell_voltage_mV;
datalayer.battery2.status.cell_min_voltage_mV = BMS2_lowest_cell_voltage_mV;
datalayer.battery2.status.temperature_min_dC = BMS2_lowest_cell_temperature * 10; // Add decimals
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
datalayer.battery2.info.total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x244:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x245:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x01) {
battery2_temperature_ambient = (rx_frame.data.u8[4] - 40);
}
break;
case 0x286:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x334:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x338:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x344:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x345:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x347:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x34A:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x35E:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x360:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x36C:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x438:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43A:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43B:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43C: // Daughterboard temperatures reside in this CAN message
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x00) {
battery2_daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
battery2_daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
battery2_daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
}
if (rx_frame.data.u8[0] == 0x01) {
battery2_daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
}
break;
case 0x43D:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_frame_index = rx_frame.data.u8[0];
if (battery2_frame_index < (CELLCOUNT_EXTENDED / 3)) {
uint8_t base2_index = battery2_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery2_cellvoltages[base2_index + i] =
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
}
}
break;
case 0x444:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x445:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x446:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x447:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4];
battery2_lowest_temperature = (rx_frame.data.u8[1] - 40);
battery2_highest_temperature = (rx_frame.data.u8[3] - 40);
break;
case 0x47B:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x524:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EF: //OBD2 PID reply from battery2
switch (rx_frame.data.u8[3]) {
case POLL_FOR_BATTERY_SOC:
BMS2_SOC = rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_VOLTAGE:
BMS2_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CURRENT:
BMS2_current = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 5000;
break;
case POLL_FOR_LOWEST_TEMP_CELL:
BMS2_lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_HIGHEST_TEMP_CELL:
BMS2_highest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
BMS2_average_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_CELL_MV_MAX:
BMS2_highest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CELL_MV_MIN:
BMS2_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
default: //Unrecognized reply
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
#endif

View file

@ -1,8 +1,12 @@
#ifndef ATTO_3_BATTERY_H
#define ATTO_3_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
// Comment out this only if you know your BMS is unlocked and able to send SOC%
#define MAXPOWER_CHARGE_W 10000
@ -14,6 +18,8 @@
/* Do not modify the rows below */
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BydAttoBattery
#define CELLCOUNT_EXTENDED 126
#define CELLCOUNT_STANDARD 104
#define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range
@ -24,7 +30,118 @@
#define MAX_CELL_VOLTAGE_MV 3650 //Charging stops if one cell exceeds this value
#define MIN_CELL_VOLTAGE_MV 2800 //Discharging stops if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class BydAttoBattery : public CanBattery {
public:
// Use this constructor for the second battery.
BydAttoBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* allows_contactor_closing_ptr,
DATALAYER_INFO_BYDATTO3* extended, int targetCan) {
datalayer_battery = datalayer_ptr;
datalayer_bydatto = extended;
allows_contactor_closing = allows_contactor_closing_ptr;
can_interface = targetCan;
}
// Use the default constructor to create the first or single battery.
BydAttoBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
can_interface = can_config.battery;
datalayer_bydatto = &datalayer_extended.bydAtto3;
}
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:
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_BYDATTO3* datalayer_bydatto;
bool* allows_contactor_closing;
int can_interface;
static const int POLL_FOR_BATTERY_SOC = 0x0005;
static const uint8_t NOT_DETERMINED_YET = 0;
static const uint8_t STANDARD_RANGE = 1;
static const uint8_t EXTENDED_RANGE = 2;
static const uint8_t NOT_RUNNING = 0xFF;
static const uint8_t STARTED = 0;
static const uint8_t RUNNING_STEP_1 = 1;
static const uint8_t RUNNING_STEP_2 = 2;
uint8_t battery_type = NOT_DETERMINED_YET;
uint8_t stateMachineClearCrash = NOT_RUNNING;
unsigned long previousMillis50 = 0; // will store last time a 50ms 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
bool SOC_method = false;
uint8_t counter_50ms = 0;
uint8_t counter_100ms = 0;
uint8_t frame6_counter = 0xB;
uint8_t frame7_counter = 0x5;
uint16_t battery_voltage = 0;
int16_t battery_temperature_ambient = 0;
int16_t battery_daughterboard_temperatures[10];
int16_t battery_lowest_temperature = 0;
int16_t battery_highest_temperature = 0;
int16_t battery_calc_min_temperature = 0;
int16_t battery_calc_max_temperature = 0;
uint16_t battery_highprecision_SOC = 0;
uint16_t BMS_SOC = 0;
uint16_t BMS_voltage = 0;
int16_t BMS_current = 0;
int16_t BMS_lowest_cell_temperature = 0;
int16_t BMS_highest_cell_temperature = 0;
int16_t BMS_average_cell_temperature = 0;
uint16_t BMS_lowest_cell_voltage_mV = 3300;
uint16_t BMS_highest_cell_voltage_mV = 3300;
uint32_t BMS_unknown0 = 0;
uint32_t BMS_unknown1 = 0;
uint16_t BMS_allowed_charge_power = 0;
uint16_t BMS_unknown3 = 0;
uint16_t BMS_unknown4 = 0;
uint16_t BMS_unknown5 = 0;
uint16_t BMS_unknown6 = 0;
uint16_t BMS_unknown7 = 0;
uint16_t BMS_unknown8 = 0;
uint16_t BMS_unknown9 = 0;
uint8_t BMS_unknown10 = 0;
uint8_t BMS_unknown11 = 0;
uint8_t BMS_unknown12 = 0;
uint8_t BMS_unknown13 = 0;
uint8_t battery_frame_index = 0;
uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
uint16_t poll_state = POLL_FOR_BATTERY_SOC;
uint16_t pid_reply = 0;
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12D,
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
CAN_frame ATTO_3_441 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x441,
.data = {0x98, 0x3A, 0x88, 0x13, 0x07, 0x00, 0xFF, 0x8C}};
CAN_frame ATTO_3_7E7_POLL = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //ACK frame for long PIDs
.data = {0x30, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_CLEAR_CRASH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif