mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-06 03:50:13 +02:00
Merge branch 'main' into feature/modbus-write-event
This commit is contained in:
commit
46267445c0
58 changed files with 2088 additions and 370 deletions
|
@ -22,6 +22,7 @@
|
|||
#include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h"
|
||||
#include "src/lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "src/lib/smaresca-SimpleISA/SimpleISA.h"
|
||||
|
||||
#include "src/datalayer/datalayer.h"
|
||||
|
||||
|
@ -62,6 +63,10 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
|
|||
ModbusServerRTU MBserver(Serial2, 2000);
|
||||
#endif
|
||||
|
||||
#ifdef ISA_SHUNT
|
||||
ISA sensor;
|
||||
#endif
|
||||
|
||||
// Common charger parameters
|
||||
volatile float charger_setpoint_HV_VDC = 0.0f;
|
||||
volatile float charger_setpoint_HV_IDC = 0.0f;
|
||||
|
@ -92,6 +97,9 @@ enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLET
|
|||
State contactorStatus = DISCONNECTED;
|
||||
|
||||
#define MAX_ALLOWED_FAULT_TICKS 1000
|
||||
/* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system.
|
||||
* t=3RC at minimum, t=5RC ideally
|
||||
*/
|
||||
#define PRECHARGE_TIME_MS 160
|
||||
#define NEGATIVE_CONTACTOR_TIME_MS 1000
|
||||
#define POSITIVE_CONTACTOR_TIME_MS 2000
|
||||
|
@ -226,6 +234,7 @@ void core_loop(void* task_time_us) {
|
|||
if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 5s normally
|
||||
{
|
||||
previousMillisUpdateVal = millis();
|
||||
update_machineryprotection();
|
||||
update_SOC(); // Check if real or calculated SOC% value should be sent
|
||||
update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
|
||||
if (DUMMY_EVENT_ENABLED) {
|
||||
|
@ -336,9 +345,11 @@ void init_stored_settings() {
|
|||
}
|
||||
|
||||
void init_CAN() {
|
||||
// CAN pins
|
||||
// CAN pins
|
||||
#ifdef CAN_SE_PIN
|
||||
pinMode(CAN_SE_PIN, OUTPUT);
|
||||
digitalWrite(CAN_SE_PIN, LOW);
|
||||
#endif
|
||||
CAN_cfg.speed = CAN_SPEED_500KBPS;
|
||||
CAN_cfg.tx_pin_id = GPIO_NUM_27;
|
||||
CAN_cfg.rx_pin_id = GPIO_NUM_26;
|
||||
|
@ -366,6 +377,7 @@ void init_CAN() {
|
|||
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
|
||||
settings.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
|
||||
const uint32_t errorCode = canfd.begin(settings, [] { canfd.isr(); });
|
||||
canfd.poll();
|
||||
if (errorCode == 0) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Bit Rate prescaler: ");
|
||||
|
@ -413,16 +425,27 @@ void init_contactors() {
|
|||
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||
digitalWrite(PRECHARGE_PIN, LOW);
|
||||
#endif
|
||||
// Init BMS contactor
|
||||
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can aslo handle this BMS contactor
|
||||
pinMode(BMS_POWER, OUTPUT);
|
||||
digitalWrite(BMS_POWER, HIGH);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_rs485() {
|
||||
// Set up Modbus RTU Server
|
||||
// Set up Modbus RTU Server
|
||||
#ifdef RS485_EN_PIN
|
||||
pinMode(RS485_EN_PIN, OUTPUT);
|
||||
digitalWrite(RS485_EN_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef RS485_SE_PIN
|
||||
pinMode(RS485_SE_PIN, OUTPUT);
|
||||
digitalWrite(RS485_SE_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef PIN_5V_EN
|
||||
pinMode(PIN_5V_EN, OUTPUT);
|
||||
digitalWrite(PIN_5V_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
#ifdef BYD_MODBUS
|
||||
|
@ -453,42 +476,16 @@ void init_inverter() {
|
|||
void init_battery() {
|
||||
// Inform user what battery is used and perform setup
|
||||
setup_battery();
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
intervalUpdateValues = 800; // This mode requires the values to be updated faster
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CAN_FD
|
||||
// Functions
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
void print_canfd_frame(CANFDMessage rx_frame) {
|
||||
// Frame ID-s that battery transmits. For debugging and development.
|
||||
// switch (frame.id)
|
||||
// {
|
||||
// case 0x7EC:
|
||||
// case 0x360:
|
||||
// case 0x3BA:
|
||||
// case 0x325:
|
||||
// case 0x330:
|
||||
// case 0x215:
|
||||
// case 0x235:
|
||||
// case 0x2FA:
|
||||
// case 0x21A:
|
||||
// case 0x275:
|
||||
// case 0x150:
|
||||
// case 0x1F5:
|
||||
// case 0x335:
|
||||
// case 0x25A:
|
||||
// case 0x365:
|
||||
// case 0x055:
|
||||
// case 0x245:
|
||||
// case 0x3F5:
|
||||
// // case 0x:
|
||||
// // case 0x:
|
||||
// // case 0x:
|
||||
// // Dont print known frames
|
||||
// return;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
int i = 0;
|
||||
Serial.print(rx_frame.id, HEX);
|
||||
Serial.print(" ");
|
||||
|
@ -499,7 +496,6 @@ void print_canfd_frame(CANFDMessage rx_frame) {
|
|||
}
|
||||
Serial.println(" ");
|
||||
}
|
||||
|
||||
#endif
|
||||
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
|
||||
CANFDMessage frame;
|
||||
|
@ -517,6 +513,11 @@ void receive_can() { // This section checks if we have a complete CAN message i
|
|||
// Depending on which battery/inverter is selected, we forward this to their respective CAN routines
|
||||
CAN_frame_t rx_frame;
|
||||
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 0) == pdTRUE) {
|
||||
|
||||
//ISA Shunt
|
||||
#ifdef ISA_SHUNT
|
||||
sensor.handleFrame(&rx_frame);
|
||||
#endif
|
||||
// Battery
|
||||
#ifndef SERIAL_LINK_RECEIVER // Only needs to see inverter
|
||||
receive_can_battery(rx_frame);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
/* Select battery used */
|
||||
//#define BMW_I3_BATTERY
|
||||
//#define CHADEMO_BATTERY
|
||||
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
|
||||
//#define IMIEV_CZERO_ION_BATTERY
|
||||
//#define KIA_HYUNDAI_64_BATTERY
|
||||
//#define KIA_E_GMP_BATTERY
|
||||
|
@ -32,6 +32,10 @@
|
|||
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
||||
//#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
|
||||
|
||||
/* Select hardware used for Battery-Emulator */
|
||||
#define HW_LILYGO
|
||||
//#define HW_STARK
|
||||
|
||||
/* Other options */
|
||||
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
|
||||
//#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production)
|
||||
|
@ -45,6 +49,7 @@
|
|||
#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings.
|
||||
//#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides any battery settings set in USER_SETTINGS.cpp)
|
||||
//#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production)
|
||||
//#define ISA_SHUNT //Enable this line to build support for ISA IVT shunts
|
||||
|
||||
/* MQTT options */
|
||||
// #define MQTT // Enable this line to enable MQTT
|
||||
|
|
|
@ -15,8 +15,6 @@ static unsigned long previousMillis640 = 0; // will store last time a 600ms C
|
|||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
|
||||
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
|
||||
|
@ -357,7 +355,6 @@ static uint16_t battery_soc = 0;
|
|||
static uint16_t battery_soc_hvmax = 0;
|
||||
static uint16_t battery_soc_hvmin = 0;
|
||||
static uint16_t battery_capacity_cah = 0;
|
||||
static uint16_t battery_cell_deviation_mV = 0;
|
||||
static int16_t battery_temperature_HV = 0;
|
||||
static int16_t battery_temperature_heat_exchanger = 0;
|
||||
static int16_t battery_temperature_max = 0;
|
||||
|
@ -454,27 +451,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
if (datalayer.battery.status.cell_voltages_mV[0] > 0 && datalayer.battery.status.cell_voltages_mV[2] > 0) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = datalayer.battery.status.cell_voltages_mV[0];
|
||||
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[2];
|
||||
|
||||
battery_cell_deviation_mV =
|
||||
(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
} else {
|
||||
battery_cell_deviation_mV = 0;
|
||||
}
|
||||
|
||||
if (battery_info_available) {
|
||||
// Start checking safeties. First up, cellvoltages!
|
||||
if (battery_cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
uint8_t report_value = 0;
|
||||
if (battery_cell_deviation_mV <= 20 * 254) {
|
||||
report_value = battery_cell_deviation_mV / 20;
|
||||
} else {
|
||||
report_value = 255;
|
||||
}
|
||||
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, report_value);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
if (detectedBattery == BATTERY_60AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
|
@ -505,18 +485,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
// Check if we have encountered any malformed CAN messages
|
||||
if (CANerror > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
}
|
||||
|
||||
// Perform other safety checks
|
||||
if (battery_status_error_locking == 2) { // HVIL seated?
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
|
@ -559,7 +527,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery_awake = true;
|
||||
CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
|
||||
|
@ -596,7 +565,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
battery_awake = true;
|
||||
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break;
|
||||
}
|
||||
battery_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
|
|
234
Software/src/battery/CHADEMO-BATTERY-TYPES.h
Normal file
234
Software/src/battery/CHADEMO-BATTERY-TYPES.h
Normal file
|
@ -0,0 +1,234 @@
|
|||
#ifndef CHADEMO_BATTERY_TYPES_H
|
||||
#define CHADEMO_BATTERY_TYPES_H
|
||||
|
||||
#define MAX_EVSE_POWER_CHARGING 3300
|
||||
#define MAX_EVSE_OUTPUT_VOLTAGE 410
|
||||
#define MAX_EVSE_OUTPUT_CURRENT 11
|
||||
|
||||
enum CHADEMO_STATE {
|
||||
CHADEMO_FAULT,
|
||||
CHADEMO_STOP,
|
||||
CHADEMO_IDLE,
|
||||
CHADEMO_CONNECTED,
|
||||
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
|
||||
CHADEMO_NEGOTIATE,
|
||||
CHADEMO_EV_ALLOWED,
|
||||
CHADEMO_EVSE_PREPARE,
|
||||
CHADEMO_EVSE_START,
|
||||
CHADEMO_EVSE_CONTACTORS_ENABLED,
|
||||
CHADEMO_POWERFLOW,
|
||||
};
|
||||
|
||||
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
|
||||
|
||||
/* Charge/discharge sequence, indicating applicable V2H guideline
|
||||
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
|
||||
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
|
||||
* Use CHADEMO_seq to decide whether emitting 209 is necessary
|
||||
* 0x0 1.0 and earlier
|
||||
* 0x1 2.0 appendix A
|
||||
* 0x2 2.0 appendix B
|
||||
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
|
||||
* Unused for now.
|
||||
uint8_t CHADEMO_seq = 0x0;
|
||||
*/
|
||||
|
||||
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
|
||||
//H100 - Vehicle - Minimum charging expectations
|
||||
//TODO decide whether default values for vehicle-origin frames is even appropriate
|
||||
struct x100_Vehicle_Charging_Limits {
|
||||
uint8_t MinimumChargeCurrent = 0;
|
||||
uint16_t MinimumBatteryVoltage = 300;
|
||||
uint16_t MaximumBatteryVoltage = 402;
|
||||
uint8_t ConstantOfChargingRateIndication = 0;
|
||||
};
|
||||
//H101 - Vehicle - Maximum charging expectations
|
||||
struct x101_Vehicle_Charging_Estimate {
|
||||
uint8_t MaxChargingTime10sBit = 0;
|
||||
uint8_t MaxChargingTime1minBit = 0;
|
||||
uint8_t EstimatedChargingTime = 0;
|
||||
uint16_t RatedBatteryCapacity = 0;
|
||||
};
|
||||
|
||||
//H102 - Vehicle - Charging targets and Status
|
||||
// peer to x109 from EVSE
|
||||
// termination triggers in both
|
||||
// TODO see also Table A.26—Charge control termination command patterns
|
||||
struct x102_Vehicle_Charging_Session { //Frame byte
|
||||
uint8_t ControlProtocolNumberEV = 0; // 0
|
||||
uint16_t TargetBatteryVoltage = 0; // 1-2
|
||||
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
|
||||
|
||||
union {
|
||||
uint8_t faults;
|
||||
struct {
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool FaultBatteryVoltageDeviation : 1; // 4
|
||||
bool FaultHighBatteryTemperature : 1; // 3
|
||||
bool FaultBatteryCurrentDeviation : 1; // 2
|
||||
bool FaultBatteryUnderVoltage : 1; // 1
|
||||
bool FaultBatteryOverVoltage : 1; // 0
|
||||
} fault;
|
||||
} f;
|
||||
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool StatusVehicleDischargeCompatible : 1; //5.7
|
||||
bool unused_2 : 1; //5.6
|
||||
bool unused_1 : 1; //5.5
|
||||
bool StatusNormalStopRequest : 1; //5.4
|
||||
bool StatusVehicle : 1; //5.3
|
||||
bool StatusChargingError : 1; //5.2
|
||||
bool StatusVehicleShifterPosition : 1; //5.1
|
||||
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
|
||||
// permission signal k needs to be active for charging to be
|
||||
// permitted -- TODO document bits per byte for these flags
|
||||
// and update variables to be more appropriate
|
||||
} status;
|
||||
} s;
|
||||
|
||||
//TODO discharge compatible is a status set here in bit 7, see beaglebone
|
||||
uint8_t StateOfCharge = 0; //6 state of charge?
|
||||
};
|
||||
|
||||
/* ---------- CHARGING: EVSE Data structures */
|
||||
struct x108_EVSE_Capabilities { // Frame byte
|
||||
bool contactor_weld_detection = 1; // 0
|
||||
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
|
||||
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
|
||||
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
|
||||
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
|
||||
// 6,7 = unused
|
||||
};
|
||||
|
||||
/* Does double duty for charging and discharging */
|
||||
struct x109_EVSE_Status { // Frame byte
|
||||
uint8_t CHADEMO_protocol_number = 0x02; // 0
|
||||
uint16_t setpoint_HV_VDC =
|
||||
0; // 1,2 NOTE: charger_stepoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
|
||||
uint8_t setpoint_HV_IDC = 0; // 3
|
||||
//
|
||||
bool discharge_compatible = true; // 4, bit 0. bits
|
||||
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool EVSE_status : 1; // 5, bit 0
|
||||
bool EVSE_error : 1; // 5, bit 1
|
||||
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
|
||||
// seen as 'energizing' during charging mode
|
||||
|
||||
bool battery_incompatible : 1; // 5, bit 3
|
||||
bool ChgDischError : 1; // 5, bit 4
|
||||
|
||||
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
|
||||
// set to false when ready to charge/discharge
|
||||
|
||||
} status;
|
||||
} s;
|
||||
|
||||
// Either, or; not both.
|
||||
// seconds field set to 0xFF by default
|
||||
// indicating only the minutes field is used instead
|
||||
// BOTH observed initially set to 0xFF in logs, so use
|
||||
// that as the initialzed value
|
||||
uint8_t remaining_time_10s = 0xFF; // 6
|
||||
uint8_t remaining_time_1m = 0xFF; // 7
|
||||
};
|
||||
|
||||
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H200 - Vehicle - Discharge limits
|
||||
struct x200_Vehicle_Discharge_Limits {
|
||||
uint8_t MaximumDischargeCurrent = 0xFF;
|
||||
uint16_t MinimumDischargeVoltage = 0;
|
||||
uint16_t MinimumBatteryDischargeLevel = 0;
|
||||
uint16_t MaxRemainingCapacityForCharging = 0;
|
||||
};
|
||||
|
||||
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
|
||||
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
|
||||
//H201 - Vehicle - Estimated capacity available
|
||||
// Intended primarily for display purposes.
|
||||
// Peer to H209
|
||||
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
|
||||
struct x201_Vehicle_Discharge_Estimate {
|
||||
uint8_t V2HchargeDischargeSequenceNum = 0;
|
||||
uint16_t ApproxDischargeCompletionTime = 0;
|
||||
uint16_t AvailableVehicleEnergy = 0;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
struct x208_EVSE_Discharge_Capability { // Frame byte
|
||||
uint8_t present_discharge_current = 0xFF; // 0
|
||||
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
|
||||
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
|
||||
// spec idiosyncracy in naming/description
|
||||
// 4,5 = unused
|
||||
uint16_t lower_threshold_voltage = 0; // 6,7
|
||||
};
|
||||
|
||||
// H209 - EVSE - Estimated Discharge Duration
|
||||
// peer to Vehicle's 201 event (Note: 209 seen
|
||||
// in CAN logs even when 201 is not)
|
||||
struct x209_EVSE_Discharge_Estimate { // Frame byte
|
||||
uint8_t sequence_control_number = 0x2; // 0
|
||||
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
|
||||
};
|
||||
|
||||
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
struct x110_Vehicle_Dynamic_Control { //Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
// TODO 118
|
||||
//H118
|
||||
//see also table a.59 page 104 IEEE
|
||||
struct x118_EVSE_Dynamic_Control { // Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H700 - Vehicle - Manufacturer identification
|
||||
//Peer to H708
|
||||
//Used to adapt to manufacturer-prescribed optional specification
|
||||
struct x700_Vehicle_Vendor_ID {
|
||||
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
|
||||
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
|
||||
};
|
||||
|
||||
void handle_chademo_sequence();
|
||||
|
||||
#endif
|
|
@ -4,11 +4,60 @@
|
|||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "CHADEMO-BATTERY-TYPES.h"
|
||||
#include "CHADEMO-BATTERY.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 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
|
||||
|
||||
bool plug_inserted = false;
|
||||
bool vehicle_can_received = false;
|
||||
bool vehicle_permission = false;
|
||||
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;
|
||||
|
||||
bool high_current_control_enabled = false; // set to true when high current control is operating
|
||||
// if true, values from 110.1 and 110.2 should be used instead of 102.3
|
||||
// and 118 should be used for evse responses
|
||||
// permissible rate of change is -20A/s to 20A/s relative to 102.3
|
||||
|
||||
Mode EVSE_mode = CHADEMO_DISCHARGE;
|
||||
CHADEMO_STATE CHADEMO_Status = CHADEMO_IDLE;
|
||||
|
||||
/* Charge/discharge sequence, indicating applicable V2H guideline
|
||||
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
|
||||
* V2H 1.1 is assumed
|
||||
* Use CHADEMO_seq to decide whether emitting 209 is necessary
|
||||
* 0x0 1.0 and earlier
|
||||
* 0x1 2.0 appendix A
|
||||
* 0x2 2.0 appendix B
|
||||
* Unused for now.
|
||||
uint8_t CHADEMO_seq = 0x0;
|
||||
*/
|
||||
|
||||
bool x201_received = false;
|
||||
bool x209_sent = false;
|
||||
|
||||
struct x100_Vehicle_Charging_Limits x100_chg_lim = {};
|
||||
struct x101_Vehicle_Charging_Estimate x101_chg_est = {};
|
||||
struct x102_Vehicle_Charging_Session x102_chg_session = {};
|
||||
struct x110_Vehicle_Dynamic_Control x110_vehicle_dyn = {};
|
||||
struct x200_Vehicle_Discharge_Limits x200_discharge_limits = {};
|
||||
struct x201_Vehicle_Discharge_Estimate x201_discharge_estimate = {};
|
||||
struct x700_Vehicle_Vendor_ID x700_vendor_id = {};
|
||||
|
||||
struct x209_EVSE_Discharge_Estimate x209_evse_dischg_est;
|
||||
struct x108_EVSE_Capabilities x108_evse_cap;
|
||||
struct x109_EVSE_Status x109_evse_state;
|
||||
struct x118_EVSE_Dynamic_Control x118_evse_dyn;
|
||||
struct x208_EVSE_Discharge_Capability x208_evse_dischg_cap;
|
||||
|
||||
CAN_frame_t CHADEMO_108 = {.FIR = {.B =
|
||||
{
|
||||
|
@ -31,7 +80,13 @@ CAN_frame_t CHADEMO_118 = {.FIR = {.B =
|
|||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x118,
|
||||
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
// OLD value from skeleton implementation, indicates dynamic control is possible.
|
||||
// Hardcode above as being incompatible for simplicity in current incarnation.
|
||||
// .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
|
||||
|
||||
// 0x200 : From vehicle-side. A V2X-ready vehicle will send this message to broadcast its “Maximum discharger current”. (It is a similar logic to the limits set in 0x100 or 0x102 during a DC charging session)
|
||||
// 0x208 : From EVSE-side. A V2X EVSE will use this to send the “present discharger current” during the session, and the “available input current”. (uses similar logic to 0x108 and 0x109 during a DC charging session)
|
||||
CAN_frame_t CHADEMO_208 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
|
@ -39,6 +94,7 @@ CAN_frame_t CHADEMO_208 = {.FIR = {.B =
|
|||
}},
|
||||
.MsgID = 0x208,
|
||||
.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
|
||||
|
||||
CAN_frame_t CHADEMO_209 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
|
@ -47,61 +103,25 @@ CAN_frame_t CHADEMO_209 = {.FIR = {.B =
|
|||
.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;
|
||||
//This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
||||
void update_values_battery() {
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
||||
|
||||
datalayer.battery.status.real_soc = ChargingRate;
|
||||
datalayer.battery.status.real_soc = x102_chg_session.StateOfCharge;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
(MaximumDischargeCurrent * MaximumBatteryVoltage); //In Watts, Convert A to P
|
||||
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
|
||||
|
||||
datalayer.battery.status.voltage_dV = TargetBatteryVoltage; //TODO: scaling?
|
||||
datalayer.battery.status.voltage_dV = x102_chg_session.TargetBatteryVoltage; //TODO: scaling?
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh =
|
||||
((RatedBatteryCapacity / 0.11) *
|
||||
((x101_chg_est.RatedBatteryCapacity / 0.11) *
|
||||
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
|
||||
|
||||
/* TODO max charging rate =
|
||||
* x200_discharge_limits.MaxRemainingCapacityForCharging /
|
||||
* x101_chg_est.RatedBatteryCapacity * 100;
|
||||
*/
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
|
@ -111,72 +131,494 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
}
|
||||
|
||||
/* To simulate or NOT to simulate battery cell voltages, that is .. A question.
|
||||
* Answer for now: Not, because they are not available in any direct manner.
|
||||
* This will impact Solax inverter support, which uses cell min/max mV to populate
|
||||
* CAN frames.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("SOC 0x100: ");
|
||||
Serial.println(ConstantOfChargingRateIndication);
|
||||
Serial.println(x100_chg_lim.ConstantOfChargingRateIndication);
|
||||
#endif
|
||||
}
|
||||
|
||||
//TODO see 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];
|
||||
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
|
||||
}
|
||||
|
||||
inline void process_vehicle_charging_maximums(CAN_frame_t rx_frame) {
|
||||
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
|
||||
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
|
||||
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
|
||||
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
}
|
||||
|
||||
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_permission = digitalRead(CHADEMO_PIN_4);
|
||||
|
||||
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
|
||||
|
||||
x102_chg_session.f.fault.FaultBatteryOverVoltage = bitRead(rx_frame.data.u8[4], 0);
|
||||
x102_chg_session.f.fault.FaultBatteryUnderVoltage = bitRead(rx_frame.data.u8[4], 1);
|
||||
x102_chg_session.f.fault.FaultBatteryCurrentDeviation = bitRead(rx_frame.data.u8[4], 2);
|
||||
x102_chg_session.f.fault.FaultHighBatteryTemperature = bitRead(rx_frame.data.u8[4], 3);
|
||||
x102_chg_session.f.fault.FaultBatteryVoltageDeviation = bitRead(rx_frame.data.u8[4], 4);
|
||||
|
||||
x102_chg_session.s.status.StatusVehicleChargingEnabled = bitRead(rx_frame.data.u8[5], 0);
|
||||
x102_chg_session.s.status.StatusVehicleShifterPosition = bitRead(rx_frame.data.u8[5], 1);
|
||||
x102_chg_session.s.status.StatusChargingError = bitRead(rx_frame.data.u8[5], 2);
|
||||
x102_chg_session.s.status.StatusVehicle = bitRead(rx_frame.data.u8[5], 3);
|
||||
x102_chg_session.s.status.StatusNormalStopRequest = bitRead(rx_frame.data.u8[5], 4);
|
||||
|
||||
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
|
||||
|
||||
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
|
||||
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
|
||||
|
||||
//Table A.26—Charge control termination command patterns -- should echo x108 handling
|
||||
|
||||
/* charge/discharge permission signal from vehicle on pin 4 should NOT be sensed before first CAN received from vehicle.
|
||||
* Also, vehicle CAN should not simultaneously indicate enabled while permissions signal is absent
|
||||
*
|
||||
* Either is a logical inconsistency per spec (vehicle has lost state, some wire/pin is broken, etc)
|
||||
* this should trigger stop and teardown
|
||||
*/
|
||||
if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) ||
|
||||
(x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Inconsistent charge/discharge state.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_FAULT;
|
||||
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.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle indicates fault, battery under voltage.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
// end
|
||||
if (priorTargetBatteryVoltage > 0 && newTargetBatteryVoltage == 0) {
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
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)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("State of charge ceiling reached, stop charging");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) {
|
||||
CHADEMO_Status = CHADEMO_EV_ALLOWED;
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO this and the next stanza influence state/control
|
||||
// and probably don't belong in this function
|
||||
// consider relocating
|
||||
if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 &&
|
||||
newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_EVSE_START;
|
||||
return;
|
||||
}
|
||||
|
||||
if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("updating vehicle request in process_vehicle_charging_session()");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("UNHANDLED STATE IN process_vehicle_charging_session()");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
/* x200 Vehicle, peer to x208 EVSE */
|
||||
inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
|
||||
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
|
||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
||||
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
||||
}
|
||||
|
||||
/* Vehicle 0x201, peer to EVSE 0x209
|
||||
* HOWEVER, 201 isn't even emitted in any of the v2x canlogs available
|
||||
*/
|
||||
inline void process_vehicle_discharge_estimate(CAN_frame_t rx_frame) {
|
||||
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
||||
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||
}
|
||||
|
||||
inline void process_vehicle_dynamic_control(CAN_frame_t rx_frame) {
|
||||
//SM Dynamic Control = Charging station can increase of decrease "available output current" during charging.
|
||||
//If you set 0x110 byte 0, bit 0 to 1 you say you can do dynamic control.
|
||||
//Charging station communicates this in 0x118 byte 0, bit 0
|
||||
x110_vehicle_dyn.u.status.HighVoltageControlStatus = bitRead(rx_frame.data.u8[0], 2);
|
||||
x110_vehicle_dyn.u.status.HighCurrentControlStatus = bitRead(rx_frame.data.u8[0], 1);
|
||||
x110_vehicle_dyn.u.status.DynamicControlStatus = bitRead(rx_frame.data.u8[0], 0);
|
||||
}
|
||||
|
||||
inline void process_vehicle_vendor_ID(CAN_frame_t rx_frame) {
|
||||
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
|
||||
x700_vendor_id.OptionalContent =
|
||||
((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) {
|
||||
CANstillAlive == 12; //We are getting CAN messages from the vehicle, inform the watchdog
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //We are getting CAN messages from the vehicle, inform the watchdog
|
||||
CANstillAlive = 12; //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(" ");
|
||||
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("");
|
||||
#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
|
||||
}
|
||||
|
||||
// used for testing vehicle sanity
|
||||
vehicle_can_received = true;
|
||||
|
||||
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];
|
||||
process_vehicle_charging_minimums(rx_frame);
|
||||
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]);
|
||||
process_vehicle_charging_maximums(rx_frame);
|
||||
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];
|
||||
process_vehicle_charging_session(rx_frame);
|
||||
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];
|
||||
process_vehicle_charging_limits(rx_frame);
|
||||
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
|
||||
x201_received = true;
|
||||
process_vehicle_discharge_estimate(rx_frame);
|
||||
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;
|
||||
process_vehicle_dynamic_control(rx_frame);
|
||||
case 0x700:
|
||||
process_vehicle_vendor_ID(rx_frame);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
handle_chademo_sequence();
|
||||
}
|
||||
|
||||
/* (re)initialize evse structures to pre-charge/discharge states */
|
||||
void evse_init() {
|
||||
// Held at 1 until start of charge when set to 0
|
||||
// returns to 1 when ceasing power flow
|
||||
// mutually exclusive values
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
x109_evse_state.s.status.EVSE_status = 0;
|
||||
|
||||
x109_evse_state.s.status.connector_locked = 0;
|
||||
x109_evse_state.s.status.battery_incompatible = 0;
|
||||
x109_evse_state.s.status.ChgDischError = 0;
|
||||
|
||||
/* values set during object initialization
|
||||
x109_evse_state.CHADEMO_protocol_number
|
||||
x109_evse_state.remaining_time_10s
|
||||
x109_evse_state.remaining_time_1m
|
||||
*/
|
||||
}
|
||||
|
||||
/* updates for x108 */
|
||||
void update_evse_capabilities(CAN_frame_t& f) {
|
||||
|
||||
/* TODO use charger defines/runtime config?
|
||||
* for now..leave as a future tweak.
|
||||
* use vehicle requests as a ceiling
|
||||
*/
|
||||
|
||||
/* interpret this as mostly a timing concern, so indicate yes we support EV contactor weld detection
|
||||
* expectations: <1s after vehicle contactors open, charger output will drop to <=25% of prior voltage
|
||||
* <2s after vehicle contactors open, charter output voltage will drop to <=10V
|
||||
*
|
||||
* see A.10.2.1 Example of welding detection logic on the vehicle
|
||||
*/
|
||||
x108_evse_cap.contactor_weld_detection = 0x1;
|
||||
|
||||
x108_evse_cap.available_output_voltage = x102_chg_session.TargetBatteryVoltage;
|
||||
|
||||
/* calculate max threshold to protect battery - using vehicle-provided max minus 2% */
|
||||
x108_evse_cap.threshold_voltage =
|
||||
x100_chg_lim.MaximumBatteryVoltage - (int)(x100_chg_lim.MaximumBatteryVoltage / 100 * 2);
|
||||
|
||||
// Power and voltage may be best derived from config/defines not from the x108 settings elsewhere, ideally
|
||||
// only set current when voltage > 0, as it is set by x102 TargetBatteryVoltage
|
||||
if (x108_evse_cap.available_output_voltage) {
|
||||
x108_evse_cap.available_output_current = MAX_EVSE_POWER_CHARGING / x108_evse_cap.available_output_voltage;
|
||||
}
|
||||
|
||||
/* update Frame - byte 6 and 7 are unused */
|
||||
CHADEMO_108.data.u8[0] = x108_evse_cap.contactor_weld_detection;
|
||||
CHADEMO_108.data.u8[1] = lowByte(x108_evse_cap.available_output_voltage);
|
||||
CHADEMO_108.data.u8[2] = highByte(x108_evse_cap.available_output_voltage);
|
||||
CHADEMO_108.data.u8[3] = x108_evse_cap.available_output_current;
|
||||
CHADEMO_108.data.u8[4] = lowByte(x108_evse_cap.threshold_voltage);
|
||||
CHADEMO_108.data.u8[5] = highByte(x108_evse_cap.threshold_voltage);
|
||||
}
|
||||
|
||||
/* updates for x109 */
|
||||
void update_evse_status(CAN_frame_t& f) {
|
||||
|
||||
x109_evse_state.s.status.EVSE_status = 1;
|
||||
x109_evse_state.s.status.EVSE_error = 0;
|
||||
x109_evse_state.s.status.ChgDischError = 0;
|
||||
x109_evse_state.s.status.ChgDischStopControl = 0;
|
||||
|
||||
/* updated only in state handler
|
||||
* TODO..why?
|
||||
x109_evse_state.s.connector_locked = 0;
|
||||
*/
|
||||
|
||||
if (EVSE_mode == CHADEMO_DISCHARGE) {
|
||||
/* Occasionally oberved as set to 0 when discharging
|
||||
* this may be true for all V2H versions
|
||||
* unless it was a logging discrepancy
|
||||
x109_evse_state.setpoint_HV_VDC = 0;
|
||||
x109_evse_state.setpoint_HV_IDC = 0;
|
||||
*/
|
||||
x109_evse_state.setpoint_HV_VDC =
|
||||
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
|
||||
x109_evse_state.setpoint_HV_IDC =
|
||||
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
|
||||
|
||||
/* TODO calculate remaining discharge time : for now == 60m */
|
||||
x109_evse_state.remaining_time_1m = 60;
|
||||
|
||||
} else if (EVSE_mode == CHADEMO_CHARGE) {
|
||||
//FIXME these are supposed to be measured values, e.g., from a shunt
|
||||
//for now we are literally saying they're equivalent to the request or max charger capability
|
||||
//this is wrong
|
||||
x109_evse_state.setpoint_HV_VDC =
|
||||
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
|
||||
x109_evse_state.setpoint_HV_IDC =
|
||||
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
|
||||
/* The spec suggests throwing a 109.5.4 = 1 if vehicle curr request 102.3 > evse curr available 108.3,
|
||||
* but realistically many chargers seem to act tolerant here and stay under limits and supply whatever they are able
|
||||
*/
|
||||
|
||||
/* 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) {
|
||||
x109_evse_state.setpoint_HV_IDC = floor(MAX_EVSE_POWER_CHARGING / x109_evse_state.setpoint_HV_VDC);
|
||||
}
|
||||
|
||||
/* TODO calculate remaining charge time : for now == 60m */
|
||||
x109_evse_state.remaining_time_1m = 60;
|
||||
}
|
||||
|
||||
/* Table A.26 - See also Charge control termination command patterns
|
||||
* This handling must be mirrored in handling for vehicle x102
|
||||
*
|
||||
*/
|
||||
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
|
||||
x109_evse_state.s.status.EVSE_error = 1;
|
||||
x109_evse_state.s.status.battery_incompatible = 1;
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
CHADEMO_Status = CHADEMO_FAULT;
|
||||
}
|
||||
|
||||
//CHADEMO_109.data.u8[0] hardcoded to 0x2 for CHAdeMO v1, 1.0.1, 1.1, 1.2
|
||||
// in initialization
|
||||
CHADEMO_109.data.u8[0] = x109_evse_state.CHADEMO_protocol_number;
|
||||
CHADEMO_109.data.u8[1] = lowByte(x109_evse_state.setpoint_HV_VDC);
|
||||
CHADEMO_109.data.u8[2] = highByte(x109_evse_state.setpoint_HV_VDC);
|
||||
CHADEMO_109.data.u8[3] = x109_evse_state.setpoint_HV_IDC;
|
||||
CHADEMO_109.data.u8[4] = x109_evse_state.discharge_compatible;
|
||||
|
||||
/* clear statuses/faults, then set explicitly */
|
||||
CHADEMO_109.data.u8[5] = 0;
|
||||
CHADEMO_109.data.u8[5] =
|
||||
x109_evse_state.s.status.EVSE_status | x109_evse_state.s.status.EVSE_error << 1 |
|
||||
x109_evse_state.s.status.connector_locked << 2 | x109_evse_state.s.status.battery_incompatible << 3 |
|
||||
x109_evse_state.s.status.ChgDischError << 4 | x109_evse_state.s.status.ChgDischStopControl << 5;
|
||||
|
||||
CHADEMO_109.data.u8[6] = x109_evse_state.remaining_time_10s;
|
||||
CHADEMO_109.data.u8[7] = x109_evse_state.remaining_time_1m;
|
||||
}
|
||||
|
||||
/* Discharge estimates: x209 EVSE = peer to x201 Vehicle
|
||||
* NOTE: x209 is emitted in CAN logs when x201 isn't even present
|
||||
* it may not be understood by leaf (or ignored unless >= a certain protocol version or v2h sequence number
|
||||
*/
|
||||
void update_evse_discharge_estimate(CAN_frame_t& f) {
|
||||
|
||||
//x209_evse_dischg_est.remaining_discharge_time_1m = x201_discharge_estimate.ApproxDischargeCompletionTime;
|
||||
|
||||
//x201_discharge_estimate.AvailableVehicleEnergy = 0;
|
||||
|
||||
//do nothing to alter the default initialized values..this may be unneeded
|
||||
/* TODO
|
||||
if (x200_discharge_limits.MaxRemainingCapacityForCharging = max charge voltage){
|
||||
if (x200_discharge_limits.MinimumBatteryDischargeLevel = kwH for v2h<1.0){
|
||||
if (x200_discharge_limits.MaxRemainingCapacityForCharging = kwH for v2h<1.0){
|
||||
*/
|
||||
|
||||
CHADEMO_209.data.u8[0] = x209_evse_dischg_est.sequence_control_number;
|
||||
CHADEMO_209.data.u8[1] = x209_evse_dischg_est.remaining_discharge_time;
|
||||
}
|
||||
|
||||
/* x208 EVSE, peer to 0x200 Vehicle */
|
||||
void update_evse_discharge_capabilities(CAN_frame_t& f) {
|
||||
//FIXME these are supposed to be measured values, e.g., from a shunt
|
||||
//we are literally saying theyre arbitrary for now
|
||||
//this is wrong
|
||||
x208_evse_dischg_cap.present_discharge_current = 0xFF - 6;
|
||||
x208_evse_dischg_cap.available_input_current = 0xFF - x200_discharge_limits.MaximumDischargeCurrent;
|
||||
|
||||
x208_evse_dischg_cap.available_input_voltage = x200_discharge_limits.MinimumDischargeVoltage;
|
||||
|
||||
/* calculate min threshold to protect battery - using vehicle-provided minimum plus 2% */
|
||||
x208_evse_dischg_cap.lower_threshold_voltage =
|
||||
x200_discharge_limits.MinimumDischargeVoltage + (int)(x200_discharge_limits.MinimumDischargeVoltage / 100 * 2);
|
||||
|
||||
/* 0x00 == unused
|
||||
if (x200_discharge_limits.MinimumBatteryDischargeLevel > 0 &&
|
||||
x208_evse_dischg_cap.minimum_input_voltage < x200_discharge_limits.MinimumBatteryDischargeLevel){
|
||||
// stop discharging, but permit charging if mode = bidirectional
|
||||
}
|
||||
*/
|
||||
|
||||
//TODO might be ideal to do the 0xFF subtraction HERE during serialization, rather than above?
|
||||
CHADEMO_208.data.u8[0] = x208_evse_dischg_cap.present_discharge_current;
|
||||
CHADEMO_208.data.u8[1] = lowByte(x208_evse_dischg_cap.available_input_voltage);
|
||||
CHADEMO_208.data.u8[2] = highByte(x208_evse_dischg_cap.available_input_voltage);
|
||||
|
||||
CHADEMO_208.data.u8[3] = x208_evse_dischg_cap.available_input_current;
|
||||
|
||||
CHADEMO_208.data.u8[6] = lowByte(x208_evse_dischg_cap.lower_threshold_voltage);
|
||||
CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage);
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
handle_chademo_sequence();
|
||||
|
||||
/* no EVSE messages should be sent until the vehicle has
|
||||
* initiated
|
||||
*/
|
||||
if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
|
@ -185,24 +627,309 @@ void send_can_battery() {
|
|||
}
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
update_evse_capabilities(CHADEMO_108);
|
||||
update_evse_status(CHADEMO_109);
|
||||
update_evse_discharge_capabilities(CHADEMO_208);
|
||||
update_evse_discharge_estimate(CHADEMO_209);
|
||||
|
||||
/* most updates to these EVSE frames are made
|
||||
* upon receipt of a Vehicle message, as
|
||||
* that is the limiting factor. Therefore, we
|
||||
* can generally send as is without tweaks here.
|
||||
*/
|
||||
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?
|
||||
/* TODO for dynamic control: can send x118 with byte 6 bit 0 set to 0 for 1s (before flipping back to 1) as a way of giving vehicle a chance to update 101.1 and 101.2
|
||||
* within 6 seconds of x118 toggle.
|
||||
* Then 109.6 and 109.7 reset remaining charging time
|
||||
* see A.11.5.3.1.3 Remaining charging time (H’109.6, H’109.7) for a better description
|
||||
*/
|
||||
|
||||
if (EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL) {
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_208);
|
||||
if (x201_received) {
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_209);
|
||||
x209_sent = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO need an update_evse_dynamic_control(..) function above before we send 118
|
||||
// 110.0.0
|
||||
if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
|
||||
#ifdef DEBUG_VIA_USB
|
||||
//FIXME REMOVE
|
||||
Serial.println("REMOVE: proto 2.0");
|
||||
#endif
|
||||
ESP32Can.CANWriteFrame(&CHADEMO_118);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* A lot of the heavy lifting happens here. This is essentially the state hander. SOME
|
||||
* state transitions happen in functions before/after this is called.
|
||||
*
|
||||
* stages according to IEEE SPEC, with our states mapped into each.
|
||||
* 1) Standby stage
|
||||
* CHADEMO_IDLE
|
||||
* 2) Preparation stage
|
||||
* CHADEMO_CONNECTED
|
||||
* CHADEMO_INIT
|
||||
* CHADEMO_NEGOTIATE
|
||||
* CHADEMO_EV_ALLOWED
|
||||
* CHADEMO_EVSE_PREPARE
|
||||
* CHADEMO_EVSE_START
|
||||
* CHADEMO_EVSE_CONTACTORS_ENABLED
|
||||
* 3) Charging/Discharging stage
|
||||
* CHADEMO_POWERFLOW
|
||||
* 4) Termination stage
|
||||
* CHADEMO_STOP
|
||||
* 5) Emergency stop stage
|
||||
* CHADEMO_FAULT
|
||||
*/
|
||||
void handle_chademo_sequence() {
|
||||
|
||||
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
|
||||
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
|
||||
contactors_ready = precharge_low && positive_high;
|
||||
vehicle_permission = digitalRead(CHADEMO_PIN_4);
|
||||
|
||||
/* ------------------- State override conditions checks ------------------- */
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle is not parked, abort.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
}
|
||||
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle charge/discharge permission ended, stop.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_STOP;
|
||||
}
|
||||
|
||||
/* ------------------- STATE HANDLER ------------------- */
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
switch (CHADEMO_Status) {
|
||||
case CHADEMO_IDLE:
|
||||
/* this is where we can unlock connector? */
|
||||
digitalWrite(CHADEMO_LOCK, LOW);
|
||||
plug_inserted = digitalRead(CHADEMO_PIN_7);
|
||||
|
||||
if (!plug_inserted) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
// Commented unless needed for debug
|
||||
// Serial.println("CHADEMO plug is not inserted.");
|
||||
//
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
CHADEMO_Status = CHADEMO_CONNECTED;
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
|
||||
#endif
|
||||
|
||||
break;
|
||||
case CHADEMO_CONNECTED:
|
||||
|
||||
/* plug_inserted is .. essentially a volatile of sorts, so verify */
|
||||
if (plug_inserted) {
|
||||
/* If connection is detectable, jumpstart handshake by
|
||||
* indicate that the EVSE is ready to begin
|
||||
*/
|
||||
digitalWrite(CHADEMO_PIN_2, HIGH);
|
||||
|
||||
/* State change to initializing. We will re-enter the handler upon receipt of CAN */
|
||||
CHADEMO_Status = CHADEMO_INIT;
|
||||
} else {
|
||||
/* this potentially-viewed-as-redundant condition checking is candidly
|
||||
* an expression racy-relaties of the real world. Depending upon
|
||||
* testing/performance, it may be better to pepper this state handler
|
||||
* with timers to have higher confidence of certain conditions hitting
|
||||
* a steady state
|
||||
*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
}
|
||||
break;
|
||||
case CHADEMO_INIT:
|
||||
/* Transient state while awaiting CAN from Vehicle.
|
||||
* Used for triggers/error handling elsewhere;
|
||||
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
|
||||
*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
|
||||
#endif
|
||||
evse_init();
|
||||
break;
|
||||
case CHADEMO_NEGOTIATE:
|
||||
/* 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);
|
||||
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
break;
|
||||
case CHADEMO_EV_ALLOWED:
|
||||
// pin 4 (j) reads high
|
||||
if (vehicle_permission) {
|
||||
//lock connector here
|
||||
digitalWrite(CHADEMO_LOCK, HIGH);
|
||||
|
||||
//TODO spec requires test to validate solenoid has indeed engaged.
|
||||
// example uses a comparator/current consumption check around solenoid
|
||||
x109_evse_state.s.status.connector_locked = true;
|
||||
}
|
||||
CHADEMO_Status = CHADEMO_EVSE_PREPARE;
|
||||
|
||||
break;
|
||||
case CHADEMO_EVSE_PREPARE:
|
||||
/* TODO voltage check of output < 10v */
|
||||
/* insulation test hypothetically happens here before triggering PIN 10 high */
|
||||
|
||||
digitalWrite(CHADEMO_PIN_10, HIGH);
|
||||
evse_permission = true;
|
||||
|
||||
// likely unnecessary but just to be sure. consider removal
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
x109_evse_state.s.status.EVSE_status = 0;
|
||||
//state changes only upon receipt of charging session request
|
||||
break;
|
||||
case CHADEMO_EVSE_START:
|
||||
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;
|
||||
|
||||
/* break rather than fall through because contactors are not instantaneous;
|
||||
* worth giving it a cycle to finish
|
||||
*/
|
||||
|
||||
break;
|
||||
case CHADEMO_EVSE_CONTACTORS_ENABLED:
|
||||
|
||||
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
|
||||
if (contactors_ready) {
|
||||
/* 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)) {
|
||||
CHADEMO_Status = CHADEMO_POWERFLOW;
|
||||
x109_evse_state.s.status.ChgDischStopControl = 0;
|
||||
x109_evse_state.s.status.EVSE_status = 1;
|
||||
}
|
||||
|
||||
if (EVSE_mode == CHADEMO_CHARGE) {
|
||||
//TODO not supported currently
|
||||
//CHADEMO_Status = CHADEMO_POWERFLOW;
|
||||
//x109_evse_state.s.status.ChgDischStopControl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* break or fall through ? TODO */
|
||||
break;
|
||||
case CHADEMO_POWERFLOW:
|
||||
/* POWERFLOW for charging, discharging, and bidirectional */
|
||||
/* Interpretation */
|
||||
if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
/* Vehicle is no longer in park */
|
||||
// vehicle will switch k off, EVSE MAY see j (pin 4) go low
|
||||
// EVEN IF evse reads read pin 4 to see high, if this condition is true then trigger EVSE charge/discharge stop
|
||||
// per spec
|
||||
// SEPARATE check for pin 4/j condition does not depend on this flag. it's an OR condition
|
||||
}
|
||||
|
||||
if (x102_chg_session.TargetBatteryVoltage == 0x00) {
|
||||
//TODO flag error and do not calculate power in EVSE response?
|
||||
// probably unnecessary as other flags will be set causing this to be caught
|
||||
}
|
||||
|
||||
// Potentially unnecessary (set in CHADEMO_EVSE_CONTACTORS_ENABLED stanza), but just in case
|
||||
x109_evse_state.s.status.EVSE_status = 1;
|
||||
x109_evse_state.s.status.ChgDischStopControl = 0;
|
||||
vehicle_permission = digitalRead(CHADEMO_PIN_4);
|
||||
break;
|
||||
case CHADEMO_STOP:
|
||||
/* back to CHADEMO_IDLE after teardown */
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
x109_evse_state.s.status.EVSE_status = 0;
|
||||
x109_evse_state.s.status.battery_incompatible = 0;
|
||||
digitalWrite(CHADEMO_PIN_10, LOW);
|
||||
digitalWrite(CHADEMO_PIN_2, LOW);
|
||||
evse_permission = false;
|
||||
vehicle_permission = false;
|
||||
x209_sent = false;
|
||||
x201_received = false;
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
break;
|
||||
case CHADEMO_FAULT:
|
||||
/* 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;
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("CHADEMO fault encountered, tearing down to make safe");
|
||||
#endif
|
||||
digitalWrite(CHADEMO_PIN_10, LOW);
|
||||
digitalWrite(CHADEMO_PIN_2, LOW);
|
||||
evse_permission = false;
|
||||
vehicle_permission = false;
|
||||
x209_sent = false;
|
||||
x201_received = false;
|
||||
|
||||
break;
|
||||
default:
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("UNHANDLED CHADEMO_STATE, setting FAULT");
|
||||
#endif
|
||||
CHADEMO_Status = CHADEMO_FAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Chademo battery selected");
|
||||
#endif
|
||||
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
|
||||
/* disallow contactors until permissions is granted by vehicle */
|
||||
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||
|
||||
//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 =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 2000; // 200.0V under this, discharging further is disabled
|
||||
datalayer.battery.info.min_design_voltage_dV = 2600; // 260.0V under this, discharging further is disabled
|
||||
|
||||
/* initialize EVSE data, state, and CAN frame representations */
|
||||
switch (EVSE_mode) {
|
||||
case CHADEMO_DISCHARGE:
|
||||
case CHADEMO_BIDIRECTIONAL:
|
||||
x109_evse_state.discharge_compatible = true;
|
||||
break;
|
||||
case CHADEMO_CHARGE:
|
||||
x109_evse_state.discharge_compatible = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
|
||||
handle_chademo_sequence();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
//Contactor control is required for CHADEMO support
|
||||
#define CONTACTOR_CONTROL
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
//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 */
|
||||
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 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;
|
||||
|
||||
|
@ -108,14 +107,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(max_temp_cel * 10);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (!BMU_Detected) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMU not detected, check wiring!");
|
||||
|
@ -144,8 +135,8 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_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
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //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);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
|
@ -23,7 +21,6 @@ static uint16_t SOC_Display = 0;
|
|||
static uint16_t batterySOH = 1000;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
static int16_t leadAcidBatteryVoltage = 120;
|
||||
static int16_t batteryAmps = 0;
|
||||
|
@ -97,10 +94,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CANFD_RX_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -113,19 +110,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
|
@ -208,7 +198,7 @@ void send_canfd_frame(CANFDMessage frame) {
|
|||
}
|
||||
|
||||
void receive_canfd_battery(CANFDMessage frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (frame.id) {
|
||||
case 0x7EC:
|
||||
// print_canfd_frame(frame);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
extern ACAN2517FD canfd;
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
#define MAXCHARGEPOWERALLOWED 10000
|
||||
#define MAXDISCHARGEPOWERALLOWED 10000
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
/* 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 previousMillis10 = 0; // will store last time a 10s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
||||
static uint16_t soc_calculated = 0;
|
||||
static uint16_t SOC_BMS = 0;
|
||||
|
@ -21,7 +19,6 @@ static uint16_t SOC_Display = 0;
|
|||
static uint16_t batterySOH = 1000;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t allowedDischargePower = 0;
|
||||
static uint16_t allowedChargePower = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
|
@ -182,14 +179,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (waterleakageSensor == 0) {
|
||||
set_event(EVENT_WATER_INGRESS, 0);
|
||||
}
|
||||
|
@ -216,19 +205,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
|
@ -305,7 +287,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x542: //BMS SOC
|
||||
startedUp = true;
|
||||
CANstillAlive = 12; //We use this message to verify that BMS is still alive
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
|
||||
break;
|
||||
case 0x594:
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
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 uint16_t CANerror = 0; //counter on how many CAN errors encountered
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
|
||||
static uint8_t mprun10 = 0; //counter 0-3
|
||||
static uint8_t mprun100 = 0; //counter 0-3
|
||||
|
@ -91,7 +89,6 @@ static uint8_t crctable[256] = {
|
|||
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
|
||||
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
|
||||
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
|
||||
|
@ -132,14 +129,13 @@ static bool Batt_Heater_Mail_Send_Request = false; //Stores info when a heat re
|
|||
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 bool stop_battery_query = true;
|
||||
static uint8_t hold_off_with_polling_10seconds = 10;
|
||||
static uint16_t cell_voltages[97]; //array with all the cellvoltages
|
||||
static uint8_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 uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t HX = 0; //Internal resistance
|
||||
static uint16_t insulation = 0; //Insulation resistance
|
||||
static uint16_t temp_raw_1 = 0;
|
||||
static uint8_t temp_raw_2_highnibble = 0;
|
||||
static uint16_t temp_raw_2 = 0;
|
||||
|
@ -308,14 +304,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
|
||||
}
|
||||
|
||||
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
|
||||
set_event(EVENT_LOW_SOH, LB_StateOfHealth);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef INTERLOCK_REQUIRED
|
||||
if (!LB_Interlock) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
|
@ -324,19 +312,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
if (CANerror >
|
||||
MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
|
||||
{
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
}
|
||||
|
||||
if (LB_HeatExist) {
|
||||
if (LB_Heating_Stop) {
|
||||
set_event(EVENT_BATTERY_WARMED_UP, 0);
|
||||
|
@ -361,7 +336,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
print_with_units(", Has heater: ", LB_HeatExist, " ");
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -369,7 +343,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x1DB:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
|
||||
|
@ -394,7 +368,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x1DC:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
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);
|
||||
|
@ -403,7 +377,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x55B:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
|
||||
|
@ -413,7 +387,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
|
||||
break;
|
||||
case 0x5BC:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
|
||||
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
|
||||
if (LB_MAX) {
|
||||
|
@ -466,7 +440,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LEAF_Battery_Type = ZE1_BATTERY;
|
||||
break;
|
||||
case 0x79B:
|
||||
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
stop_battery_query = true; //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:
|
||||
|
@ -521,15 +495,9 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = min_max_voltage[1];
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_max_voltage[0];
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
@ -797,7 +765,7 @@ void send_can_battery() {
|
|||
if (hold_off_with_polling_10seconds > 0) {
|
||||
hold_off_with_polling_10seconds--;
|
||||
} else {
|
||||
stop_battery_query = 0;
|
||||
stop_battery_query = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
||||
bool is_message_corrupt(CAN_frame_t rx_frame);
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
//Actual content messages
|
||||
CAN_frame_t PYLON_3010 = {.FIR = {.B =
|
||||
|
@ -96,18 +95,10 @@ void update_values_battery() {
|
|||
datalayer.battery.info.max_design_voltage_dV = charge_cutoff_voltage;
|
||||
|
||||
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x7310:
|
||||
case 0x7311:
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -34,9 +34,7 @@ static uint16_t LB_Charge_Power_Limit = 0;
|
|||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint16_t LB_MaxChargeAllowed_W = 0;
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
static uint8_t GVI_Pollcounter = 0;
|
||||
static uint8_t LB_EOCR = 0;
|
||||
|
@ -130,27 +128,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, (LB_Cell_Max_Voltage / 20));
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, (LB_Cell_Min_Voltage / 20));
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter:");
|
||||
|
@ -189,14 +172,16 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
void receive_can_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
|
||||
case 0x155: //BMS1
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_MaxChargeAllowed_W = (rx_frame.data.u8[0] * 300);
|
||||
LB_Current = word((rx_frame.data.u8[1] & 0xF), rx_frame.data.u8[2]) * 0.25 - 500; //OK!
|
||||
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])) * 0.0025; //OK!
|
||||
break;
|
||||
case 0x424: //BMS2
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
case 0x424: //BMS2
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_EOCR = (rx_frame.data.u8[0] & 0x03);
|
||||
LB_HVBUV = (rx_frame.data.u8[0] & 0x0C) >> 2;
|
||||
LB_HVBIR = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
|
@ -212,11 +197,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7]) - 40); //OK!
|
||||
break;
|
||||
case 0x425:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_kWh_Remaining = word((rx_frame.data.u8[0] & 0x1), rx_frame.data.u8[1]) / 10; //OK!
|
||||
break;
|
||||
case 0x445:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_Cell_Max_Voltage = 1000 + word((rx_frame.data.u8[3] & 0x1), rx_frame.data.u8[4]) * 10; //OK!
|
||||
LB_Cell_Min_Voltage = 1000 + (word(rx_frame.data.u8[5], rx_frame.data.u8[6]) >> 7) * 10; //OK!
|
||||
|
||||
|
@ -227,7 +214,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
break;
|
||||
case 0x7BB:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x10) { //1st response Bytes 0-7
|
||||
GVB_79B_Continue = true;
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
#include "RENAULT-ZOE-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
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 errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
|
@ -21,7 +20,6 @@ static int32_t LB_Current = 0;
|
|||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint32_t LB_Battery_Voltage = 3700;
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
|
||||
|
@ -65,27 +63,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter:");
|
||||
|
@ -122,7 +105,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x42E: //HV SOC & Battery Temp & Charging Power
|
||||
break;
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE \
|
||||
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE \
|
||||
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
|
||||
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ TODO: Map all values from battery CAN messages
|
|||
/* 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 uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
static int SOC_1 = 0;
|
||||
static int SOC_2 = 0;
|
||||
|
@ -77,21 +76,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x200:
|
||||
break;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
uint8_t CalculateCRC8(CAN_frame_t rx_frame);
|
||||
void setup_battery(void);
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#ifndef MAX_CELL_DEVIATION_MV
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
#endif
|
||||
|
||||
#include "../include.h"
|
||||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
/* 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 uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
|
||||
|
||||
CAN_frame_t TESLA_221_1 = {
|
||||
.FIR = {.B =
|
||||
|
@ -232,14 +231,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
/* Value mapping is completed. Start to check all safeties */
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!stillAliveCAN) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
stillAliveCAN--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
||||
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
|
||||
} else {
|
||||
|
@ -513,7 +504,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
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
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS
|
||||
bat_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]);
|
||||
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));
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//#define LFP_CHEMISTRY // Enable this line to startup in LFP mode
|
||||
|
||||
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
|
||||
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
|
||||
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
|
||||
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0
|
||||
|
|
|
@ -16,6 +16,7 @@ void print_units(char* header, int value, char* 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%
|
||||
|
@ -46,6 +47,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
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");
|
||||
|
@ -62,7 +66,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) { // All CAN messages recieved will be logged via serial
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// 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);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
/* 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 previousMillis60s = 0; // will store last time a 60s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4210 //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 MAX_CELL_VOLTAGE 4210 //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
|
||||
|
||||
static float BATT_U = 0; //0x3A
|
||||
static float MAX_U = 0; //0x3A
|
||||
|
@ -26,16 +24,14 @@ static float BATT_T_MIN = 0; //0x413
|
|||
static float BATT_T_AVG = 0; //0x413
|
||||
static uint16_t SOC_BMS = 0; //0X37D
|
||||
static uint16_t SOC_CALC = 0;
|
||||
static uint16_t CELL_U_MAX = 0; //0x37D
|
||||
static uint16_t CELL_U_MIN = 0; //0x37D
|
||||
static uint8_t CELL_ID_U_MAX = 0; //0x37D
|
||||
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
|
||||
|
||||
static uint16_t CELL_U_MAX = 0; //0x37D
|
||||
static uint16_t CELL_U_MIN = 0; //0x37D
|
||||
static uint8_t CELL_ID_U_MAX = 0; //0x37D
|
||||
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
|
||||
static uint8_t batteryModuleNumber = 0x10; // First battery module
|
||||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t rxConsecutiveFrames = 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 min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint32_t remaining_capacity = 0;
|
||||
static uint16_t cell_voltages[108]; //array with all the cellvoltages
|
||||
|
@ -114,14 +110,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("BMS reported SOC%: ");
|
||||
Serial.println(SOC_BMS);
|
||||
|
@ -159,8 +147,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
Serial.println(min_max_voltage[1]);
|
||||
Serial.print("Lowest cell voltage: ");
|
||||
Serial.println(min_max_voltage[0]);
|
||||
Serial.print("Cell deviation voltage: ");
|
||||
Serial.println(cell_deviation_mV);
|
||||
Serial.print("Cell voltage,");
|
||||
while (cnt < 108) {
|
||||
Serial.print(cell_voltages[cnt++]);
|
||||
|
@ -171,7 +157,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x3A:
|
||||
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
||||
|
@ -314,12 +300,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ typedef struct {
|
|||
typedef struct {
|
||||
/** int32_t */
|
||||
/** Instantaneous battery power in Watts */
|
||||
/* Positive value = Battery Charging */
|
||||
/* Negative value = Battery Discharging */
|
||||
int32_t active_power_W;
|
||||
|
||||
/** uint32_t */
|
||||
|
@ -68,6 +70,14 @@ typedef struct {
|
|||
* battery.settings.soc_scaling_active
|
||||
*/
|
||||
uint16_t reported_soc;
|
||||
/** A counter that increases incase a CAN CRC read error occurs */
|
||||
uint16_t CAN_error_counter;
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from battery.
|
||||
* This value then gets decremented each 5 seconds. Incase we reach 0
|
||||
* we report the battery as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/** Other */
|
||||
/** The current BMS status */
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
|
||||
/* Select HW - DONT TOUCH */
|
||||
#define HW_LILYGO
|
||||
|
||||
#if defined(HW_LILYGO)
|
||||
#include "hw_lilygo.h"
|
||||
#elif defined(HW_STARK)
|
||||
#include "hw_stark.h"
|
||||
#elif defined(HW_SJB_V1)
|
||||
#include "hw_sjb_v1.h"
|
||||
#endif
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// CHAdeMO support pin dependencies
|
||||
#define CHADEMO_PIN_2 12
|
||||
#define CHADEMO_PIN_10 5
|
||||
#define CHADEMO_PIN_7 34
|
||||
#define CHADEMO_PIN_4 35
|
||||
#define CHADEMO_LOCK 18
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
|
@ -62,4 +69,10 @@
|
|||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#ifdef DUAL_CAN
|
||||
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
66
Software/src/devboard/hal/hw_stark.h
Normal file
66
Software/src/devboard/hal/hw_stark.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef __HW_STARK06_H__
|
||||
#define __HW_STARK06_H__
|
||||
|
||||
// Board boot-up time
|
||||
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
|
||||
|
||||
// Core assignment
|
||||
#define CORE_FUNCTION_CORE 1
|
||||
#define MODBUS_CORE 0
|
||||
#define WIFI_CORE 0
|
||||
|
||||
// RS485
|
||||
// #define PIN_5V_EN 16 // No function, GPIO 16 used instead as MCP_SCK
|
||||
// #define RS485_EN_PIN 17 // RE, No function, GPIO 17 is instead available as extra GPIO via pin header
|
||||
#define RS485_TX_PIN 22
|
||||
#define RS485_RX_PIN 21
|
||||
// #define RS485_SE_PIN 19 // No function, GPIO 19 is instead available as extra GPIO via pin header
|
||||
|
||||
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings
|
||||
#define CAN_1_TYPE ESP32CAN
|
||||
|
||||
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB
|
||||
#define CAN_TX_PIN GPIO_NUM_27
|
||||
#define CAN_RX_PIN GPIO_NUM_26
|
||||
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
|
||||
|
||||
// CAN2 defines below
|
||||
|
||||
// DUAL_CAN defines
|
||||
//#define MCP2515_SCK 12 // SCK input of MCP2515
|
||||
//#define MCP2515_MOSI 5 // SDI input of MCP2515
|
||||
//#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
|
||||
//#define MCP2515_CS 18 // CS input of MCP2515
|
||||
//#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
|
||||
|
||||
// CAN_FD defines
|
||||
#define MCP2517_SCK 16 // SCK input of MCP2517 (Changed from 12 to 16 since 12 can be used for JTAG TDI)
|
||||
#define MCP2517_SDI 5 // SDI input of MCP2517
|
||||
#define MCP2517_SDO 34 // SDO output of MCP2517
|
||||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
#define PRECHARGE_PIN 25
|
||||
#define BMS_POWER 23 // Also connected to MCP_SCK
|
||||
|
||||
// SD card
|
||||
//#define SD_MISO_PIN 2
|
||||
//#define SD_MOSI_PIN 15
|
||||
//#define SD_SCLK_PIN 14
|
||||
//#define SD_CS_PIN 13
|
||||
|
||||
// LED
|
||||
#define LED_PIN 4
|
||||
#define LED_MAX_BRIGHTNESS 40
|
||||
|
||||
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
|
||||
#ifndef HW_CONFIGURED
|
||||
#define HW_CONFIGURED
|
||||
#else
|
||||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#endif
|
84
Software/src/devboard/safety/safety.cpp
Normal file
84
Software/src/devboard/safety/safety.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
#include "../utils/events.h"
|
||||
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
|
||||
void update_machineryprotection() {
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > 500) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||
}
|
||||
|
||||
// Battery is frozen!
|
||||
if (datalayer.battery.status.temperature_min_dC < -250) {
|
||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FROZEN);
|
||||
}
|
||||
|
||||
// Battery voltage is over designed max voltage!
|
||||
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery voltage is under designed min voltage!
|
||||
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||
if (datalayer.battery.status.soh_pptt < 2500) {
|
||||
set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
|
||||
// Check diff between highest and lowest cell
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Inverter is charging with more power than battery wants!
|
||||
if (datalayer.battery.status.active_power_W > 0) { // Charging
|
||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
// Inverter is pulling too much power from battery!
|
||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
||||
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_RX_WARNING);
|
||||
}
|
||||
}
|
9
Software/src/devboard/safety/safety.h
Normal file
9
Software/src/devboard/safety/safety.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#ifndef SAFETY_H
|
||||
#define SAFETY_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MAX_CAN_FAILURES 50
|
||||
|
||||
void update_machineryprotection();
|
||||
|
||||
#endif
|
|
@ -139,15 +139,21 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
|
||||
|
@ -209,6 +215,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
case EVENT_CAN_TX_FAILURE:
|
||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is charging faster than battery is allowing.";
|
||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is discharging faster than battery is allowing.";
|
||||
case EVENT_WATER_INGRESS:
|
||||
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
|
||||
case EVENT_12V_LOW:
|
||||
|
@ -221,6 +231,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: Battery is completely discharged";
|
||||
case EVENT_BATTERY_FULL:
|
||||
return "Info: Battery is fully charged";
|
||||
case EVENT_BATTERY_FROZEN:
|
||||
return "Info: Battery is too cold to operate optimally. Consider warming it up!";
|
||||
case EVENT_BATTERY_CAUTION:
|
||||
return "Info: Battery has raised a general caution flag. Might want to inspect it closely.";
|
||||
case EVENT_BATTERY_CHG_STOP_REQ:
|
||||
|
@ -233,6 +245,12 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: COLD BATTERY! Battery requesting heating pads to activate!";
|
||||
case EVENT_BATTERY_WARMED_UP:
|
||||
return "Info: Battery requesting heating pads to stop. The battery is now warm enough.";
|
||||
case EVENT_BATTERY_OVERHEAT:
|
||||
return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!";
|
||||
case EVENT_BATTERY_OVERVOLTAGE:
|
||||
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
|
||||
case EVENT_BATTERY_UNDERVOLTAGE:
|
||||
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
|
||||
case EVENT_LOW_SOH:
|
||||
return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle "
|
||||
"battery.";
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0005 // 0x0000 to 0xFFFF
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0006 // 0x0000 to 0xFFFF
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
@ -32,16 +32,22 @@
|
|||
XX(EVENT_CANFD_RX_FAILURE) \
|
||||
XX(EVENT_CAN_RX_WARNING) \
|
||||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BATTERY_EMPTY) \
|
||||
XX(EVENT_BATTERY_FULL) \
|
||||
XX(EVENT_BATTERY_FROZEN) \
|
||||
XX(EVENT_BATTERY_CAUTION) \
|
||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_OVERHEAT) \
|
||||
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||
XX(EVENT_BATTERY_WARMED_UP) \
|
||||
XX(EVENT_LOW_SOH) \
|
||||
|
|
|
@ -28,6 +28,7 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
|
|||
#define INTERVAL_100_MS_DELAYED 120
|
||||
#define INTERVAL_500_MS_DELAYED 550
|
||||
|
||||
#define MAX_CAN_FAILURES 500 // Amount of malformed CAN messages to allow before raising a warning
|
||||
#define CAN_STILL_ALIVE \
|
||||
12 // Set by battery each time we get a CAN message. Decrements every 5seconds. Incase we reach 0 (after 60 seconds of inactivity)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String cellmonitor_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
// Page format
|
||||
content += "<style>";
|
||||
|
|
|
@ -13,7 +13,7 @@ const char EVENTS_HTML_END[] = R"=====(
|
|||
)=====";
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content.reserve(5000);
|
||||
// Page format
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const char index_html[] = R"rawliteral(
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style><h2>Battery Emulator</h2>%ABC%
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style>%X%
|
||||
)rawliteral";
|
||||
|
||||
/* The above code is minified (https://kangax.github.io/html-minifier/) to increase performance. Here is the full HTML function:
|
||||
|
@ -14,8 +14,7 @@ const char index_html[] = R"rawliteral(
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Battery Emulator</h2>
|
||||
%ABC%
|
||||
%X%
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String settings_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
//Page format
|
||||
content += "<style>";
|
||||
|
|
|
@ -30,7 +30,7 @@ bool ota_active = false;
|
|||
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
|
||||
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
|
||||
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
|
||||
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
|
@ -358,8 +358,9 @@ void init_ElegantOTA() {
|
|||
}
|
||||
|
||||
String processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content += "<h2>" + String(ssidAP) + "</h2>"; // ssidAP name is used as header name
|
||||
//Page format
|
||||
content += "<style>";
|
||||
content += "body { background-color: black; color: white; }";
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "system_settings.h"
|
||||
|
||||
#include "devboard/hal/hal.h"
|
||||
#include "devboard/safety/safety.h"
|
||||
#include "devboard/utils/time_meas.h"
|
||||
#include "devboard/utils/types.h"
|
||||
|
||||
|
|
Binary file not shown.
BIN
Software/src/lib/smaresca-SimpleISA/DOCUMENTATION/23430.pdf
Normal file
BIN
Software/src/lib/smaresca-SimpleISA/DOCUMENTATION/23430.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
http://www.digikey.com/short/3c2wwr
|
||||
|
||||
This digikey shopping cart contains all the connectors and pins
|
||||
for the ISA IVT-1K-U3-TOI-CAN2-12 Current Sensor
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
9
Software/src/lib/smaresca-SimpleISA/README.md
Normal file
9
Software/src/lib/smaresca-SimpleISA/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SimpleISA
|
||||
Simple library for IVT shunts.
|
||||
Based on the EVTV library of 2016, revised for use with CHAdeMO.
|
||||
Originally intended to integrate with Arduino Due.
|
||||
Adapted for ESP32 and ESP32-Arduino-CAN for use in the Battery-Emulator project https://github.com/dalathegreat/Battery-Emulator
|
||||
hosted at https://github.com/smaresca/SimpleISA-ESP32-Arduino-CAN
|
||||
|
||||
Derived from https://github.com/isaac96/simpleISA/ and https://github.com/damienmaguire/SimpleISA/
|
||||
|
396
Software/src/lib/smaresca-SimpleISA/SimpleISA.cpp
Normal file
396
Software/src/lib/smaresca-SimpleISA/SimpleISA.cpp
Normal file
|
@ -0,0 +1,396 @@
|
|||
/* This library supports ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
|
||||
|
||||
|
||||
|
||||
This library was written by Jack Rickard of EVtv - http://www.evtv.me
|
||||
copyright 2014
|
||||
You are licensed to use this library for any purpose, commercial or private,
|
||||
without restriction.
|
||||
|
||||
2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "SimpleISA.h"
|
||||
|
||||
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }
|
||||
|
||||
|
||||
ISA::ISA() // Define the constructor.
|
||||
{
|
||||
|
||||
timestamp = millis();
|
||||
debug=false;
|
||||
debug2=false;
|
||||
framecount=0;
|
||||
firstframe=true;
|
||||
}
|
||||
|
||||
|
||||
ISA::~ISA() //Define destructor
|
||||
{
|
||||
}
|
||||
|
||||
void ISA::begin(int Port, int speed)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ISA::handleFrame(CAN_frame_t *frame)
|
||||
|
||||
//This is our CAN interrupt service routine to catch inbound frames
|
||||
{
|
||||
|
||||
|
||||
switch (frame->MsgID)
|
||||
{
|
||||
case 0x511:
|
||||
|
||||
break;
|
||||
|
||||
case 0x521:
|
||||
handle521(frame);
|
||||
break;
|
||||
|
||||
case 0x522:
|
||||
handle522(frame);
|
||||
break;
|
||||
|
||||
case 0x523:
|
||||
handle523(frame);
|
||||
break;
|
||||
|
||||
case 0x524:
|
||||
handle524(frame);
|
||||
break;
|
||||
|
||||
case 0x525:
|
||||
handle525(frame);
|
||||
break;
|
||||
|
||||
case 0x526:
|
||||
handle526(frame);
|
||||
break;
|
||||
|
||||
case 0x527:
|
||||
handle527(frame);
|
||||
break;
|
||||
|
||||
case 0x528:
|
||||
handle528(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
if(debug)printCAN(frame);
|
||||
}
|
||||
|
||||
void ISA::handle521(CAN_frame_t *frame) //AMperes
|
||||
|
||||
{
|
||||
framecount++;
|
||||
long current=0;
|
||||
current = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
milliamps=current;
|
||||
Amperes=current/1000.0f;
|
||||
|
||||
if(debug2)Serial<<"Current: "<<Amperes<<" amperes "<<milliamps<<" ma frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
void ISA::handle522(CAN_frame_t *frame) //Voltage
|
||||
|
||||
{
|
||||
framecount++;
|
||||
long volt=0;
|
||||
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage=volt/1000.0f;
|
||||
Voltage1=Voltage-(Voltage2+Voltage3);
|
||||
if(framecount<150)
|
||||
{
|
||||
VoltageLO=Voltage;
|
||||
Voltage1LO=Voltage1;
|
||||
}
|
||||
if(Voltage<VoltageLO && framecount>150)VoltageLO=Voltage;
|
||||
if(Voltage>VoltageHI && framecount>150)VoltageHI=Voltage;
|
||||
if(Voltage1<Voltage1LO && framecount>150)Voltage1LO=Voltage1;
|
||||
if(Voltage1>Voltage1HI && framecount>150)Voltage1HI=Voltage1;
|
||||
|
||||
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 1: "<<Voltage1<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
void ISA::handle523(CAN_frame_t *frame) //Voltage2
|
||||
|
||||
{
|
||||
framecount++;
|
||||
long volt=0;
|
||||
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage2=volt/1000.0f;
|
||||
if(Voltage2>3)Voltage2-=Voltage3;
|
||||
if(framecount<150)Voltage2LO=Voltage2;
|
||||
if(Voltage2<Voltage2LO && framecount>150)Voltage2LO=Voltage2;
|
||||
if(Voltage2>Voltage2HI&& framecount>150)Voltage2HI=Voltage2;
|
||||
|
||||
|
||||
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 2: "<<Voltage2<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
|
||||
|
||||
|
||||
}
|
||||
|
||||
void ISA::handle524(CAN_frame_t *frame) //Voltage3
|
||||
|
||||
{
|
||||
framecount++;
|
||||
long volt=0;
|
||||
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage3=volt/1000.0f;
|
||||
if(framecount<150)Voltage3LO=Voltage3;
|
||||
if(Voltage3<Voltage3LO && framecount>150 && Voltage3>10)Voltage3LO=Voltage3;
|
||||
if(Voltage3>Voltage3HI && framecount>150)Voltage3HI=Voltage3;
|
||||
|
||||
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 3: "<<Voltage3<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
|
||||
}
|
||||
|
||||
void ISA::handle525(CAN_frame_t *frame) //Temperature
|
||||
|
||||
{
|
||||
framecount++;
|
||||
long temp=0;
|
||||
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Temperature=temp/10;
|
||||
|
||||
if(debug2)Serial<<"Temperature: "<<Temperature<<" C frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ISA::handle526(CAN_frame_t *frame) //Kilowatts
|
||||
|
||||
{
|
||||
framecount++;
|
||||
watt=0;
|
||||
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
KW=watt/1000.0f;
|
||||
|
||||
if(debug2)Serial<<"Power: "<<watt<<" Watts "<<KW<<" kW frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ISA::handle527(CAN_frame_t *frame) //Ampere-Hours
|
||||
|
||||
{
|
||||
framecount++;
|
||||
As=0;
|
||||
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
|
||||
|
||||
AH+=(As-lastAs)/3600.0f;
|
||||
lastAs=As;
|
||||
|
||||
|
||||
if(debug2)Serial<<"Amphours: "<<AH<<" Ampseconds: "<<As<<" frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
void ISA::handle528(CAN_frame_t *frame) //kiloWatt-hours
|
||||
|
||||
{
|
||||
framecount++;
|
||||
|
||||
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
KWH+=(wh-lastWh)/1000.0f;
|
||||
lastWh=wh;
|
||||
if(debug2)Serial<<"KiloWattHours: "<<KWH<<" Watt Hours: "<<wh<<" frames:"<<framecount<<"\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ISA::printCAN(CAN_frame_t *frame)
|
||||
{
|
||||
|
||||
//This routine simply prints a timestamp and the contents of the
|
||||
//incoming CAN message
|
||||
|
||||
milliseconds = (int) (millis()/1) %1000 ;
|
||||
seconds = (int) (millis() / 1000) % 60 ;
|
||||
minutes = (int) ((millis() / (1000*60)) % 60);
|
||||
hours = (int) ((millis() / (1000*60*60)) % 24);
|
||||
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
|
||||
Serial<<buffer<<" ";
|
||||
sprintf(bigbuffer,"%02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
frame->MsgID, frame->data.u8[0],frame->data.u8[1],frame->data.u8[2],
|
||||
frame->data.u8[3],frame->data.u8[4],frame->data.u8[5],frame->data.u8[6],frame->data.u8[7],0);
|
||||
Serial<<"Rcvd ISA frame: 0x"<<bigbuffer<<"\n";
|
||||
|
||||
}
|
||||
void ISA::initialize()
|
||||
{
|
||||
|
||||
|
||||
firstframe=false;
|
||||
STOP();
|
||||
delay(700);
|
||||
for(int i=0;i<9;i++)
|
||||
{
|
||||
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=(0x20+i);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x02;
|
||||
outframe.data.u8[3]=(0x60+(i*18));
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe);
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
}
|
||||
// delay(500);
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void ISA::STOP()
|
||||
{
|
||||
|
||||
//SEND STOP///////
|
||||
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug) {printCAN(&outframe);} //If the debug variable is set, show our transmitted frame
|
||||
}
|
||||
void ISA::sendSTORE()
|
||||
{
|
||||
|
||||
//SEND STORE///////
|
||||
|
||||
outframe.data.u8[0]=0x32;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
|
||||
|
||||
}
|
||||
|
||||
void ISA::START()
|
||||
{
|
||||
|
||||
//SEND START///////
|
||||
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x01;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
|
||||
}
|
||||
|
||||
void ISA::RESTART()
|
||||
{
|
||||
//Has the effect of zeroing AH and KWH
|
||||
|
||||
outframe.data.u8[0]=0x3F;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
|
||||
}
|
||||
|
||||
|
||||
void ISA::deFAULT()
|
||||
{
|
||||
//Returns module to original defaults
|
||||
|
||||
outframe.data.u8[0]=0x3D;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
|
||||
}
|
||||
|
||||
|
||||
void ISA::initCurrent()
|
||||
{
|
||||
STOP();
|
||||
delay(500);
|
||||
|
||||
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=(0x21);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=(0x61);
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
if(debug)printCAN(&outframe);
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
|
||||
// delay(500);
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
}
|
||||
|
104
Software/src/lib/smaresca-SimpleISA/SimpleISA.h
Normal file
104
Software/src/lib/smaresca-SimpleISA/SimpleISA.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
#ifndef SimpleISA_h
|
||||
#define SimpleISA_h
|
||||
|
||||
/* This library supports the ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
|
||||
|
||||
This library was written by Jack Rickard of EVtv - http://www.evtv.me copyright 2016
|
||||
You are licensed to use this library for any purpose, commercial or private,
|
||||
without restriction.
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "../miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
class ISA
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public:
|
||||
ISA();
|
||||
~ISA();
|
||||
void initialize();
|
||||
void begin(int Port, int speed);
|
||||
void initCurrent();
|
||||
void sendSTORE();
|
||||
void STOP();
|
||||
void START();
|
||||
void RESTART();
|
||||
void deFAULT();
|
||||
|
||||
|
||||
float Amperes; // Floating point with current in Amperes
|
||||
double AH; //Floating point with accumulated ampere-hours
|
||||
double KW;
|
||||
double KWH;
|
||||
|
||||
|
||||
double Voltage;
|
||||
double Voltage1;
|
||||
double Voltage2;
|
||||
double Voltage3;
|
||||
double VoltageHI;
|
||||
double Voltage1HI;
|
||||
double Voltage2HI;
|
||||
double Voltage3HI;
|
||||
double VoltageLO;
|
||||
double Voltage1LO;
|
||||
double Voltage2LO;
|
||||
double Voltage3LO;
|
||||
|
||||
double Temperature;
|
||||
|
||||
bool debug;
|
||||
bool debug2;
|
||||
bool firstframe;
|
||||
int framecount;
|
||||
unsigned long timestamp;
|
||||
double milliamps;
|
||||
long watt;
|
||||
long As;
|
||||
long lastAs;
|
||||
long wh;
|
||||
long lastWh;
|
||||
void handleFrame(CAN_frame_t *frame); // CAN handler
|
||||
uint8_t page;
|
||||
|
||||
private:
|
||||
CAN_frame_t frame;
|
||||
unsigned long elapsedtime;
|
||||
double ampseconds;
|
||||
int milliseconds ;
|
||||
int seconds;
|
||||
int minutes;
|
||||
int hours;
|
||||
char buffer[9];
|
||||
char bigbuffer[90];
|
||||
uint32_t inbox;
|
||||
CAN_frame_t outframe = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x411,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
|
||||
|
||||
void printCAN(CAN_frame_t *frame);
|
||||
void handle521(CAN_frame_t *frame);
|
||||
void handle522(CAN_frame_t *frame);
|
||||
void handle523(CAN_frame_t *frame);
|
||||
void handle524(CAN_frame_t *frame);
|
||||
void handle525(CAN_frame_t *frame);
|
||||
void handle526(CAN_frame_t *frame);
|
||||
void handle527(CAN_frame_t *frame);
|
||||
void handle528(CAN_frame_t *frame);
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif /* SimpleISA_h */
|
|
@ -0,0 +1,188 @@
|
|||
|
||||
|
||||
|
||||
#include <due_can.h>
|
||||
#include "variant.h"
|
||||
#include <SimpleISA.h>
|
||||
|
||||
#define Serial SerialUSB //Use native port
|
||||
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; } //Allow streaming
|
||||
|
||||
float Version=2.00;
|
||||
uint16_t loopcount=0;
|
||||
unsigned long startime=0;
|
||||
unsigned long elapsedtime=0;
|
||||
uint port=0;
|
||||
uint16_t datarate=500;
|
||||
|
||||
ISA Sensor; //Instantiate ISA Module Sensor object to measure current and voltage
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Sensor.begin(port,datarate); //Start ISA object on CAN 0 at 500 kbps
|
||||
|
||||
Serial<<"\nISA Scale Startup Successful \n";
|
||||
|
||||
printMenu();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if(loopcount++==40000)
|
||||
{
|
||||
printStatus();
|
||||
loopcount-0;
|
||||
}
|
||||
checkforinput(); //Check keyboard for user input
|
||||
}
|
||||
|
||||
|
||||
void printStatus()
|
||||
{
|
||||
char buffer[40];
|
||||
//printimestamp();
|
||||
|
||||
sprintf(buffer,"%4.2f",Sensor.Voltage);
|
||||
Serial<<"Volt:"<<buffer<<"v ";
|
||||
sprintf(buffer,"%4.2f",Sensor.Voltage1);
|
||||
Serial<<"V1:"<<buffer<<"v ";
|
||||
sprintf(buffer,"%4.2f",Sensor.Voltage2);
|
||||
Serial<<"V2:"<<buffer<<"v ";
|
||||
sprintf(buffer,"%4.2f",Sensor.Voltage3);
|
||||
Serial<<"V3:"<<buffer<<"v ";
|
||||
|
||||
sprintf(buffer,"%4.3f",Sensor.Amperes);
|
||||
Serial<<"Amps:"<<buffer<<"A ";
|
||||
|
||||
sprintf(buffer,"%4.3f",Sensor.KW);
|
||||
Serial<<buffer<<"kW ";
|
||||
|
||||
sprintf(buffer,"%4.3f",Sensor.AH);
|
||||
Serial<<buffer<<"Ah ";
|
||||
|
||||
sprintf(buffer,"%4.3f",Sensor.KWH);
|
||||
Serial<<buffer<<"kWh";
|
||||
|
||||
sprintf(buffer,"%4.0f",Sensor.Temperature);
|
||||
Serial<<buffer<<"C ";
|
||||
|
||||
Serial<<"Frame:"<<Sensor.framecount<<" \n";
|
||||
}
|
||||
|
||||
void printimestamp()
|
||||
{
|
||||
//Prints a timestamp to the serial port
|
||||
elapsedtime=millis() - startime;
|
||||
|
||||
int milliseconds = (elapsedtime/1) %1000 ;
|
||||
int seconds = (elapsedtime / 1000) % 60 ;
|
||||
int minutes = ((elapsedtime / (1000*60)) % 60);
|
||||
int hours = ((elapsedtime / (1000*60*60)) % 24);
|
||||
char buffer[19];
|
||||
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
|
||||
Serial<<buffer<<" ";
|
||||
}
|
||||
|
||||
void printMenu()
|
||||
{
|
||||
Serial<<"\f\n=========== ISA Scale Sample Program Version "<<Version<<" ==============\n************ List of Available Commands ************\n\n";
|
||||
Serial<<" ? - Print this menu\n ";
|
||||
Serial<<" d - toggles Debug off and on to print recieved CAN data traffic\n";
|
||||
Serial<<" D - toggles Debug2 off and on to print derived values\n";
|
||||
Serial<<" f - zero frame count\n ";
|
||||
Serial<<" i - initialize new sensor\n ";
|
||||
Serial<<" p - Select new CAN port\n ";
|
||||
Serial<<" r - Set new datarate\n ";
|
||||
Serial<<" z - zero ampere-hours\n ";
|
||||
|
||||
Serial<<"**************************************************************\n==============================================================\n\n";
|
||||
|
||||
}
|
||||
|
||||
void checkforinput()
|
||||
{
|
||||
//Checks for keyboard input from Native port
|
||||
if (Serial.available())
|
||||
{
|
||||
int inByte = Serial.read();
|
||||
switch (inByte)
|
||||
{
|
||||
case 'z': //Zeroes ampere-hours
|
||||
Sensor.KWH=0;
|
||||
Sensor.AH=0;
|
||||
Sensor.RESTART();
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
getPort();
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
getRate();
|
||||
break;
|
||||
|
||||
|
||||
case 'f':
|
||||
Sensor.framecount=0;
|
||||
break;
|
||||
|
||||
case 'd': //Causes ISA object to print incoming CAN messages for debugging
|
||||
Sensor.debug=!Sensor.debug;
|
||||
break;
|
||||
|
||||
case 'D': //Causes ISA object to print derived values for debugging
|
||||
Sensor.debug2=!Sensor.debug2;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
Sensor.initialize();
|
||||
break;
|
||||
|
||||
case '?': //Print a menu describing these functions
|
||||
printMenu();
|
||||
break;
|
||||
|
||||
case '1':
|
||||
Sensor.STOP();
|
||||
break;
|
||||
|
||||
case '3':
|
||||
Sensor.START();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void getRate()
|
||||
{
|
||||
Serial<<"\n Enter the Data Rate in Kbps you want for CAN : ";
|
||||
while(Serial.available() == 0){}
|
||||
float V = Serial.parseFloat();
|
||||
if(V>0)
|
||||
{
|
||||
Serial<<"Datarate:"<<V<<"\n\n";
|
||||
uint8_t rate=V;
|
||||
|
||||
datarate=V*1000;
|
||||
|
||||
Sensor.begin(port,datarate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void getPort()
|
||||
{
|
||||
Serial<<"\n Enter port selection: c0=CAN0 c1=CAN1 ";
|
||||
while(Serial.available() == 0){}
|
||||
int P = Serial.parseInt();
|
||||
if(P>1) Serial<<"Entry out of range, enter 0 or 1 \n";
|
||||
else
|
||||
{
|
||||
port=P;
|
||||
Sensor.begin(port,datarate);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue