diff --git a/Software/Software.ino b/Software/Software.ino index 4fccaea9..2074f191 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -57,7 +57,7 @@ const char* version_number = "7.8.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers -unsigned long previousMillis10ms = 50; +unsigned long previousMillis10ms = 0; unsigned long previousMillisUpdateVal = 0; // CAN parameters @@ -118,6 +118,16 @@ MyTimer check_pause_2s(INTERVAL_2_S); enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED }; State contactorStatus = DISCONNECTED; +#define ON 1 +#define OFF 0 + +#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic +#undef ON +#define ON 0 +#undef OFF +#define OFF 1 +#endif + #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 @@ -125,20 +135,32 @@ State contactorStatus = DISCONNECTED; #define PRECHARGE_TIME_MS 160 #define NEGATIVE_CONTACTOR_TIME_MS 1000 #define POSITIVE_CONTACTOR_TIME_MS 2000 -#ifdef PWM_CONTACTOR_CONTROL #define PWM_Freq 20000 // 20 kHz frequency, beyond audible range #define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100% -#define PWM_Hold_Duty 250 -#define PWM_Off_Duty 0 -#define PWM_On_Duty 1023 +#define PWM_HOLD_DUTY 250 +#define PWM_OFF_DUTY 0 +#define PWM_ON_DUTY 1023 #define POSITIVE_PWM_Ch 0 #define NEGATIVE_PWM_Ch 1 -#endif unsigned long prechargeStartTime = 0; unsigned long negativeStartTime = 0; unsigned long timeSpentInFaultedMode = 0; #endif +void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF) { +#ifdef PWM_CONTACTOR_CONTROL + if (pwm_freq != 0xFFFFFFFFFF) { + ledcWrite(pin, pwm_freq); + return; + } +#endif + if (direction == 1) { + digitalWrite(pin, HIGH); + } else { // 0 + digitalWrite(pin, LOW); + } +} + #ifdef EQUIPMENT_STOP_BUTTON const unsigned long equipment_button_long_press_duration = 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH @@ -280,9 +302,6 @@ void core_loop(void* task_time_us) { previousMillis10ms = millis(); led_exe(); handle_contactors(); // Take care of startup precharge/contactor closing -#ifdef DOUBLE_BATTERY - check_interconnect_available(); -#endif } END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); @@ -292,6 +311,7 @@ void core_loop(void* task_time_us) { update_values_battery(); // Fetch battery values #ifdef DOUBLE_BATTERY update_values_battery2(); + check_interconnect_available(); #endif update_calculated_values(); #ifndef SERIAL_LINK_RECEIVER @@ -497,27 +517,34 @@ void init_CAN() { void init_contactors() { // Init contactor pins #ifdef CONTACTOR_CONTROL -#ifndef PWM_CONTACTOR_CONTROL - pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); - pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); -#else +#ifdef PWM_CONTACTOR_CONTROL ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); // Set Positive PWM to 0% - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); // Set Negative PWM to 0% + ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Positive PWM to 0% + ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Negative PWM to 0% +#else //Normal CONTACTOR_CONTROL + pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); + set(POSITIVE_CONTACTOR_PIN, OFF); + pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(NEGATIVE_CONTACTOR_PIN, OFF); #endif pinMode(PRECHARGE_PIN, OUTPUT); - digitalWrite(PRECHARGE_PIN, LOW); -#endif + set(PRECHARGE_PIN, OFF); +#endif //CONTACTOR_CONTROL +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + + pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); +#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY // Init BMS contactor #ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor pinMode(BMS_POWER, OUTPUT); digitalWrite(BMS_POWER, HIGH); -#endif +#endif //HW_STARK } void init_rs485() { @@ -695,14 +722,18 @@ void check_interconnect_available() { return; // Both voltage values need to be available to start check } - if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 30) { // If we are within 3.0V + uint16_t voltage_diff = abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV); + + if (voltage_diff <= 30) { // If we are within 3.0V between the batteries clear_event(EVENT_VOLTAGE_DIFFERENCE); - if (datalayer.battery.status.bms_status != FAULT) { // Only proceed if we are not in faulted state + if (datalayer.battery.status.bms_status == FAULT) { + // If main battery is in fault state, disengage the second battery + datalayer.system.status.battery2_allows_contactor_closing = false; + } else { // If main battery is OK, allow second battery to join datalayer.system.status.battery2_allows_contactor_closing = true; } - } else { //We are over 3.0V diff - set_event(EVENT_VOLTAGE_DIFFERENCE, - (uint8_t)(abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) / 10)); + } else { //Voltage between the two packs is too large + set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10)); } } #endif //DOUBLE_BATTERY @@ -712,6 +743,10 @@ void handle_contactors() { datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN); #endif +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + handle_contactors_battery2(); +#endif + #ifdef CONTACTOR_CONTROL // First check if we have any active errors, incase we do, turn off the battery if (datalayer.battery.status.bms_status == FAULT) { @@ -720,50 +755,38 @@ void handle_contactors() { timeSpentInFaultedMode = 0; } - //handle contactor control SHUTDOWN_REQUESTED vs DISCONNECTED - if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS || - (datalayer.system.settings.equipment_stop_active && contactorStatus != SHUTDOWN_REQUESTED)) { + //handle contactor control SHUTDOWN_REQUESTED + if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) { contactorStatus = SHUTDOWN_REQUESTED; - datalayer.system.settings.equipment_stop_active = true; - } - if (contactorStatus == SHUTDOWN_REQUESTED && !datalayer.system.settings.equipment_stop_active) { - contactorStatus = DISCONNECTED; } + if (contactorStatus == SHUTDOWN_REQUESTED) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); - datalayer.system.status.contactor_control_closed = false; + datalayer.system.status.contactors_engaged = false; return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) } // After that, check if we are OK to start turning on the battery if (contactorStatus == DISCONNECTED) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); if (datalayer.system.status.battery_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing) { + datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) { contactorStatus = PRECHARGE; } } // In case the inverter requests contactors to open, set the state accordingly if (contactorStatus == COMPLETED) { - if (!datalayer.system.status.inverter_allows_contactor_closing) + //Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable) + if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) { contactorStatus = DISCONNECTED; + } // Skip running the state machine below if it has already completed return; } @@ -772,18 +795,14 @@ void handle_contactors() { // Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge switch (contactorStatus) { case PRECHARGE: - digitalWrite(PRECHARGE_PIN, HIGH); + set(PRECHARGE_PIN, ON); prechargeStartTime = currentTime; contactorStatus = NEGATIVE; break; case NEGATIVE: if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) { -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, HIGH); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_On_Duty); -#endif + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); negativeStartTime = currentTime; contactorStatus = POSITIVE; } @@ -791,24 +810,18 @@ void handle_contactors() { case POSITIVE: if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH); -#else - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_On_Duty); -#endif + set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); contactorStatus = PRECHARGE_OFF; } break; case PRECHARGE_OFF: if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifdef PWM_CONTACTOR_CONTROL - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Hold_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Hold_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); + set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); contactorStatus = COMPLETED; - datalayer.system.status.contactor_control_closed = true; + datalayer.system.status.contactors_engaged = true; } break; default: @@ -817,6 +830,20 @@ void handle_contactors() { #endif // CONTACTOR_CONTROL } +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY +void handle_contactors_battery2() { + if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) { + set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); + set(SECOND_POSITIVE_CONTACTOR_PIN, ON); + datalayer.system.status.contactors_battery2_engaged = true; + } else { // Closing contactors on secondary battery not allowed + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + datalayer.system.status.contactors_battery2_engaged = false; + } +} +#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY + void update_calculated_values() { /* Calculate allowed charge/discharge currents*/ if (datalayer.battery.status.voltage_dV > 10) { diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index be34557b..3c6c6763 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -55,12 +55,16 @@ //#define HW_STARK //#define HW_3LB +/* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */ +//#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins) +//#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins) +//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled. +//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting! + /* 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_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting -//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence -//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled. //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) #define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 429d8681..21267422 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -136,10 +136,10 @@ static uint16_t battery2_TEMP = 0; //Temporary value used in s static uint16_t battery2_Wh_Remaining = 0; //Amount of energy in battery, in Wh static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode static uint16_t battery2_MAX = 0; -static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode -static uint16_t battery2_StateOfHealth = 99; //State of health % -static uint16_t battery2_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] -static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] +static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery2_StateOfHealth = 99; //State of health % +static uint16_t battery2_Total_Voltage2 = 0; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] +static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C static int16_t battery2_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55 @@ -514,11 +514,7 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); battery2_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); battery2_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5); - if (battery2_MainRelayOn_flag) { - datalayer.system.status.battery2_allows_contactor_closing = true; - } else { - datalayer.system.status.battery2_allows_contactor_closing = false; - } + //battery2_allows_contactor_closing written by check_interconnect_available(); battery2_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4); battery2_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3); break; diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index f925d30c..dcf75b7b 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -192,7 +192,9 @@ typedef struct { bool inverter_allows_contactor_closing = true; #ifdef CONTACTOR_CONTROL /** True if the contactor controlled by battery-emulator is closed */ - bool contactor_control_closed = false; + bool contactors_engaged = false; + /** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */ + bool contactors_battery2_engaged = false; #endif } DATALAYER_SYSTEM_STATUS_TYPE; diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h index 137d83f3..a54de2a4 100644 --- a/Software/src/devboard/hal/hw_3LB.h +++ b/Software/src/devboard/hal/hw_3LB.h @@ -52,9 +52,9 @@ #define NEGATIVE_CONTACTOR_PIN 33 #define PRECHARGE_PIN 25 -#define 2ND_POSITIVE_CONTACTOR_PIN 13 -#define 2ND_NEGATIVE_CONTACTOR_PIN 16 -#define 2ND_PRECHARGE_PIN 18 +#define SECOND_POSITIVE_CONTACTOR_PIN 13 +#define SECOND_NEGATIVE_CONTACTOR_PIN 16 +#define SECOND_PRECHARGE_PIN 18 // SMA CAN contactor pins #define INVERTER_CONTACTOR_ENABLE_PIN 36 diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 640429dc..360787d2 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -642,7 +642,7 @@ String processor(const String& var) { #ifdef CONTACTOR_CONTROL content += "