Stand alone high speed K-Line logging to an sdcard using an arduino. Canbus support is a work in progress. Although no Romraider Logger code was used directly it was an essential guide to get "fast polling" of ecu data. That project can be found at https://github.com/RomRaider/RomRaider
Standalone SSM (K-Line) data logger for the Arduino Uno R4 Minima. Reads RomRaider
logger.xml profiles + logger_*.xml definitions from the SD card, builds a complete
per-ECU parameter dictionary, and logs to CSV via SSM fast-poll (continuous) batch reads.
Auto-generates a profile when the ECU is unknown or no logger.xml is present.
- Source:
firmware/sssal/sssal.ino - Compiled binary:
firmware/binaries/sssal_alpha83.bin/.hex - Build:
arduino-cli compile --fqbn arduino:renesas_uno:minima(libs: SdFat 2.3.0, RTClib 2.1.4, Arduino_CAN). USB-MSC requires two one-line core patches — seefirmware/BUILD_NOTES.md. - Flash:
arduino-cli upload -p <COMx> --fqbn arduino:renesas_uno:minima(Renesas RA4M1, DFU).
★ Hardware-validated on-car (2026-10-05). First on-car run on an EJ255 (ROM
2F12515506) detected the ECU, resolved 55 params, and logged ~96k clean closed-loop rows. Earlier alphas were bench-validated only via the pre-flight tool; the logger path now has a real on-car run.
Plug the logger into a PC and it enumerates as a USB flash drive, exposing the SD card so you
can copy logs off and drop config files on without removing the microSD. Mode is chosen at
boot: a USB host present → USB-drive mode; powered from the car with no host → normal logger.
This needs the two core patches in BUILD_NOTES.md (one of which fixes a real upstream core bug
in the full-speed MSC endpoint descriptor).
-
v4: SSM-over-CAN transport stubbed in with a byte-exact ISO15765 spec (seam only, not yet active); ECU-init buffer 96→112 bytes; default profile tweaks.
-
v5: USB Mass Storage (above); RTC set-on-boot via a one-shot
/logger/settime.txt(YYYY-MM-DD HH:MM:SS) with a compile-time fallback when the RTC is unset; FAT directory timestamps now track the RTC (SdFat date/time callback) so file-property dates match the filename; K-line transceiver sleep pin (D2) driven awake at boot. -
alpha6: standby-recovery fix. After an engine-off stream-watchdog the logger re-enters Phase 1; the low-power idle path (Software Standby) previously never recovered when the OBD port stays powered (nothing power-cycles the board). Now re-inits the UART + transceiver on every wake before probing, and emits an
IDLE_WAKE count=Nstatus line so a log can tell "woke, found nothing" from "never woke".status.logrotates tostatus.1.logat 2 KB instead of being deleted, so the close/wake history survives a reboot. (Flashed; on-car verification of the standby path pending.) -
alpha7.0: RomRaider-aligned log format. Each log opens with a
#-prefixed provenance remark (# Created by SSSAL <ver> | ROM <id> | <datetime> | defs <file>); the time column is nowTime (msec)and is zeroed per log (first row = 0) to match RomRaider's relative timestamp; value-column headers areName (units) [ID]. Data values keep full SSSAL resolution (uniform 2 decimals — not rounded to RomRaider's per-columnformat). -
alpha8.0: low-power Software Standby wake now works. The parked idle path had never recovered: the board entered Software Standby and stayed there, so with a continuously-powered OBD port (nothing power-cycles the logger) every drive after an engine-off was lost. Two real defects in the AGT1 wake-timer setup, both found by measuring registers on-car rather than by inspection:
- Wrong register offsets.
AGTMR1/AGTMR2were addressed at+0x18/+0x19instead of+0x09/+0x0A(verified against the RA4M1 SVD). The namesAGTIOC/AGTISRpointed at the real mode registers, so the "clear the block" step zeroed them and the intended config writes landed outside the peripheral.AGTMR1.TCKstayed000= PCLKB — a clock that stops in Software Standby — so the timer froze the moment the board slept. AGTMR2.LPMwas set while the timer was stopped. That prevents the counter from ever starting:AGTCR.TCSTFnever sets and the count stays frozen. Renesas' own FSPr_agt.csets only theCKSdivider before start, starts withAGTCR = 0xF1(the status flags are write-0-to-clear, so0x01silently clears them), and waits forTCSTF == 1before modifying any other AGT register. LPM must be enabled only after that.
Bench-proven on hardware: a 10 s standby returned
rtc_s=10on the external DS1307 withmillis_s=0(the MCU clock did not advance — genuine Software Standby), one AGT1 underflow wake, then stable operation. Parked draw drops from ~7.5 mA to ~40 µA.STANDBY_ENABLEDis a compile-time switch; set it to0for the fast-search build that stays awake (~7.5 mA parked) if a standby problem is ever suspected.status.lognow rotates at 32 KB instead of 2 KB so theIDLE_WAKEhistory survives a long park. On-car verified 2026-10-12 (on alpha8.1): one boot, then 70 consecutive 2-minute standby wakes across two parks, with two separate drives detected and logged straight out of standby — no reboot, no lost drive. - Wrong register offsets.
-
alpha8.1: stop advertising a broken disk. The MSC interface is always in the USB descriptor —
__USBInstallMSD()is resolved at link time and the core assignsinstall_MSDduring static init, beforesetup()runs — so a host always finds a mass-storage device even on a boot that goes to logger mode. TinyUSB only defaults toMEDIUM NOT PRESENTwhen no sense is already set (if (p_msc->sense_key == 0)), and sense data is sticky until aREQUEST SENSEclears it, so a stale key from any earlier failed command masked it and Windows offered to format the card holding the only copy of the logs. Every not-serving path now sets the sense explicitly (SCSI_SENSE_NOT_READY, ASC0x3A) andtud_msc_capacity_cbreports0/0soREAD CAPACITYstalls with the same status — the host sees an empty card-reader slot, which is what the device actually is. Read/write failures while serving now reportMEDIUM ERRORwith a real ASC instead of a bare-1. Also fixed: a failedenterMscMode()used toreturnfromsetup()anyway, leaving the board neither serving the card nor logging, with nothing instatus.logto explain it; it now falls through to logger mode.Pulling logs: the MSC-vs-logger choice is latched once at boot. Plugging a laptop into an already-powered board does not produce a drive — power-cycle it so it boots with the host attached. With standby enabled a parked board is usually asleep, and USB is deliberately not a standby wake source, so it will not enumerate until its next AGT1 underflow.
-
alpha8.2: code-review fixes.
- A corrupt K-line frame no longer ends the log. Any header/checksum error used to be
treated exactly like the stream stopping, so one bad byte closed the log and restarted the
ECU search (and
status.logsaid "no data" either way). The reader now matches the80 F0 10 <N+1> E8header byte by byte, skips bad or cut-off frames, and resyncs on the next one; the log only closes after 5 s with no valid frame. Skipped frames are reported per log asSTREAM_ERRORS <file> bad_frames=N resync_bytes=M(written only when nonzero; an occasionalbad_frames=1at key-off is a frame cut off by the engine stopping). - Second drive in one power-up no longer gains extra columns. Hidden dependency params pulled in for calculated params were re-loaded as visible columns on the next engine start — a path that only became reachable once standby recovery worked.
- Nothing-to-log selections (every selected param unsupported) write one
NO_FETCHABLE_PARAMSline per boot and sleep, instead of creating an empty log on every start. BATCHMAX=inaddress_map.csvis clamped to 48 (the probe's own ceiling); a hand-edited value of 84+ overflowed the request builder.- The K-line transceiver is put to sleep for each standby window (woken again before the probe). (On-car verified 2026-10-12: two key-on cycles detected straight out of standby.)
- A corrupt K-line frame no longer ends the log. Any header/checksum error used to be
treated exactly like the stream stopping, so one bad byte closed the log and restarted the
ECU search (and
-
alpha8.3: bug-sweep fixes.
- E-params no longer lost for 77 ROMs. The definitions parser read lines into a 256-byte
buffer; 53 of the
<ecu id="...">ROM lists inlogger_IMP_EN_v370.xmlare longer (up to 855 characters), so any ROM ID past the cut was never matched — 559 ROM×parameter pairs, up to 29 E-params for one ROM. The parser now has its own 1 KB buffer and reports any line that still overflows (DEFS_LINE_TRUNCATED).address_map.csvgains aFMT=2header line, so a cache built by the old parser is rebuilt once (CACHE_REGEN reason=BUILDER_UPDATE, ~19 s). - A failed cache build can no longer stick. The dictionary is built into
address_map.tmpand only renamed into place if the whole SSM section was read and written; previously a defs read error or power loss mid-build left an empty/partial cache that was reused forever. Failures logCACHE_BUILD_FAILand retry at the next wake instead of halting. - No more 50 MB files after a power cut. SdFat's
preAllocate()sets the file size to the full reservation immediately, so a log that never closed was 50 MB of rows plus stale card data. Pre-allocation is removed (at ~0.5 KB/s it bought nothing); an unclosed log now ends at the last 1 s sync. - Fatal errors no longer drain the battery, and their LED pattern is actually visible. The
halt loop ran awake forever (~15 mA). It also blinked an LED that could not light: D13 is the
SD card's SPI clock, and
digitalWrite()doesn't take a pin back from a peripheral. Fatal states now flash a visible pattern for 60 s (4 blinks = config error, reason instatus.log; 6 blinks = SD card failed), then sleep in Software Standby and repeat the pattern every ~4 min. An empty profile is no longer fatal — it is reported asNO_FETCHABLE_PARAMSand parks. NO_FETCHABLE_PARAMSis now really quiet: later wakes that find the same ECU go back to sleep instead of re-running detection and re-writing its status lines every 2 minutes.- Wake-timer re-arm waits for the timer to stop before reconfiguring it, as Renesas' FSP driver does. It worked before only because every re-arm wrote identical values.
- E-params no longer lost for 77 ROMs. The definitions parser read lines into a 256-byte
buffer; 53 of the