Merge pull request #619 from dalathegreat/feature/double-automatic-contactor

Feature: Double contactor support + NC support
This commit is contained in:
Daniel Öster 2024-11-24 01:14:10 +02:00 committed by GitHub
commit 9e74fcd032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 120 additions and 90 deletions

View file

@ -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) {