Merge pull request #368 from obbardc/wip/obbardc/ipace

Initial iPace battery support
This commit is contained in:
Daniel Öster 2024-07-07 21:43:19 +03:00 committed by GitHub
commit 32e86ea00c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 451 additions and 0 deletions

View file

@ -12,6 +12,7 @@
//#define BYD_ATTO_3_BATTERY
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
//#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY
//#define KIA_HYUNDAI_64_BATTERY
//#define KIA_E_GMP_BATTERY
//#define KIA_HYUNDAI_HYBRID_BATTERY

View file

@ -19,6 +19,10 @@
#include "IMIEV-CZERO-ION-BATTERY.h"
#endif
#ifdef JAGUAR_IPACE_BATTERY
#include "JAGUAR-IPACE-BATTERY.h"
#endif
#ifdef KIA_E_GMP_BATTERY
#include "KIA-E-GMP-BATTERY.h"
#endif

View file

@ -0,0 +1,436 @@
#include "../include.h"
#ifdef JAGUAR_IPACE_BATTERY
#include "../datalayer/datalayer.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "TEST-FAKE-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
/*
You mentioned earlier that the modules use LG 3.7V 60AH HW001 cells? Is this still the current understanding?
Reason I ask is that I'm trying to piece together the technical spec for these module cells (in absence of any known datasheet)
Do these specs listed below here match up with what you have found?
Nominal Voltage: 3.7V although I have also see 3.65 and 3.6 listed (Jaguar now state in the owners manual that it is a 388V (nominal) pack therefore with 108S pack that gives nominal 3.6V per cell)
Max charge current: 60A
Nominal capacity: 60000mah
Net weight: 850g
Internal resistance: 1m(ohm)
Discharge current (continuous) 120(2C)
Plus discharge current: 300(5C)
Cutoff charge voltage: 4.2V
Cut-off discharge voltage: 2.75V
That would all be true. But i would not go to the edges of SOC. I use cell from 3.0V to 4.08V and that is it. Everything else i can confirm.
* The configuration of the BMS is one master and six slave modules, and communication between them is CAN-based.
* Each slave module has two sections (seen in the pictures above). Each is based on TI 76PL455ATQ (cell monitoring) and NXP processor for communication. So there are 12 CAN nodes, each monitoring 9S cells (3 blocks x 3S)
https://www.jaguarforums.com/forum/general-tech-help-7/canbus-hacking-ipace-242124/
https://www.i-paceforum.com/threads/parts-numbers-descriptions-and-names.6839/
https://docs.google.com/spreadsheets/d/1wNMtpPqMAejNeOZGsPCcgau8HODROzceFcUSfk2lVz8/edit?gid=445693275#gid=445693275
Now, with the latest sample data I collected this morning (ext. temp is -14c) I am 95% sure that the battery temperature can be read on the CANbus on ECU ID 7E4[7EC] (BECM), and the PID for the battery temperature are 0x492B, 0x492C, 0x492D, 0x492E, 0x492F, 0x4930. These are the 6 values for the 6 plates in the battery. The EV battery coolant outlet temperature sensor is on 7E4[7EC]:0x491B, and the EV battery coolant inlet temperature sensor is on 7E4[7EC]:0x491C. The formula for those temperature is (value-40), and the result is in deg celcius. The external ambient temperature can be read on the HVAC module 733[73B]:0x9924, and the formula is (value*0.5 -40). If you have a way to read the High Speed CANbus (pin 6/14 on the diagnostic connector), ie. TorquePro, you can read it yourself.
On the BECM module, PID 0x4910, 0x4911, 0x4914 seems to be the real battery % (divide the value by 100). At 100% of battery , this value is around 9600 (9600/100 = 96%), and with a linear regression, I can extrapolate that the value would be around 3% with the dash says 0% of battery. SO I can see a real 3% when the dash says 0%, and a real 96% at 100%.
PID 0x4913 on BECM seems to be the max regen:
- 2 bytes
- when battery is fully charged => 0
- when battery SOC =93%, 950
- when battery SOC =82, 4600
- when battery SOC =60, 10600
- when battery SOC =8, 15000
So if you take that value and divide by 100, this gives 150kw at 8% of SOC, 106kw at 60%, etc...
PID 0x498f on BCCM seems to be the Voltage of the charger plugged in the car
Unplug: 0
plug on 240v:
- 76 7a
- 77 32,
- 79 18,
- 76 8d,
Plug on 120v:
- 38 2a
If you take that (value / 128), you have something close to 240 or close to 110.
In the BECM module, PID 0x4901, 0x4909, 0x490e ,0x490f are all within the same value range (36000-44000). If we divide them by 100, that could be a battery voltage.
Since the battery pack is organized with 9 cells in serie, and 4 groups of 9 cells in parallel, I am expecting to have 4 values for these 4 groups in the range of 36000-44000. That's what we have.
Now I am looking for the Amp (something around 58Ah per cell, or 232Ah per row
PID 0x490A on BECM is interesting. it is a one 1 Byte. On my sample data, the min is 58 and max is 146. If I apply the formula (value/2 - 40), just like the external temperature sensor , this PID gives a value close to the external temperature when the car stand still in the driveway, without charging, this value goes up a bit when the car is charging on the 240v charger, but significantly higher (+33c) when I did a fast charge yesterday, even if the external temp was -5c. Could be an internal temp sensor on an electronic module.
This is the status so far for the BECM
PID Description Formula Unit
4886
4887
48c2
4900
4901 Voltage for battery row#1 (9 modules) (256A+B)/100 Volt
4902
4903 Max voltage of the pouch cells (256A+B)/1000 Volt
4904 Min voltage of the pouch cells (256A+B)/1000 Volt
4905 Maybe a temperature of a componant ?? (A*0.5)-40
4906 Maybe a temperature of a componant ?? (A*0.5)-41
4907 Maybe a temperature of a componant ?? (A*0.5)-42
4908 Maybe a temperature of a componant ?? (A*0.5)-43
4909 Voltage for battery row#2 (9 modules) (256A+B)/100 Volt
490a Maybe a temperature of a componant ?? (A*0.5)-43
490b
490c Battery current in and out ((256A+B)-0x8000)/24 or 25 Amp
490d
490e Voltage for battery row#3 (9 modules) (256A+B)/100 Volt
490f Voltage for battery row#4 (9 modules) (256A+B)/100 Volt
4910 Average Battery SOC (256A+B)/100 %
4911 Min Battery SOC (256A+B)/100 %
4912 ??? Battery current ??? (256A+B)/160 Amp
4913 Max Regen (256A+B)/100 Kw
4914 Max Battery SOC (256A+B)/100 %
4915
4916
4917
4918
4919
491a
491b EV battery coolant outlet temperature A-40 DegC
491c EV battery coolant inlet temperature A-40 DegC
491d
491e set of bit / flag
491f
4920 maybe some voltage 256A+B Volt
4921 maybe some voltage 256A+B Volt
4923 set of bit / flag
492b Battery CSC #1 temperature A-40 DegC
492c Battery CSC #2 temperature A-40 DegC
492d Battery CSC #3 temperature A-40 DegC
492e Battery CSC #4 temperature A-40 DegC
492f Battery CSC #5 temperature A-40 DegC
4930 Battery CSC #6 temperature A-40 DegC
4931
4933
4934
4935
4936
4937
4938
4939
4941
4944 set of bit / flag
4945 set of bit / flag
494e set of bit / flag
4970
4971
497a maybe some voltage 256A+B Volt
497b maybe some voltage 256A+B Volt
497c maybe some voltage 256A+B Volt
497e
497f
4980
4981 maybe some voltage 256A+B Volt
498c
d015
d018
d019
d020
d021
d05b
d100
d10e set of bit / flag
d14d
d703
dd00 Elaspe time since factory built (16777216*A+65536*B+256*C+D)/10 Second
dd01 Odometer 65536*A+256*B+C KM
dd02
dd04
dd05
dd06
dd08
dd09
*/
/* TODO: Actually use a proper keepalive message */
CAN_frame_t ipace_keep_alive = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x063,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame_t ipace_7e4 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x7e4,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void print_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
}
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
datalayer.battery.status.real_soc = 5000; // 50.00%
datalayer.battery.status.soh_pptt = 9900; // 99.00%
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
datalayer.battery.status.current_dA = 0; // 0 A
datalayer.battery.info.total_capacity_Wh = 30000; // 30kWh
datalayer.battery.status.remaining_capacity_Wh = 15000; // 15kWh
datalayer.battery.status.cell_max_voltage_mV = 3596;
datalayer.battery.status.cell_min_voltage_mV = 3500;
datalayer.battery.status.active_power_W = 0; // 0W
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C
datalayer.battery.status.max_discharge_power_W = 5000; // 5kW
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
}
//Fake that we get CAN messages
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
print_units(", Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W ");
print_units(", Max charge power: ", datalayer.battery.status.max_charge_power_W, "W ");
print_units(", Max temp: ", (datalayer.battery.status.temperature_max_dC * 0.1), "°C ");
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
Serial.println("");
#endif
}
void receive_can_battery(CAN_frame_t rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// Do not log noisy startup messages - there are many !
if (rx_frame.MsgID == 0 && rx_frame.FIR.B.DLC == 8 &&
rx_frame.data.u8[0] == 0 &&
rx_frame.data.u8[1] == 0 &&
rx_frame.data.u8[2] == 0 &&
rx_frame.data.u8[3] == 0 &&
rx_frame.data.u8[4] == 0 &&
rx_frame.data.u8[5] == 0 &&
rx_frame.data.u8[6] == 0x80 &&
rx_frame.data.u8[7] == 0) {
return;
}
// Discard non-interesting can messages
if (rx_frame.MsgID < 0x500) {
return;
}
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.MsgID, HEX);
Serial.print(" ");
Serial.print(rx_frame.FIR.B.DLC);
Serial.print(" ");
for (int i = 0; i < rx_frame.FIR.B.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println("");
/*
Startup messages from battery ...
*/
}
int state = 0;
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
//ESP32Can.CANWriteFrame(&ipace_keep_alive);
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
CAN_frame_t msg;
int err;
switch (state) {
case 0:
// car response: 7ec 07 59 02 8f c0 64 88 28
// response: 7EC 07 59 02 8F F0 01 00 28
// response: 7EC 03 59 02 8F 00 00 00 00
// 7EC 8 3 7F 19 11 0 0 0 0
msg = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x7e4,
.data = {0x03, 0x19, 0x02, 0x8f, 0x00, 0x00, 0x00, 0x00}};
err = ESP32Can.CANWriteFrame(&msg);
if (err == 0)
state++;
break;
case 1:
// car response: 7EC 11 fa 59 04 c0 64 88 28
// response:
msg = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x7e4,
.data = {0x06, 0x19, 0x04, 0xc0, 0x64, 0x88, 0xff, 0x00}};
err = ESP32Can.CANWriteFrame(&msg);
if (err == 0)
state++;
break;
case 2:
/* reset */
state = 0;
break;
default:
break;
}
// TODO -1 is an error !!
Serial.print("sending 7e4 err:");
Serial.println(err);
Serial.print("sending 7e4_2 err:");
Serial.println(err);
/*
on car this is ... 10x messages of 0x522 01 12 0 3f 0 0 0 0
9x total
77605 522 8 22 12 0 0 0 0 0 0
77914 522 8 22 1 0 0 0 0 0 0
78014 522 8 22 12 0 0 0 0 0 0
78324 522 8 22 1 0 0 0 0 0 0
78424 522 8 22 12 0 0 0 0 0 0
78734 522 8 22 1 0 0 0 0 0 0
78834 522 8 22 12 0 0 0 0 0 0
79144 522 8 22 1 0 0 0 0 0 0
79247 522 8 22 12 0 0 0 0 0 0
79554 522 8 22 1 0 0 0 0 0 0
*/
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Jaguar iPace TODOkWh battery selected"); // TODO add kWh
#endif
// TODO check values
datalayer.battery.info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled
}
#endif

View file

@ -0,0 +1,10 @@
#ifndef JAGUAR_IPACE_BATTERY_H
#define JAGUAR_IPACE_BATTERY_H
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999 // TODO is this ok ?
void setup_battery(void);
#endif