feat: ramp DC current to zero over 3 ticks before RUN → STOP

When vcucmd transitions to COMPLETE while in EVSEACTIVATE the previous
behavior was to clear acpres_out / evseact_out and transition to STOP
in a single 100 ms tick — cutting the OBC AC enable immediately. The
VCU's HV contactor opens at roughly the same moment; under active 9 kW
charging (~23 A) this hard disconnect transient floods the Tesla BMS
state-change CAN path and starves the VCU's task scheduler past the
IWDG window. Bench-confirmed 2026-05-21 as the trigger for the
iteration N limit-lower 0x002A reset loop.

Add a 3-tick (300 ms) ramp inside EVSEACTIVATE before the STOP
transition: stash the original DC-current reference, scale it 2/3 →
1/3 → 0 across the remaining ticks, and only then clear the AC enable
GPIOs and go STOP. The PI controller's natural response walks the AC
current limit down so output current decays smoothly rather than
hard-stopping.

If vcucmd reverts to Charging mid-ramp (user raised limit again),
restore the original reference cleanly before the existing branches
take over.

The VCU also defers its own HV-contactor open by 1500 ms (see VCU
iteration O commit 5781f47). Belt-and-suspenders: either fix alone
substantially reduces the disconnect transient; together they make
RUN → STOP graceful on any controller that interoperates with this
charger.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bastian de Byl
2026-05-21 13:33:29 -04:00
parent aae23f53a6
commit 0514940877
+46 -1
View File
@@ -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)
{
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;
}
else if (vcucmd == VCUCMD_STOPPED)
break;
}
// 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