CHADEMO - Miscellaneous incremental improvements.

This commit is contained in:
Steven Maresca 2024-05-30 19:31:55 -04:00
parent bf3cada068
commit 5fb26ffa0e

View file

@ -10,6 +10,18 @@
#include "CHADEMO-BATTERY-TYPES.h"
#include "CHADEMO-BATTERY.h"
/* CHADEMO handling runs at 6.25 times the rate of most other code, so, rather than the
* default value of 12 (for 12 iterations of the 5s value update loop) * 5 for a 60s timeout,
* instead use 75 for 75*0.8s = 60s
*/
#undef CAN_STILL_ALIVE
#define CAN_STILL_ALIVE 75
//#define CH_CAN_DEBUG
static unsigned long setupMillis = 0;
static unsigned long handlerBeforeMillis = 0;
static unsigned long handlerAfterMillis = 0;
/* 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 unsigned long previousMillis5000 =
@ -17,6 +29,7 @@ static unsigned long previousMillis5000 =
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
bool plug_inserted = false;
bool vehicle_can_initialized = false;
bool vehicle_can_received = false;
bool vehicle_permission = false;
bool evse_permission = false;
@ -24,8 +37,8 @@ bool evse_permission = false;
bool precharge_low = false;
bool positive_high = false;
bool contactors_ready = false;
uint8_t maximum_soc = 90;
uint8_t minimum_soc = 10;
uint8_t framecount = 0;
uint8_t max_discharge_current = 0; //TODO not sure on this one, but really influenced by inverter capability
@ -121,7 +134,7 @@ void update_values_battery() {
datalayer.battery.status.max_discharge_power_W =
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
datalayer.battery.status.voltage_dV = x102_chg_session.TargetBatteryVoltage; //TODO: scaling?
datalayer.battery.status.voltage_dV = sensor.Voltage * 10;
datalayer.battery.info.total_capacity_Wh =
((x101_chg_est.RatedBatteryCapacity / 0.11) *
@ -141,58 +154,17 @@ void update_values_battery() {
* CAN frames.
*/
#ifdef DEBUG_VIA_USB
Serial.print("SOC 0x100: ");
Serial.println(x100_chg_lim.ConstantOfChargingRateIndication);
#ifdef ISA_SENSOR
Serial.print("Volts: ");
Serial.println(sensor.Voltage1);
Serial.println(sensor.Voltage2);
Serial.println(sensor.Voltage3);
Serial.print("Amps: ");
Serial.println(sensor.Amperes);
Serial.print("Power: ");
Serial.println(sensor.KW);
Serial.print("Ah: ");
Serial.println(sensor.AH);
Serial.print("kWh: ");
Serial.print(sensor.KWH);
Serial.print("Temp C: ");
Serial.println(sensor.Temperature);
#endif
#endif
if (vehicle_can_received) {
uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
}
}
}
//TODO see Table A.26—Charge control termination command pattern on pg58
//TODO simplified start/stop helper functions
//see IEEE Table A.26—Charge control termination command pattern on pg58
//for stop conditions
void evse_stop() {
/*
/// Sets 109.5.5 high
x109_evse_state.status_charger_stop_control = true;
x109_evse_state.output_voltage = 0.0;
x109_evse_state.output_current = 0;
x109_evse_state.remaining_charging_time_10s_bit = 0;
x109_evse_state.remaining_charging_time_1min_bit = 0;
x109_evse_state.status.fault_battery_incompatibility = false;
x109_evse_state.status.fault_charging_system_malfunction = false;
x109_evse_state.status.fault_station_malfunction = false;
*/
}
void evse_charge_start() {
/*
x109_evse_state.status_charger_stop_control = false;
x109_evse_state.status_station = true;
x109_evse_state.remaining_charging_time_10s_bit = 255;
x109_evse_state.remaining_charging_time_1min_bit = 60;
*/
}
inline void process_vehicle_charging_minimums(CAN_frame_t rx_frame) {
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
@ -209,11 +181,14 @@ inline void process_vehicle_charging_maximums(CAN_frame_t rx_frame) {
}
inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
vehicle_can_initialized = true;
vehicle_permission = digitalRead(CHADEMO_PIN_4);
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
@ -232,14 +207,21 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
//NOTE: behavior differs in the case of high current control (x110 support TBD)
// In that mode, ChargingCurrentRequest is set to 0xFF when >= 1 A is specified in
// in “request charging current (for extended) (x110.1, x110.2),” and then afterward in x102,
// it will not be updated
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
#ifdef DEBUG_VIA_USB
//Note on p131
uint8_t chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
Serial.print("Charge Rate (kW):");
Serial.println(chargingrate);
uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
Serial.print("Charge Rate (kW):");
Serial.println(chargingrate);
}
#endif
//Table A.26—Charge control termination command patterns -- should echo x108 handling
@ -259,14 +241,6 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
return;
}
if (!vehicle_permission || !x102_chg_session.s.status.StatusVehicleChargingEnabled) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates dis/charging should cease");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery over voltage.");
@ -305,16 +279,10 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
return;
}
if (x102_chg_session.StateOfCharge < minimum_soc) {
CHADEMO_Status = CHADEMO_STOP;
return;
}
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE &&
(x102_chg_session.StateOfCharge >= maximum_soc || !vehicle_permission)) {
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("State of charge ceiling reached, stop charging");
Serial.println("State of charge ceiling reached or charging interrupted, stop charging");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
@ -355,7 +323,6 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
/* x200 Vehicle, peer to x208 EVSE */
inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
unsigned long currentMillis = millis();
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
@ -363,15 +330,18 @@ inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
#ifdef DEBUG_VIA_USB
/* unsigned long currentMillis = millis();
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
Serial.println("x200 Max remaining capacity for charging/discharging:");
Serial.println(x200_discharge_limits.MaxRemainingCapacityForCharging);
// initially this is set to 0, which is represented as 0xFF
Serial.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
}
*/
#endif
#ifdef ISA_SHUNT
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage) {
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
@ -416,11 +386,7 @@ inline void process_vehicle_vendor_ID(CAN_frame_t rx_frame) {
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
}
//#define CH_CAN_DEBUG
void receive_can_battery(CAN_frame_t rx_frame) {
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //We are getting CAN messages from the vehicle, inform the watchdog
#ifdef CH_CAN_DEBUG
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
@ -435,17 +401,20 @@ void receive_can_battery(CAN_frame_t rx_frame) {
Serial.println("");
#endif
/* e.g., CHADEMO_INIT state is a transient, used to indicate when CAN
* has not yet been receied from a vehicle
*/
if (CHADEMO_Status == CHADEMO_INIT) {
// First CAN messages received, entering into negotiation
CHADEMO_Status = CHADEMO_NEGOTIATE;
// TODO consider tracking delta since transition time for expiry
// CHADEMO coexists with a CAN-based shunt. Only process CHADEMO-specific IDs
// 202 is unknown
if (!((rx_frame.MsgID >= 0x100 && rx_frame.MsgID <= 0x202) || rx_frame.MsgID == 0x700)) {
return;
}
// used for testing vehicle sanity
vehicle_can_received = true;
/* CHADEMO_INIT state is a transient, used to indicate when CAN
* has not yet been receied from a vehicle
*/
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //We are getting CAN messages from the vehicle, inform the watchdog
switch (rx_frame.MsgID) {
case 0x100:
@ -455,7 +424,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
process_vehicle_charging_maximums(rx_frame);
break;
case 0x102:
framecount++;
//the first few frames start as 0x03, then like 20 of 0x01
if (vehicle_can_initialized && framecount < 20) {
return;
}
process_vehicle_charging_session(rx_frame);
/* counter to help discard inital frames with bad SOC data */
break;
case 0x200: //For V2X
process_vehicle_charging_limits(rx_frame);
@ -469,10 +444,17 @@ void receive_can_battery(CAN_frame_t rx_frame) {
case 0x700:
process_vehicle_vendor_ID(rx_frame);
break;
case 0x202: // unknown. LEAF specific?
default:
break;
}
if (CHADEMO_Status == CHADEMO_INIT) {
// First CAN messages received, entering into negotiation
// TODO consider tracking delta since transition time for expiry
CHADEMO_Status = CHADEMO_NEGOTIATE;
}
handle_chademo_sequence();
}
@ -511,7 +493,8 @@ void update_evse_capabilities(CAN_frame_t& f) {
*/
x108_evse_cap.contactor_weld_detection = 0x1;
x108_evse_cap.available_output_voltage = x102_chg_session.TargetBatteryVoltage;
/* should this be set to MAX_EVSE_OUTPUT_VOLTAGE or x102_chg_session.TargetBatteryVoltage ? */
x108_evse_cap.available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE;
/* calculate max threshold to protect battery - using vehicle-provided max minus 2% */
x108_evse_cap.threshold_voltage =
@ -579,7 +562,8 @@ void update_evse_status(CAN_frame_t& f) {
*/
/* if power overcommitted, back down to just below while maintaining voltage target */
if (x109_evse_state.setpoint_HV_IDC * x109_evse_state.setpoint_HV_VDC > MAX_EVSE_POWER_CHARGING) {
if (x109_evse_state.setpoint_HV_VDC > 0 &&
x109_evse_state.setpoint_HV_IDC * x109_evse_state.setpoint_HV_VDC > MAX_EVSE_POWER_CHARGING) {
x109_evse_state.setpoint_HV_IDC = floor(MAX_EVSE_POWER_CHARGING / x109_evse_state.setpoint_HV_VDC);
}
@ -593,8 +577,7 @@ void update_evse_status(CAN_frame_t& f) {
*/
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
///Battery incompatibility” flag #109.5.3 to 1
//Toggl battery incompatibility flag 109.5.3
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.battery_incompatible = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -697,14 +680,9 @@ void send_can_battery() {
unsigned long currentMillis = millis();
handlerBeforeMillis = currentMillis;
handle_chademo_sequence();
/* no EVSE messages should be sent until the vehicle has
* initiated
*/
if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
return;
}
handlerAfterMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -714,6 +692,14 @@ void send_can_battery() {
}
previousMillis100 = currentMillis;
/* no EVSE messages should be sent until the vehicle has
* initiated
*/
// if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
if (CHADEMO_Status <= CHADEMO_INIT) {
return;
}
update_evse_capabilities(CHADEMO_108);
update_evse_status(CHADEMO_109);
update_evse_discharge_capabilities(CHADEMO_208);
@ -776,6 +762,7 @@ void send_can_battery() {
*/
void handle_chademo_sequence() {
unsigned long currentMillis = millis();
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
contactors_ready = precharge_low && positive_high;
@ -822,6 +809,10 @@ void handle_chademo_sequence() {
break;
case CHADEMO_CONNECTED:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
//Serial.println("CHADEMO_CONNECTED State");
#endif
/* plug_inserted is .. essentially a volatile of sorts, so verify */
if (plug_inserted) {
/* If connection is detectable, jumpstart handshake by
@ -850,7 +841,7 @@ void handle_chademo_sequence() {
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
*/
#ifdef DEBUG_VIA_USB
Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
// Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
#endif
evse_init();
break;
@ -858,16 +849,17 @@ void handle_chademo_sequence() {
/* Vehicle and EVSE dance */
//TODO if pin 4 / j goes high,
Serial.print("State of charge: ");
Serial.println(x102_chg_session.StateOfCharge);
Serial.print("Parked?: ");
Serial.println(x102_chg_session.s.status.StatusVehicleShifterPosition);
Serial.print("Target Battery Voltage: ");
Serial.println(x102_chg_session.TargetBatteryVoltage);
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
// Serial.println("CHADEMO_NEGOTIATE State");
#endif
x109_evse_state.s.status.ChgDischStopControl = 1;
break;
case CHADEMO_EV_ALLOWED:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_EV_ALLOWED State");
#endif
// If we are in this state, vehicle_permission was already set to true...but re-verify
// that pin 4 (j) reads high
if (vehicle_permission) {
@ -882,6 +874,10 @@ void handle_chademo_sequence() {
}
break;
case CHADEMO_EVSE_PREPARE:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_PREPARE State");
#endif
/* TODO voltage check of output < 20v
* insulation test hypothetically happens here before triggering PIN 10 high
* see Table A.28Requirements for the insulation test for output DC circuit
@ -901,7 +897,7 @@ void handle_chademo_sequence() {
digitalWrite(CHADEMO_PIN_10, HIGH);
evse_permission = true;
} else {
Serial.print("Insulation check measures > 20v ");
Serial.println("Insulation check measures > 20v ");
}
// likely unnecessary but just to be sure. consider removal
@ -914,21 +910,38 @@ void handle_chademo_sequence() {
//state changes to CHADEMO_EVSE_START only upon receipt of charging session request
break;
case CHADEMO_EVSE_START:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_START State");
#endif
datalayer.system.status.battery_allows_contactor_closing = true;
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
#ifdef DEBUG_VIA_USB
Serial.println("Initiating contactors");
#endif
/* break rather than fall through because contactors are not instantaneous;
* worth giving it a cycle to finish
*/
break;
case CHADEMO_EVSE_CONTACTORS_ENABLED:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_CONTACTORS State");
#endif
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
if (contactors_ready) {
#ifdef DEBUG_VIA_USB
Serial.println("Contactors ready");
Serial.print("Voltage: ");
Serial.println(sensor.Voltage);
#endif
/* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
(EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) {
@ -947,6 +960,10 @@ void handle_chademo_sequence() {
/* break or fall through ? TODO */
break;
case CHADEMO_POWERFLOW:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_POWERFLOW State");
#endif
/* POWERFLOW for charging, discharging, and bidirectional */
/* Interpretation */
if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
@ -974,6 +991,10 @@ void handle_chademo_sequence() {
x109_evse_state.s.status.EVSE_status = 1;
break;
case CHADEMO_STOP:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_STOP State");
#endif
/* back to CHADEMO_IDLE after teardown */
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
@ -998,6 +1019,10 @@ void handle_chademo_sequence() {
break;
case CHADEMO_FAULT:
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
Serial.println("CHADEMO_FAULT State");
#endif
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.ChgDischError = 1;
@ -1034,6 +1059,19 @@ void setup_battery(void) { // Performs one time setup at startup
/* disallow contactors until permissions is granted by vehicle */
datalayer.system.status.battery_allows_contactor_closing = false;
/* Pretend that we know the SOH, assert that it is 99% */
datalayer.battery.status.soh_pptt = 9900;
/* Briefly assert that we're starting at a modest SOC of 30% */
datalayer.battery.status.real_soc = 300;
//TODO Must be user configured, most likely. Artificially capped for the time being
datalayer.battery.status.max_discharge_power_W = 1000;
datalayer.battery.status.max_charge_power_W = 1000;
datalayer.battery.status.current_dA = 0;
datalayer.battery.status.remaining_capacity_Wh = 12000;
//TODO this is probably fine for a baseline, though CHADEMO can go as low as 150v and as high as 1500v in the latest revision
//the below is relative to a 96 cell NMC. lower end is possibly too low
datalayer.battery.info.max_design_voltage_dV =
@ -1056,5 +1094,7 @@ void setup_battery(void) { // Performs one time setup at startup
x109_evse_state.s.status.ChgDischStopControl = 1;
handle_chademo_sequence();
setupMillis = millis();
}
#endif