Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Software/src/battery/BATTERIES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ const char* name_for_battery_type(BatteryType type) {
return Kia64FDBattery::Name;
case BatteryType::KiaHyundaiHybrid:
return KiaHyundaiHybridBattery::Name;
case BatteryType::LandRoverVelar:
return LandRoverVelarPhevBattery::Name;
case BatteryType::Meb:
return MebBattery::Name;
#ifndef SMALL_FLASH_DEVICE
Expand Down Expand Up @@ -211,6 +213,8 @@ Battery* create_battery(BatteryType type) {
return new KiaHyundai64Battery();
case BatteryType::KiaHyundaiHybrid:
return new KiaHyundaiHybridBattery();
case BatteryType::LandRoverVelar:
return new LandRoverVelarPhevBattery();
case BatteryType::Meb:
return new MebBattery();
#ifndef SMALL_FLASH_DEVICE
Expand Down
1 change: 1 addition & 0 deletions Software/src/battery/BATTERIES.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void setup_shunt();
#include "KIA-E-GMP-BATTERY.h"
#include "KIA-HYUNDAI-64-BATTERY.h"
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
#include "LAND-ROVER-VELAR-PHEV-BATTERY.h"
#include "MEB-BATTERY.h"
#include "MG-5-BATTERY.h"
#include "MG-HS-PHEV-BATTERY.h"
Expand Down
1 change: 1 addition & 0 deletions Software/src/battery/Battery.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum class BatteryType {
GeelySea = 50,
ThunderstruckBMS = 51,
EnnoidBMS = 52,
LandRoverVelar = 53,
Highest
};

Expand Down
286 changes: 286 additions & 0 deletions Software/src/battery/LAND-ROVER-VELAR-PHEV-BATTERY.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
#include "LAND-ROVER-VELAR-PHEV-BATTERY.h"
#include <cstring> //For unit test
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"

/* TODO
This integration has not been tested, there are lots of TODOs left like CAN sending towards battery and the allowed charge power/current values
*/

void LandRoverVelarPhevBattery::update_values() {

datalayer.battery.status.real_soc = HVBattSOCAverage;

datalayer.battery.status.soh_pptt = HVBattStateofHealth * 10;

datalayer.battery.status.voltage_dV = HVBattVoltageExt * 10;

//datalayer.battery.status.current_dA;

//datalayer.battery.status.max_charge_power_W;

//datalayer.battery.status.max_discharge_power_W;

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.cell_max_voltage_mV = HVBattCellVoltageMax;

datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMin;

datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10;

datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10;

if (HVBattHVILStatus) {
set_event(EVENT_HVIL_FAILURE, 0);
} else {
clear_event(EVENT_HVIL_FAILURE);
}

if (HVBattAuxiliaryFuse) {
set_event(EVENT_BATTERY_FUSE, 0);
} else {
clear_event(EVENT_BATTERY_FUSE);
}

if (HVBattAuxiliaryFuse) {
set_event(EVENT_BATTERY_FUSE, 1);
} else {
clear_event(EVENT_BATTERY_FUSE);
}

if (HVBattAuxiliaryFuse) {
set_event(EVENT_BATTERY_FUSE, 2);
} else {
clear_event(EVENT_BATTERY_FUSE);
}

if (HVBattStatusCritical == 2) {
set_event(EVENT_THERMAL_RUNAWAY, 0);
}
}

void LandRoverVelarPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x080:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x088:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattChgExtGpCS
//HVBattChgExtGpCounter
//HVBattChgVoltageLimit
//HVBattChgPowerLimitExt
//HVBattChgContPwrLmt
break;
case 0x08A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattTotalCapacity
//HVBattChgCurrentLimit
//HVBattFastChgCounter
//HVBattPrechargeAllowed
//HVBattEndOfCharge
//HVBattDerateWarning
//HVBattCCCVChargeMode
break;
case 0x08C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattDchExtGpCS
//HVBattDchExtGpCounter
//HVBattDchVoltageLimit
//HVBattDchContPwrLmt
//HVBattDchPowerLimitExt
break;
case 0x08E:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattEstEgyDischLoss
//HVBattDchCurrentLimit
//HVBattLossDchRouteTrac
//HVBattLossDchRouteTot
break;
case 0x090:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattVoltageOC
//HVBattCurrentWarning
//HVBattMILRequest
HVBattTractionFuseF = (rx_frame.data.u8[2] & 0x40) >> 6;
HVBattTractionFuseR = (rx_frame.data.u8[2] & 0x80) >> 7;
break;
case 0x098:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattStatusGpCS
//HVBattStatusGpCounter
//HVBattOCMonitorStatus
//HVBattContactorStatus
HVBattHVILStatus = (rx_frame.data.u8[1] & 0x80) >> 7;
//HVBattWeldCheckStatus
//HVBattStatusCAT7NowBPO
//HVBattStatusCAT6DlyBPO
//HVBattStatusCAT5BPOChg
//HVBattStatusCAT4Derate
//HVBattStatusCAT3
//HVBattIsolationStatus
//HVBattCAT6Count
HVBattAuxiliaryFuse = (rx_frame.data.u8[3] & 0x80) >> 7;
HVBattStateofHealth = (((rx_frame.data.u8[4] & 0x03) << 8) | rx_frame.data.u8[5]);
HVBattStatusCritical = (rx_frame.data.u8[5] & 0x0C) >> 2;
//HVBattIsolationStatus2
//HVBattClntPumpDiagStat
//HVBattValveCtrlStat
//HVIsolationTestStatus
//HVBattIsoRes
break;
case 0x0C8:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattPwrExtGpCS
//HVBattPwrExtGpCounter
//HVBattVoltageBusTF
//HVBattVoltageBusTR
//HVBattCurrentTR
break;
case 0x0CA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattPwrGpCS
//HVBattVoltageBus
HVBattVoltageExt = (((rx_frame.data.u8[3] & 0x03) << 8) | rx_frame.data.u8[4]);
//HVBattPwrGpCounter
//HVBattCurrentExt
break;
case 0x0EA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattStatExtGpCS
//HVBattStatExtGpCounter
//HVBattOCMonitorStatusTR
//HVBattOCMonitorStatusTF
//HVBattWeldCheckStatusT
//HVBattWeldCheckStatusC
//HVBattContactorStatusT
break;
case 0x18B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x146:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattEstimatedLossChg
//HVBattEstimatedLossDch
//HVBattEstLossDchTgtSoC
//HVBattEstLossTracDch
break;
case 0x148:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattThermalMgtStatus
//HVBattThermalOvercheck
//HVBattTempWarning
//HVBattAvTempAtEvent
//HVBattTempUpLimit
HVBattCellTempHottest = (rx_frame.data.u8[5] / 2) - 40;
HVBattCellTempColdest = (rx_frame.data.u8[6] / 2) - 40;
//HVBattCellTempAverage
break;
case 0x14C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattCellVoltUpLimit
HVBattCellVoltageMin = (((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5]);
HVBattCellVoltageMax = (((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[7]);
//HVBattCellVoltWarning
break;
case 0x14E:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattWarmupRateChg
//HVBattWarmupRateDch
//HVBattWarmupRateRtTot
//HVBattWakeUpDchReq
//HVBattWarmupRateRtTrac
//HVBattWakeUpThermalReq
//HVBattWakeUpTopUpReq
break;
case 0x171:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattHeaterCtrlStat
//HVBattCoolingRequestExt
//HVBattFanDutyRequest
//HVBattInletCoolantTemp
//HVBattHeatPowerGenChg
//HVBattHeatPowerGenDch
//HVBattHeatPwrGenRtTot
//HVBattCoolantLevel
//HVBattHeatPwrGenRtTrac
//HVBattHeatingRequest
break;
case 0x204:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattChgExtGp2CS
//HVBattChgExtGp2AC
//HVBattChgContPwrLmtExt
break;
case 0x207:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattAvgSOCOAtEvent
//HVBattSOCLowestCell
//HVBattSOCHighestCell
HVBattSOCAverage = (((rx_frame.data.u8[6] & 0x3F) << 8) | rx_frame.data.u8[7]);
break;
case 0x27A: //Cellvoltages
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// Extract module ID (1-8) and sequence counter from first byte
module_id = rx_frame.data.u8[0] >> 1; // Bits 7-1: 1-8

// Extract voltage group from second byte (1-4 for groups of 3 cells)
voltage_group = rx_frame.data.u8[1] >> 2; // Bits 7-2: 1-4

// Calculate the starting index for these 3 cells
// Each module has 12 cells (4 groups × 3 cells)
// Module 1: cells 0-11, Module 2: cells 12-23, etc.
// Voltage group 1: first 3 cells, group 2: next 3, etc.
base_index = ((module_id - 1) * 12) + ((voltage_group - 1) * 3);

// Decode the 3 cell voltages from the message
// Format appears to be: high 4 bits in byte 2/4/6, low 8 bits in following byte
datalayer.battery.status.cell_voltages_mV[base_index] = ((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[3];
datalayer.battery.status.cell_voltages_mV[base_index + 1] =
((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5];
datalayer.battery.status.cell_voltages_mV[base_index + 2] =
((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[7];
break;
case 0x27E:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//celltemperatures same as above
break;
case 0x310:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//HVBattEnergyAvailable
//HVBattEnergyUsableBulk
//HVBattEnergyUsableMax
//HVBattEnergyUsableMin
break;
case 0x318:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//ID of min/max cells for temps and voltages
break;
default:
break;
}
}

void LandRoverVelarPhevBattery::transmit_can(unsigned long currentMillis) {
// Send 50ms CAN Message
if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) {
previousMillis50ms = currentMillis;

//transmit_can_frame(&VELAR_18B);
}
}

void LandRoverVelarPhevBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 72;
datalayer.battery.info.total_capacity_Wh = 17000;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
48 changes: 48 additions & 0 deletions Software/src/battery/LAND-ROVER-VELAR-PHEV-BATTERY.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef LAND_ROVER_VELAR_PHEV_BATTERY_H
#define LAND_ROVER_VELAR_PHEV_BATTERY_H

#include "CanBattery.h"

class LandRoverVelarPhevBattery : public CanBattery {
public:
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);
static constexpr const char* Name = "Land Rover Velar 17kWh PHEV battery (L560)";

private:
/* Change the following to suit your battery */
static const int MAX_PACK_VOLTAGE_DV = 4710;
static const int MIN_PACK_VOLTAGE_DV = 3000;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150;

unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent

uint16_t HVBattStateofHealth = 1000;
uint16_t HVBattSOCAverage = 5000;
uint16_t HVBattVoltageExt = 370;
uint16_t HVBattCellVoltageMin = 3700;
uint16_t HVBattCellVoltageMax = 3700;
int16_t HVBattCellTempHottest = 0;
int16_t HVBattCellTempColdest = 0;
uint8_t HVBattStatusCritical = 1; //1=OK, 2 = FAULT
uint8_t voltage_group = 0;
uint8_t module_id = 0;
uint8_t base_index = 0;
bool HVBattHVILStatus = false; //0=OK, 1=Not OK
bool HVBattAuxiliaryFuse = false; //0=OK, 1=Not OK
bool HVBattTractionFuseF = false; //0=OK, 1=Not OK
bool HVBattTractionFuseR = false; //0=OK, 1=Not OK

//CAN messages needed by battery (LOG needed!)
CAN_frame VELAR_18B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x18B, //CONTENT??? TODO
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};

#endif
Loading