feat: S12 — vcucmd 3-state command from VCU (replaces binary vcustop) #10

Open
bastian wants to merge 3 commits from feat/s12-vcucmd-3state into fix/canmap-revert-clear-add-reentrancy-guard
Owner

Promote 0x212 byte 3 from S11's binary vcustop to the full Dilong-style
OBC_ControlCMD vocabulary so the VCU's two charger paths can share more
state-machine logic. VCU is now authoritative for the OI-OBC lifecycle.

What

Value Name Meaning
0 Charging Walk to / hold at EVSEACTIVATE, produce current
1 Stopped Sit idle in OFF/WAITSTART/ENABLE, no current
2 Complete Drop to STOP (cycle terminated by VCU)

Internal FSM changes:

  • ENABLE → ACTIVATE is now gated on vcucmd == Charging (was unconditional). Complete drops to STOP; Stopped holds HV-armed.
  • EVSEACTIVATE self-decided exits (CheckVoltage, CheckTimeout) are removed. Those were band-aids for the absence of an authoritative VCU stop signal. vcucmd == Stopped parks back in ENABLE (HV held); Complete drops to STOP. CheckUnplugged + CheckChargerFaults still own hardware-safety exits.
  • STOP → OFF now also triggers on vcucmd != Complete (previously unplug-only).

Why this matters

S11's binary vcustop wedged the charger on every limit-raise mid-COMPLETE: VCU set vcustop=1 → charger STOP → user raised limit → VCU cleared the BMS-side latch → VCU went IDLE expecting STOP→OFF→WAITSTART... but STOP only exited on physical unplug. User had to unplug+replug to restart. S12 fixes that by exiting STOP whenever vcucmd != Complete.

Param rename

Param::vcustopParam::vcucmd with new 3-state enum (VCUCMDS = "0=Charging, 1=Stopped, 2=Complete"). External openinverter web UI tooling that reads/writes the S11 vcustop param will see the new vcucmd param — single-user bench, acceptable.

VERSTR bumped to S12.

Companion VCU PR

stm32-hal-vcu refactor/charger-slot-polymorphism branch — lands the matching VCU_OIOBCCommand_t enum + Charger_TransmitChargeLimit signature change + _UpdateChargingStateOIOBC simplification.

Test plan

  • Build verified locally (Makefile, GCC ARM)
  • Bench: SoC=80%, limit=75%, plug in → clean stop, COMPLETE (regression)
  • Bench: raise limit 75→93 mid-COMPLETE → charging resumes at ~9 kW within ~3-5 s (S11 wedged here)
  • Bench: lower limit 93→75 mid-CHARGE_ACTIVE → clean stop (regression)
  • Bench: unplug at any state → charger goes to OFF immediately (hardware safety)
  • Bench: raise limit while no EVSE plugged → no spurious charger activity
Promote 0x212 byte 3 from S11's binary `vcustop` to the full Dilong-style `OBC_ControlCMD` vocabulary so the VCU's two charger paths can share more state-machine logic. VCU is now authoritative for the OI-OBC lifecycle. ## What | Value | Name | Meaning | |-------|------|---------| | 0 | Charging | Walk to / hold at EVSEACTIVATE, produce current | | 1 | Stopped | Sit idle in OFF/WAITSTART/ENABLE, no current | | 2 | Complete | Drop to STOP (cycle terminated by VCU) | Internal FSM changes: - **ENABLE → ACTIVATE** is now gated on `vcucmd == Charging` (was unconditional). `Complete` drops to STOP; `Stopped` holds HV-armed. - **EVSEACTIVATE** self-decided exits (`CheckVoltage`, `CheckTimeout`) are removed. Those were band-aids for the absence of an authoritative VCU stop signal. `vcucmd == Stopped` parks back in ENABLE (HV held); `Complete` drops to STOP. `CheckUnplugged` + `CheckChargerFaults` still own hardware-safety exits. - **STOP → OFF** now also triggers on `vcucmd != Complete` (previously unplug-only). ## Why this matters S11's binary `vcustop` wedged the charger on every limit-raise mid-COMPLETE: VCU set `vcustop=1` → charger STOP → user raised limit → VCU cleared the BMS-side latch → VCU went IDLE expecting STOP→OFF→WAITSTART... but STOP only exited on physical unplug. User had to unplug+replug to restart. S12 fixes that by exiting STOP whenever `vcucmd != Complete`. ## Param rename `Param::vcustop` → `Param::vcucmd` with new 3-state enum (`VCUCMDS = "0=Charging, 1=Stopped, 2=Complete"`). External openinverter web UI tooling that reads/writes the S11 vcustop param will see the new vcucmd param — single-user bench, acceptable. VERSTR bumped to S12. ## Companion VCU PR stm32-hal-vcu `refactor/charger-slot-polymorphism` branch — lands the matching `VCU_OIOBCCommand_t` enum + `Charger_TransmitChargeLimit` signature change + `_UpdateChargingStateOIOBC` simplification. ## Test plan - [ ] Build verified locally (Makefile, GCC ARM) - [ ] Bench: SoC=80%, limit=75%, plug in → clean stop, COMPLETE (regression) - [ ] Bench: raise limit 75→93 mid-COMPLETE → charging resumes at ~9 kW within ~3-5 s (S11 wedged here) - [ ] Bench: lower limit 93→75 mid-CHARGE_ACTIVE → clean stop (regression) - [ ] Bench: unplug at any state → charger goes to OFF immediately (hardware safety) - [ ] Bench: raise limit while no EVSE plugged → no spurious charger activity
bastian added 1 commit 2026-05-18 18:12:26 -04:00
Promote 0x212 byte 3 from S11's binary vcustop to the full Dilong-style
OBC_ControlCMD vocabulary:
  0 = Charging — walk to / hold at EVSEACTIVATE, produce current
  1 = Stopped  — sit idle in OFF/WAITSTART/ENABLE, no current
  2 = Complete — drop to STOP (cycle terminated by VCU)

VCU is now authoritative for the charge-cycle lifecycle. The charger
keeps hardware-level wisdom (EVSE pilot via CheckStartCondition,
plug-out safety via CheckUnplugged, module-fault detection via
CheckChargerFaults) but obeys vcucmd for "should I be charging?".

FSM changes:
  - ENABLE→ACTIVATE now gated on vcucmd==Charging (was unconditional).
    ENABLE on vcucmd==Complete drops to STOP; vcucmd==Stopped holds
    HV-armed waiting for next Charging command.
  - EVSEACTIVATE self-decided exits (CheckVoltage, CheckTimeout) are
    gone. Those were band-aids for the absence of an authoritative VCU
    stop signal. vcucmd==Stopped now parks back in ENABLE (HV held);
    vcucmd==Complete drops to STOP. CheckUnplugged + CheckChargerFaults
    still own hardware-safety exits.
  - STOP→OFF now also triggers on vcucmd != Complete (was unplug-only).
    This is the fix for the "raise ChargeLimit mid-COMPLETE doesn't
    restart charging" wedge — the VCU's vcucmd flip from Complete to
    Stopped on a limit-raise lets the natural OFF→WAITSTART→ENABLE→
    ACTIVATE→EVSEACTIVATE flow resume charging without an unplug cycle.

Param rename vcustop→vcucmd. External tooling that reads/writes the
S11 vcustop param via the openinverter web UI will see the new vcucmd
param with 3-state semantics — single-user bench, acceptable.

VERSTR bumped to S12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bastian added 1 commit 2026-05-21 13:33:31 -04:00
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>
bastian added 1 commit 2026-05-21 16:23:50 -04:00
The iteration O 3-tick ramp ended with dcCurController.SetRef(0) before
transitioning to STOP. Param::Change only fires when idcspnt or idclim
parameter values change — a normal STOP → OFF → WAITSTART → ENABLE →
ACTIVATE → RUN → EVSEACTIVATE restart cycle never touches those, so
nothing restored the PI reference. On the next entry to EVSEACTIVATE
the controller targeted 0 A and the charger sipped ~0.1 kW.

Bench-confirmed 2026-05-21 on iteration O firmware: after lowering the
ChargeLimit_pct to drop into COMPLETE and then raising it back, charging
restarted at 0.1 kW instead of ramping to 9 kW.

Fix: on the final ramp tick, restore SetRef to MIN(idcspnt, idclim)
instead of zeroing it. The GPIO clears (acpres_out, evseact_out) still
cut AC enable so the output current decays through the filter caps;
the reference value is irrelevant during STOP but matters for the next
EVSEACTIVATE entry.

The ramp-restore block in the vcucmd != Complete branch (charger.cpp:431
in iteration O) only handles the mid-ramp reversal case (counter ≠ 0);
it didn't help when the ramp completed cleanly. This commit closes that
gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/s12-vcucmd-3state:feat/s12-vcucmd-3state
git checkout feat/s12-vcucmd-3state
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Skudak/stm32-openinverter-teslacharger#10