You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build the device-side and host-side AutoResearch validation harness for the LPC845 SPI + DMA0 async driver that just landed in #3453 / PR #3454. This is the natural next step in the #3453 series — Phase 1 source is in but cannot be exercised until the harness exists and the toolchain blockers (#3450 B1/B2) clear.
Background
PR #3454 added src/platforms/arm/lpc/spi_arm_lpc_dma.h against UM11029 §15 (SPI) and §17 (DMA). The header self-gates on FL_IS_ARM_LPC_845 && FASTLED_LPC_SPI_DMA, so default builds are unaffected. The driver is host-compile clean (340/340 tests pass), but every TODO(3453) marker in it points at a register-level assumption that only silicon can confirm — TXCTL persistence semantics, SYSAHBCLKCTRL0 bit 29 layout, BURSTPOWER default of 0, INMUX wiring for channel overrides > 15, SRAMBASE allocation, chunked-stream support for frames > buffer capacity.
The existing AutoResearch driver flags (--parlio, --rmt, --spi, --object-fled, etc.) all follow the same pattern: a per-driver header (e.g. AutoResearchSimd.h) gets pulled into AutoResearch.ino under a build macro, and the host wrapper in ci/autoresearch/phases.py adds the corresponding -D to the compile + a pass/fail phase that calls RPC bindings on the device.
A new harness header, gated on #if defined(FL_IS_ARM_LPC_845) && defined(FASTLED_LPC_SPI_DMA), conditionally included from AutoResearch.ino (or AutoResearchLowMemory.h if the 64 KB flash budget pushes back). Wires three RPC handlers on top of the existing remote.bind(...) infrastructure:
dmaSpi.transferOnce(N, byte_pattern) — allocates an N-byte buffer of byte_pattern, calls ARMHardwareSPIOutputDMA::kickDmaStream(buf, N), returns {started_us, done_us, cpu_was_busy_during_transfer: bool}. The "cpu_was_busy" probe is the inverse of the async claim — if true, the DMA stream did not actually offload from the CPU.
dmaSpi.transferOverlap(N, byte_pattern) — kicks the same stream but also runs a tight beacon-toggle loop on the CPU until done() returns true. Reports {toggle_count, completion_us}. Pass criterion:toggle_count >= floor(completion_us * expected_toggles_per_us). This is the affirmative async proof — non-zero toggle count under DMA load.
dmaSpi.measureSck(freq_target, divider) — drives a known waveform; host correlates the SCK pin (loopbacked via jumper to an SCT-capture-capable pin) to measure the actual SCK Hz versus the _SPI_CLOCK_DIVIDER template arg.
Implementation reference: existing examples/AutoResearch/AutoResearchSimd.h for the header layout + remote.bind pattern, ARMHardwareSPIOutputDMA<> for the driver surface.
Host-side: ci/autoresearch/phases.py + wrapper
Add --dma-spi flag to the bash autoresearch wrapper, parallel to the existing --spi / --parlio / --object-fled flags.
Add the corresponding phase entry in ci/autoresearch/phases.py that:
Compiles examples/AutoResearch with -DFASTLED_LPC_SPI_DMA -DFASTLED_LPC_SPI_DMA_CHANNEL=4 added to build_flags.
Calls the three RPC handlers above in sequence.
Pass criteria:
transferOnce: done_us - started_us within ±10% of 8 * N / SCK_Hz.
transferOverlap: cpu_was_busy_during_transfer == false AND toggle_count >= 0.95 * theoretical_max.
measureSck: measured SCK within ±5% of target.
On LPC, document the mutual exclusion: --dma-spi and --pwm-dma-cl (Phase 2 of LPC8xx: async DMA output drivers — Phase 1 SPI, Phase 2 clockless bring-up #3453) cannot be enabled in the same build because both consume DMA0 channels and the LowMemory flash budget doesn't fit both. The wrapper should reject --dma-spi --pwm-dma-cl with a clear error.
Loopback wiring
On LPC845-BRK, document a single jumper from the SPI0 MOSI pin to one SCT-capture-capable input pin. Reuses the existing LPC_SCT_CAPTURE plumbing (LPC845-BRK: WS2812 loopback validation via SCT-capture RX channel #3015) — once that backend's begin() body lands (referenced as a closed but bench-validation-pending leaf in src/platforms/arm/lpc/rx_sct_capture.cpp.hpp), dmaSpi.transferOnce can self-capture and byte-compare what it transmitted.
Acceptance criteria
examples/AutoResearch/AutoResearchSpiDma.h lands with the three RPC bindings.
--dma-spi flag in bash autoresearch lpc845 wraps to the new phase.
All three pass criteria above are bench-validated on LPC845-BRK and reported in the closing comment with scope/LA captures (or screenshots from the SCT-capture self-loopback path).
At least one TODO(3453) marker in spi_arm_lpc_dma.h is closed out as a result of the bench validation (typically TXCTL persistence is the first to confirm or refute).
The device-side header and host-side phase code can both land while these are open — they just won't gate CI on a passing run until the blockers clear.
Out of scope
Phase 2 (PWM-DMA clockless) validation harness — AutoResearchPwmDmaClockless.h + --pwm-dma-cl host phase. Will get its own follow-up issue once Phase 1's harness is bench-validated.
LPC804 SPI DMA — Phase 1 is LPC845-only; LPC804's 4-channel DMA needs separate allocation review.
Scope
Build the device-side and host-side AutoResearch validation harness for the LPC845 SPI + DMA0 async driver that just landed in #3453 / PR #3454. This is the natural next step in the #3453 series — Phase 1 source is in but cannot be exercised until the harness exists and the toolchain blockers (#3450 B1/B2) clear.
Background
PR #3454 added
src/platforms/arm/lpc/spi_arm_lpc_dma.hagainst UM11029 §15 (SPI) and §17 (DMA). The header self-gates onFL_IS_ARM_LPC_845 && FASTLED_LPC_SPI_DMA, so default builds are unaffected. The driver is host-compile clean (340/340 tests pass), but everyTODO(3453)marker in it points at a register-level assumption that only silicon can confirm — TXCTL persistence semantics, SYSAHBCLKCTRL0 bit 29 layout, BURSTPOWER default of 0, INMUX wiring for channel overrides > 15, SRAMBASE allocation, chunked-stream support for frames > buffer capacity.The existing AutoResearch driver flags (
--parlio,--rmt,--spi,--object-fled, etc.) all follow the same pattern: a per-driver header (e.g.AutoResearchSimd.h) gets pulled intoAutoResearch.inounder a build macro, and the host wrapper inci/autoresearch/phases.pyadds the corresponding-Dto the compile + a pass/fail phase that calls RPC bindings on the device.Deliverables
Device-side:
examples/AutoResearch/AutoResearchSpiDma.hA new harness header, gated on
#if defined(FL_IS_ARM_LPC_845) && defined(FASTLED_LPC_SPI_DMA), conditionally included fromAutoResearch.ino(orAutoResearchLowMemory.hif the 64 KB flash budget pushes back). Wires three RPC handlers on top of the existingremote.bind(...)infrastructure:dmaSpi.transferOnce(N, byte_pattern)— allocates an N-byte buffer ofbyte_pattern, callsARMHardwareSPIOutputDMA::kickDmaStream(buf, N), returns{started_us, done_us, cpu_was_busy_during_transfer: bool}. The "cpu_was_busy" probe is the inverse of the async claim — if true, the DMA stream did not actually offload from the CPU.dmaSpi.transferOverlap(N, byte_pattern)— kicks the same stream but also runs a tight beacon-toggle loop on the CPU untildone()returns true. Reports{toggle_count, completion_us}. Pass criterion:toggle_count >= floor(completion_us * expected_toggles_per_us). This is the affirmative async proof — non-zero toggle count under DMA load.dmaSpi.measureSck(freq_target, divider)— drives a known waveform; host correlates the SCK pin (loopbacked via jumper to an SCT-capture-capable pin) to measure the actualSCKHz versus the_SPI_CLOCK_DIVIDERtemplate arg.Implementation reference: existing
examples/AutoResearch/AutoResearchSimd.hfor the header layout +remote.bindpattern,ARMHardwareSPIOutputDMA<>for the driver surface.Host-side:
ci/autoresearch/phases.py+ wrapper--dma-spiflag to thebash autoresearchwrapper, parallel to the existing--spi/--parlio/--object-fledflags.ci/autoresearch/phases.pythat:examples/AutoResearchwith-DFASTLED_LPC_SPI_DMA -DFASTLED_LPC_SPI_DMA_CHANNEL=4added tobuild_flags.transferOnce:done_us - started_uswithin ±10% of8 * N / SCK_Hz.transferOverlap:cpu_was_busy_during_transfer == falseANDtoggle_count >= 0.95 * theoretical_max.measureSck: measuredSCKwithin ±5% of target.--dma-spiand--pwm-dma-cl(Phase 2 of LPC8xx: async DMA output drivers — Phase 1 SPI, Phase 2 clockless bring-up #3453) cannot be enabled in the same build because both consume DMA0 channels and the LowMemory flash budget doesn't fit both. The wrapper should reject--dma-spi --pwm-dma-clwith a clear error.Loopback wiring
LPC_SCT_CAPTUREplumbing (LPC845-BRK: WS2812 loopback validation via SCT-capture RX channel #3015) — once that backend'sbegin()body lands (referenced as a closed but bench-validation-pending leaf insrc/platforms/arm/lpc/rx_sct_capture.cpp.hpp),dmaSpi.transferOncecan self-capture and byte-compare what it transmitted.Acceptance criteria
examples/AutoResearch/AutoResearchSpiDma.hlands with the three RPC bindings.--dma-spiflag inbash autoresearch lpc845wraps to the new phase.TODO(3453)marker inspi_arm_lpc_dma.his closed out as a result of the bench validation (typicallyTXCTLpersistence is the first to confirm or refute).Dependencies
Hard-blocked by:
nxp-lpcGitHub URL form. fix(core): matchnxp-lpc(hyphenated GitHub form) in platform parser fbuild#900 open; once it lands + fbuild 2.3.15 ships, thepyproject.tomlbump unblocksbash compile lpc845brk.framework-arduino-lpc8xxtarball checksum mismatch from the 2026-06-28 org transfer. Needs the checksum re-record alongside the next fbuild release.Soft-blocked by:
The device-side header and host-side phase code can both land while these are open — they just won't gate CI on a passing run until the blockers clear.
Out of scope
AutoResearchPwmDmaClockless.h+--pwm-dma-clhost phase. Will get its own follow-up issue once Phase 1's harness is bench-validated.Relationships
spi_arm_lpc_dma.h).