Skip to content

SERCOM async/DMA with SAME5x support - #395

Open
crabel99 wants to merge 95 commits into
adafruit:masterfrom
crabel99:sercom-async-dma-merge-same5x
Open

crabel99 wants to merge 95 commits into
adafruit:masterfrom
crabel99:sercom-async-dma-merge-same5x

Conversation

@crabel99

@crabel99 crabel99 commented Aug 22, 2026 •

Copy link
Copy Markdown

Summary

  • Extend the existing SERCOM asynchronous/DMA work across SAMD21 and SAME5x.
  • Retain asynchronous Wire, SPI, and UART transaction support.
  • Make SPI.end() abort active transfers and clear queued source pointers before callers release their buffers. Preserve the incoming interrupt-mask state.
  • Add SAME53/SAME54 core, interrupt, timing, USB, and peripheral-family guards.
  • Add multimaster Wire role transitions, slave DMA, transaction chaining, and bounded error recovery.
  • Pin the reviewed TinyUSB and ZeroDMA dependency revisions.

This replaces #385. The original PR remains available for its discussion and history, but its sercom-async-dma head diverged before the SAME5x integration work. This replacement allows CI and review to run against the branch containing the SAME5x integration without rewriting the old public branch.

Validation

Current I2C admission and recovery validation

  • Exact PR commit 5bc95c648c84287af9b08ba012c5cbef07fefcd9 passed the complete local 12-board upstream example matrix: 287 successful compiles, zero failures and 433 existing skips.

The latest commits add coalesced delayed PendSV service and simplify asynchronous Wire ownership. A fresh master activation must observe UNKNOWN or BUSY before admitting an address. Fresh IDLE/OWNER is rejected and retried by the existing delayed service. SDA/SCL remain permanently assigned to SERCOM, BUSY arbitration remains hardware-owned, and no code forces BUSSTATE to IDLE.

Queued-master cancellation preserves active slave work. Completion restores slave listening before application callbacks. An asynchronous timeout cancels queued phases once and consumes stale delayed wakes without restarting them.

  • The integrated bundle commit ed4edee9 passed Metro M0/M4 example builds with both USB stacks, 155 successful compiles and zero failures.
  • Native failure before the correction: captured LOW/LOW startup IDLE admitted ADDR=1 prematureSTART=1.
  • All 71 Framework native tests pass, including cancellation, retained OWNER, stale wakes, receive-buffer admission, and CRC engine boundaries. Regression source is in Framework_test PR #2.
  • Integrated SimIO-bundle hardware qualification passed 1,280 timed overlap cases on SAME54P20A, SAMD21G18A and SAMD21E18A at 400 kHz. This includes seven trigger offsets, 38/251/252/255-byte writes, full 255-byte readback with CRC, and 1,000 repetitions of the isolated collision trigger. Every write completed once. USB priority stayed normal. This hardware qualification uses the bundle's TinyUSB/ZeroDMA integration revisions; the PR retains its separately pinned revisions below.
  • Six development Device application builds pass, covering A/B images on all three bench MCUs. These are compile checks, not Device-level deduplication or USB backpressure acceptance.

Earlier SPI fix validation

  • GitHub CI: all 12 jobs pass on the updated PR head.

  • Full local 12-job CI matrix: 287 successful example compiles, zero failures, and 433 existing skips at 76492a7993f63a2c4687fcdeb5a57853a2272598.

  • Native SPI buffer-lifetime reproduction: all eight cases pass.

Earlier validation

  • GitHub Actions passed all required checks on the preceding PR head, 0b814e16.
  • Native and contract tests: 32 of 32 pass against the pinned dependency revisions.
  • Metro M0 example matrix: 24 passed, 0 failed, 37 skipped.
  • Metro M4 example matrix: 20 passed, 0 failed, 41 skipped.
  • SAME54 Xplained production configuration builds successfully.
  • SparkFun Qwiic Micro SAMD21 production configuration builds successfully.

Hardware validation

  • MCUs: SAMD21G18A and SAME54P20A.
  • Configuration: two-node I2C bus with dynamic master/slave role reversal.
  • Covered asynchronous writes and reads, combined write/read transactions, repeated START, DMA and byte-driven transfers, queued transfers, and error-recovery paths.
  • The complete 17-case hardware matrix passed with the pinned dependency revisions.

Dependencies

  • Adafruit TinyUSB Arduino: 057f6734f589680072de3a467b3b4051e57f3a9e
  • Adafruit ZeroDMA: 25e92a0811b364a068989c43788727c18803fc77

SPI buffer lifetime regression

SPI.end() previously left the DMA engine and queued transaction pointers active after returning. The change to SERCOM::resetSPI() cancels transfer activity and clears those references before releasing caller-owned memory.

The native reproduction failed against the previous implementation and passes against this commit. A SAME54 J-Link probe independently reproduced the stale DMA ownership and verified cancellation after the fix. This also supports NeoPixel SERCOM output, which retains each encoded source buffer until SPI completion.

Optional Wire CRC integration

Wire now supports opt-in CRC-32/ISO-HDLC for each data-bearing I2C phase. The four-byte little-endian trailer is separate from all 255 payload bytes. Address-only probes carry no trailer. CRC remains disabled by default; SPI and UART are unchanged by this integration. Framework retains DMA selection, CRC engine ownership and physical completion. Interrupt-driven phases feed CRCDATAIN rather than silently omitting protection. Failed or truncated receives deliver zero bytes.

The shared DmacCrc dependency is merged from #398. Two preceding receive-retirement guards are a separate fix commit. A native reproduction failed before that correction and passed afterward; paired D21/E54 repeated-START tests also passed at the original full-buffer boundary.

Additional validation includes all 43 Framework native cases; 106 paired corruption/truncation cases with valid 255-byte recovery in both bus directions; three full-capacity repeated-START phases; address-NACK recovery; and 100 iterations of the older slave-TX stall reproduction at 100 kHz. Device acceptance verified real register rejection, signed firmware validation, unchanged inactive flash after every rejected page, and unchanged flash/offset after valid-page replay. Hardware tests used the integrated bundle's matching CRC source.

All 12 local Arduino CI configurations pass on exact branch commit 431bd1931c53a27a0d7fca257a9691d4395874cd, including TinyUSB builds using that branch's own pinned library. Earlier interrupted/overridden-library runs remain recorded separately.

crabel99 added 30 commits June 20, 2026 09:46
…added stopTransmissionWIRE to allow sync closeout, handle errors and continue processing the transaction queue
- Fix misleading indentation in retry logic (lines 847, 857)
- Remove ambiguous overload for Wire.begin() with integer literals
  (uint16_t version now requires explicit enableGeneralCall parameter)
- Remove unused variable in SPI.cpp
- Remove redundant unsigned < 0 check in setPending()
- Add __attribute__((weak)) to all SPI interrupt handlers (SERCOM4, SPI1, etc)
  This allows variants to override them when SERCOM is used for other
  peripherals (e.g., MKR variants use SERCOM4 for Serial2/UART)

- Explicitly cast slave addresses to uint8_t in Wire examples to avoid
  any potential overload resolution issues on different compiler versions
Release the idle slave descriptor rearmed by Wire's request-completion callback before checking for queued master work. Otherwise the queue remains blocked after a completed slave response.

Validated with a failing-before/passing-after Framework_test regression, paired E54/D21 hardware reproduction, and full signed updates routed through the E54.
Queue delayed services from the millisecond tick and cancel deadlines when
services are removed. Initialize core services with every USB configuration
so Wire retries also run on boards with USB disabled.
Keep SERCOM pads connected during role configuration and let hardware wait
on BUSY. Reject fresh IDLE/OWNER activation until a synchronized activation
observes UNKNOWN/BUSY, then use the existing delayed service for admission.

Separate deferred starts from completions, preserve the active slave when
cancelling queued master work, restore listening before callbacks, and honor
external receive-buffer admission.

Verified by 71 Framework native tests and 1280 timed physical overlap cases,
including 251/252/255-byte DMA boundaries and exactly-once callbacks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants