mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
add battery folder
This commit is contained in:
parent
40526f4029
commit
a3583b6897
16 changed files with 29 additions and 29 deletions
32
Software/src/battery/BATTERIES.h
Normal file
32
Software/src/battery/BATTERIES.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef BATTERIES_H
|
||||
#define BATTERIES_H
|
||||
|
||||
#ifdef BATTERY_TYPE_LEAF
|
||||
#include "NISSAN-LEAF-BATTERY.h" //See this file for more LEAF battery settings
|
||||
#endif
|
||||
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
#include "TESLA-MODEL-3-BATTERY.h" //See this file for more Tesla battery settings
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
#include "RENAULT-ZOE-BATTERY.h" //See this file for more Zoe battery settings
|
||||
#endif
|
||||
|
||||
#ifdef BMW_I3_BATTERY
|
||||
#include "BMW-I3-BATTERY.h" //See this file for more i3 battery settings
|
||||
#endif
|
||||
|
||||
#ifdef IMIEV_ION_CZERO_BATTERY
|
||||
#include "IMIEV-CZERO-ION-BATTERY.h" //See this file for more triplet battery settings
|
||||
#endif
|
||||
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
#include "KIA-HYUNDAI-64-BATTERY.h" //See this file for more 64kWh battery settings
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO
|
||||
#include "CHADEMO-BATTERY.h" //See this file for more Chademo settings
|
||||
#endif
|
||||
|
||||
#endif
|
192
Software/src/battery/BMW-I3-BATTERY.cpp
Normal file
192
Software/src/battery/BMW-I3-BATTERY.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include "BMW-I3-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
//TODO before using
|
||||
// Map the final values in update_values_i3_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 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
|
||||
|
||||
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
|
||||
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
|
||||
|
||||
CAN_frame_t BMW_10B = {.FIR = {.B = {.DLC = 3,.FF = CAN_frame_std,}},.MsgID = 0x10B,.data = {0xCD, 0x01, 0xFC}};
|
||||
CAN_frame_t BMW_512 = {.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
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
void update_values_i3_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
//Calculate the SOC% value to send to inverter
|
||||
Calculated_SOC = (Display_SOC * 10); //Increase decimal amount
|
||||
Calculated_SOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (Calculated_SOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
|
||||
if (Calculated_SOC < 0)
|
||||
{ //We are in the real SOC% range of 0-20%, always set SOC sent to inverter as 0%
|
||||
Calculated_SOC = 0;
|
||||
}
|
||||
if (Calculated_SOC > 1000)
|
||||
{ //We are in the real SOC% range of 80-100%, always set SOC sent to inverter as 100%
|
||||
Calculated_SOC = 1000;
|
||||
}
|
||||
SOC = (Calculated_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
battery_voltage = Battery_Volts; //Unit V+1 (5000 = 500.0V)
|
||||
|
||||
battery_current = Battery_Current;
|
||||
|
||||
capacity_Wh = BATTERY_WH_MAX;
|
||||
|
||||
remaining_capacity_Wh = (Battery_Capacity_kWh * 1000);
|
||||
|
||||
if(SOC > 9900) //If Soc is over 99%, stop charging
|
||||
{
|
||||
max_target_charge_power = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
max_target_charge_power = 5000; //Hardcoded value for testing. TODO, read real value from battery when discovered
|
||||
}
|
||||
|
||||
if(SOC < 500) //If Soc is under 5%, stop dicharging
|
||||
{
|
||||
max_target_discharge_power = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
max_target_discharge_power = 5000; //Hardcoded value for testing. TODO, read real value from battery when discovered
|
||||
}
|
||||
|
||||
Battery_Power = (Battery_Current * (Battery_Volts/10));
|
||||
|
||||
stat_batt_power = Battery_Power; //TODO, is mapping OK?
|
||||
|
||||
temperature_min; //hardcoded to 5*C in startup, TODO, find from battery CAN later
|
||||
|
||||
temperature_max; //hardcoded to 6*C in startup, TODO, find from battery CAN later
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("SOC% battery: ");
|
||||
Serial.print(Display_SOC);
|
||||
Serial.print(" SOC% sent to inverter: ");
|
||||
Serial.print(SOC);
|
||||
Serial.print(" Battery voltage: ");
|
||||
Serial.print(battery_voltage);
|
||||
Serial.print(" Remaining Wh: ");
|
||||
Serial.print(remaining_capacity_Wh);
|
||||
Serial.print(" Max charge power: ");
|
||||
Serial.print(max_target_charge_power);
|
||||
Serial.print(" Max discharge power: ");
|
||||
Serial.print(max_target_discharge_power);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_i3_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;
|
||||
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);
|
||||
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];
|
||||
break;
|
||||
case 0x430:
|
||||
break;
|
||||
case 0x1FA:
|
||||
break;
|
||||
case 0x40D:
|
||||
break;
|
||||
case 0x2FF:
|
||||
break;
|
||||
case 0x239:
|
||||
break;
|
||||
case 0x2BD:
|
||||
break;
|
||||
case 0x2F5:
|
||||
break;
|
||||
case 0x3EB:
|
||||
break;
|
||||
case 0x363:
|
||||
break;
|
||||
case 0x507:
|
||||
break;
|
||||
case 0x41C:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_i3_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 600ms CAN Message
|
||||
if (currentMillis - previousMillis600 >= interval600)
|
||||
{
|
||||
previousMillis600 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&BMW_512);
|
||||
}
|
||||
//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;
|
||||
}
|
||||
|
||||
ESP32Can.CANWriteFrame(&BMW_10B);
|
||||
}
|
||||
}
|
38
Software/src/battery/BMW-I3-BATTERY.h
Normal file
38
Software/src/battery/BMW-I3-BATTERY.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef BMW_I3_BATTERY_H
|
||||
#define BMW_I3_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the inverter
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
extern uint8_t batteryAllowsContactorClosing;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
void update_values_i3_battery();
|
||||
void receive_can_i3_battery(CAN_frame_t rx_frame);
|
||||
void send_can_i3_battery();
|
||||
|
||||
#endif
|
201
Software/src/battery/CHADEMO-BATTERY.cpp
Normal file
201
Software/src/battery/CHADEMO-BATTERY.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#include "CHADEMO-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static const int interval100 = 100; // interval (ms) at which send CAN Messages
|
||||
const int rx_queue_size = 10; // Receive Queue size
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
|
||||
CAN_frame_t CHADEMO_108 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x108,.data = {0x01, 0xF4, 0x01, 0x0F, 0xB3, 0x01, 0x00, 0x00}};
|
||||
CAN_frame_t CHADEMO_109 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x109,.data = {0x02, 0x00, 0x00, 0x00, 0x01, 0x20, 0xFF, 0xFF}};
|
||||
//For chademo v2.0 only
|
||||
CAN_frame_t CHADEMO_118 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x118,.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
|
||||
CAN_frame_t CHADEMO_208 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x208,.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
|
||||
CAN_frame_t CHADEMO_209 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x209,.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
//H100
|
||||
uint8_t MinimumChargeCurrent = 0;
|
||||
uint16_t MinumumBatteryVoltage = 0;
|
||||
uint16_t MaximumBatteryVoltage = 0;
|
||||
uint8_t ConstantOfChargingRateIndication = 0;
|
||||
//H101
|
||||
uint8_t MaxChargingTime10sBit = 0;
|
||||
uint8_t MaxChargingTime1minBit = 0;
|
||||
uint8_t EstimatedChargingTime = 0;
|
||||
uint16_t RatedBatteryCapacity = 0;
|
||||
//H102
|
||||
uint8_t ControlProtocolNumberEV = 0;
|
||||
uint16_t TargetBatteryVoltage = 0;
|
||||
uint8_t ChargingCurrentRequest = 0;
|
||||
uint8_t FaultBatteryVoltageDeviation = 0;
|
||||
uint8_t FaultHighBatteryTemperature = 0;
|
||||
uint8_t FaultBatteryCurrentDeviation = 0;
|
||||
uint8_t FaultBatteryUndervoltage = 0;
|
||||
uint8_t FaultBatteryOvervoltage = 0;
|
||||
uint8_t StatusNormalStopRequest = 0;
|
||||
uint8_t StatusVehicle = 0;
|
||||
uint8_t StatusChargingSystem = 0;
|
||||
uint8_t StatusVehicleShifterPosition = 0;
|
||||
uint8_t StatusVehicleCharging = 0;
|
||||
uint8_t ChargingRate = 0;
|
||||
//H200
|
||||
uint8_t MaximumDischargeCurrent = 0;
|
||||
uint16_t MinimumDischargeVoltage = 0;
|
||||
uint8_t MinimumBatteryDischargeLevel = 0;
|
||||
uint8_t MaxRemainingCapacityForCharging = 0;
|
||||
//H201
|
||||
uint8_t V2HchargeDischargeSequenceNum = 0;
|
||||
uint16_t ApproxDischargeCompletionTime = 0;
|
||||
uint16_t AvailableVehicleEnergy = 0;
|
||||
//H700
|
||||
uint8_t AutomakerCode = 0;
|
||||
uint32_t OptionalContent = 0;
|
||||
//H118
|
||||
uint8_t DynamicControlStatus = 0;
|
||||
uint8_t HighCurrentControlStatus = 0;
|
||||
uint8_t HighVoltageControlStatus = 0;
|
||||
|
||||
|
||||
void update_values_chademo_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
SOC = ChargingRate;
|
||||
|
||||
max_target_discharge_power = (MaximumDischargeCurrent*MaximumBatteryVoltage); //In Watts, Convert A to P
|
||||
|
||||
battery_voltage = TargetBatteryVoltage; //Todo, scaling?
|
||||
|
||||
capacity_Wh = ((RatedBatteryCapacity/0.11)*1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
|
||||
|
||||
remaining_capacity_Wh = (SOC/100)*capacity_Wh;
|
||||
|
||||
/* Check if the Vehicle is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
errorCode = 7;
|
||||
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
if(errorCode > 0)
|
||||
{
|
||||
Serial.print("ERROR CODE ACTIVE IN SYSTEM. NUMBER: ");
|
||||
Serial.println(errorCode);
|
||||
}
|
||||
Serial.print("BMS Status (3=OK): ");
|
||||
Serial.println(bms_status);
|
||||
switch (bms_char_dis_status)
|
||||
{
|
||||
case 0:
|
||||
Serial.println("Battery Idle");
|
||||
break;
|
||||
case 1:
|
||||
Serial.println("Battery Discharging");
|
||||
break;
|
||||
case 2:
|
||||
Serial.println("Battery Charging");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Serial.print("Max discharge power: ");
|
||||
Serial.println(max_target_discharge_power);
|
||||
Serial.print("Max charge power: ");
|
||||
Serial.println(max_target_charge_power);
|
||||
Serial.print("SOH%: ");
|
||||
Serial.println(StateOfHealth);
|
||||
Serial.print("SOC% to Inverter: ");
|
||||
Serial.println(SOC);
|
||||
Serial.print("Temperature Min: ");
|
||||
Serial.println(temperature_min);
|
||||
Serial.print("Temperature Max: ");
|
||||
Serial.println(temperature_max);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_chademo_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
CANstillAlive == 12; //We are getting CAN messages from the vehicle, inform the watchdog
|
||||
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x100:
|
||||
MinimumChargeCurrent = rx_frame.data.u8[0];
|
||||
MinumumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
ConstantOfChargingRateIndication = rx_frame.data.u8[6];
|
||||
break;
|
||||
case 0x101:
|
||||
MaxChargingTime10sBit = rx_frame.data.u8[1];
|
||||
MaxChargingTime1minBit = rx_frame.data.u8[2];
|
||||
EstimatedChargingTime = rx_frame.data.u8[3];
|
||||
RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
break;
|
||||
case 0x102:
|
||||
ControlProtocolNumberEV = rx_frame.data.u8[0];
|
||||
TargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
ChargingCurrentRequest = rx_frame.data.u8[3];
|
||||
FaultBatteryOvervoltage = (rx_frame.data.u8[4] & 0x01);
|
||||
FaultBatteryUndervoltage = (rx_frame.data.u8[4] & 0x02) >> 1;
|
||||
FaultBatteryCurrentDeviation = (rx_frame.data.u8[4] & 0x04) >> 2;
|
||||
FaultHighBatteryTemperature = (rx_frame.data.u8[4] & 0x08) >> 3;
|
||||
FaultBatteryVoltageDeviation = (rx_frame.data.u8[4] & 0x10) >> 4;
|
||||
StatusVehicleCharging = (rx_frame.data.u8[5] & 0x01);
|
||||
StatusVehicleShifterPosition = (rx_frame.data.u8[5] & 0x02) >> 1;
|
||||
StatusChargingSystem = (rx_frame.data.u8[5] & 0x04) >> 2;
|
||||
StatusVehicle = (rx_frame.data.u8[5] & 0x08) >> 3;
|
||||
StatusNormalStopRequest = (rx_frame.data.u8[5] & 0x10) >> 4;
|
||||
ChargingRate = rx_frame.data.u8[6];
|
||||
break;
|
||||
case 0x200: //For V2X
|
||||
MaximumDischargeCurrent = rx_frame.data.u8[0];
|
||||
MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
||||
MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x201: //For V2X
|
||||
V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
||||
ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||
break;
|
||||
case 0x700:
|
||||
AutomakerCode = rx_frame.data.u8[0];
|
||||
OptionalContent = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
|
||||
break;
|
||||
case 0x110: //Only present on Chademo v2.0
|
||||
DynamicControlStatus = (rx_frame.data.u8[0] & 0x01);
|
||||
HighCurrentControlStatus = (rx_frame.data.u8[0] & 0x02) >> 1;
|
||||
HighVoltageControlStatus = (rx_frame.data.u8[0] & 0x04) >> 2;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_chademo_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_108);
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_109);
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_208);
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_209);
|
||||
|
||||
if(ControlProtocolNumberEV >= 0x03)
|
||||
{ //Only send the following on Chademo 2.0 vehicles?
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_118);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
37
Software/src/battery/CHADEMO-BATTERY.h
Normal file
37
Software/src/battery/CHADEMO-BATTERY.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef CHADEMO_BATTERY_H
|
||||
#define CHADEMO_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
void update_values_chademo_battery();
|
||||
void receive_can_chademo_battery(CAN_frame_t rx_frame);
|
||||
void send_can_chademo_battery();
|
||||
|
||||
#endif
|
242
Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
Normal file
242
Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
#include "IMIEV-CZERO-ION-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
//Code still work in progress, TODO:
|
||||
//Figure out if CAN messages need to be sent to keep the system happy?
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
#define BMU_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
|
||||
#define BMU_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint8_t BMU_Detected = 0;
|
||||
static uint8_t CMU_Detected = 0;
|
||||
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static const int interval10 = 10; // interval (ms) at which send CAN Messages
|
||||
static const int interval100 = 100; // interval (ms) at which send CAN Messages
|
||||
|
||||
static int pid_index = 0;
|
||||
static int cmu_id = 0;
|
||||
static int voltage_index = 0;
|
||||
static int temp_index = 0;
|
||||
static uint8_t BMU_SOC = 0;
|
||||
static int temp_value = 0;
|
||||
static double temp1 = 0;
|
||||
static double temp2 = 0;
|
||||
static double temp3 = 0;
|
||||
static double voltage1 = 0;
|
||||
static double voltage2 = 0;
|
||||
static double BMU_Current = 0;
|
||||
static double BMU_PackVoltage = 0;
|
||||
static double BMU_Power = 0;
|
||||
static double cell_voltages[89]; //array with all the cellvoltages //Todo, what is max array size? 80/88 cells?
|
||||
static double cell_temperatures[89]; //array with all the celltemperatures //Todo, what is max array size? 80/88cells?
|
||||
static double max_volt_cel = 3.70;
|
||||
static double min_volt_cel = 3.70;
|
||||
static double max_temp_cel = 20.00;
|
||||
static double min_temp_cel = 19.00;
|
||||
|
||||
|
||||
void update_values_imiev_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
SOC = (uint16_t)(BMU_SOC * 100); //increase BMU_SOC range from 0-100 -> 100.00
|
||||
|
||||
battery_voltage = (uint16_t)(BMU_PackVoltage * 10); // Multiply by 10 and cast to uint16_t
|
||||
|
||||
battery_current = (BMU_Current*10); //Todo, scaling?
|
||||
|
||||
capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
|
||||
|
||||
remaining_capacity_Wh = (uint16_t)((SOC/10000)*capacity_Wh);
|
||||
|
||||
//We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC%
|
||||
if(SOC == 10000){ //100.00%
|
||||
max_target_charge_power = 0; //When battery is 100% full, set allowed charge W to 0
|
||||
}
|
||||
else{
|
||||
max_target_charge_power = 10000; //Otherwise we can push 10kW into the pack!
|
||||
}
|
||||
|
||||
if(SOC < 200){ //2.00%
|
||||
max_target_discharge_power = 0; //When battery is empty (below 2%), set allowed discharge W to 0
|
||||
}
|
||||
else{
|
||||
max_target_discharge_power = 10000; //Otherwise we can discharge 10kW from the pack!
|
||||
}
|
||||
|
||||
stat_batt_power = BMU_Power; //TODO, Scaling?
|
||||
|
||||
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
|
||||
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (cell_voltages[i] > max_volt_cel) {
|
||||
max_volt_cel = cell_voltages[i]; // Update max if we find a larger element
|
||||
}
|
||||
}
|
||||
min_volt_cel = cell_voltages[0]; // Initialize min with the first element of the array
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (cell_voltages[i] < min_volt_cel) {
|
||||
min_volt_cel = cell_voltages[i]; // Update min if we find a smaller element
|
||||
}
|
||||
}
|
||||
|
||||
static int m = sizeof(cell_voltages) / sizeof(cell_temperatures[0]);
|
||||
max_temp_cel = cell_temperatures[0]; // Initialize max with the first element of the array
|
||||
for (int i = 1; i < m; i++) {
|
||||
if (cell_temperatures[i] > max_temp_cel) {
|
||||
max_temp_cel = cell_temperatures[i]; // Update max if we find a larger element
|
||||
}
|
||||
}
|
||||
min_temp_cel = cell_temperatures[0]; // Initialize min with the first element of the array
|
||||
for (int i = 1; i < m; i++) {
|
||||
if (cell_temperatures[i] < min_temp_cel) {
|
||||
if(min_temp_cel != -50.00){ //-50.00 means this sensor not connected
|
||||
min_temp_cel = cell_temperatures[i]; // Update max if we find a smaller element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cell_max_voltage = (uint16_t)(max_volt_cel*1000);
|
||||
|
||||
cell_min_voltage = (uint16_t)(min_volt_cel*1000);
|
||||
|
||||
temperature_min = (uint16_t)(min_temp_cel*1000);
|
||||
|
||||
temperature_max = (uint16_t)(max_temp_cel*1000);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
|
||||
if(!BMU_Detected){
|
||||
Serial.println("BMU not detected, check wiring!");
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
Serial.println("Battery Values");
|
||||
Serial.print("BMU SOC: ");
|
||||
Serial.print(BMU_SOC);
|
||||
Serial.print(" BMU Current: ");
|
||||
Serial.print(BMU_Current);
|
||||
Serial.print(" BMU Battery Voltage: ");
|
||||
Serial.print(BMU_PackVoltage);
|
||||
Serial.print(" BMU_Power: ");
|
||||
Serial.print(BMU_Power);
|
||||
Serial.print(" Cell max voltage: ");
|
||||
Serial.print(max_volt_cel);
|
||||
Serial.print(" Cell min voltage: ");
|
||||
Serial.print(min_volt_cel);
|
||||
Serial.print(" Cell max temp: ");
|
||||
Serial.print(max_temp_cel);
|
||||
Serial.print(" Cell min temp: ");
|
||||
Serial.println(min_temp_cel);
|
||||
|
||||
Serial.println("Values sent to inverter");
|
||||
Serial.print("SOC% (0-100.00): ");
|
||||
Serial.print(SOC);
|
||||
Serial.print(" Voltage (0-400.0): ");
|
||||
Serial.print(battery_voltage);
|
||||
Serial.print(" Capacity WH full (0-60000): ");
|
||||
Serial.print(capacity_Wh);
|
||||
Serial.print(" Capacity WH remain (0-60000): ");
|
||||
Serial.print(remaining_capacity_Wh);
|
||||
Serial.print(" Max charge power W (0-10000): ");
|
||||
Serial.print(max_target_charge_power);
|
||||
Serial.print(" Max discharge power W (0-10000): ");
|
||||
Serial.print(max_target_discharge_power);
|
||||
Serial.print(" Temp max ");
|
||||
Serial.print(temperature_max);
|
||||
Serial.print(" Temp min ");
|
||||
Serial.print(temperature_min);
|
||||
Serial.print(" Cell mV max ");
|
||||
Serial.print(cell_max_voltage);
|
||||
Serial.print(" Cell mV min ");
|
||||
Serial.print(cell_min_voltage);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_imiev_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
CANstillAlive = 12; //Todo, move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x374: //BMU message, 10ms - SOC
|
||||
temp_value = ((rx_frame.data.u8[1] - 10) / 2);
|
||||
if(temp_value >= 0 && temp_value <= 101){
|
||||
BMU_SOC = temp_value;
|
||||
}
|
||||
break;
|
||||
case 0x373: //BMU message, 100ms - Pack Voltage and current
|
||||
BMU_Current = ((((((rx_frame.data.u8[2] * 256.0) + rx_frame.data.u8[3])) - 32768)) * 0.01);
|
||||
BMU_PackVoltage = ((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.1);
|
||||
BMU_Power = (BMU_Current * BMU_PackVoltage);
|
||||
break;
|
||||
case 0x6e1: //BMU message, 25ms - Battery temperatures and voltages
|
||||
case 0x6e2:
|
||||
case 0x6e3:
|
||||
case 0x6e4:
|
||||
BMU_Detected = 1;
|
||||
//Pid index 0-3
|
||||
pid_index = (rx_frame.MsgID) - 1761;
|
||||
//cmu index 1-12: ignore high order nibble which appears to sometimes contain other status bits
|
||||
cmu_id = (rx_frame.data.u8[0] & 0x0f);
|
||||
//
|
||||
temp1 = rx_frame.data.u8[1] - 50.0;
|
||||
temp2 = rx_frame.data.u8[2] - 50.0;
|
||||
temp3 = rx_frame.data.u8[3] - 50.0;
|
||||
|
||||
voltage1 = (((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.005) + 2.1);
|
||||
voltage2 = (((rx_frame.data.u8[6] * 256.0 + rx_frame.data.u8[7]) * 0.005) + 2.1);
|
||||
|
||||
voltage_index = ((cmu_id - 1) * 8 + (2 * pid_index));
|
||||
temp_index = ((cmu_id - 1) * 6 + (2 * pid_index));
|
||||
if (cmu_id >= 7)
|
||||
{
|
||||
voltage_index -= 4;
|
||||
temp_index -= 3;
|
||||
}
|
||||
cell_voltages[voltage_index] = voltage1;
|
||||
cell_voltages[voltage_index + 1] = voltage2;
|
||||
|
||||
if (pid_index == 0)
|
||||
{
|
||||
cell_temperatures[temp_index] = temp2;
|
||||
cell_temperatures[temp_index + 1] = temp3;
|
||||
}
|
||||
else
|
||||
{
|
||||
cell_temperatures[temp_index] = temp1;
|
||||
if (cmu_id != 6 && cmu_id != 12)
|
||||
{
|
||||
cell_temperatures[temp_index + 1] = temp2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_imiev_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
}
|
||||
}
|
41
Software/src/battery/IMIEV-CZERO-ION-BATTERY.h
Normal file
41
Software/src/battery/IMIEV-CZERO-ION-BATTERY.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef IMIEV_CZERO_ION_BATTERY_H
|
||||
#define IMIEV_CZERO_ION_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the Gen24
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
extern uint16_t cell_max_voltage;
|
||||
extern uint16_t cell_min_voltage;
|
||||
extern uint8_t batteryAllowsContactorClosing;
|
||||
extern uint8_t LEDcolor;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
void update_values_imiev_battery();
|
||||
void receive_can_imiev_battery(CAN_frame_t rx_frame);
|
||||
void send_can_imiev_battery();
|
||||
|
||||
#endif
|
134
Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp
Normal file
134
Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#include "KIA-HYUNDAI-64-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static const int interval10 = 10; // interval (ms) at which send CAN Messages
|
||||
static const int interval100 = 100; // interval (ms) at which send CAN Messages
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
|
||||
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
|
||||
|
||||
static int SOC_1 = 0;
|
||||
static int SOC_2 = 0;
|
||||
static int SOC_3 = 0;
|
||||
|
||||
void update_values_kiaHyundai_64_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
SOC;
|
||||
|
||||
battery_voltage;
|
||||
|
||||
battery_current;
|
||||
|
||||
capacity_Wh = BATTERY_WH_MAX;
|
||||
|
||||
remaining_capacity_Wh;
|
||||
|
||||
max_target_discharge_power;
|
||||
|
||||
max_target_charge_power;
|
||||
|
||||
stat_batt_power;
|
||||
|
||||
temperature_min;
|
||||
|
||||
temperature_max;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("SOC% candidate 1: ");
|
||||
Serial.println(SOC_1);
|
||||
Serial.print("SOC% candidate 2: ");
|
||||
Serial.println(SOC_2);
|
||||
Serial.print("SOC% candidate 3: ");
|
||||
Serial.println(SOC_3);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
CANstillAlive = 12;
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x3F6:
|
||||
break;
|
||||
case 0x491:
|
||||
break;
|
||||
case 0x493:
|
||||
break;
|
||||
case 0x497:
|
||||
break;
|
||||
case 0x498:
|
||||
break;
|
||||
case 0x4DD:
|
||||
break;
|
||||
case 0x4DE:
|
||||
break;
|
||||
case 0x4E2:
|
||||
break;
|
||||
case 0x542:
|
||||
SOC_1 = rx_frame.data.u8[0];
|
||||
break;
|
||||
case 0x594:
|
||||
SOC_2 = rx_frame.data.u8[5];
|
||||
break;
|
||||
case 0x595:
|
||||
break;
|
||||
case 0x596:
|
||||
break;
|
||||
case 0x597:
|
||||
break;
|
||||
case 0x598:
|
||||
SOC_3 = (rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]);
|
||||
break;
|
||||
case 0x599:
|
||||
break;
|
||||
case 0x59C:
|
||||
break;
|
||||
case 0x59E:
|
||||
break;
|
||||
case 0x5A3:
|
||||
break;
|
||||
case 0x5D5:
|
||||
break;
|
||||
case 0x5D6:
|
||||
break;
|
||||
case 0x5D7:
|
||||
break;
|
||||
case 0x5D8:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_kiaHyundai_64_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
}
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= interval10)
|
||||
{
|
||||
previousMillis10 = currentMillis;
|
||||
}
|
||||
}
|
38
Software/src/battery/KIA-HYUNDAI-64-BATTERY.h
Normal file
38
Software/src/battery/KIA-HYUNDAI-64-BATTERY.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef KIA_HYUNDAI_64_BATTERY_H
|
||||
#define KIA_HYUNDAI_64_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the Gen24
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
extern uint8_t batteryAllowsContactorClosing;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
void update_values_kiaHyundai_64_battery();
|
||||
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame);
|
||||
void send_can_kiaHyundai_64_battery();
|
||||
|
||||
#endif
|
872
Software/src/battery/NISSAN-LEAF-BATTERY.cpp
Normal file
872
Software/src/battery/NISSAN-LEAF-BATTERY.cpp
Normal file
|
@ -0,0 +1,872 @@
|
|||
#include "NISSAN-LEAF-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
|
||||
static const int interval10 = 10; // interval (ms) at which send CAN Messages
|
||||
static const int interval100 = 100; // interval (ms) at which send CAN Messages
|
||||
static const int interval10s = 10000; // interval (ms) at which send CAN Messages
|
||||
uint16_t CANerror = 0; //counter on how many CAN errors encountered
|
||||
#define MAX_CAN_FAILURES 5000 //Amount of malformed CAN messages to allow before raising a warning
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
|
||||
static byte mprun10 = 0; //counter 0-3
|
||||
static byte mprun100 = 0; //counter 0-3
|
||||
|
||||
CAN_frame_t LEAF_1F2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x1F2,.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
|
||||
CAN_frame_t LEAF_50B = {.FIR = {.B = {.DLC = 7,.FF = CAN_frame_std,}},.MsgID = 0x50B,.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t LEAF_50C = {.FIR = {.B = {.DLC = 6,.FF = CAN_frame_std,}},.MsgID = 0x50C,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t LEAF_1D4 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x1D4,.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
|
||||
//These CAN messages need to be sent towards the battery to keep it alive
|
||||
|
||||
CAN_frame_t LEAF_GROUP_REQUEST = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x79B,.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
|
||||
const CAN_frame_t LEAF_NEXT_LINE_REQUEST = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x79B,.data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
// The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many
|
||||
// groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second
|
||||
// replies with all the battery’s cells voltages in millivolt, the third and the fifth one are still unknown, the
|
||||
// fourth contains the four battery packs temperatures, and the last one tells which cell has the shunt active.
|
||||
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
|
||||
// found the SOH value, and group 84 that replies with the HV battery production serial.
|
||||
|
||||
static uint8_t crctable[256] = {0,133,143,10,155,30,20,145,179,54,60,185,40,173,167,34,227,102,108,233,120,253,247,114,80,213,223,90,203,78,68,193,67,
|
||||
198,204,73,216,93,87,210,240,117,127,250,107,238,228,97,160,37,47,170,59,190,180,49,19,150,156,25,136,13,7,130,134,3,9,
|
||||
140,29,152,146,23,53,176,186,63,174,43,33,164,101,224,234,111,254,123,113,244,214,83,89,220,77,200,194,71,197,64,74,207,
|
||||
94,219,209,84,118,243,249,124,237,104,98,231,38,163,169,44,189,56,50,183,149,16,26,159,14,139,129,4,137,12,6,131,18,151,
|
||||
157,24,58,191,181,48,161,36,46,171,106,239,229,96,241,116,126,251,217,92,86,211,66,199,205,72,202,79,69,192,81,212,222,
|
||||
91,121,252,246,115,226,103,109,232,41,172,166,35,178,55,61,184,154,31,21,144,1,132,142,11,15,138,128,5,148,17,27,158,188,
|
||||
57,51,182,39,162,168,45,236,105,99,230,119,242,248,125,95,218,208,85,196,65,75,206,76,201,195,70,215,82,88,221,255,122,
|
||||
112,245,100,225,235,110,175,42,32,165,52,177,187,62,28,153,147,22,135,2,8,141};
|
||||
|
||||
//Nissan LEAF battery parameters from constantly sent CAN
|
||||
#define ZE0_BATTERY 0
|
||||
#define AZE0_BATTERY 1
|
||||
#define ZE1_BATTERY 2
|
||||
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define WH_PER_GID 77 //One GID is this amount of Watt hours
|
||||
#define LB_MAX_SOC 1000 //LEAF BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
|
||||
#define LB_MIN_SOC 0 //LEAF BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
|
||||
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
|
||||
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
|
||||
static int16_t LB_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
|
||||
static int16_t LB_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
|
||||
static int16_t CalculatedSOC = 0; // Temporary value used for calculating SOC
|
||||
static uint16_t LB_TEMP = 0; //Temporary value used in status checks
|
||||
static uint16_t LB_Wh_Remaining = 0; //Amount of energy in battery, in Wh
|
||||
static uint16_t LB_GIDS = 0;
|
||||
static uint16_t LB_MAX = 0;
|
||||
static uint16_t LB_Max_GIDS = 273; //Startup in 24kWh mode
|
||||
static uint16_t LB_StateOfHealth = 99; //State of health %
|
||||
static uint16_t LB_Total_Voltage = 370; //Battery voltage (0-450V)
|
||||
static int16_t LB_Current = 0; //Current in A going in/out of battery
|
||||
static int16_t LB_Power = 0; //Watts going in/out of battery
|
||||
static int16_t LB_HistData_Temperature_MAX = 6; //-40 to 86*C
|
||||
static int16_t LB_HistData_Temperature_MIN = 5; //-40 to 86*C
|
||||
static int16_t LB_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
|
||||
static uint8_t LB_Relay_Cut_Request = 0; //LB_FAIL
|
||||
static uint8_t LB_Failsafe_Status = 0; //LB_STATUS = 000b = normal start Request
|
||||
//001b = Main Relay OFF Request
|
||||
//010b = Charging Mode Stop Request
|
||||
//011b = Main Relay OFF Request
|
||||
//100b = Caution Lamp Request
|
||||
//101b = Caution Lamp Request & Main Relay OFF Request
|
||||
//110b = Caution Lamp Request & Charging Mode Stop Request
|
||||
//111b = Caution Lamp Request & Main Relay OFF Request
|
||||
static byte LB_Interlock = 1; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
|
||||
static byte LB_Full_CHARGE_flag = 0; //LB_FCHGEND , Goes to 1 if battery is fully charged
|
||||
static byte LB_MainRelayOn_flag = 0; //No-Permission=0, Main Relay On Permission=1
|
||||
static byte LB_Capacity_Empty = 0; //LB_EMPTY, , Goes to 1 if battery is empty
|
||||
|
||||
// Nissan LEAF battery data from polled CAN messages
|
||||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t group_7bb = 0;
|
||||
static uint8_t group = 1;
|
||||
static uint8_t stop_battery_query = 1;
|
||||
static uint8_t hold_off_with_polling_10seconds = 10;
|
||||
static uint16_t cell_voltages[97]; //array with all the cellvoltages
|
||||
static uint16_t cellcounter = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint16_t HX = 0; //Internal resistance
|
||||
static uint16_t insulation = 0; //Insulation resistance
|
||||
static int32_t Battery_current_1 = 0; //High Voltage battery current; it’s positive if discharged, negative when charging
|
||||
static int32_t Battery_current_2 = 0; //High Voltage battery current; it’s positive if discharged, negative when charging (unclear why two values exist)
|
||||
static uint16_t temp_raw_1 = 0;
|
||||
static uint8_t temp_raw_2_highnibble = 0;
|
||||
static uint16_t temp_raw_2 = 0;
|
||||
static uint16_t temp_raw_3 = 0;
|
||||
static uint16_t temp_raw_4 = 0;
|
||||
static uint16_t temp_raw_max = 0;
|
||||
static uint16_t temp_raw_min = 0;
|
||||
static int16_t temp_polled_max = 0;
|
||||
static int16_t temp_polled_min = 0;
|
||||
|
||||
void print_with_units(char *header, int value, char *units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
|
||||
void update_values_leaf_battery()
|
||||
{ /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
/* Start with mapping all values */
|
||||
|
||||
StateOfHealth = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
//Calculate the SOC% value to send to Fronius
|
||||
CalculatedSOC = LB_SOC;
|
||||
CalculatedSOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (CalculatedSOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
|
||||
if (CalculatedSOC < 0){ //We are in the real SOC% range of 0-20%, always set SOC sent to Fronius as 0%
|
||||
CalculatedSOC = 0;
|
||||
}
|
||||
if (CalculatedSOC > 1000){ //We are in the real SOC% range of 80-100%, always set SOC sent to Fronius as 100%
|
||||
CalculatedSOC = 1000;
|
||||
}
|
||||
SOC = (CalculatedSOC * 10); //increase CalculatedSOC range from 0-100.0 -> 100.00
|
||||
|
||||
battery_voltage = (LB_Total_Voltage*10); //One more decimal needed
|
||||
|
||||
battery_current = convert2unsignedint16(LB_Current*10); //One more decimal needed, sign if needed
|
||||
|
||||
capacity_Wh = (LB_Max_GIDS * WH_PER_GID);
|
||||
|
||||
remaining_capacity_Wh = LB_Wh_Remaining;
|
||||
|
||||
LB_Power = LB_Total_Voltage * LB_Current;//P = U * I
|
||||
stat_batt_power = convert2unsignedint16(LB_Power); //add sign if needed
|
||||
|
||||
//Update temperature readings. Method depends on which generation LEAF battery is used
|
||||
if(LEAF_Battery_Type == ZE0_BATTERY){
|
||||
//Since we only have average value, send the minimum as -1.0 degrees below average
|
||||
temperature_min = convert2unsignedint16((LB_AverageTemperature * 10)-10); //add sign if negative and increase range
|
||||
temperature_max = convert2unsignedint16((LB_AverageTemperature * 10));
|
||||
}
|
||||
else if(LEAF_Battery_Type == AZE0_BATTERY){
|
||||
//Use the value sent constantly via CAN in 5C0 (only available on AZE0)
|
||||
temperature_min = convert2unsignedint16((LB_HistData_Temperature_MIN * 10)); //add sign if negative and increase range
|
||||
temperature_max = convert2unsignedint16((LB_HistData_Temperature_MAX * 10));
|
||||
}
|
||||
else
|
||||
{ // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value)
|
||||
if(temp_raw_min != 0) //We have a polled value available
|
||||
{
|
||||
temp_polled_min = ((Temp_fromRAW_to_F(temp_raw_min) - 320 ) * 5) / 9; //Convert from F to C
|
||||
temp_polled_max = ((Temp_fromRAW_to_F(temp_raw_max) - 320 ) * 5) / 9; //Convert from F to C
|
||||
if(temp_polled_min < temp_polled_max){ //Catch any edge cases from Temp_fromRAW_to_F function
|
||||
temperature_min = convert2unsignedint16((temp_polled_min));
|
||||
temperature_max = convert2unsignedint16((temp_polled_max));
|
||||
}
|
||||
else{
|
||||
temperature_min = convert2unsignedint16((temp_polled_max));
|
||||
temperature_max = convert2unsignedint16((temp_polled_min));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define power able to be discharged from battery
|
||||
if(LB_Discharge_Power_Limit > 30) { //if >30kW can be pulled from battery
|
||||
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
|
||||
}
|
||||
else{
|
||||
max_target_discharge_power = (LB_Discharge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if(SOC == 0){ //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
max_target_discharge_power = 0;
|
||||
}
|
||||
|
||||
// Define power able to be put into the battery
|
||||
if(LB_Charge_Power_Limit > 30){ //if >30kW can be put into the battery
|
||||
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
|
||||
}
|
||||
else{
|
||||
max_target_charge_power = (LB_Charge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if(SOC == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
max_target_charge_power = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
|
||||
/*Extra safety functions below*/
|
||||
if(LB_GIDS < 10) //800Wh left in battery
|
||||
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
|
||||
SOC = 0;
|
||||
max_target_discharge_power = 0;
|
||||
}
|
||||
|
||||
if(LB_Full_CHARGE_flag)
|
||||
{ //Battery reports that it is fully charged stop all further charging incase it hasn't already
|
||||
max_target_charge_power = 0;
|
||||
}
|
||||
|
||||
if(LB_Relay_Cut_Request)
|
||||
{ //LB_FAIL, BMS requesting shutdown and contactors to be opened
|
||||
Serial.println("Battery requesting immediate shutdown and contactors to be opened!");
|
||||
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
|
||||
errorCode = 1;
|
||||
max_target_discharge_power = 0;
|
||||
max_target_charge_power = 0;
|
||||
}
|
||||
|
||||
if(LB_Failsafe_Status > 0) // 0 is normal, start charging/discharging
|
||||
{
|
||||
switch(LB_Failsafe_Status)
|
||||
{
|
||||
case(1):
|
||||
//Normal Stop Request
|
||||
//This means that battery is fully discharged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
|
||||
break;
|
||||
case(2):
|
||||
//Charging Mode Stop Request
|
||||
//This means that battery is fully charged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
|
||||
break;
|
||||
case(3):
|
||||
//Charging Mode Stop Request & Normal Stop Request
|
||||
//Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this.
|
||||
break;
|
||||
case(4):
|
||||
//Caution Lamp Request
|
||||
Serial.println("ERROR: Battery raised caution indicator. Inspect battery status!");
|
||||
break;
|
||||
case(5):
|
||||
//Caution Lamp Request & Normal Stop Request
|
||||
bms_status = FAULT;
|
||||
errorCode = 2;
|
||||
Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!");
|
||||
break;
|
||||
case(6):
|
||||
//Caution Lamp Request & Charging Mode Stop Request
|
||||
bms_status = FAULT;
|
||||
errorCode = 3;
|
||||
Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!");
|
||||
break;
|
||||
case(7):
|
||||
//Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request
|
||||
bms_status = FAULT;
|
||||
errorCode = 4;
|
||||
Serial.println("ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(LB_StateOfHealth < 25)
|
||||
{ //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
|
||||
if(LB_StateOfHealth != 0)
|
||||
{ //Extra check to see that we actually have a SOH Value available
|
||||
Serial.println("ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery.");
|
||||
bms_status = FAULT;
|
||||
errorCode = 5;
|
||||
max_target_discharge_power = 0;
|
||||
max_target_charge_power = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef INTERLOCK_REQUIRED
|
||||
if(!LB_Interlock)
|
||||
{
|
||||
Serial.println("ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be disabled!");
|
||||
bms_status = FAULT;
|
||||
errorCode = 6;
|
||||
SOC = 0;
|
||||
max_target_discharge_power = 0;
|
||||
max_target_charge_power = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
errorCode = 7;
|
||||
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
if(CANerror > MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
|
||||
{
|
||||
errorCode = 10;
|
||||
LEDcolor = YELLOW;
|
||||
Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!");
|
||||
}
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
if(errorCode > 0)
|
||||
{
|
||||
Serial.print("ERROR CODE ACTIVE IN SYSTEM. NUMBER: ");
|
||||
Serial.println(errorCode);
|
||||
}
|
||||
Serial.println("Values going to inverter");
|
||||
print_with_units("SOH%: ", (StateOfHealth*0.01), "% ");
|
||||
print_with_units(", SOC% scaled: ", (SOC*0.01), "% ");
|
||||
print_with_units(", Voltage: ", LB_Total_Voltage, "V ");
|
||||
print_with_units(", Max discharge power: ", max_target_discharge_power, "W ");
|
||||
print_with_units(", Max charge power: ", max_target_charge_power, "W ");
|
||||
print_with_units(", Max temp: ", (temperature_max*0.1), "°C ");
|
||||
print_with_units(", Min temp: ", (temperature_min*0.1), "°C ");
|
||||
Serial.println("");
|
||||
Serial.print("BMS Status: ");
|
||||
if(bms_status == 3){
|
||||
Serial.print("Active, ");
|
||||
}
|
||||
else{
|
||||
Serial.print("FAULT, ");
|
||||
}
|
||||
switch (bms_char_dis_status){
|
||||
case 0:
|
||||
Serial.print("Idle");
|
||||
break;
|
||||
case 1:
|
||||
Serial.print("Discharging");
|
||||
break;
|
||||
case 2:
|
||||
Serial.print("Charging");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
print_with_units(", Power: ", LB_Power, "W ");
|
||||
Serial.println("");
|
||||
Serial.println("Values from battery");
|
||||
print_with_units("Real SOC%: ", (LB_SOC*0.1), "% ");
|
||||
print_with_units(", GIDS: ", LB_GIDS, " (x77Wh) ");
|
||||
print_with_units(", Battery gen: ", LEAF_Battery_Type, " ");
|
||||
print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV ");
|
||||
print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV ");
|
||||
print_with_units(", Cell deviation: ", cell_deviation_mV, "mV ");
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_leaf_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x1DB:
|
||||
if(is_message_corrupt(rx_frame)){
|
||||
CANerror++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Current = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
|
||||
if (LB_Current & 0x0400){
|
||||
// negative so extend the sign bit
|
||||
LB_Current |= 0xf800;
|
||||
}
|
||||
// Scale down the value by 0.5
|
||||
LB_Current /= 2;
|
||||
|
||||
LB_Total_Voltage = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6) / 2;
|
||||
|
||||
//Collect various data from the BMS
|
||||
LB_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
|
||||
LB_Failsafe_Status = (rx_frame.data.u8[1] & 0x07);
|
||||
LB_MainRelayOn_flag = (byte) ((rx_frame.data.u8[3] & 0x20) >> 5);
|
||||
if(LB_MainRelayOn_flag){
|
||||
batteryAllowsContactorClosing = 1;
|
||||
}
|
||||
else{
|
||||
batteryAllowsContactorClosing = 0;
|
||||
}
|
||||
LB_Full_CHARGE_flag = (byte) ((rx_frame.data.u8[3] & 0x10) >> 4);
|
||||
LB_Interlock = (byte) ((rx_frame.data.u8[3] & 0x08) >> 3);
|
||||
break;
|
||||
case 0x1DC:
|
||||
if(is_message_corrupt(rx_frame)){
|
||||
CANerror++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
|
||||
LB_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0);
|
||||
LB_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10);
|
||||
break;
|
||||
case 0x55B:
|
||||
if(is_message_corrupt(rx_frame)){
|
||||
CANerror++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
|
||||
if (LB_TEMP != 0x3ff) { //3FF is unavailable value
|
||||
LB_SOC = LB_TEMP;
|
||||
}
|
||||
break;
|
||||
case 0x5BC:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
|
||||
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
|
||||
if (LB_MAX){
|
||||
LB_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
|
||||
//Max gids active, do nothing
|
||||
//Only the 30/40/62kWh packs have this mux
|
||||
}
|
||||
else{ //Normal current GIDS value is transmitted
|
||||
LB_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
|
||||
LB_Wh_Remaining = (LB_GIDS * WH_PER_GID);
|
||||
}
|
||||
|
||||
if(LEAF_Battery_Type == ZE0_BATTERY){
|
||||
LB_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55
|
||||
}
|
||||
|
||||
LB_TEMP = (rx_frame.data.u8[4] >> 1);
|
||||
if (LB_TEMP != 0){
|
||||
LB_StateOfHealth = LB_TEMP; //Collect state of health from battery
|
||||
}
|
||||
break;
|
||||
case 0x5C0: //This method only works for 2013-2017 AZE0 LEAF packs, the mux is different on other generations
|
||||
if(LEAF_Battery_Type == AZE0_BATTERY){
|
||||
if ((rx_frame.data.u8[0]>>6) == 1){ // Battery MAX temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
|
||||
LB_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40);
|
||||
}
|
||||
if ((rx_frame.data.u8[0]>>6) == 3){ // Battery MIN temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
|
||||
LB_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x59E:
|
||||
//AZE0 2013-2017 or ZE1 2018-2023 battery detected
|
||||
//Only detect as AZE0 if not already set as ZE1
|
||||
if(LEAF_Battery_Type != ZE1_BATTERY){
|
||||
LEAF_Battery_Type = AZE0_BATTERY;
|
||||
}
|
||||
break;
|
||||
case 0x1ED:
|
||||
case 0x1C2:
|
||||
//ZE1 2018-2023 battery detected!
|
||||
LEAF_Battery_Type = ZE1_BATTERY;
|
||||
break;
|
||||
case 0x79B:
|
||||
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
|
||||
break;
|
||||
case 0x7BB:
|
||||
//First check which group data we are getting
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
|
||||
group_7bb = rx_frame.data.u8[3];
|
||||
if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(stop_battery_query){ //Leafspy is active, stop our own polling
|
||||
break;
|
||||
}
|
||||
|
||||
ESP32Can.CANWriteFrame(&LEAF_NEXT_LINE_REQUEST); //Request the next frame for the group
|
||||
|
||||
if(group_7bb == 1) //High precision SOC, Current, voltages etc.
|
||||
{
|
||||
if(rx_frame.data.u8[0] == 0x10){ //First frame
|
||||
Battery_current_1 = (rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16 | ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]));
|
||||
if(Battery_current_1 & 0x8000000 == 0x8000000){
|
||||
Battery_current_1 = (( Battery_current_1 | -0x100000000 ) / 1024);
|
||||
}
|
||||
else{
|
||||
Battery_current_1 = (Battery_current_1 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
if(rx_frame.data.u8[0] == 0x21){ //Second frame
|
||||
Battery_current_2 = (rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16 | ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]));
|
||||
if(Battery_current_2 & 0x8000000 == 0x8000000){
|
||||
Battery_current_2 = (( Battery_current_2 | -0x100000000 ) / 1024);
|
||||
}
|
||||
else{
|
||||
Battery_current_2 = (Battery_current_2 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
if(rx_frame.data.u8[0] == 0x23){ // Fourth frame
|
||||
insulation = (uint16_t) ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
}
|
||||
|
||||
if(rx_frame.data.u8[0] == 0x24){ // Fifth frame
|
||||
HX = (uint16_t) ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(group_7bb == 2) //Cell Voltages
|
||||
{
|
||||
if(rx_frame.data.u8[0] == 0x10){ //first frame is anomalous
|
||||
battery_request_idx = 0;
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
|
||||
break;
|
||||
}
|
||||
if(rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C){ //Last frame
|
||||
//Last frame does not contain any cell data, calculate the result
|
||||
min_max_voltage[0] = 9999;
|
||||
min_max_voltage[1] = 0;
|
||||
for(cellcounter = 0; cellcounter < 96; cellcounter++){
|
||||
if(min_max_voltage[0] > cell_voltages[cellcounter]) min_max_voltage[0] = cell_voltages[cellcounter];
|
||||
if(min_max_voltage[1] < cell_voltages[cellcounter]) min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
cell_max_voltage = min_max_voltage[1];
|
||||
cell_min_voltage = min_max_voltage[0];
|
||||
|
||||
if(cell_deviation_mV > MAX_CELL_DEVIATION){
|
||||
LEDcolor = YELLOW;
|
||||
Serial.println("HIGH CELL DEVIATION!!! Inspect battery!");
|
||||
}
|
||||
|
||||
if(min_max_voltage[1] >= MAX_CELL_VOLTAGE){
|
||||
bms_status = FAULT;
|
||||
errorCode = 8;
|
||||
Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
|
||||
}
|
||||
if(min_max_voltage[0] <= MIN_CELL_VOLTAGE){
|
||||
bms_status = FAULT;
|
||||
errorCode = 9;
|
||||
Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if((rx_frame.data.u8[0] % 2) == 0){ //even frames
|
||||
cell_voltages[battery_request_idx++] |= rx_frame.data.u8[1];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
|
||||
} else { //odd frames
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
||||
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
||||
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
if(group_7bb == 4) { //Temperatures
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First message
|
||||
temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
temp_raw_2_highnibble = rx_frame.data.u8[7];
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x21) { //Second message
|
||||
temp_raw_2 = (temp_raw_2_highnibble << 8) | rx_frame.data.u8[1];
|
||||
temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
||||
temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x22) { //Third message
|
||||
//All values read, let's figure out the min/max!
|
||||
|
||||
if(temp_raw_3 == 65535){ //We are on a 2013+ pack that only has three temp sensors.
|
||||
//Start with finding max value
|
||||
temp_raw_max = temp_raw_1;
|
||||
if(temp_raw_2 > temp_raw_max){
|
||||
temp_raw_max = temp_raw_2;
|
||||
}
|
||||
if(temp_raw_4 > temp_raw_max){
|
||||
temp_raw_max = temp_raw_4;
|
||||
}
|
||||
//Then find min
|
||||
temp_raw_min = temp_raw_1;
|
||||
if(temp_raw_2 < temp_raw_min){
|
||||
temp_raw_min = temp_raw_2;
|
||||
}
|
||||
if(temp_raw_4 < temp_raw_min){
|
||||
temp_raw_min = temp_raw_4;
|
||||
}
|
||||
}
|
||||
else{ //All 4 temp sensors available on 2011-2012
|
||||
//Start with finding max value
|
||||
temp_raw_max = temp_raw_1;
|
||||
if(temp_raw_2 > temp_raw_max){
|
||||
temp_raw_max = temp_raw_2;
|
||||
}
|
||||
if(temp_raw_3 > temp_raw_max){
|
||||
temp_raw_max = temp_raw_3;
|
||||
}
|
||||
if(temp_raw_4 > temp_raw_max){
|
||||
temp_raw_max = temp_raw_4;
|
||||
}
|
||||
//Then find min
|
||||
temp_raw_min = temp_raw_1;
|
||||
if(temp_raw_2 < temp_raw_min){
|
||||
temp_raw_min = temp_raw_2;
|
||||
}
|
||||
if(temp_raw_3 < temp_raw_min){
|
||||
temp_raw_min = temp_raw_2;
|
||||
}
|
||||
if(temp_raw_4 < temp_raw_min){
|
||||
temp_raw_min = temp_raw_4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_leaf_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&LEAF_50B); //Always send 50B as a static message (Contains HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1)
|
||||
|
||||
mprun100++;
|
||||
if (mprun100 > 3){
|
||||
mprun100 = 0;
|
||||
}
|
||||
|
||||
if (mprun100 == 0){
|
||||
LEAF_50C.data.u8[3] = 0x00;
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0xC8;
|
||||
}
|
||||
else if(mprun100 == 1){
|
||||
LEAF_50C.data.u8[3] = 0x01;
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x31;
|
||||
}
|
||||
else if(mprun100 == 2){
|
||||
LEAF_50C.data.u8[3] = 0x02;
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0x63;
|
||||
}
|
||||
else if(mprun100 == 3){
|
||||
LEAF_50C.data.u8[3] = 0x03;
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x9A;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_50C);
|
||||
}
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= interval10)
|
||||
{
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
if(mprun10 == 0){
|
||||
LEAF_1D4.data.u8[4] = 0x07;
|
||||
LEAF_1D4.data.u8[7] = 0x12;
|
||||
}
|
||||
else if(mprun10 == 1){
|
||||
LEAF_1D4.data.u8[4] = 0x47;
|
||||
LEAF_1D4.data.u8[7] = 0xD5;
|
||||
}
|
||||
else if(mprun10 == 2){
|
||||
LEAF_1D4.data.u8[4] = 0x87;
|
||||
LEAF_1D4.data.u8[7] = 0x19;
|
||||
}
|
||||
else if(mprun10 == 3){
|
||||
LEAF_1D4.data.u8[4] = 0xC7;
|
||||
LEAF_1D4.data.u8[7] = 0xDE;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_1D4);
|
||||
|
||||
mprun10++;
|
||||
if (mprun10 > 3){
|
||||
mprun10 = 0;
|
||||
}
|
||||
|
||||
switch(mprun10r)
|
||||
{
|
||||
case(0):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case(1):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case(2):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case(3):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case(4):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case(5): // Set 2
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case(6):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case(7):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case(8):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case(9):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case(10): // Set 3
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case(11):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case(12):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case(13):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case(14):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case(15): // Set 4
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case(16):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case(17):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case(18):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case(19):
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ESP32Can.CANWriteFrame(&LEAF_1F2); //Contains (CHG_STA_RQ == 1 == Normal Charge)
|
||||
|
||||
mprun10r++;
|
||||
if(mprun10r > 19){ // 0x1F2 patter repeats after 20 messages,
|
||||
mprun10r = 0;
|
||||
}
|
||||
}
|
||||
//Send 10s CAN messages
|
||||
if (currentMillis - previousMillis10s >= interval10s)
|
||||
{
|
||||
previousMillis10s = currentMillis;
|
||||
|
||||
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
|
||||
if(!stop_battery_query){
|
||||
if (group == 1){ // Cycle between group 1, 2, and 4 using bit manipulation
|
||||
group = 2;
|
||||
}
|
||||
else if (group == 2){
|
||||
group = 4;
|
||||
}
|
||||
else if (group == 4){
|
||||
group = 1;
|
||||
}
|
||||
LEAF_GROUP_REQUEST.data.u8[2] = group;
|
||||
ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST);
|
||||
}
|
||||
|
||||
if(hold_off_with_polling_10seconds > 0){
|
||||
hold_off_with_polling_10seconds--;
|
||||
}
|
||||
else
|
||||
{
|
||||
stop_battery_query = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t convert2unsignedint16(int16_t signed_value)
|
||||
{
|
||||
if(signed_value < 0){
|
||||
return(65535 + signed_value);
|
||||
}
|
||||
else{
|
||||
return (uint16_t)signed_value;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_message_corrupt(CAN_frame_t rx_frame)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
for (uint8_t j = 0; j < 7; j++){
|
||||
crc = crctable[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
|
||||
}
|
||||
return crc != rx_frame.data.u8[7];
|
||||
}
|
||||
|
||||
uint16_t Temp_fromRAW_to_F(uint16_t temperature)
|
||||
{ //This function feels horrible, but apparently works well
|
||||
if (temperature == 1021) {
|
||||
return 10;
|
||||
} else if (temperature >= 589) {
|
||||
return static_cast<uint16_t>(1620 - temperature * 1.81);
|
||||
} else if (temperature >= 569) {
|
||||
return static_cast<uint16_t>(572 + (579 - temperature) * 1.80);
|
||||
} else if (temperature >= 558) {
|
||||
return static_cast<uint16_t>(608 + (558 - temperature) * 1.6363636363636364);
|
||||
} else if (temperature >= 548) {
|
||||
return static_cast<uint16_t>(626 + (548 - temperature) * 1.80);
|
||||
} else if (temperature >= 537) {
|
||||
return static_cast<uint16_t>(644 + (537 - temperature) * 1.6363636363636364);
|
||||
} else if (temperature >= 447) {
|
||||
return static_cast<uint16_t>(662 + (527 - temperature) * 1.8);
|
||||
} else if (temperature >= 438) {
|
||||
return static_cast<uint16_t>(824 + (438 - temperature) * 2);
|
||||
} else if (temperature >= 428) {
|
||||
return static_cast<uint16_t>(842 + (428 - temperature) * 1.80);
|
||||
} else if (temperature >= 365) {
|
||||
return static_cast<uint16_t>(860 + (419 - temperature) * 2.0);
|
||||
} else if (temperature >= 357) {
|
||||
return static_cast<uint16_t>(986 + (357 - temperature) * 2.25);
|
||||
} else if (temperature >= 348) {
|
||||
return static_cast<uint16_t>(1004 + (348 - temperature) * 2);
|
||||
} else if (temperature >= 316) {
|
||||
return static_cast<uint16_t>(1022 + (340 - temperature) * 2.25);
|
||||
}
|
||||
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
|
||||
}
|
47
Software/src/battery/NISSAN-LEAF-BATTERY.h
Normal file
47
Software/src/battery/NISSAN-LEAF-BATTERY.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef NISSAN_LEAF_BATTERY_H
|
||||
#define NISSAN_LEAF_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the inverter
|
||||
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
|
||||
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
|
||||
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
|
||||
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
|
||||
extern uint16_t capacity_Wh; //Wh, 0-60000
|
||||
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
|
||||
extern uint16_t max_target_discharge_power; //W, 0-60000
|
||||
extern uint16_t max_target_charge_power; //W, 0-60000
|
||||
extern uint16_t bms_status; //Enum, 0-5
|
||||
extern uint16_t bms_char_dis_status; //Enum, 0-2
|
||||
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
|
||||
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
|
||||
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
|
||||
extern uint16_t cell_max_voltage; //mV, 0-4350
|
||||
extern uint16_t cell_min_voltage; //mV, 0-4350
|
||||
extern uint8_t batteryAllowsContactorClosing; //Bool, 1=true, 0=false
|
||||
extern uint8_t LEDcolor; //Enum, 0-2
|
||||
// Definitions for bms_status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
// LED colors
|
||||
#define GREEN 0
|
||||
#define YELLOW 1
|
||||
#define RED 2
|
||||
|
||||
void update_values_leaf_battery();
|
||||
void receive_can_leaf_battery(CAN_frame_t rx_frame);
|
||||
void send_can_leaf_battery();
|
||||
uint16_t convert2unsignedint16(int16_t signed_value);
|
||||
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
||||
bool is_message_corrupt(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
167
Software/src/battery/RENAULT-ZOE-BATTERY.cpp
Normal file
167
Software/src/battery/RENAULT-ZOE-BATTERY.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include "RENAULT-ZOE-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
|
||||
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
|
||||
const int rx_queue_size = 10; // Receive Queue size
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static int16_t LB_SOC = 0;
|
||||
static int16_t LB_SOH = 0;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
static int16_t LB_MAX_TEMPERATURE = 0;
|
||||
static uint16_t LB_Discharge_Power_Limit = 0;
|
||||
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
|
||||
static uint16_t LB_Charge_Power_Limit = 0;
|
||||
static uint32_t LB_Charge_Power_Limit_Watts = 0;
|
||||
|
||||
|
||||
CAN_frame_t ZOE_423 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x423,.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static const int interval10 = 10; // interval (ms) at which send CAN Messages
|
||||
static const int interval100 = 100; // interval (ms) at which send CAN Messages
|
||||
static int BMSPollCounter = 0;
|
||||
|
||||
void update_values_zoe_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
bms_status = ACTIVE; //Startout in active mode
|
||||
|
||||
StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
//Calculate the SOC% value to send to Fronius
|
||||
LB_SOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (LB_SOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
|
||||
if (LB_SOC < 0)
|
||||
{ //We are in the real SOC% range of 0-20%, always set SOC sent to Fronius as 0%
|
||||
LB_SOC = 0;
|
||||
}
|
||||
if (LB_SOC > 1000)
|
||||
{ //We are in the real SOC% range of 80-100%, always set SOC sent to Fronius as 100%
|
||||
LB_SOC = 1000;
|
||||
}
|
||||
SOC = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
battery_voltage;
|
||||
battery_current;
|
||||
capacity_Wh = BATTERY_WH_MAX;
|
||||
remaining_capacity_Wh;
|
||||
|
||||
LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be discharged from battery */
|
||||
if(LB_Discharge_Power_Limit_Watts > 30000) //if >30kW can be pulled from battery
|
||||
{
|
||||
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
|
||||
}
|
||||
else
|
||||
{
|
||||
max_target_discharge_power = LB_Discharge_Power_Limit_Watts;
|
||||
}
|
||||
if(SOC == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
{
|
||||
max_target_discharge_power = 0;
|
||||
}
|
||||
|
||||
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be put into the battery */
|
||||
if(LB_Charge_Power_Limit_Watts > 30000) //if >30kW can be put into the battery
|
||||
{
|
||||
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
|
||||
}
|
||||
if(LB_Charge_Power_Limit_Watts < 0)
|
||||
{
|
||||
max_target_charge_power = 0; //cap calue so we dont do under the Fronius limits
|
||||
}
|
||||
else
|
||||
{
|
||||
max_target_charge_power = LB_Charge_Power_Limit_Watts;
|
||||
}
|
||||
if(SOC == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
max_target_charge_power = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!CANstillAlive)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CANstillAlive--;
|
||||
}
|
||||
|
||||
stat_batt_power;
|
||||
temperature_min;
|
||||
temperature_max;
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("BMS Status (3=OK): ");
|
||||
Serial.println(bms_status);
|
||||
Serial.print("Max discharge power: ");
|
||||
Serial.println(max_target_discharge_power);
|
||||
Serial.print("Max charge power: ");
|
||||
Serial.println(max_target_charge_power);
|
||||
Serial.print("SOH%: ");
|
||||
Serial.println(LB_SOH);
|
||||
Serial.print("SOH% to Fronius: ");
|
||||
Serial.println(StateOfHealth);
|
||||
Serial.print("LB_SOC: ");
|
||||
Serial.println(LB_SOC);
|
||||
Serial.print("SOC% to Fronius: ");
|
||||
Serial.println(SOC);
|
||||
Serial.print("Temperature Min: ");
|
||||
Serial.println(temperature_min);
|
||||
Serial.print("Temperature Max: ");
|
||||
Serial.println(temperature_max);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_zoe_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x155: //BMS1
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
//LB_Max_Charge_Amps =
|
||||
//LB_Current = (((rx_frame.data.u8[1] & 0xF8) << 5) | (rx_frame.data.u8[2]));
|
||||
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5]));
|
||||
break;
|
||||
case 0x424: //BMS2
|
||||
LB_Charge_Power_Limit = (rx_frame.data.u8[2]);
|
||||
LB_Discharge_Power_Limit = (rx_frame.data.u8[3]);
|
||||
LB_SOH = (rx_frame.data.u8[5]);
|
||||
LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4] & 0x7F) - 40);
|
||||
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7] & 0x7F) - 40);
|
||||
break;
|
||||
case 0x425: //BMS3 (could also be 445?)
|
||||
//LB_kWh_Remaining =
|
||||
//LB_Cell_Max_Voltage =
|
||||
//LB_Cell_Min_Voltage =
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_zoe_battery()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
BMSPollCounter++; //GKOE
|
||||
|
||||
if (BMSPollCounter < 46) //GKOE
|
||||
{ //The Kangoo batteries (also Zoe?) dont like getting this message continously, so we pause after 4.6s, and resume after 40s
|
||||
ESP32Can.CANWriteFrame(&ZOE_423); //Send 0x423 to keep BMS happy
|
||||
}
|
||||
if (BMSPollCounter > 400) //GKOE
|
||||
{
|
||||
BMSPollCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
38
Software/src/battery/RENAULT-ZOE-BATTERY.h
Normal file
38
Software/src/battery/RENAULT-ZOE-BATTERY.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef RENAULT_ZOE_BATTERY_H
|
||||
#define RENAULT_ZOE_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the Gen24
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
void update_values_zoe_battery();
|
||||
void receive_can_zoe_battery(CAN_frame_t rx_frame);
|
||||
void send_can_zoe_battery();
|
||||
uint16_t convert2unsignedint16(uint16_t signed_value);
|
||||
|
||||
#endif
|
405
Software/src/battery/TESLA-MODEL-3-BATTERY.cpp
Normal file
405
Software/src/battery/TESLA-MODEL-3-BATTERY.cpp
Normal file
|
@ -0,0 +1,405 @@
|
|||
#include "TESLA-MODEL-3-BATTERY.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
|
||||
|
||||
static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send
|
||||
static const int interval30 = 30; // interval (ms) at which send CAN Messages
|
||||
static uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
|
||||
|
||||
CAN_frame_t TESLA_221_1 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x221,.data = {0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96}}; //Contactor frame 221 - close contactors
|
||||
CAN_frame_t TESLA_221_2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x221,.data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive
|
||||
|
||||
static uint32_t temporaryvariable = 0;
|
||||
static uint32_t total_discharge = 0;
|
||||
static uint32_t total_charge = 0;
|
||||
static uint16_t volts = 0; // V
|
||||
static int16_t amps = 0; // A
|
||||
static uint16_t raw_amps = 0; // A
|
||||
static int16_t max_temp = 6; // C*
|
||||
static int16_t min_temp = 5; // C*
|
||||
static uint16_t energy_buffer = 0;
|
||||
static uint16_t energy_to_charge_complete = 0;
|
||||
static uint16_t expected_energy_remaining = 0;
|
||||
static uint8_t full_charge_complete = 0;
|
||||
static uint16_t ideal_energy_remaining = 0;
|
||||
static uint16_t nominal_energy_remaining = 0;
|
||||
static uint16_t nominal_full_pack_energy = 0;
|
||||
static uint16_t battery_charge_time_remaining = 0; // Minutes
|
||||
static uint16_t regenerative_limit = 0;
|
||||
static uint16_t discharge_limit = 0;
|
||||
static uint16_t max_heat_park = 0;
|
||||
static uint16_t hvac_max_power = 0;
|
||||
static uint16_t min_voltage = 0;
|
||||
static uint16_t max_discharge_current = 0;
|
||||
static uint16_t max_charge_current = 0;
|
||||
static uint16_t max_voltage = 0;
|
||||
static uint16_t high_voltage = 0;
|
||||
static uint16_t low_voltage = 0;
|
||||
static uint16_t output_current = 0;
|
||||
static uint16_t soc_min = 0;
|
||||
static uint16_t soc_max = 0;
|
||||
static uint16_t soc_vi = 0;
|
||||
static uint16_t soc_ave = 0;
|
||||
static uint16_t cell_max_v = 3700;
|
||||
static uint16_t cell_min_v = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint8_t max_vno = 0;
|
||||
static uint8_t min_vno = 0;
|
||||
static uint8_t contactor = 0; //State of contactor
|
||||
static uint8_t hvil_status = 0;
|
||||
static uint8_t packContNegativeState = 0;
|
||||
static uint8_t packContPositiveState = 0;
|
||||
static uint8_t packContactorSetState = 0;
|
||||
static uint8_t packCtrsClosingAllowed = 0;
|
||||
static uint8_t pyroTestInProgress = 0;
|
||||
static uint8_t send221still = 10;
|
||||
static const char* contactorText[] = {"UNKNOWN(0)","OPEN","CLOSING","BLOCKED","OPENING","CLOSED","UNKNOWN(6)","WELDED","POS_CL","NEG_CL","UNKNOWN(10)","UNKNOWN(11)","UNKNOWN(12)"};
|
||||
static const char* contactorState[] = {"SNA","OPEN","PRECHARGE","BLOCKED","PULLED_IN","OPENING","ECONOMIZED","WELDED","UNKNOWN(8)","UNKNOWN(9)","UNKNOWN(10)","UNKNOWN(11)"};
|
||||
static const char* hvilStatusState[] = {"NOT OK","STATUS_OK","CURRENT_SOURCE_FAULT","INTERNAL_OPEN_FAULT","VEHICLE_OPEN_FAULT","PENTHOUSE_LID_OPEN_FAULT","UNKNOWN_LOCATION_OPEN_FAULT","VEHICLE_NODE_FAULT","NO_12V_SUPPLY","VEHICLE_OR_PENTHOUSE_LID_OPENFAULT","UNKNOWN(10)","UNKNOWN(11)","UNKNOWN(12)","UNKNOWN(13)","UNKNOWN(14)","UNKNOWN(15)"};
|
||||
|
||||
|
||||
#define MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
|
||||
#define MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value (These values might need tweaking based on chemistry)
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value (These values might need tweaking based on chemistry)
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
void print_int_with_units(char *header, int value, char *units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
void print_SOC(char *header, int SOC) {
|
||||
Serial.print(header);
|
||||
Serial.print(SOC / 100);
|
||||
Serial.print(".");
|
||||
int hundredth = SOC % 100;
|
||||
if(hundredth < 10)
|
||||
Serial.print(0);
|
||||
Serial.print(hundredth);
|
||||
Serial.println("%");
|
||||
}
|
||||
|
||||
void update_values_tesla_model_3_battery()
|
||||
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
//After values are mapped, we perform some safety checks, and do some serial printouts
|
||||
StateOfHealth = 9900; //Hardcoded to 99%SOH
|
||||
|
||||
//Calculate the SOC% value to send to inverter
|
||||
soc_vi = MIN_SOC + (MAX_SOC - MIN_SOC) * (soc_vi - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
|
||||
if (soc_vi < 0)
|
||||
{ //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
|
||||
soc_vi = 0;
|
||||
}
|
||||
if (soc_vi > 1000)
|
||||
{ //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
|
||||
soc_vi = 1000;
|
||||
}
|
||||
SOC = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00
|
||||
|
||||
battery_voltage = (volts*10); //One more decimal needed (370 -> 3700)
|
||||
|
||||
battery_current = amps; //TODO, this needs verifying if scaling is right
|
||||
|
||||
capacity_Wh = (nominal_full_pack_energy * 100); //Scale up 75.2kWh -> 75200Wh
|
||||
if(capacity_Wh > 60000)
|
||||
{
|
||||
capacity_Wh = 60000;
|
||||
}
|
||||
|
||||
remaining_capacity_Wh = (expected_energy_remaining * 100); //Scale up 60.3kWh -> 60300Wh
|
||||
|
||||
//Calculate the allowed discharge power, cap it if it gets too large
|
||||
temporaryvariable = (max_discharge_current * volts);
|
||||
if(temporaryvariable > 60000){
|
||||
max_target_discharge_power = 60000;
|
||||
}
|
||||
else{
|
||||
max_target_discharge_power = temporaryvariable;
|
||||
}
|
||||
|
||||
//The allowed charge power behaves strangely. We instead estimate this value
|
||||
if(SOC == 10000){
|
||||
max_target_charge_power = 0; //When battery is 100% full, set allowed charge W to 0
|
||||
}
|
||||
else{
|
||||
max_target_charge_power = 15000; //Otherwise we can push 15kW into the pack!
|
||||
}
|
||||
|
||||
stat_batt_power = (volts * amps); //TODO, check if scaling is OK
|
||||
|
||||
min_temp = (min_temp * 10);
|
||||
temperature_min = convert2unsignedInt16(min_temp);
|
||||
|
||||
max_temp = (max_temp * 10);
|
||||
temperature_max = convert2unsignedInt16(max_temp);
|
||||
|
||||
cell_max_voltage = cell_max_v;
|
||||
|
||||
cell_min_voltage = cell_min_v;
|
||||
|
||||
/* Value mapping is completed. Start to check all safeties */
|
||||
|
||||
bms_status = ACTIVE; //Startout in active mode before checking if we have any faults
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if(!stillAliveCAN)
|
||||
{
|
||||
bms_status = FAULT;
|
||||
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
|
||||
}
|
||||
else
|
||||
{
|
||||
stillAliveCAN--;
|
||||
}
|
||||
|
||||
if (hvil_status == 3){ //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
||||
bms_status = FAULT;
|
||||
Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!");
|
||||
}
|
||||
|
||||
if(cell_max_v >= MAX_CELL_VOLTAGE){
|
||||
bms_status = FAULT;
|
||||
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
|
||||
}
|
||||
if(cell_min_v <= MIN_CELL_VOLTAGE){
|
||||
bms_status = FAULT;
|
||||
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
|
||||
}
|
||||
|
||||
cell_deviation_mV = (cell_max_v - cell_min_v);
|
||||
|
||||
if(cell_deviation_mV > MAX_CELL_DEVIATION){
|
||||
LEDcolor = YELLOW;
|
||||
Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!");
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
if (packCtrsClosingAllowed == 0)
|
||||
{
|
||||
Serial.println("ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: ");
|
||||
}
|
||||
if (pyroTestInProgress == 1)
|
||||
{
|
||||
Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!");
|
||||
}
|
||||
|
||||
Serial.print("STATUS: Contactor: ");
|
||||
Serial.print(contactorText[contactor]); //Display what state the contactor is in
|
||||
Serial.print(", HVIL: ");
|
||||
Serial.print(hvilStatusState[hvil_status]);
|
||||
Serial.print(", NegativeState: ");
|
||||
Serial.print(contactorState[packContNegativeState]);
|
||||
Serial.print(", PositiveState: ");
|
||||
Serial.print(contactorState[packContPositiveState]);
|
||||
Serial.print(", setState: ");
|
||||
Serial.print(contactorState[packContactorSetState]);
|
||||
Serial.print(", close allowed: ");
|
||||
Serial.print(packCtrsClosingAllowed);
|
||||
Serial.print(", Pyrotest: ");
|
||||
Serial.println(pyroTestInProgress);
|
||||
|
||||
Serial.print("Battery values: ");
|
||||
Serial.print(" Vi SOC: ");
|
||||
Serial.print(soc_vi);
|
||||
Serial.print(", SOC max: ");
|
||||
Serial.print(soc_max);
|
||||
Serial.print(", SOC min: ");
|
||||
Serial.print(soc_min);
|
||||
Serial.print(", SOC avg: ");
|
||||
Serial.print(soc_ave);
|
||||
print_int_with_units(", Battery voltage: ", volts, "V");
|
||||
print_int_with_units(", Battery current: ", amps, "A");
|
||||
Serial.println("");
|
||||
print_int_with_units("Discharge limit battery: ", discharge_limit, "kW");
|
||||
Serial.print(", ");
|
||||
print_int_with_units("Charge limit battery: ", regenerative_limit, "kW");
|
||||
Serial.print("kW");
|
||||
Serial.print(", Fully charged?: ");
|
||||
if(full_charge_complete)
|
||||
Serial.print("YES, ");
|
||||
else
|
||||
Serial.print("NO, ");
|
||||
print_int_with_units("Min voltage allowed: ", min_voltage, "V");
|
||||
Serial.print(", ");
|
||||
print_int_with_units("Max voltage allowed: ", max_voltage, "V");
|
||||
Serial.println("");
|
||||
print_int_with_units("Max charge current: ", max_charge_current, "A");
|
||||
Serial.print(", ");
|
||||
print_int_with_units("Max discharge current: ", max_discharge_current, "A");
|
||||
Serial.println("");
|
||||
Serial.print("Cellstats, Max: ");
|
||||
Serial.print(cell_max_v);
|
||||
Serial.print("mV (cell ");
|
||||
Serial.print(max_vno);
|
||||
Serial.print("), Min: ");
|
||||
Serial.print(cell_min_v);
|
||||
Serial.print("mV (cell ");
|
||||
Serial.print(min_vno);
|
||||
Serial.print("), Imbalance: ");
|
||||
Serial.print(cell_deviation_mV);
|
||||
Serial.println("mV.");
|
||||
|
||||
print_int_with_units("High Voltage Output Pins: ", high_voltage, "V");
|
||||
Serial.print(", ");
|
||||
print_int_with_units("Low Voltage: ", low_voltage, "V");
|
||||
Serial.println("");
|
||||
print_int_with_units("Current Output: ", output_current, "A");
|
||||
Serial.println("");
|
||||
|
||||
Serial.println("Values passed to the inverter: ");
|
||||
print_SOC(" SOC: ", SOC);
|
||||
print_int_with_units(" Max discharge power: ", max_target_discharge_power, "W");
|
||||
Serial.print(", ");
|
||||
print_int_with_units(" Max charge power: ", max_target_charge_power, "W");
|
||||
Serial.println("");
|
||||
print_int_with_units(" Max temperature: ", temperature_max, "C");
|
||||
Serial.print(", ");
|
||||
print_int_with_units(" Min temperature: ", temperature_min, "C");
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame)
|
||||
{
|
||||
static int mux = 0;
|
||||
static int temp = 0;
|
||||
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x352:
|
||||
//SOC
|
||||
nominal_full_pack_energy = (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh)
|
||||
nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * 0.1; //Example 1247 * 0.1 = 124.7kWh
|
||||
expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh)
|
||||
ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * 0.1; //Example 311 * 0.1 = 31.1kWh
|
||||
energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 0.1; //Example 147 * 0.1 = 14.7kWh
|
||||
energy_buffer = (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0
|
||||
full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7);
|
||||
break;
|
||||
case 0x20A:
|
||||
//Contactor state
|
||||
packContNegativeState = (rx_frame.data.u8[0] & 0x07);
|
||||
packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3;
|
||||
contactor = (rx_frame.data.u8[1] & 0x0F);
|
||||
packContactorSetState = (rx_frame.data.u8[1] & 0x0F);
|
||||
packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3;
|
||||
pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5;
|
||||
hvil_status = (rx_frame.data.u8[5] & 0x0F);
|
||||
break;
|
||||
case 0x252:
|
||||
//Limits
|
||||
regenerative_limit = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW
|
||||
discharge_limit = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117???
|
||||
max_heat_park = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW
|
||||
hvac_max_power = (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW?
|
||||
break;
|
||||
case 0x132:
|
||||
//battery amps/volts
|
||||
volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V
|
||||
amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A)
|
||||
if (amps > 32768)
|
||||
{
|
||||
amps = - (65535 - amps);
|
||||
}
|
||||
amps = amps * 0.1;
|
||||
raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ?
|
||||
battery_charge_time_remaining = (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min
|
||||
if(battery_charge_time_remaining == 4095)
|
||||
{
|
||||
battery_charge_time_remaining = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x3D2:
|
||||
// total charge/discharge kwh
|
||||
total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.001;
|
||||
total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * 0.001;
|
||||
break;
|
||||
case 0x332:
|
||||
//min/max hist values
|
||||
mux = (rx_frame.data.u8[0] & 0x03);
|
||||
|
||||
if(mux == 1) //Cell voltages
|
||||
{
|
||||
temp = ((rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[0] >> 2));
|
||||
temp = (temp & 0xFFF);
|
||||
cell_max_v = temp*2;
|
||||
temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
temp = (temp & 0xFFF);
|
||||
cell_min_v = temp*2;
|
||||
max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage
|
||||
min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage
|
||||
}
|
||||
if(mux == 0)//Temperature sensors
|
||||
{
|
||||
temp = rx_frame.data.u8[2];
|
||||
max_temp = (temp * 0.5) - 40; //in celcius, Example 24
|
||||
|
||||
temp = rx_frame.data.u8[3];
|
||||
min_temp = (temp * 0.5) - 40; //in celcius , Example 24
|
||||
}
|
||||
break;
|
||||
case 0x2d2:
|
||||
//Min / max limits
|
||||
min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V
|
||||
max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V
|
||||
max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1?
|
||||
max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4?
|
||||
break;
|
||||
case 0x2b4:
|
||||
low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625;
|
||||
high_voltage = (((rx_frame.data.u8[2] << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484;
|
||||
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
|
||||
break;
|
||||
case 0x292:
|
||||
stillAliveCAN = 12; //We are getting CAN messages from the BMS, set the CAN detect counter
|
||||
soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
|
||||
soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2));
|
||||
soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4));
|
||||
soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_tesla_model_3_battery()
|
||||
{
|
||||
/*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then,
|
||||
to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so
|
||||
two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send
|
||||
the first, for a few cycles, then stop all messages which causes the contactor to open. */
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
//Send 30ms message
|
||||
if (currentMillis - previousMillis30 >= interval30)
|
||||
{
|
||||
previousMillis30 = currentMillis;
|
||||
|
||||
if(bms_status == ACTIVE){
|
||||
send221still = 10;
|
||||
ESP32Can.CANWriteFrame(&TESLA_221_1);
|
||||
ESP32Can.CANWriteFrame(&TESLA_221_2);
|
||||
}
|
||||
else{ //bms_status == FAULT
|
||||
if(send221still > 0){
|
||||
ESP32Can.CANWriteFrame(&TESLA_221_1);
|
||||
send221still--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint16_t convert2unsignedInt16(int16_t signed_value)
|
||||
{
|
||||
if(signed_value < 0){
|
||||
return(65535 + signed_value);
|
||||
}
|
||||
else{
|
||||
return (uint16_t)signed_value;
|
||||
}
|
||||
}
|
45
Software/src/battery/TESLA-MODEL-3-BATTERY.h
Normal file
45
Software/src/battery/TESLA-MODEL-3-BATTERY.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef TESLA_MODEL_3_BATTERY_H
|
||||
#define TESLA_MODEL_3_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#define ABSOLUTE_MAX_VOLTAGE 4030 // 403.0V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_MIN_VOLTAGE 2450 // 245.0V if battery voltage goes under this, discharging further is disabled
|
||||
|
||||
// These parameters need to be mapped for the Inverter
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t battery_voltage;
|
||||
extern uint16_t battery_current;
|
||||
extern uint16_t capacity_Wh;
|
||||
extern uint16_t remaining_capacity_Wh;
|
||||
extern uint16_t max_target_discharge_power;
|
||||
extern uint16_t max_target_charge_power;
|
||||
extern uint16_t bms_status;
|
||||
extern uint16_t bms_char_dis_status;
|
||||
extern uint16_t stat_batt_power;
|
||||
extern uint16_t temperature_min;
|
||||
extern uint16_t temperature_max;
|
||||
extern uint16_t CANerror;
|
||||
extern uint16_t cell_max_voltage;
|
||||
extern uint16_t cell_min_voltage;
|
||||
extern uint8_t LEDcolor;
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
// LED colors
|
||||
#define GREEN 0
|
||||
#define YELLOW 1
|
||||
#define RED 2
|
||||
|
||||
void update_values_tesla_model_3_battery();
|
||||
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame);
|
||||
void send_can_tesla_model_3_battery();
|
||||
uint16_t convert2unsignedInt16(int16_t signed_value);
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue