diff --git a/src/charger.cpp b/src/charger.cpp index 2dc9f34..9a3ee3d 100644 --- a/src/charger.cpp +++ b/src/charger.cpp @@ -383,13 +383,58 @@ void ChargerStateMachine() // VCU command is the primary exit. CheckUnplugged + CheckChargerFaults // remain for hardware safety; the self-decided CheckVoltage/CheckTimeout // exits are gone (S12 — VCU is authoritative). + // + // SKUDAK: ramp the DC-current reference toward zero across 3 ticks + // (300 ms) before transitioning to STOP. Without this, the VCU + // opening its HV contactor at the moment we go STOP triggers a hard + // 23 A → 0 disconnect transient that floods the Tesla BMS state- + // change CAN path and starves the VCU's task scheduler past its + // IWDG window (bench-confirmed 2026-05-21 limit-lower 0x002A loop). + // The VCU also defers its contactor open by ~1500 ms; together + // they make the disconnect graceful even on a hard limit-lower + // mid-RUN. completeRampTicks counts DOWN from 3 → 0; the original + // reference is stashed so if vcucmd reverts to Charging mid-ramp + // we restore cleanly. + static uint8_t completeRampTicks = 0; + static s32fp completeRampOriginalRef = 0; if (vcucmd == VCUCMD_COMPLETE) { - DigIo::acpres_out.Clear(); - DigIo::evseact_out.Clear(); - state = STOP; + if (completeRampTicks == 0) + { + completeRampOriginalRef = MIN(Param::Get(Param::idcspnt), Param::Get(Param::idclim)); + completeRampTicks = 3; + } + if (completeRampTicks > 1) + { + // Scale 2/3 → 1/3 → 0 across remaining ticks. + s32fp scaled = (completeRampOriginalRef * (completeRampTicks - 1)) / 3; + dcCurController.SetRef(scaled); + completeRampTicks--; + // Stay in EVSEACTIVATE this tick; current is decaying. + } + else + { + dcCurController.SetRef(0); + DigIo::acpres_out.Clear(); + DigIo::evseact_out.Clear(); + completeRampTicks = 0; + completeRampOriginalRef = 0; + state = STOP; + } + break; } - else if (vcucmd == VCUCMD_STOPPED) + + // vcucmd not Complete — if we were mid-ramp the user reversed + // (raised limit again); restore reference before the existing + // branches take over. + if (completeRampTicks != 0) + { + dcCurController.SetRef(completeRampOriginalRef); + completeRampTicks = 0; + completeRampOriginalRef = 0; + } + + if (vcucmd == VCUCMD_STOPPED) { // VCU revoked the charge command (e.g. user lowered limit below // current SOC but BMS isn't full yet). Park HV-armed in ENABLE