SERCOM async/DMA for Wire, SPI, and UART#385
Open
crabel99 wants to merge 25 commits intoadafruit:masterfrom
Open
SERCOM async/DMA for Wire, SPI, and UART#385crabel99 wants to merge 25 commits intoadafruit:masterfrom
crabel99 wants to merge 25 commits intoadafruit:masterfrom
Conversation
…in a multi-master configuration
…erving legacy behavior.
…added stopTransmissionWIRE to allow sync closeout, handle errors and continue processing the transaction queue
…g ISR called APIs
…or pin assignment validation
Upstream commit 289a272 added ERROR flag clearing for synchronous blocking I2C operations, but this branch uses ISR-driven async architecture where errors are handled and cleared immediately in interrupt context. The upstream fix doesn't apply to the rewritten async code.
- 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue: #382
SERCOM Async/DMA API Comparison: sercom-async-dma vs Master
Executive Summary
Motivation
The SAMD21/SAMD51 SERCOM peripherals support hardware-accelerated DMA transfers, but the Arduino core's synchronous blocking APIs don't expose this capability. This creates performance bottlenecks in applications that need to communicate with multiple peripherals efficiently. The master branch Wire library had internal async/DMA support, but the API remained entirely synchronous, and the patterns weren't extended to SPI or UART.
This branch extends transparent async/DMA operation across all three major SERCOM interfaces (Wire/I2C, SPI, UART) while maintaining 100% backward compatibility with existing synchronous code.
Design Intent
Primary Goals:
Key Design Decision:
Non-Goals:
writeAsync()methods)Philosophy
"Seamless by default, async by choice"
The API design follows a simple principle: when a callback is provided (
!= nullptr), the operation is asynchronous and returns immediately; when no callback is provided (== nullptr), the operation is synchronous and blocks until complete. This allows:USE_ZERODMAis definedThe transaction pool architecture (8 transactions matching SERCOM queue depth) enables efficient pipelining of operations without exposing queue management to applications.
Hardware Testing Status
✅ Tested Configurations
Testing Notes:
USE_ZERODMAenabled)USE_ZERODMAdisabled)USE_ZERODMA) has been tested alongside fallback paths (without DMA library)Recommended Pre-Merge Validation
Before merging to master, reviewers should consider:
Known Limitations & Future Development
Current Limitations (SAMD21/SAMD51 Silicon Errata):
Future Development Roadmap:
Hardware CRC Integration (Requires DMA):
Additional Testing:
Performance Optimization:
SAMD51 Clock Selection Enhancement:
Strict I2C Pad Validation:
Optional Companion Libraries (Future):
API Change Summary
Quick reference of what changed across the three interfaces:
endTransmission()+requestFrom()now accept callbacksread(buffer, size, callback)andwrite(buffer, size, callback)transfer()now accepts callbacksDetailed API Comparison
Wire API Changes
Master Branch (Original)
Note: Master branch Wire already had some async operation support through internal transaction mechanisms, but the API was entirely synchronous (blocking).
sercom-async-dma Branch (Enhanced)
Wire API Summary
beginTransmission(addr)endTransmission(stop)endTransmission(stop=true)endTransmission(stop, callback, user)requestFrom(addr, qty, stop)requestFrom(addr, qty, stop=true)requestFrom(addr, qty, stop, rxBuf, cb, user)requestFrom(rxBuffer=ptr)setRxBuffer/setTxBuffer/...begin(addr, ..., enable10Bit)begin(..., speed)write(data, qty, setExternal=true)Wire Design Pattern Notes
Wire uses a multi-stage transaction builder pattern:
beginTransmission(address)- Start buildingwrite(...)- Add data to staging buffer (loader transaction)endTransmission(callback)- Execute the built transaction (sync or async)Or for reads:
requestFrom(address, quantity, callback)- Execute read transaction directlyThis pattern influenced the unified API approach for UART and SPI, but those interfaces use single-call operations rather than multi-stage building.
Key Enhancement: The sercom-async-dma branch adds async callback support to the Wire API while maintaining full backward compatibility with the synchronous blocking behavior. When callbacks are nullptr (default), behavior is identical to master branch.
UART API Changes
Master Branch (Original)
sercom-async-dma Branch (New)
UART API Summary
int read()size_t write(uint8_t)read(buf, size)read(buf, size, callback)write(buf, size)write(buf, size, callback)API Strategy: Single unified method with optional callback parameter (like Wire)
SPI API Changes
Master Branch (Original)
sercom-async-dma Branch (New)
SPI API Summary
void transfer(void*)transfer(tx, rx, count, true)transfer(tx, rx, count, true, callback)nullptr)API Strategy: Extended existing method signature with optional callback parameters
Wire API (For Reference)
Wire already had async/DMA in master, but documentation for pattern:
Cross-Interface API Patterns
Design Consistency
Async Pattern (All Three Interfaces)
Note: For async calls, any user-provided buffers must remain valid until the completion callback fires.
Coverage Analysis
UART Coverage
Before (Master):
After (sercom-async-dma):
SPI Coverage
Before (Master):
After (sercom-async-dma):
waitForTransfer()check for non-blocking code pathsFingerprint (Method Signature) Differences
UART: New Overloads Added
SPI: Existing Signature Extended
Default Parameter Behavior
UART New Methods (Defaults)
SPI Extended Method (Defaults)
Implementation Transparency
Test Coverage
UART Functional Tests (NEW)
SPI Hardware Tests (NEW)
Master Branch
Summary of Changes
What's New
UART:
read(buffer, size, callback, user)write(buffer, size, callback, user)SPI:
transfer()signaturewaitForTransfer()andisBusy()for non-blocking patternsBoth:
What Changed in Existing API
transfer()signature extended with optional parametersWhat Stayed the Same
Migration Guide: Master → sercom-async-dma
UART: No changes required
Opt-in to new async features
SPI: No changes required
Opt-in to new async features
Verdict
✅ API design is clean and consistent:
Update 17 Feb 2026: added links to repositories for SerialRTT and DebugUtils