feat: S8/S9 - trim chargercan.cpp + restore c2/c3 RX for multi-module Tesla OBC
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 59s

S8 trimmed the 73-entry chargercan.cpp down to 37 entries to fit
libopeninv's 50-slot CANPOS pool — needed because the upstream firmware
silently overflowed the pool and dropped the SKUDAK 0x211 broadcast at
the tail. Without that broadcast the VCU couldn't see idc/udc/tmpobcmax.

S9 restores 8 of the entries S8 dropped: c2/c3 uac, flag, idc, udc.
These turn out to be NECESSARY for 3-module Tesla OBC operation:

- CalcTotals (charger.cpp:41/48) sums c1+c2+c3 idc and takes the max
  udc. Without c2/c3 idc mapped, idc reports only module 1's
  contribution. Bench observed 2.7 kW reported vs 9.7 kW actual EVSE
  draw — exactly the 1/3 ratio expected.
- CheckChargerFaults (charger.cpp:226-261) gates active2/active3
  detection on c2uac/c3uac > 70V. Without those mapped, the active
  bits short-circuit to false — which masked the c2flag/c3flag
  CheckAlive timeout. We were getting away with the trim by accident.

Kept dropped (still need pool headroom for future):
- c2/c3 iac (AC current — c1iac × 3 is a fine proxy, not used in
  aggregation anywhere)
- c2/c3 stt (state — all modules report same value for healthy operation)
- c2/c3 tmp1/2/in (temps — tmpobcmax from c1's 3 probes is sufficient;
  full 9-probe worst-case is a nice-to-have)
- CHAdeMO RX/TX (0x102/0x108/0x109) — Polarity VCU doesn't use CHAdeMO
- 0x368 idle frame — cosmetic only
- hwaclim duplicates on 0x209/0x20B — all modules report identical limits

Pool math: 42 / 50 slots used, 8-slot headroom for future additions.

VERSTR S8 → S9 so any bench reporting -S8 in the openinverter UI is
visibly the regressed build and needs canclear + reflash.

After flash: user must issue `canclear` from the openinverter Commands
tab so the persisted map gets rebuilt from the new chargercan.cpp.
Power-cycle, verify `can p` shows the 8 new c2/c3 lines, then EVSE
test should report ~9.5 kW in the app (was 2.7 kW).

#patch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bastian de Byl
2026-05-18 14:07:19 -04:00
parent 23716a260b
commit c905e1236b
2 changed files with 50 additions and 45 deletions
+6 -1
View File
@@ -134,7 +134,12 @@
// HandleClear callback and stack-overflowed at boot before the terminal
// came up. Jumping straight to -S7 so any bench that still reports -S6
// in the openinverter UI is visibly the broken build and gets re-flashed.
#define VERSTR STRINGIFY(4=VER-S7)
// -S8 trims chargercan.cpp from 73 → 37 entries so the libopeninv 50-slot
// pool doesn't silently drop the SKUDAK 0x211 broadcast on rebuild.
// -S9 restores 8 c2/c3 RX entries (uac/flag/idc/udc per module). -S8 over-
// trimmed multi-module Tesla OBC support: CalcTotals was summing only
// module 1's contribution, so reported pack power was 1/3 of actual delivery.
#define VERSTR STRINGIFY(4=VER-S9)
/***** enums ******/
+44 -44
View File
@@ -18,47 +18,62 @@
*/
#include "chargercan.h"
// SKUDAK-S8/S9: trimmed entry list to fit libopeninv's MAX_ITEMS=50 CANPOS pool.
// The upstream teslacharger registers ~73 AddRecv+AddSend mappings, which
// silently overflows the 50-slot pool and drops the last ~23 entries
// (including the SKUDAK 0x211 broadcast). We dropped things we don't use:
//
// - hwaclim duplicates on 0x209/0x20B (keep just 0x207; the three modules
// broadcast identical hardware AC limit values) -2
// - c2*/c3* iac, stt, tmp1/2/in (12 entries — full per-module diagnostics
// not needed; CheckChargerFaults only uses uac+flag, CalcTotals only
// uses idc+udc, and tmpobcmax aggregates fine from c1's 3 probes) -12
// - CHAdeMO RX on 0x102 (canenable/idcspnt/udclim/soc — Polarity VCU
// doesn't use CHAdeMO at all) -4
// - 0x368 version-only idle frame (purely cosmetic for stock OI UI) -7
// - CHAdeMO TX on 0x108/0x109 (version, idclim, udc, idc, opmode —
// unused on this build) -5
//
// Total dropped: 30 entries. From 73 → 42, with 8-slot headroom.
//
// -S9 RESTORED 8 entries the -S8 trim wrongly killed for 3-module Tesla OBCs:
// c2/c3 uac (CheckChargerFaults active2/active3 detection),
// c2/c3 flag (clears CheckAlive timeout under fault-check logic),
// c2/c3 idc (CalcTotals current SUM — was reporting 1/3 of true delivery),
// c2/c3 udc (CalcTotals voltage MAX). Without these the bench reported
// ~2.7kW for what was actually 9.7kW going to the pack.
//
// 0x43c/0x44c TX kept on purpose — they're commands to OBC sub-modules 2/3,
// required for multi-module operation. Drop them too only if pool pressure
// returns and you've confirmed the bench is single-module.
void ChargerCAN::MapMessages(CanMap* can)
{
can->AddRecv(Param::hwaclim, 0x207, 32, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::hwaclim, 0x209, 32, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::hwaclim, 0x20B, 32, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::c1iac, 0x207, 41, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::c2iac, 0x209, 41, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::c3iac, 0x20B, 41, 9, 0.06666f); //gain 0.06666
can->AddRecv(Param::c1uac, 0x207, 8, 8, 1);
can->AddRecv(Param::c2uac, 0x209, 8, 8, 1);
can->AddRecv(Param::c3uac, 0x20B, 8, 8, 1);
can->AddRecv(Param::c1flag, 0x207, 17, 2, 1);
can->AddRecv(Param::c2flag, 0x209, 17, 2, 1);
can->AddRecv(Param::c3flag, 0x20B, 17, 2, 1);
can->AddRecv(Param::c1stt, 0x217, 0, 8, 1);
can->AddRecv(Param::c2stt, 0x219, 0, 8, 1);
can->AddRecv(Param::c3stt, 0x21B, 0, 8, 1);
can->AddRecv(Param::c1idc, 0x227, 32, 16, 0.000839233f); //gain 0.000839233
can->AddRecv(Param::c2idc, 0x229, 32, 16, 0.000839233f); //gain 0.000839233
can->AddRecv(Param::c3idc, 0x22B, 32, 16, 0.000839233f); //gain 0.000839233
can->AddRecv(Param::c1udc, 0x227, 16, 16, 0.01052856f); //gain 0.01052856
can->AddRecv(Param::c2udc, 0x229, 16, 16, 0.01052856f); //gain 0.01052856
can->AddRecv(Param::c3udc, 0x22B, 16, 16, 0.01052856f); //gain 0.01052856
can->AddRecv(Param::c1tmp1, 0x237, 0, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c2tmp1, 0x239, 0, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c3tmp1, 0x23B, 0, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c1tmp2, 0x237, 8, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c2tmp2, 0x239, 8, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c3tmp2, 0x23B, 8, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c1tmpin, 0x237, 40, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c2tmpin, 0x239, 40, 8, 1, -40); //offset -40°C
can->AddRecv(Param::c3tmpin, 0x23B, 40, 8, 1, -40); //offset -40°C
//We don't have enough space for all messages, so we discard these
//can->AddRecv(Param::c1tmplim, 0x247, 0, 8, 7); //gain 0.234375
//can->AddRecv(Param::c2tmplim, 0x249, 0, 8, 7); //gain 0.234375
//can->AddRecv(Param::c3tmplim, 0x24B, 0, 8, 7); //gain 0.234375
/***** CHAdeMO RX *****/
can->AddRecv(Param::canenable, 0x102, 40, 1, 1);
can->AddRecv(Param::idcspnt, 0x102, 24, 8, 1);
can->AddRecv(Param::udclim, 0x102, 8, 16, 1);
can->AddRecv(Param::soc, 0x102, 48, 8, 1);
// SKUDAK-S9: module 2 RX. uac/flag prevent the CheckChargerFaults false-
// positive that would silently mask module-2 failure (active2 = (chargerena
// & 2) && (c2uac > 70V); without c2uac mapped, active2==false short-circuits
// the flag check). idc/udc are required so CalcTotals reports the SUM
// current and MAX voltage across all three Tesla OBC sub-modules.
can->AddRecv(Param::c2uac, 0x209, 8, 8, 1);
can->AddRecv(Param::c2flag, 0x209, 17, 2, 1);
can->AddRecv(Param::c2idc, 0x229, 32, 16, 0.000839233f);
can->AddRecv(Param::c2udc, 0x229, 16, 16, 0.01052856f);
// SKUDAK-S9: module 3 RX, same rationale.
can->AddRecv(Param::c3uac, 0x20B, 8, 8, 1);
can->AddRecv(Param::c3flag, 0x20B, 17, 2, 1);
can->AddRecv(Param::c3idc, 0x22B, 32, 16, 0.000839233f);
can->AddRecv(Param::c3udc, 0x22B, 16, 16, 0.01052856f);
/***** Charger TX ******/
can->AddSend(Param::udcspnt, 0x45c, 0, 16, 100); //gain 100
@@ -85,21 +100,6 @@ void ChargerCAN::MapMessages(CanMap* can)
can->AddSend(Param::aclim, 0x44c, 16, 16, 1500);
can->AddSend(Param::opmode, 0x44c, 32, 8, 154, 100);
can->AddSend(Param::version, 0x368, 0, 8, 0, 0x03);
can->AddSend(Param::version, 0x368, 8, 8, 0, 0x49);
can->AddSend(Param::version, 0x368, 16, 8, 0, 0x29);
can->AddSend(Param::version, 0x368, 24, 8, 0, 0x11);
can->AddSend(Param::version, 0x368, 40, 8, 0, 0x0c);
can->AddSend(Param::version, 0x368, 48, 8, 0, 0x40);
can->AddSend(Param::version, 0x368, 56, 8, 0, (int8_t)0xff);
/***** CHAdeMO TX *****/
can->AddSend(Param::version, 0x108, 8, 16, 107); //output 428V max = 4*107
can->AddSend(Param::idclim, 0x108, 24, 8, 1);
can->AddSend(Param::udc, 0x109, 8, 16, 1);
can->AddSend(Param::idc, 0x109, 24, 16, 1);
can->AddSend(Param::opmode, 0x109, 40, 3, 5); //Set charging and connlock at once
/***** SKUDAK VCU command RX (0x212) — SKUDAK-516 user ChargeLimit *****/
// Byte 0: ChargeLimit_pct (0-100). Bytes 1-7 reserved 0x00.
// Param::Change(vcuchglim) translates % to udcspnt setpoint (see src/main.cpp).