From 3f45eb5e3f9982cb9e50d3e8741b956d2dd7deb6 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Tue, 27 Jan 2026 19:38:10 -0500 Subject: [PATCH 01/24] define sercom transaction structure for enqueueing async transactions. --- cores/arduino/SERCOM.h | 1 + cores/arduino/SERCOM_Txn.h | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 cores/arduino/SERCOM_Txn.h diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 35c90f1c4..9cd3ef0f3 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -20,6 +20,7 @@ #define _SERCOM_CLASS_ #include "sam.h" +#include "SERCOM_Txn.h" // SAMD51 has configurable MAX_SPI, else use peripheral clock default. // Update: changing MAX_SPI via compiler flags is DEPRECATED, because diff --git a/cores/arduino/SERCOM_Txn.h b/cores/arduino/SERCOM_Txn.h new file mode 100644 index 000000000..438de28a3 --- /dev/null +++ b/cores/arduino/SERCOM_Txn.h @@ -0,0 +1,66 @@ +#ifndef _SERCOM_TXN_H_ +#define _SERCOM_TXN_H_ + +#include +#include + +struct SercomTxn { + uint16_t config; // common + protocol-specific flags/fields + uint16_t address; // I2C addr, SPI CS/baud, UART RTS/CTS + size_t length; + const uint8_t* txPtr; + uint8_t* rxPtr; + void (*onComplete)(void* user, int status); + void* user; +}; + +// I2C config flags and helpers +enum : uint16_t { + I2C_CFG_READ = 1u << 0, + I2C_CFG_STOP = 1u << 1, + I2C_CFG_CRC = 1u << 2, + I2C_CFG_10BIT = 1u << 3, + I2C_CFG_RESTART = 1u << 4, +}; + +static inline uint16_t I2C_ADDR(uint16_t addr10) { return addr10 & 0x03FFu; } +static inline uint8_t I2C_ADDR7(uint16_t addr) { return (uint8_t)(addr & 0x7Fu); } + +// SPI config flags and helpers +enum : uint16_t { + SPI_CFG_READ = 1u << 0, + SPI_CFG_STOP = 1u << 1, + SPI_CFG_CRC = 1u << 2, + SPI_CFG_CPOL = 1u << 3, + SPI_CFG_CPHA = 1u << 4, + SPI_CFG_DORD = 1u << 5, + SPI_CFG_CHAR9 = 1u << 6, + SPI_CFG_CS_HOLD = 1u << 7, + SPI_CFG_DUMMY = 1u << 8, + SPI_CFG_TX_ONLY = 1u << 9, +}; + +static inline uint16_t SPI_ADDR_CS(uint8_t cs) { return (uint16_t)cs; } +static inline uint16_t SPI_ADDR_BAUD(uint8_t baud) { return (uint16_t)baud << 8; } +static inline uint8_t SPI_ADDR_GET_CS(uint16_t addr) { return (uint8_t)(addr & 0x00FFu); } +static inline uint8_t SPI_ADDR_GET_BAUD(uint16_t addr) { return (uint8_t)(addr >> 8); } + +// UART config flags and helpers +enum : uint16_t { + UART_CFG_READ = 1u << 0, + UART_CFG_STOP = 1u << 1, + UART_CFG_CRC = 1u << 2, + UART_CFG_BREAK = 1u << 3, + UART_CFG_MARK = 1u << 4, + UART_CFG_TX_ONLY = 1u << 5, + UART_CFG_RX_ONLY = 1u << 6, +}; + +static inline uint16_t UART_ADDR_RTS(uint8_t pin) { return (uint16_t)pin; } +static inline uint16_t UART_ADDR_CTS(uint8_t pin) { return (uint16_t)pin << 8; } +static inline uint8_t UART_ADDR_GET_RTS(uint16_t addr) { return (uint8_t)(addr & 0x00FFu); } +static inline uint8_t UART_ADDR_GET_CTS(uint16_t addr) { return (uint8_t)(addr >> 8); } + +static constexpr uint8_t SERCOM_PIN_NONE = 0xFFu; + +#endif From de2435657a6053abbe55784cd627adea22a500cd Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Wed, 28 Jan 2026 07:38:52 -0500 Subject: [PATCH 02/24] provide unified structure for maintaining and accessing sercom protocol configuration --- cores/arduino/SERCOM.cpp | 73 ++++++++++++++++++++++++++++++++++++++++ cores/arduino/SERCOM.h | 22 ++++++++++++ 2 files changed, 95 insertions(+) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 2c2309a10..4444f70e5 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -790,6 +790,79 @@ static const struct { #endif // end !SAMD51 +std::array SERCOM::s_states = {}; +volatile uint32_t SERCOM::s_pendingMask = 0; + +bool SERCOM::claim(uint8_t sercomId, Role role) +{ + if (sercomId >= kSercomCount) + return false; + + SercomState &state = s_states[sercomId]; + if (state.role != Role::None && state.role != role) + return false; + + state.role = role; + return true; +} + +void SERCOM::release(uint8_t sercomId) +{ + if (sercomId >= kSercomCount) + return; + + SercomState &state = s_states[sercomId]; + state.role = Role::None; + state.service = nullptr; + s_pendingMask &= ~(1u << sercomId); +} + +bool SERCOM::registerService(uint8_t sercomId, ServiceFn fn) +{ + if (sercomId >= kSercomCount) + return false; + + s_states[sercomId].service = fn; + return true; +} + +void SERCOM::setPending(uint8_t sercomId) +{ + if (sercomId >= kSercomCount) + return; + + __disable_irq(); + s_pendingMask |= (1u << sercomId); + __enable_irq(); + __DMB(); + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; +} + +void SERCOM::dispatchPending(void) +{ + uint32_t pending; + + __disable_irq(); + pending = s_pendingMask; + s_pendingMask = 0; + __enable_irq(); + + for (size_t i = 0; i < kSercomCount; ++i) + { + if ((pending & (1u << i)) == 0) + continue; + + ServiceFn fn = s_states[i].service; + if (fn) + fn(); + } +} + +extern "C" void PendSV_Handler(void) +{ + SERCOM::dispatchPending(); +} + int8_t SERCOM::getSercomIndex(void) { for(uint8_t i=0; i<(sizeof(sercomData) / sizeof(sercomData[0])); i++) { if(sercom == sercomData[i].sercomPtr) return i; diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 9cd3ef0f3..24e0ccdd4 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -21,6 +21,7 @@ #include "sam.h" #include "SERCOM_Txn.h" +#include // SAMD51 has configurable MAX_SPI, else use peripheral clock default. // Update: changing MAX_SPI via compiler flags is DEPRECATED, because @@ -255,12 +256,33 @@ class SERCOM uint32_t getFreqRef(void) { return F_CPU; }; #endif + // --- Async SERCOM scaffolding (Phase 1) --- + enum class Role : uint8_t { None = 0, UART, SPI, I2C }; + using ServiceFn = void (*)(); + + static bool claim(uint8_t sercomId, Role role); + static void release(uint8_t sercomId); + static bool registerService(uint8_t sercomId, ServiceFn fn); + static void setPending(uint8_t sercomId); + static void dispatchPending(void); + private: Sercom *sercom; uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) SercomClockSource clockSource; #endif + #if defined(SERCOM_INST_NUM) && (SERCOM_INST_NUM > 0) + static constexpr size_t kSercomCount = SERCOM_INST_NUM; + #else + static constexpr size_t kSercomCount = 6; + #endif + struct SercomState { + Role role = Role::None; + ServiceFn service = nullptr; + }; + static std::array s_states; + static volatile uint32_t s_pendingMask; uint8_t calculateBaudrateSynchronous(uint32_t baudrate); uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; From 2c9e540cb1e10bdcb7ffba50658e4a08a71c3031 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Wed, 28 Jan 2026 09:08:32 -0500 Subject: [PATCH 03/24] add support for mux pin deconfliction during development. --- cores/arduino/SERCOM.cpp | 54 +++++++++++++++++++++++++++++++++++++++- cores/arduino/SERCOM.h | 45 ++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 4444f70e5..b65800d16 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -814,6 +814,9 @@ void SERCOM::release(uint8_t sercomId) SercomState &state = s_states[sercomId]; state.role = Role::None; state.service = nullptr; +#ifdef SERCOM_STRICT_PADS + clearPads(sercomId); +#endif // SERCOM_STRICT_PADS s_pendingMask &= ~(1u << sercomId); } @@ -826,9 +829,58 @@ bool SERCOM::registerService(uint8_t sercomId, ServiceFn fn) return true; } -void SERCOM::setPending(uint8_t sercomId) +#ifdef SERCOM_STRICT_PADS +bool SERCOM::registerPads(uint8_t sercomId, const PadFunc (&pads)[4], bool muxFunctionD) { if (sercomId >= kSercomCount) + return false; + + SercomState &state = s_states[sercomId]; + for (size_t i = 0; i < 4; ++i) + { + PadFunc desired = pads[i]; + if (desired == PadFunc::None) + continue; + PadFunc existing = state.pads[i]; + if (existing != PadFunc::None && existing != desired) + return false; + } + if (state.padsConfigured && state.muxFunctionD != muxFunctionD) + return false; + + bool any = false; + for (size_t i = 0; i < 4; ++i) + { + PadFunc desired = pads[i]; + if (desired == PadFunc::None) + continue; + state.pads[i] = desired; + any = true; + } + if (any) + { + state.padsConfigured = true; + state.muxFunctionD = muxFunctionD; + } + return true; +} + +void SERCOM::clearPads(uint8_t sercomId) +{ + if (sercomId >= kSercomCount) + return; + + SercomState &state = s_states[sercomId]; + for (size_t i = 0; i < 4; ++i) + state.pads[i] = PadFunc::None; + state.padsConfigured = false; + state.muxFunctionD = false; +} +#endif // SERCOM_STRICT_PADS + +void SERCOM::setPending(uint8_t sercomId) +{ + if (sercomId >= kSercomCount || sercomId < 0) return; __disable_irq(); diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 24e0ccdd4..f2ee1532b 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -239,7 +239,8 @@ class SERCOM int availableWIRE( void ) ; uint8_t readDataWIRE( void ) ; int8_t getSercomIndex(void); - uint32_t getSercomFreqRef(void); + uint32_t getSercomFreqRef(void); + #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) // SERCOM clock source override is only available on // SAMD51 (not 21) ... but these functions are declared @@ -266,21 +267,47 @@ class SERCOM static void setPending(uint8_t sercomId); static void dispatchPending(void); - private: - Sercom *sercom; - uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource +#ifdef SERCOM_STRICT_PADS + enum class PadFunc : uint8_t { + None = 0, + UartTx, + UartRx, + SpiMosi, + SpiMiso, + SpiSck, + SpiSs, + WireSda, + WireScl + }; + + static bool registerPads(uint8_t sercomId, const PadFunc (&pads)[4], bool muxFunctionD); + static void clearPads(uint8_t sercomId); +#endif // SERCOM_STRICT_PADS + + private: + Sercom *sercom; + uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) - SercomClockSource clockSource; + SercomClockSource clockSource; #endif - #if defined(SERCOM_INST_NUM) && (SERCOM_INST_NUM > 0) + +#if defined(SERCOM_INST_NUM) && (SERCOM_INST_NUM > 0) static constexpr size_t kSercomCount = SERCOM_INST_NUM; - #else - static constexpr size_t kSercomCount = 6; - #endif +#else + #pragma message("SERCOM_INST_NUM not defined; SERCOM support disabled.") + static constexpr size_t kSercomCount = 0; +#endif // SERCOM_INST_NUM + struct SercomState { Role role = Role::None; ServiceFn service = nullptr; +#ifdef SERCOM_STRICT_PADS + PadFunc pads[4] = { PadFunc::None, PadFunc::None, PadFunc::None, PadFunc::None }; + bool padsConfigured = false; + bool muxFunctionD = false; +#endif // SERCOM_STRICT_PADS }; + static std::array s_states; static volatile uint32_t s_pendingMask; uint8_t calculateBaudrateSynchronous(uint32_t baudrate); From 86ee1c4f6b93eed1001dec1e815f4d60f416d1f8 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Wed, 28 Jan 2026 12:06:15 -0500 Subject: [PATCH 04/24] integrate needed dma sercom structure into the SERCOM class --- cores/arduino/SERCOM.cpp | 251 +++++++++++++++++++++++++++++++++++++++ cores/arduino/SERCOM.h | 47 ++++++++ 2 files changed, 298 insertions(+) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index b65800d16..cee502963 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -21,6 +21,10 @@ #include "variant.h" #include "Arduino.h" +#ifdef USE_ZERODMA +#include +#endif + #ifndef WIRE_RISE_TIME_NANOSECONDS // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors // you can override this value in your variant if needed @@ -829,6 +833,253 @@ bool SERCOM::registerService(uint8_t sercomId, ServiceFn fn) return true; } +#ifdef USE_ZERODMA +SERCOM::DmaStatus SERCOM::dmaInit(uint8_t txTrigger, uint8_t rxTrigger) +{ + if (_dmaConfigured) + return DmaStatus::Ok; + + _dmaTxTrigger = txTrigger; + _dmaRxTrigger = rxTrigger; + + if (!_dmaTx) + _dmaTx = new Adafruit_ZeroDMA(); + if (!_dmaRx) + _dmaRx = new Adafruit_ZeroDMA(); + if (!_dmaTx || !_dmaRx) + { + _dmaLastError = DmaStatus::AllocateFailed; + dmaRelease(); + return _dmaLastError; + } + + if (_dmaTx->allocate() != DMA_STATUS_OK) + { + _dmaLastError = DmaStatus::AllocateFailed; + dmaRelease(); + return _dmaLastError; + } + if (_dmaRx->allocate() != DMA_STATUS_OK) + { + _dmaLastError = DmaStatus::AllocateFailed; + dmaRelease(); + return _dmaLastError; + } + + _dmaTx->setTrigger(_dmaTxTrigger); + _dmaTx->setAction(DMA_TRIGGER_ACTON_BEAT); + _dmaRx->setTrigger(_dmaRxTrigger); + _dmaRx->setAction(DMA_TRIGGER_ACTON_BEAT); + + if (_dmaTxCb) + _dmaTx->setCallback(_dmaTxCb); + if (_dmaRxCb) + _dmaRx->setCallback(_dmaRxCb); + + _dmaConfigured = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +void SERCOM::dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb) +{ + _dmaTxCb = txCb; + _dmaRxCb = rxCb; + + if (_dmaConfigured) + { + if (_dmaTxCb) + _dmaTx->setCallback(_dmaTxCb); + if (_dmaRxCb) + _dmaRx->setCallback(_dmaRxCb); + } +} + +SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, void* dstReg, size_t len) +{ + if (!_dmaConfigured || !_dmaTx) + { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (src == nullptr || dstReg == nullptr) + { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) + { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + + if (_dmaTxDesc == nullptr) + _dmaTxDesc = _dmaTx->addDescriptor((void*)src, dstReg, len, DMA_BEAT_SIZE_BYTE, true, false); + else + _dmaTx->changeDescriptor(_dmaTxDesc, (void*)src, dstReg, len); + + if (_dmaTxDesc == nullptr) + { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + ZeroDMAstatus status = _dmaTx->startJob(); + if (status != DMA_STATUS_OK) + { + _dmaTx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaTxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, void* srcReg, size_t len) +{ + if (!_dmaConfigured || !_dmaRx) + { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (dst == nullptr || srcReg == nullptr) + { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) + { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + + if (_dmaRxDesc == nullptr) + _dmaRxDesc = _dmaRx->addDescriptor(srcReg, dst, len, DMA_BEAT_SIZE_BYTE, false, true); + else + _dmaRx->changeDescriptor(_dmaRxDesc, srcReg, dst, len); + + if (_dmaRxDesc == nullptr) + { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + ZeroDMAstatus status = _dmaRx->startJob(); + if (status != DMA_STATUS_OK) + { + _dmaRx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaRxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +SERCOM::DmaStatus SERCOM::dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, + const uint8_t* dummyTx) +{ + if (len == 0) + { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + DmaStatus st = dmaStartRx(rxDst, rxReg, len); + if (st != DmaStatus::Ok) + return st; + static const uint8_t kDummyByte = 0xFF; + const void* txPtr = txSrc ? txSrc : (dummyTx ? dummyTx : &kDummyByte); + st = dmaStartTx(txPtr, txReg, len); + if (st != DmaStatus::Ok) + { + dmaAbortRx(); + return st; + } + return DmaStatus::Ok; +} + +void SERCOM::dmaAbortTx() +{ + if (_dmaTx) + _dmaTx->abort(); + _dmaTxActive = false; +} + +void SERCOM::dmaAbortRx() +{ + if (_dmaRx) + _dmaRx->abort(); + _dmaRxActive = false; +} + +void SERCOM::dmaRelease() +{ + if (!_dmaConfigured) + { + dmaResetDescriptors(); + if (_dmaTx) + { + delete _dmaTx; + _dmaTx = nullptr; + } + if (_dmaRx) + { + delete _dmaRx; + _dmaRx = nullptr; + } + _dmaLastError = DmaStatus::Ok; + return; + } + + dmaAbortTx(); + dmaAbortRx(); + + if (_dmaTx) + _dmaTx->free(); + if (_dmaRx) + _dmaRx->free(); + + dmaResetDescriptors(); + + _dmaConfigured = false; + if (_dmaTx) + { + delete _dmaTx; + _dmaTx = nullptr; + } + if (_dmaRx) + { + delete _dmaRx; + _dmaRx = nullptr; + } + _dmaLastError = DmaStatus::Ok; +} + +void SERCOM::dmaResetDescriptors() +{ + _dmaTxDesc = nullptr; + _dmaRxDesc = nullptr; +} + +bool SERCOM::dmaTxBusy() const +{ + return _dmaTxActive; +} + +bool SERCOM::dmaRxBusy() const +{ + return _dmaRxActive; +} + +SERCOM::DmaStatus SERCOM::dmaLastError() const +{ + return _dmaLastError; +} +#endif + #ifdef SERCOM_STRICT_PADS bool SERCOM::registerPads(uint8_t sercomId, const PadFunc (&pads)[4], bool muxFunctionD) { diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index f2ee1532b..8d577bcee 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -23,6 +23,10 @@ #include "SERCOM_Txn.h" #include +#ifdef USE_ZERODMA +class Adafruit_ZeroDMA; +#endif + // SAMD51 has configurable MAX_SPI, else use peripheral clock default. // Update: changing MAX_SPI via compiler flags is DEPRECATED, because // this affects ALL SPI peripherals including some that should NOT be @@ -267,6 +271,34 @@ class SERCOM static void setPending(uint8_t sercomId); static void dispatchPending(void); +#ifdef USE_ZERODMA + // --- SERCOM ZeroDMA helpers (Phase 1) --- + using DmaCallback = void (*)(Adafruit_ZeroDMA*); + enum class DmaStatus : uint8_t { + Ok = 0, + NotConfigured, + NullPtr, + ZeroLen, + AllocateFailed, + DescriptorFailed, + StartFailed + }; + + DmaStatus dmaInit(uint8_t txTrigger, uint8_t rxTrigger); + void dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb); + DmaStatus dmaStartTx(const void* src, void* dstReg, size_t len); + DmaStatus dmaStartRx(void* dst, void* srcReg, size_t len); + DmaStatus dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, + const uint8_t* dummyTx = nullptr); + void dmaRelease(); + void dmaResetDescriptors(); + void dmaAbortTx(); + void dmaAbortRx(); + bool dmaTxBusy() const; + bool dmaRxBusy() const; + DmaStatus dmaLastError() const; +#endif + #ifdef SERCOM_STRICT_PADS enum class PadFunc : uint8_t { None = 0, @@ -313,6 +345,21 @@ class SERCOM uint8_t calculateBaudrateSynchronous(uint32_t baudrate); uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; + +#ifdef USE_ZERODMA + Adafruit_ZeroDMA* _dmaTx = nullptr; + Adafruit_ZeroDMA* _dmaRx = nullptr; + DmacDescriptor* _dmaTxDesc = nullptr; + DmacDescriptor* _dmaRxDesc = nullptr; + DmaCallback _dmaTxCb = nullptr; + DmaCallback _dmaRxCb = nullptr; + uint8_t _dmaTxTrigger = 0; + uint8_t _dmaRxTrigger = 0; + bool _dmaConfigured = false; + bool _dmaTxActive = false; + bool _dmaRxActive = false; + DmaStatus _dmaLastError = DmaStatus::Ok; +#endif }; #endif From 82b1d3a1d85387e241353bcd2f903fc70ba88a4a Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 29 Jan 2026 11:13:42 -0500 Subject: [PATCH 05/24] refactored initMaster/SlaveWIRE() to support rapid swapping of roles in a multi-master configuration --- cores/arduino/SERCOM.cpp | 134 ++++++++++++++++++++------------------- cores/arduino/SERCOM.h | 43 +++++++++++-- 2 files changed, 106 insertions(+), 71 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index cee502963..5a896ecf5 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -402,87 +402,90 @@ void SERCOM::resetWIRE() //Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0 while(sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); + + _wire = WireConfig{}; } -void SERCOM::enableWIRE() +void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall, uint8_t speed ) { - // I2C Master and Slave modes share the ENABLE bit function. - - // Enable the I2C master mode - sercom->I2CM.CTRLA.bit.ENABLE = 1 ; - - while ( sercom->I2CM.SYNCBUSY.bit.ENABLE != 0 ) - { - // Waiting the enable bit from SYNCBUSY is equal to 0; + if (!_wire.inited) { + initClockNVIC(); + _wire.inited = true; } - // Setting bus idle mode - sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; - - while ( sercom->I2CM.SYNCBUSY.bit.SYSOP != 0 ) - { - // Wait the SYSOP bit from SYNCBUSY coming back to 0 - } + _wire.slaveSpeed = speed; + _wire.addr = SERCOM_I2CS_ADDR_ADDR(ucAddress & 0x7Ful) | // 0x7F, select only 7 bits + SERCOM_I2CS_ADDR_ADDRMASK(0x00ul) | // 0x00, only match exact address + enableGeneralCall; // enable general call (address 0x00) + setSlaveWIRE(); } -void SERCOM::disableWIRE() +void SERCOM::initMasterWIRE( uint32_t baudrate ) { - // I2C Master and Slave modes share the ENABLE bit function. - - // Enable the I2C master mode - sercom->I2CM.CTRLA.bit.ENABLE = 0 ; - - while ( sercom->I2CM.SYNCBUSY.bit.ENABLE != 0 ) - { - // Waiting the enable bit from SYNCBUSY is equal to 0; + if (!_wire.inited) { + initClockNVIC(); + _wire.inited = true; } + + setBaudrateWIRE(baudrate); + setMasterWIRE(); } -void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall ) +void SERCOM::setMasterWIRE(void) { - // Initialize the peripheral clock and interruption - initClockNVIC() ; - resetWIRE() ; + disableWIRE(); - // Set slave mode - sercom->I2CS.CTRLA.bit.MODE = I2C_SLAVE_OPERATION; + sercom->I2CM.CTRLB.reg = _wire.ctrlb |SERCOM_I2CM_CTRLB_QCEN; + sercom->I2CM.BAUD.reg = _wire.baud; + bool sclsm = (_wire.masterSpeed == 0x2); - sercom->I2CS.ADDR.reg = SERCOM_I2CS_ADDR_ADDR( ucAddress & 0x7Ful ) | // 0x7F, select only 7 bits - SERCOM_I2CS_ADDR_ADDRMASK( 0x00ul ); // 0x00, only match exact address - if (enableGeneralCall) { - sercom->I2CS.ADDR.reg |= SERCOM_I2CS_ADDR_GENCEN; // enable general call (address 0x00) - } + // Set master mode and clock settings + sercom->I2CM.CTRLA.reg = _wire.ctrla | + SERCOM_I2CM_CTRLA_MODE(I2C_MASTER_OPERATION) | + SERCOM_I2CM_CTRLA_SPEED(_wire.masterSpeed) | + (sclsm ? SERCOM_I2CM_CTRLA_SCLSM : 0 ); - // Set the interrupt register - sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_PREC | // Stop - SERCOM_I2CS_INTENSET_AMATCH | // Address Match - SERCOM_I2CS_INTENSET_DRDY ; // Data Ready + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; - while ( sercom->I2CM.SYNCBUSY.bit.SYSOP != 0 ) - { - // Wait the SYSOP bit from SYNCBUSY to come back to 0 - } + // Setting bus idle mode + sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; + + // Disable slave interrupts: address match, data ready, stop/restart. + sercom->I2CS.INTENCLR.reg = SERCOM_I2CS_INTENSET_AMATCH | + SERCOM_I2CS_INTENSET_DRDY | + SERCOM_I2CS_INTENSET_PREC; } -void SERCOM::initMasterWIRE( uint32_t baudrate ) +void SERCOM::setSlaveWIRE(void) { - // Initialize the peripheral clock and interruption - initClockNVIC() ; + disableWIRE(); - resetWIRE() ; + sercom->I2CS.ADDR.reg = _wire.addr; + sercom->I2CS.CTRLB.reg = _wire.ctrlb; + bool sclsm = (_wire.slaveSpeed == 0x2); - // Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit) - sercom->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* | - SERCOM_I2CM_CTRLA_SCLSM*/ ; + // Set master mode and clock settings + sercom->I2CS.CTRLA.reg = _wire.ctrla | + SERCOM_I2CS_CTRLA_MODE(I2C_SLAVE_OPERATION) | + SERCOM_I2CS_CTRLA_SPEED(_wire.slaveSpeed) | + (sclsm ? SERCOM_I2CS_CTRLA_SCLSM : 0 ); - // Enable Smart mode and Quick Command - //sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN /*| SERCOM_I2CM_CTRLB_QCEN*/ ; + while (sercom->I2CS.SYNCBUSY.bit.ENABLE != 0) ; + // Setting bus idle mode + sercom->I2CS.STATUS.bit.BUSSTATE = 1 ; + while (sercom->I2CS.SYNCBUSY.bit.SYSOP != 0) ; - // Enable all interrupts - // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; + // Enable slave interrupts: address match, data ready, stop/restart. + sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_PREC | // Stop + SERCOM_I2CS_INTENSET_AMATCH | // Address Match + SERCOM_I2CS_INTENSET_DRDY; // Data Ready +} - // Determine speed mode based on requested baudrate +void SERCOM::setBaudrateWIRE(uint32_t baudrate) +{ +// Determine speed mode based on requested baudrate const uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)} uint8_t speedBit; uint8_t clockStretchMode; // See: 28.6.2.4.6 (SERCOM I2C Highspeed mode) @@ -499,18 +502,21 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) clockStretchMode = 1; } - sercom->I2CM.CTRLA.bit.SPEED = speedBit; - sercom->I2CM.CTRLA.bit.SCLSM = clockStretchMode; + _wire.masterSpeed = speedBit; - uint32_t minBaudrate = freqRef / 512; // BAUD = 255: SAMD51(@100MHz) ~195kHz, SAMD21 ~94kHz + uint32_t fREF = getSercomFreqRef(); + uint32_t minBaudrate = fREF / 512; // BAUD = 255: SAMD51(@100MHz) ~195kHz, SAMD21 ~94kHz uint32_t maxBaudrate = topSpeeds[speedBit]; baudrate = max(minBaudrate, min(baudrate, maxBaudrate)); if (speedBit == 0x2) - sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1; + _wire.baud = SERCOM_I2CM_BAUD_HSBAUD(fREF / (2 * baudrate) - 1); else - sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - - (freqRef/1000000ul * WIRE_RISE_TIME_NANOSECONDS) / 2000; + _wire.baud = SERCOM_I2CM_BAUD_BAUD(fREF / (2 * baudrate) - 5 - + (fREF/1000000ul * WIRE_RISE_TIME_NANOSECONDS) / 2000); + + if (isMasterWIRE()) + setMasterWIRE(); } void SERCOM::prepareNackBitWIRE( void ) @@ -567,7 +573,7 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag // Send start and address sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) | - ((sercom->I2CM.CTRLA.bit.SPEED == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); + ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); // Address Transmitted if ( flag == WIRE_WRITE_FLAG ) // Write mode @@ -647,7 +653,7 @@ bool SERCOM::sendDataSlaveWIRE(uint8_t data) bool SERCOM::isMasterWIRE( void ) { - return sercom->I2CS.CTRLA.bit.MODE == I2C_MASTER_OPERATION; + return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; } bool SERCOM::isSlaveWIRE( void ) diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 8d577bcee..f2f521338 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -215,15 +215,32 @@ class SERCOM bool isReceiveCompleteSPI( void ) ; /* ========== WIRE ========== */ - void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false) ; + void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; void initMasterWIRE(uint32_t baudrate) ; + void setSlaveWIRE( void ) ; + void setMasterWIRE( void ) ; void resetWIRE( void ) ; - void enableWIRE( void ) ; - void disableWIRE( void ); - void prepareNackBitWIRE( void ) ; - void prepareAckBitWIRE( void ) ; - void prepareCommandBitsWire(uint8_t cmd); + inline void enableWIRE( void ) + { + // I2C Master and Slave modes share the ENABLE bit function. + sercom->I2CM.CTRLA.bit.ENABLE = 1 ; + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; + + // Setting bus idle mode + sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; + } + inline void disableWIRE( void ) + { + // I2C Master and Slave modes share the ENABLE bit function. + sercom->I2CM.CTRLA.bit.ENABLE = 0 ; + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; + } + void setBaudrateWIRE(uint32_t baudrate) ; + void prepareNackBitWIRE( void ) ; + void prepareAckBitWIRE( void ) ; + void prepareCommandBitsWire(uint8_t cmd) ; bool startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) ; bool sendDataMasterWIRE(uint8_t data) ; bool sendDataSlaveWIRE(uint8_t data) ; @@ -239,9 +256,10 @@ class SERCOM bool isRestartDetectedWIRE( void ) ; bool isAddressMatch( void ) ; bool isMasterReadOperationWIRE( void ) ; - bool isRXNackReceivedWIRE( void ) ; + bool isRXNackReceivedWIRE( void ) ; int availableWIRE( void ) ; uint8_t readDataWIRE( void ) ; + int8_t getSercomIndex(void); uint32_t getSercomFreqRef(void); @@ -346,6 +364,17 @@ class SERCOM uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; + // Cached I2C master/slave configuration for fast role switching + struct WireConfig { + uint32_t ctrla = 0x00000002; // default CTRLA value: auto ENABLE + uint32_t ctrlb = 0x00000500; // default CTRLB value: SMEN | AACKEN + uint32_t baud = 0x000000FF; // default to lowest supported speed + uint32_t addr = 0x00000000; // default address no GCEN, no ADDRMASK, 7-bit address only + uint8_t masterSpeed = 0x0; // default to lowest speed + uint8_t slaveSpeed = 0x0; // default to lowest speed + bool inited = false; + } _wire; + #ifdef USE_ZERODMA Adafruit_ZeroDMA* _dmaTx = nullptr; Adafruit_ZeroDMA* _dmaRx = nullptr; From b0d39fdfb26dff8e3fef3e6eef87287d66b45bdb Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 29 Jan 2026 11:51:10 -0500 Subject: [PATCH 06/24] generalize RingBuffer to support any type not just uint8_t while preserving legacy behavior. --- cores/arduino/RingBuffer.h | 89 ++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/cores/arduino/RingBuffer.h b/cores/arduino/RingBuffer.h index deecb733c..b6989a5e0 100644 --- a/cores/arduino/RingBuffer.h +++ b/cores/arduino/RingBuffer.h @@ -22,6 +22,7 @@ #define _RING_BUFFER_ #include +#include // Define constants and variables for buffering incoming serial data. We're // using a ring buffer (I think), in which head is the index of the location @@ -32,16 +33,19 @@ #define SERIAL_BUFFER_SIZE 350 #endif -template +template class RingBufferN { public: - uint8_t _aucBuffer[N] ; + T _aucBuffer[N] ; volatile int _iHead ; volatile int _iTail ; public: RingBufferN( void ) ; + bool store( const T& c ) ; + bool read( T& out ) ; + bool peek( T& out ) const ; void store_char( uint8_t c ) ; void clear(); int read_char(); @@ -57,15 +61,16 @@ class RingBufferN typedef RingBufferN RingBuffer; -template +template RingBufferN::RingBufferN( void ) { - memset( _aucBuffer, 0, N ) ; + for (int i = 0; i < N; ++i) + _aucBuffer[i] = T{}; clear(); } -template -void RingBufferN::store_char( uint8_t c ) +template +bool RingBufferN::store( const T& c ) { int i = nextIndex(_iHead); @@ -77,30 +82,58 @@ void RingBufferN::store_char( uint8_t c ) { _aucBuffer[_iHead] = c ; _iHead = i ; + return true; } + return false; } -template -void RingBufferN::clear() +template +bool RingBufferN::read( T& out ) { - _iHead = 0; - _iTail = 0; + if(_iTail == _iHead) + return false; + + out = _aucBuffer[_iTail]; + _iTail = nextIndex(_iTail); + return true; } -template -int RingBufferN::read_char() +template +bool RingBufferN::peek( T& out ) const { if(_iTail == _iHead) - return -1; + return false; - uint8_t value = _aucBuffer[_iTail]; - _iTail = nextIndex(_iTail); + out = _aucBuffer[_iTail]; + return true; +} + +template +void RingBufferN::store_char( uint8_t c ) +{ + static_assert(std::is_same::value, "store_char only valid for uint8_t buffers"); + (void)store(static_cast(c)); +} + +template +void RingBufferN::clear() +{ + _iHead = 0; + _iTail = 0; +} +template +int RingBufferN::read_char() +{ + static_assert(std::is_same::value, "read_char only valid for uint8_t buffers"); + uint8_t value; + if (!read(value)) + return -1; return value; } -template -int RingBufferN::available() +template +int RingBufferN::available() { int delta = _iHead - _iTail; @@ -110,8 +143,8 @@ int RingBufferN::available() return delta; } -template -int RingBufferN::availableForStore() +template +int RingBufferN::availableForStore() { if (_iHead >= _iTail) return N - 1 - _iHead + _iTail; @@ -119,23 +152,25 @@ int RingBufferN::availableForStore() return _iTail - _iHead - 1; } -template -int RingBufferN::peek() +template +int RingBufferN::peek() { - if(_iTail == _iHead) + static_assert(std::is_same::value, "peek() only valid for uint8_t buffers"); + uint8_t value; + if (!peek(value)) return -1; - return _aucBuffer[_iTail]; + return value; } -template -int RingBufferN::nextIndex(int index) +template +int RingBufferN::nextIndex(int index) { return (uint32_t)(index + 1) % N; } -template -bool RingBufferN::isFull() +template +bool RingBufferN::isFull() { return (nextIndex(_iHead) == _iTail); } From 3586cf63d2a91c022fcdd42b16ec3bc9a8c400f1 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 29 Jan 2026 15:43:40 -0500 Subject: [PATCH 07/24] refactored stopTransmissionWIRE to support DMA and async operations, added stopTransmissionWIRE to allow sync closeout, handle errors and continue processing the transaction queue --- cores/arduino/SERCOM.cpp | 121 ++++++++++++++++++++++++------------- cores/arduino/SERCOM.h | 20 +++++- cores/arduino/SERCOM_Txn.h | 19 ++++++ 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 5a896ecf5..6782487e2 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -478,7 +478,8 @@ void SERCOM::setSlaveWIRE(void) while (sercom->I2CS.SYNCBUSY.bit.SYSOP != 0) ; // Enable slave interrupts: address match, data ready, stop/restart. - sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_PREC | // Stop + sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_ERROR | // Error + SERCOM_I2CS_INTENSET_PREC | // Stop SERCOM_I2CS_INTENSET_AMATCH | // Address Match SERCOM_I2CS_INTENSET_DRDY; // Data Ready } @@ -553,10 +554,15 @@ void SERCOM::prepareCommandBitsWire(uint8_t cmd) } } -bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) +bool SERCOM::startTransmissionWIRE(void) { - // 7-bits address + 1-bits R/W - address = (address << 0x1ul) | flag; + SercomTxn* txn = nullptr; + if (!_txnQueue.peek(txn) || txn == nullptr) + return false; + + const bool read = (txn->config & I2C_CFG_READ) != 0; + uint16_t addr = (txn->config & I2C_CFG_10BIT) ? I2C_ADDR(txn->address) : I2C_ADDR7(txn->address); + addr = (uint16_t)((addr << 1) | (read ? 1u : 0u)); // If another master owns the bus or the last bus owner has not properly // sent a stop, return failure early. This will prevent some misbehaved @@ -565,57 +571,90 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag // possible bus states. if(!isBusOwnerWIRE()) { - if( isBusBusyWIRE() || (isArbLostWIRE() && !isBusIdleWIRE()) || isBusUnknownWIRE() ) - { + if (isArbLostWIRE() && !isBusIdleWIRE()) { + stopTransmissionWIRE(SercomWireError::ARBITRATION_LOST); + return false; + } + if (isBusUnknownWIRE()) { + stopTransmissionWIRE(SercomWireError::BUS_STATE_UNKNOWN); return false; } } - // Send start and address - sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) | - ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); + uint32_t addrReg = SERCOM_I2CM_ADDR_ADDR(addr) | + ((txn->config & I2C_CFG_10BIT) ? SERCOM_I2CM_ADDR_TENBITEN : 0) | + ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); - // Address Transmitted - if ( flag == WIRE_WRITE_FLAG ) // Write mode +#ifdef USE_ZERODMA + if (txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP)) { - while( !sercom->I2CM.INTFLAG.bit.MB ) { - // Wait transmission complete - - // If certain errors occur, the MB bit may never be set (RFTM: SAMD21 sec:28.10.6; SAMD51 sec:36.10.7). - // The data transfer errors that can occur (including BUSERR) are all - // rolled up into INTFLAG.bit.ERROR from STATUS.reg - if (sercom->I2CM.INTFLAG.bit.ERROR) { - return false; - } + if (!_dmaConfigured) + dmaInit(_dmaTxTrigger, _dmaRxTrigger); + + if (!_dmaConfigured || !_dmaTx || !_dmaRx) { + stopTransmissionWIRE(SercomWireError::OTHER); + return false; } + + addrReg |= SERCOM_I2CM_ADDR_LENEN | SERCOM_I2CM_ADDR_LEN((uint8_t)txn->length); } - else // Read mode - { - while( !sercom->I2CM.INTFLAG.bit.SB ) { - // Wait transmission complete +#endif + + // Send start and address (non-blocking; ISR handles MB/SB) + sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB; + sercom->I2CM.ADDR.reg = addrReg; - // If the slave NACKS the address, the MB bit will be set. - // A variety of errors in the STATUS register can set the ERROR bit in the INTFLAG register - // In that case, send a stop condition and return false. - if (sercom->I2CM.INTFLAG.bit.MB || sercom->I2CM.INTFLAG.bit.ERROR) { - sercom->I2CM.CTRLB.bit.CMD = 3; // Stop condition - return false; - } - } + return true; +} + +SercomTxn* SERCOM::stopTransmissionWIRE( void ) +{ + return stopTransmissionWIRE( _wire.returnValue ); +} + +SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) +{ + // Policy: only auto-retry recoverable bus-state errors here. All other + // errors are surfaced to the transaction callback for protocol handling. + // Retry/backoff policy is intentionally deferred; a future change may add + // a retry budget or tick-based delay if needed. + + SercomTxn* txn = nullptr; + _txnQueue.peek(txn); - // Clean the 'Slave on Bus' flag, for further usage. - //sercom->I2CM.INTFLAG.bit.SB = 0x1ul; + if (error == SercomWireError::BUS_STATE_UNKNOWN) { + sercom->I2CM.STATUS.bit.BUSSTATE = 1; + + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; + + if (txn) + startTransmissionWIRE(); + + return txn; } - //ACK received (0: ACK, 1: NACK) - if(sercom->I2CM.STATUS.bit.RXNACK) - { - return false; + if (error == SercomWireError::ARBITRATION_LOST || error == SercomWireError::BUS_ERROR) { + sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag + sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; + + if (txn) + startTransmissionWIRE(); + + return txn; } - else - { - return true; + + // Callbacks are expected to run in non-ISR context (main loop/PendSV). + if (_txnQueue.read(txn) && txn != nullptr) { + if (txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); } + + SercomTxn* next = nullptr; + + if (_txnQueue.peek(next) && next) + startTransmissionWIRE(); + + return txn; } bool SERCOM::sendDataMasterWIRE(uint8_t data) diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index f2f521338..d933067d2 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -21,6 +21,7 @@ #include "sam.h" #include "SERCOM_Txn.h" +#include "RingBuffer.h" #include #ifdef USE_ZERODMA @@ -41,6 +42,10 @@ class Adafruit_ZeroDMA; #define SERCOM_FREQ_REF 48000000ul #define SERCOM_NVIC_PRIORITY ((1<<__NVIC_PRIO_BITS) - 1) +#ifndef SERCOM_QUEUE_LENGTH +#define SERCOM_QUEUE_LENGTH 8 +#endif + typedef enum { UART_EXT_CLOCK = 0, @@ -241,7 +246,10 @@ class SERCOM void prepareNackBitWIRE( void ) ; void prepareAckBitWIRE( void ) ; void prepareCommandBitsWire(uint8_t cmd) ; - bool startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) ; + bool startTransmissionWIRE( void ) ; + bool startTransmissionWIRE( uint8_t address, SercomWireReadWriteFlag flag ) = delete; + SercomTxn* stopTransmissionWIRE( void ) ; + SercomTxn* stopTransmissionWIRE( SercomWireError error ) ; bool sendDataMasterWIRE(uint8_t data) ; bool sendDataSlaveWIRE(uint8_t data) ; bool isMasterWIRE( void ) ; @@ -364,17 +372,23 @@ class SERCOM uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; - // Cached I2C master/slave configuration for fast role switching + // Cached I2C master/slave configuration for fast role switching. + // This can be expanded to support additional configuration options + // as needed in the future. For now, it just provides default support + // for (hs) mode and DMA. struct WireConfig { uint32_t ctrla = 0x00000002; // default CTRLA value: auto ENABLE - uint32_t ctrlb = 0x00000500; // default CTRLB value: SMEN | AACKEN + uint32_t ctrlb = 0x00000500; // default CTRLB value: SMEN (both) | AACKEN (Slave only) uint32_t baud = 0x000000FF; // default to lowest supported speed uint32_t addr = 0x00000000; // default address no GCEN, no ADDRMASK, 7-bit address only uint8_t masterSpeed = 0x0; // default to lowest speed uint8_t slaveSpeed = 0x0; // default to lowest speed bool inited = false; + SercomWireError returnValue = SercomWireError::SUCCESS; } _wire; + RingBufferN _txnQueue; + #ifdef USE_ZERODMA Adafruit_ZeroDMA* _dmaTx = nullptr; Adafruit_ZeroDMA* _dmaRx = nullptr; diff --git a/cores/arduino/SERCOM_Txn.h b/cores/arduino/SERCOM_Txn.h index 438de28a3..5ce7c5efe 100644 --- a/cores/arduino/SERCOM_Txn.h +++ b/cores/arduino/SERCOM_Txn.h @@ -14,6 +14,25 @@ struct SercomTxn { void* user; }; +// Mirrors WireDMA I2CError values for SERCOM-level reporting. +enum class SercomWireError : uint8_t +{ + SUCCESS = 0, // No error/operation successful/ACK received + DATA_TOO_LONG = 1, // Payload exceeds DMA buffer or queue slot + NACK_ON_ADDRESS = 2, // Target NACKed during address phase + NACK_ON_DATA = 3, // Target NACKed during data phase + OTHER = 4, // Generic catch-all + BUS_CONFLICT = 5, // Local transaction blocked by active bus use + QUEUE_FULL = 6, // No room in transaction queue + ARBITRATION_LOST = 7, // Lost multi-master arbitration (STATUS.ARBLOST) + BUS_ERROR = 8, // Misplaced START/STOP or illegal bus condition (STATUS.BUSERR) + BUS_STATE_UNKNOWN = 9, // BUSSTATE = 0b00 (unknown/idle state reported) + MASTER_TIMEOUT = 10, // Master timeout (STATUS.MEXTTOUT) + SLAVE_TIMEOUT = 11, // Slave timeout (STATUS.SEXTTOUT) + LENGTH_ERROR = 12, // LENERR when LEN/LENEN mismatch (STATUS.LENERR) + UNKNOWN_ERROR = 13 // Error flag set but no specific bit matched +}; + // I2C config flags and helpers enum : uint16_t { I2C_CFG_READ = 1u << 0, From 7a353b0c8ad59b69a0aee4725763fa3c2bff1b90 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 29 Jan 2026 16:10:30 -0500 Subject: [PATCH 08/24] add 10-bit address support for client operations --- cores/arduino/SERCOM.cpp | 16 ++++++++++++---- cores/arduino/SERCOM.h | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 6782487e2..adb18b4be 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -406,17 +406,25 @@ void SERCOM::resetWIRE() _wire = WireConfig{}; } -void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall, uint8_t speed ) +void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall ) +{ + initSlaveWIRE( ucAddress & 0x7Fu, enableGeneralCall, false, 0x0, false ); +} + +void SERCOM::initSlaveWIRE( uint16_t ucAddress, bool enableGeneralCall, uint8_t speed, bool enable10Bit ) { if (!_wire.inited) { initClockNVIC(); _wire.inited = true; } + uint16_t mask = enable10Bit ? 0x03FFul : 0x007Ful; + _wire.slaveSpeed = speed; - _wire.addr = SERCOM_I2CS_ADDR_ADDR(ucAddress & 0x7Ful) | // 0x7F, select only 7 bits - SERCOM_I2CS_ADDR_ADDRMASK(0x00ul) | // 0x00, only match exact address - enableGeneralCall; // enable general call (address 0x00) + _wire.addr = SERCOM_I2CS_ADDR_ADDR(ucAddress & mask) | // select either 7 or 10-bits + SERCOM_I2CS_ADDR_ADDRMASK(0x00ul) | // 0x00, only match exact address + (enable10Bit ? SERCOM_I2CS_ADDR_TENBITEN : 0) | // 10-bit addressing + enableGeneralCall; // enable general call (address 0x00) setSlaveWIRE(); } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index d933067d2..088beca95 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -220,7 +220,8 @@ class SERCOM bool isReceiveCompleteSPI( void ) ; /* ========== WIRE ========== */ - void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; + void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false) ; + void initSlaveWIRE(uint16_t address, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false) ; void initMasterWIRE(uint32_t baudrate) ; void setSlaveWIRE( void ) ; void setMasterWIRE( void ) ; From b0fbebcf3e183c3fc374a839ee2907a671ed2dc4 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 29 Jan 2026 22:17:13 -0500 Subject: [PATCH 09/24] finish implementing wire async and dma callback structure and inlining ISR called APIs --- cores/arduino/SERCOM.cpp | 227 ++++++++++------------------------ cores/arduino/SERCOM.h | 65 +++++----- cores/arduino/SERCOM_inline.h | 189 ++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+), 196 deletions(-) create mode 100644 cores/arduino/SERCOM_inline.h diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index adb18b4be..3c85c3e71 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -34,6 +34,9 @@ SERCOM::SERCOM(Sercom* s) { sercom = s; + int8_t idx = getSercomIndex(); + if (idx >= 0 && idx < (int8_t)kSercomCount) + s_instances[idx] = this; #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) // A briefly-available but now deprecated feature had the SPI clock source @@ -405,21 +408,50 @@ void SERCOM::resetWIRE() _wire = WireConfig{}; } +void SERCOM::initWIRE(void) +{ + if (!_wire.inited) { + uint8_t idx = getSercomIndex(); + initClockNVIC(); + registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); + _wire.inited = true; + } +#ifdef USE_ZERODMA + initWireDma(); +#endif +} + +#ifdef USE_ZERODMA +void SERCOM::initWireDma(void) +{ +#ifdef SERCOM0_DMAC_ID_TX + if (_dmaConfigured) + return; + + int8_t id = getSercomIndex(); + if (id < 0) + return; + + _dmaTxTrigger = SERCOM0_DMAC_ID_TX + (id * 2); + _dmaRxTrigger = SERCOM0_DMAC_ID_RX + (id * 2); + dmaSetCallbacks(SERCOM::dmaTxCallbackWIRE, SERCOM::dmaRxCallbackWIRE); + dmaInit(_dmaTxTrigger, _dmaRxTrigger); +#else + (void)0; +#endif +} +#endif -void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall ) +void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall, uint8_t speed ) { - initSlaveWIRE( ucAddress & 0x7Fu, enableGeneralCall, false, 0x0, false ); + initSlaveWIRE( ucAddress & 0x7Fu, enableGeneralCall, speed, false ); } void SERCOM::initSlaveWIRE( uint16_t ucAddress, bool enableGeneralCall, uint8_t speed, bool enable10Bit ) { - if (!_wire.inited) { - initClockNVIC(); - _wire.inited = true; - } + initWIRE(); uint16_t mask = enable10Bit ? 0x03FFul : 0x007Ful; - _wire.slaveSpeed = speed; _wire.addr = SERCOM_I2CS_ADDR_ADDR(ucAddress & mask) | // select either 7 or 10-bits SERCOM_I2CS_ADDR_ADDRMASK(0x00ul) | // 0x00, only match exact address @@ -430,10 +462,7 @@ void SERCOM::initSlaveWIRE( uint16_t ucAddress, bool enableGeneralCall, uint8_t void SERCOM::initMasterWIRE( uint32_t baudrate ) { - if (!_wire.inited) { - initClockNVIC(); - _wire.inited = true; - } + initWIRE(); setBaudrateWIRE(baudrate); setMasterWIRE(); @@ -568,6 +597,10 @@ bool SERCOM::startTransmissionWIRE(void) if (!_txnQueue.peek(txn) || txn == nullptr) return false; + _wire.currentTxn = txn; + _wire.txnIndex = 0; + _wire.txnLength = txn->length; + const bool read = (txn->config & I2C_CFG_READ) != 0; uint16_t addr = (txn->config & I2C_CFG_10BIT) ? I2C_ADDR(txn->address) : I2C_ADDR7(txn->address); addr = (uint16_t)((addr << 1) | (read ? 1u : 0u)); @@ -594,7 +627,9 @@ bool SERCOM::startTransmissionWIRE(void) ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); #ifdef USE_ZERODMA - if (txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP)) + _wire.useDma = txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP); + + if (_wire.useDma) { if (!_dmaConfigured) dmaInit(_dmaTxTrigger, _dmaRxTrigger); @@ -665,49 +700,6 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) return txn; } -bool SERCOM::sendDataMasterWIRE(uint8_t data) -{ - //Send data - sercom->I2CM.DATA.bit.DATA = data; - - //Wait transmission successful - while(!sercom->I2CM.INTFLAG.bit.MB) { - // If a data transfer error occurs, the MB bit may never be set. - // Check the error bit and bail if it's set. - if (sercom->I2CM.STATUS.bit.BUSERR) { - return false; - } - } - - //Problems on line? nack received? - if(sercom->I2CM.STATUS.bit.RXNACK) - return false; - else - return true; -} - -bool SERCOM::sendDataSlaveWIRE(uint8_t data) -{ - //Send data - sercom->I2CS.DATA.bit.DATA = data; - - //Problems on line? nack received? - if(!sercom->I2CS.INTFLAG.bit.DRDY || sercom->I2CS.STATUS.bit.RXNACK) - return false; - else - return true; -} - -bool SERCOM::isMasterWIRE( void ) -{ - return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; -} - -bool SERCOM::isSlaveWIRE( void ) -{ - return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; -} - bool SERCOM::isBusIdleWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; @@ -771,30 +763,6 @@ int SERCOM::availableWIRE( void ) return sercom->I2CS.INTFLAG.bit.DRDY; } -uint8_t SERCOM::readDataWIRE( void ) -{ - if(isMasterWIRE()) - { - while (sercom->I2CM.INTFLAG.bit.SB == 0) { - // Waiting complete receive - // A variety of errors in the STATUS register can set the ERROR bit in the INTFLAG register - // In that case, send a stop condition and return false. - // readDataWIRE should really be able to indicate an error (which would never be used - // because the readDataWIRE callers (in Wire.cpp) should have checked availableWIRE() first and timed it - // out if the data never showed up - if (sercom->I2CM.INTFLAG.bit.MB || sercom->I2CM.INTFLAG.bit.ERROR) { - sercom->I2CM.CTRLB.bit.CMD = 3; // Stop condition - return 0xFF; - } - } - - return sercom->I2CM.DATA.bit.DATA ; - } - else - { - return sercom->I2CS.DATA.reg ; - } -} #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) @@ -848,6 +816,7 @@ static const struct { #endif // end !SAMD51 std::array SERCOM::s_states = {}; +std::array SERCOM::s_instances = {}; volatile uint32_t SERCOM::s_pendingMask = 0; bool SERCOM::claim(uint8_t sercomId, Role role) @@ -929,6 +898,17 @@ SERCOM::DmaStatus SERCOM::dmaInit(uint8_t txTrigger, uint8_t rxTrigger) if (_dmaRxCb) _dmaRx->setCallback(_dmaRxCb); + if (_dmaTxDesc == nullptr) + _dmaTxDesc = _dmaTx->addDescriptor(&_dmaDummy, (void*)&sercom->I2CM.DATA.reg, 1, DMA_BEAT_SIZE_BYTE, true, false); + if (_dmaRxDesc == nullptr) + _dmaRxDesc = _dmaRx->addDescriptor((void*)&sercom->I2CM.DATA.reg, &_dmaDummy, 1, DMA_BEAT_SIZE_BYTE, false, true); + if (_dmaTxDesc == nullptr || _dmaRxDesc == nullptr) + { + _dmaLastError = DmaStatus::DescriptorFailed; + dmaRelease(); + return _dmaLastError; + } + _dmaConfigured = true; _dmaLastError = DmaStatus::Ok; return _dmaLastError; @@ -948,90 +928,6 @@ void SERCOM::dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb) } } -SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, void* dstReg, size_t len) -{ - if (!_dmaConfigured || !_dmaTx) - { - _dmaLastError = DmaStatus::NotConfigured; - return _dmaLastError; - } - if (src == nullptr || dstReg == nullptr) - { - _dmaLastError = DmaStatus::NullPtr; - return _dmaLastError; - } - if (len == 0) - { - _dmaLastError = DmaStatus::ZeroLen; - return _dmaLastError; - } - - if (_dmaTxDesc == nullptr) - _dmaTxDesc = _dmaTx->addDescriptor((void*)src, dstReg, len, DMA_BEAT_SIZE_BYTE, true, false); - else - _dmaTx->changeDescriptor(_dmaTxDesc, (void*)src, dstReg, len); - - if (_dmaTxDesc == nullptr) - { - _dmaLastError = DmaStatus::DescriptorFailed; - return _dmaLastError; - } - - ZeroDMAstatus status = _dmaTx->startJob(); - if (status != DMA_STATUS_OK) - { - _dmaTx->abort(); - _dmaLastError = DmaStatus::StartFailed; - return _dmaLastError; - } - - _dmaTxActive = true; - _dmaLastError = DmaStatus::Ok; - return _dmaLastError; -} - -SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, void* srcReg, size_t len) -{ - if (!_dmaConfigured || !_dmaRx) - { - _dmaLastError = DmaStatus::NotConfigured; - return _dmaLastError; - } - if (dst == nullptr || srcReg == nullptr) - { - _dmaLastError = DmaStatus::NullPtr; - return _dmaLastError; - } - if (len == 0) - { - _dmaLastError = DmaStatus::ZeroLen; - return _dmaLastError; - } - - if (_dmaRxDesc == nullptr) - _dmaRxDesc = _dmaRx->addDescriptor(srcReg, dst, len, DMA_BEAT_SIZE_BYTE, false, true); - else - _dmaRx->changeDescriptor(_dmaRxDesc, srcReg, dst, len); - - if (_dmaRxDesc == nullptr) - { - _dmaLastError = DmaStatus::DescriptorFailed; - return _dmaLastError; - } - - ZeroDMAstatus status = _dmaRx->startJob(); - if (status != DMA_STATUS_OK) - { - _dmaRx->abort(); - _dmaLastError = DmaStatus::StartFailed; - return _dmaLastError; - } - - _dmaRxActive = true; - _dmaLastError = DmaStatus::Ok; - return _dmaLastError; -} - SERCOM::DmaStatus SERCOM::dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, const uint8_t* dummyTx) { @@ -1209,8 +1105,9 @@ void SERCOM::dispatchPending(void) continue; ServiceFn fn = s_states[i].service; - if (fn) - fn(); + SERCOM* inst = s_instances[i]; + if (fn && inst) + (inst->*fn)(); } } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 088beca95..88358ce8a 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -220,46 +220,31 @@ class SERCOM bool isReceiveCompleteSPI( void ) ; /* ========== WIRE ========== */ - void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false) ; + void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; void initSlaveWIRE(uint16_t address, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false) ; void initMasterWIRE(uint32_t baudrate) ; void setSlaveWIRE( void ) ; void setMasterWIRE( void ) ; void resetWIRE( void ) ; - inline void enableWIRE( void ) - { - // I2C Master and Slave modes share the ENABLE bit function. - sercom->I2CM.CTRLA.bit.ENABLE = 1 ; - while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; - - // Setting bus idle mode - sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; - } - inline void disableWIRE( void ) - { - // I2C Master and Slave modes share the ENABLE bit function. - sercom->I2CM.CTRLA.bit.ENABLE = 0 ; - while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; - } + inline void enableWIRE( void ) ; + inline void disableWIRE( void ) ; void setBaudrateWIRE(uint32_t baudrate) ; void prepareNackBitWIRE( void ) ; void prepareAckBitWIRE( void ) ; void prepareCommandBitsWire(uint8_t cmd) ; bool startTransmissionWIRE( void ) ; - bool startTransmissionWIRE( uint8_t address, SercomWireReadWriteFlag flag ) = delete; + bool startTransmissionWIRE( uint8_t address, SercomWireReadWriteFlag flag ) = delete ; SercomTxn* stopTransmissionWIRE( void ) ; SercomTxn* stopTransmissionWIRE( SercomWireError error ) ; - bool sendDataMasterWIRE(uint8_t data) ; - bool sendDataSlaveWIRE(uint8_t data) ; - bool isMasterWIRE( void ) ; - bool isSlaveWIRE( void ) ; + inline bool sendDataWIRE( void ) ; + inline bool isMasterWIRE( void ) ; + inline bool isSlaveWIRE( void ) ; bool isBusIdleWIRE( void ) ; bool isBusOwnerWIRE( void ) ; bool isBusUnknownWIRE( void ) ; - bool isArbLostWIRE( void ); - bool isBusBusyWIRE( void ); + bool isArbLostWIRE( void ) ; + bool isBusBusyWIRE( void ) ; bool isDataReadyWIRE( void ) ; bool isStopDetectedWIRE( void ) ; bool isRestartDetectedWIRE( void ) ; @@ -267,10 +252,10 @@ class SERCOM bool isMasterReadOperationWIRE( void ) ; bool isRXNackReceivedWIRE( void ) ; int availableWIRE( void ) ; - uint8_t readDataWIRE( void ) ; + inline bool readDataWIRE( void ); - int8_t getSercomIndex(void); - uint32_t getSercomFreqRef(void); + int8_t getSercomIndex(void) ; + uint32_t getSercomFreqRef(void) ; #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) // SERCOM clock source override is only available on @@ -290,7 +275,7 @@ class SERCOM // --- Async SERCOM scaffolding (Phase 1) --- enum class Role : uint8_t { None = 0, UART, SPI, I2C }; - using ServiceFn = void (*)(); + using ServiceFn = void (SERCOM::*)(); static bool claim(uint8_t sercomId, Role role); static void release(uint8_t sercomId); @@ -313,8 +298,8 @@ class SERCOM DmaStatus dmaInit(uint8_t txTrigger, uint8_t rxTrigger); void dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb); - DmaStatus dmaStartTx(const void* src, void* dstReg, size_t len); - DmaStatus dmaStartRx(void* dst, void* srcReg, size_t len); + inline DmaStatus dmaStartTx(const void* src, void* dstReg, size_t len); + inline DmaStatus dmaStartRx(void* dst, void* srcReg, size_t len); DmaStatus dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, const uint8_t* dummyTx = nullptr); void dmaRelease(); @@ -324,6 +309,12 @@ class SERCOM bool dmaTxBusy() const; bool dmaRxBusy() const; DmaStatus dmaLastError() const; + // DMA callbacks are protocol-owned (Wire/SPI/UART) and registered via dmaSetCallbacks(). + static inline SERCOM* findDmaOwner(Adafruit_ZeroDMA* dma, bool tx); + + // --- WIRE DMA callbacks (ISR-safe, PendSV-only completion) --- + static inline void dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma); + static inline void dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma); #endif #ifdef SERCOM_STRICT_PADS @@ -368,10 +359,15 @@ class SERCOM }; static std::array s_states; + static std::array s_instances; static volatile uint32_t s_pendingMask; uint8_t calculateBaudrateSynchronous(uint32_t baudrate); uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; +#ifdef USE_ZERODMA + void initWireDma(void); +#endif + void initWIRE(void); // Cached I2C master/slave configuration for fast role switching. // This can be expanded to support additional configuration options @@ -384,8 +380,12 @@ class SERCOM uint32_t addr = 0x00000000; // default address no GCEN, no ADDRMASK, 7-bit address only uint8_t masterSpeed = 0x0; // default to lowest speed uint8_t slaveSpeed = 0x0; // default to lowest speed - bool inited = false; + bool inited = false; // whether initMaster/SlaveWIRE has been called + bool useDma = false; // per transaction DMA use flag for Host/Client modes SercomWireError returnValue = SercomWireError::SUCCESS; + SercomTxn* currentTxn = nullptr; + size_t txnIndex = 0; + size_t txnLength = 0; } _wire; RingBufferN _txnQueue; @@ -397,6 +397,7 @@ class SERCOM DmacDescriptor* _dmaRxDesc = nullptr; DmaCallback _dmaTxCb = nullptr; DmaCallback _dmaRxCb = nullptr; + uint8_t _dmaDummy = 0; uint8_t _dmaTxTrigger = 0; uint8_t _dmaRxTrigger = 0; bool _dmaConfigured = false; @@ -406,4 +407,6 @@ class SERCOM #endif }; +#include "SERCOM_inline.h" + #endif diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h new file mode 100644 index 000000000..244869163 --- /dev/null +++ b/cores/arduino/SERCOM_inline.h @@ -0,0 +1,189 @@ +#ifndef SERCOM_INLINE_H +#define SERCOM_INLINE_H + +inline void SERCOM::enableWIRE(void) +{ + // I2C Master and Slave modes share the ENABLE bit function. + sercom->I2CM.CTRLA.bit.ENABLE = 1; + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; + + // Setting bus idle mode + sercom->I2CM.STATUS.bit.BUSSTATE = 1; + while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; +} + +inline void SERCOM::disableWIRE(void) +{ + // I2C Master and Slave modes share the ENABLE bit function. + sercom->I2CM.CTRLA.bit.ENABLE = 0; + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; +} + +inline bool SERCOM::sendDataWIRE( void ) +{ + SercomTxn* txn = _wire.currentTxn; + if (txn && txn->txPtr == nullptr) return false; + +#ifdef USE_ZERODMA + if (_wire.useDma) { + if (!_dmaTxActive) + dmaStartTx(txn->txPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); + return true; + } +#endif + if (_wire.txnIndex < _wire.txnLength) { + sercom->I2CM.DATA.bit.DATA = txn->txPtr[_wire.txnIndex++]; + return true; + } + + return false; +} + +inline bool SERCOM::isMasterWIRE( void ) +{ + return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; +} + +inline bool SERCOM::isSlaveWIRE( void ) +{ + return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; +} + +inline bool SERCOM::readDataWIRE( void ) +{ + SercomTxn* txn = _wire.currentTxn; + if (txn && txn->rxPtr == nullptr) return false; + + if (isMasterWIRE() && (_wire.txnIndex + 1 >= _wire.txnLength)) + sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; + else + sercom->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; + +#ifdef USE_ZERODMA + if (_wire.useDma) { + if (!_dmaRxActive) + dmaStartRx(txn->rxPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); + return true; + } +#endif + + if ( _wire.txnIndex >= _wire.txnLength) + return false; + + txn->rxPtr[_wire.txnIndex++] = sercom->I2CM.DATA.bit.DATA; + return true; +} + +#ifdef USE_ZERODMA +inline SERCOM* SERCOM::findDmaOwner(Adafruit_ZeroDMA* dma, bool tx) +{ + if (dma == nullptr) + return nullptr; + for (size_t i = 0; i < kSercomCount; ++i) + { + SERCOM* inst = s_instances[i]; + if (inst == nullptr) + continue; + if (tx) + { + if (inst->_dmaTx == dma) + return inst; + } + else + { + if (inst->_dmaRx == dma) + return inst; + } + } + return nullptr; +} + +inline SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, void* dstReg, size_t len) +{ + if (!_dmaConfigured || !_dmaTx) { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (src == nullptr || dstReg == nullptr) { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + if (_dmaTxDesc == nullptr) { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + _dmaTx->changeDescriptor(_dmaTxDesc, (void*)src, dstReg, len); + + if (_dmaTx->startJob() != DMA_STATUS_OK) { + _dmaTx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaTxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +inline SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, void* srcReg, size_t len) +{ + if (!_dmaConfigured || !_dmaRx) { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (dst == nullptr || srcReg == nullptr) { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + if (_dmaRxDesc == nullptr) { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + _dmaRx->changeDescriptor(_dmaRxDesc, srcReg, dst, len); + + if (_dmaRx->startJob() != DMA_STATUS_OK) { + _dmaRx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaRxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +inline void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, true); + if (!inst) return; + + inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + inst->_wire.returnValue = SercomWireError::SUCCESS; + inst->_dmaTxActive = false; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} + +inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, false); + if (!inst) return; + + inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + inst->_wire.txnIndex = inst->_wire.txnLength; + inst->_wire.returnValue = SercomWireError::SUCCESS; + inst->_dmaRxActive = false; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} +#endif + +#endif // SERCOM_INLINE_H From a956137f5ceaec1b765f39753c80fc31951f1a0d Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sat, 31 Jan 2026 17:41:07 -0500 Subject: [PATCH 10/24] add async and automatic dma support for client mode operations. --- cores/arduino/SERCOM.cpp | 149 +++++--------- cores/arduino/SERCOM.h | 47 +++-- cores/arduino/SERCOM_inline.h | 87 ++++++-- libraries/Wire/Wire.cpp | 365 ++++++++++++++++++++++++++-------- libraries/Wire/Wire.h | 33 ++- 5 files changed, 465 insertions(+), 216 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 3c85c3e71..914fbaf39 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -468,6 +468,19 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) setMasterWIRE(); } +void SERCOM::registerWireReceive(void (*cb)(void* user, int length), void* user) +{ + _wireDeferredCb = cb; + _wireDeferredUser = user; +} + +void SERCOM::deferWireReceive(int length) +{ + _wireDeferredLength = length; + _wireDeferredPending = true; + setPending((uint8_t)getSercomIndex()); +} + void SERCOM::setMasterWIRE(void) { disableWIRE(); @@ -557,45 +570,12 @@ void SERCOM::setBaudrateWIRE(uint32_t baudrate) setMasterWIRE(); } -void SERCOM::prepareNackBitWIRE( void ) -{ - if(isMasterWIRE()) { - // Send a NACK - sercom->I2CM.CTRLB.bit.ACKACT = 1; - } else { - sercom->I2CS.CTRLB.bit.ACKACT = 1; - } -} - -void SERCOM::prepareAckBitWIRE( void ) -{ - if(isMasterWIRE()) { - // Send an ACK - sercom->I2CM.CTRLB.bit.ACKACT = 0; - } else { - sercom->I2CS.CTRLB.bit.ACKACT = 0; - } -} - -void SERCOM::prepareCommandBitsWire(uint8_t cmd) -{ - if(isMasterWIRE()) { - sercom->I2CM.CTRLB.bit.CMD = cmd; - - while(sercom->I2CM.SYNCBUSY.bit.SYSOP) - { - // Waiting for synchronization - } - } else { - sercom->I2CS.CTRLB.bit.CMD = cmd; - } -} -bool SERCOM::startTransmissionWIRE(void) +SercomTxn* SERCOM::startTransmissionWIRE(void) { SercomTxn* txn = nullptr; if (!_txnQueue.peek(txn) || txn == nullptr) - return false; + return nullptr; _wire.currentTxn = txn; _wire.txnIndex = 0; @@ -614,11 +594,11 @@ bool SERCOM::startTransmissionWIRE(void) { if (isArbLostWIRE() && !isBusIdleWIRE()) { stopTransmissionWIRE(SercomWireError::ARBITRATION_LOST); - return false; + return nullptr; } if (isBusUnknownWIRE()) { stopTransmissionWIRE(SercomWireError::BUS_STATE_UNKNOWN); - return false; + return nullptr; } } @@ -627,16 +607,16 @@ bool SERCOM::startTransmissionWIRE(void) ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); #ifdef USE_ZERODMA - _wire.useDma = txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP); + setWireDma(txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP)); - if (_wire.useDma) + if (isWireDma()) { if (!_dmaConfigured) dmaInit(_dmaTxTrigger, _dmaRxTrigger); if (!_dmaConfigured || !_dmaTx || !_dmaRx) { stopTransmissionWIRE(SercomWireError::OTHER); - return false; + return nullptr; } addrReg |= SERCOM_I2CM_ADDR_LENEN | SERCOM_I2CM_ADDR_LEN((uint8_t)txn->length); @@ -644,12 +624,30 @@ bool SERCOM::startTransmissionWIRE(void) #endif // Send start and address (non-blocking; ISR handles MB/SB) + _wire.active = true; sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB; sercom->I2CM.ADDR.reg = addrReg; + return txn; +} + +bool SERCOM::enqueueWIRE(SercomTxn* txn) +{ + if (txn == nullptr) + return false; + if (!_txnQueue.store(txn)) + return false; + if (!_wire.active) + return startTransmissionWIRE() != nullptr; return true; } +void SERCOM::deferStopWIRE(SercomWireError error) +{ + _wire.returnValue = error; + setPending((uint8_t)getSercomIndex()); +} + SercomTxn* SERCOM::stopTransmissionWIRE( void ) { return stopTransmissionWIRE( _wire.returnValue ); @@ -688,6 +686,8 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) // Callbacks are expected to run in non-ISR context (main loop/PendSV). if (_txnQueue.read(txn) && txn != nullptr) { + _wire.active = false; + _wire.currentTxn = nullptr; if (txn->onComplete) txn->onComplete(txn->user, static_cast(error)); } @@ -697,73 +697,14 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if (_txnQueue.peek(next) && next) startTransmissionWIRE(); - return txn; -} - -bool SERCOM::isBusIdleWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; -} - -bool SERCOM::isBusOwnerWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_OWNER_STATE; -} - -bool SERCOM::isBusUnknownWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_UNKNOWN_STATE; -} - -bool SERCOM::isArbLostWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.ARBLOST == 1; -} - -bool SERCOM::isBusBusyWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_BUSY_STATE; -} - -bool SERCOM::isDataReadyWIRE( void ) -{ - return sercom->I2CS.INTFLAG.bit.DRDY; -} - -bool SERCOM::isStopDetectedWIRE( void ) -{ - return sercom->I2CS.INTFLAG.bit.PREC; -} - -bool SERCOM::isRestartDetectedWIRE( void ) -{ - return sercom->I2CS.STATUS.bit.SR; -} - -bool SERCOM::isAddressMatch( void ) -{ - return sercom->I2CS.INTFLAG.bit.AMATCH; -} - -bool SERCOM::isMasterReadOperationWIRE( void ) -{ - return sercom->I2CS.STATUS.bit.DIR; -} - -bool SERCOM::isRXNackReceivedWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.RXNACK; -} + if (_wireDeferredPending && _wireDeferredCb) { + _wireDeferredPending = false; + _wireDeferredCb(_wireDeferredUser, _wireDeferredLength); + } -int SERCOM::availableWIRE( void ) -{ - if(isMasterWIRE()) - return sercom->I2CM.INTFLAG.bit.SB; - else - return sercom->I2CS.INTFLAG.bit.DRDY; + return txn; } - #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) static const struct { diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 88358ce8a..2e7e137c9 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -223,6 +223,11 @@ class SERCOM void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; void initSlaveWIRE(uint16_t address, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false) ; void initMasterWIRE(uint32_t baudrate) ; + inline void setWireTxn(SercomTxn* txn, size_t length, bool useDma); + inline void setWireDma(bool useDma); + inline bool isWireDma(void) const; + void registerWireReceive(void (*cb)(void* user, int length), void* user); + void deferWireReceive(int length); void setSlaveWIRE( void ) ; void setMasterWIRE( void ) ; @@ -230,29 +235,36 @@ class SERCOM inline void enableWIRE( void ) ; inline void disableWIRE( void ) ; void setBaudrateWIRE(uint32_t baudrate) ; - void prepareNackBitWIRE( void ) ; - void prepareAckBitWIRE( void ) ; - void prepareCommandBitsWire(uint8_t cmd) ; + inline void prepareNackBitWIRE( void ) ; + inline void prepareAckBitWIRE( void ) ; + inline void prepareCommandBitsWire(uint8_t cmd) ; bool startTransmissionWIRE( void ) ; bool startTransmissionWIRE( uint8_t address, SercomWireReadWriteFlag flag ) = delete ; SercomTxn* stopTransmissionWIRE( void ) ; SercomTxn* stopTransmissionWIRE( SercomWireError error ) ; + bool enqueueWIRE(SercomTxn* txn); + void deferStopWIRE(SercomWireError error); + inline uint8_t getINTFLAG( void ) const; + inline uint16_t getSTATUS( void ) const; + inline void clearINTFLAG( void ); inline bool sendDataWIRE( void ) ; inline bool isMasterWIRE( void ) ; inline bool isSlaveWIRE( void ) ; - bool isBusIdleWIRE( void ) ; - bool isBusOwnerWIRE( void ) ; - bool isBusUnknownWIRE( void ) ; - bool isArbLostWIRE( void ) ; - bool isBusBusyWIRE( void ) ; - bool isDataReadyWIRE( void ) ; - bool isStopDetectedWIRE( void ) ; - bool isRestartDetectedWIRE( void ) ; - bool isAddressMatch( void ) ; - bool isMasterReadOperationWIRE( void ) ; - bool isRXNackReceivedWIRE( void ) ; - int availableWIRE( void ) ; + inline bool isBusIdleWIRE( void ) ; + inline bool isBusOwnerWIRE( void ) ; + inline bool isBusUnknownWIRE( void ) ; + inline bool isArbLostWIRE( void ) ; + inline bool isBusBusyWIRE( void ) ; + inline bool isDataReadyWIRE( void ) ; + inline bool isStopDetectedWIRE( void ) ; + inline bool isRestartDetectedWIRE( void ) ; + inline bool isAddressMatch( void ) ; + inline bool isMasterReadOperationWIRE( void ) ; + inline bool isRXNackReceivedWIRE( void ) ; + inline int availableWIRE( void ) ; inline bool readDataWIRE( void ); + inline void writeDataWIRE(uint8_t data); + inline uint8_t readDataWIREByte(void); int8_t getSercomIndex(void) ; uint32_t getSercomFreqRef(void) ; @@ -382,6 +394,7 @@ class SERCOM uint8_t slaveSpeed = 0x0; // default to lowest speed bool inited = false; // whether initMaster/SlaveWIRE has been called bool useDma = false; // per transaction DMA use flag for Host/Client modes + bool active = false; // active transaction in progress SercomWireError returnValue = SercomWireError::SUCCESS; SercomTxn* currentTxn = nullptr; size_t txnIndex = 0; @@ -389,6 +402,10 @@ class SERCOM } _wire; RingBufferN _txnQueue; + void (*_wireDeferredCb)(void* user, int length) = nullptr; + void* _wireDeferredUser = nullptr; + int _wireDeferredLength = 0; + bool _wireDeferredPending = false; #ifdef USE_ZERODMA Adafruit_ZeroDMA* _dmaTx = nullptr; diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index 244869163..36c32eba0 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -1,7 +1,9 @@ #ifndef SERCOM_INLINE_H #define SERCOM_INLINE_H -inline void SERCOM::enableWIRE(void) +#ifdef __cplusplus + +inline void SERCOM::enableWIRE( void ) { // I2C Master and Slave modes share the ENABLE bit function. sercom->I2CM.CTRLA.bit.ENABLE = 1; @@ -12,7 +14,7 @@ inline void SERCOM::enableWIRE(void) while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; } -inline void SERCOM::disableWIRE(void) +inline void SERCOM::disableWIRE( void ) { // I2C Master and Slave modes share the ENABLE bit function. sercom->I2CM.CTRLA.bit.ENABLE = 0; @@ -25,7 +27,7 @@ inline bool SERCOM::sendDataWIRE( void ) if (txn && txn->txPtr == nullptr) return false; #ifdef USE_ZERODMA - if (_wire.useDma) { + if (isWireDma()) { if (!_dmaTxActive) dmaStartTx(txn->txPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); return true; @@ -39,15 +41,34 @@ inline bool SERCOM::sendDataWIRE( void ) return false; } -inline bool SERCOM::isMasterWIRE( void ) -{ - return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; -} - -inline bool SERCOM::isSlaveWIRE( void ) +inline bool SERCOM::isMasterWIRE( void ) { return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; } +inline bool SERCOM::isSlaveWIRE( void ) { return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; } + +inline bool SERCOM::isBusIdleWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; } +inline bool SERCOM::isBusOwnerWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_OWNER_STATE; } +inline bool SERCOM::isBusUnknownWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_UNKNOWN_STATE; } +inline bool SERCOM::isArbLostWIRE( void ) { return sercom->I2CM.STATUS.bit.ARBLOST == 1; } +inline bool SERCOM::isBusBusyWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_BUSY_STATE; } +inline bool SERCOM::isDataReadyWIRE( void ) { return sercom->I2CS.INTFLAG.bit.DRDY; } +inline bool SERCOM::isStopDetectedWIRE( void ) { return sercom->I2CS.INTFLAG.bit.PREC; } +inline bool SERCOM::isRestartDetectedWIRE( void ) { return sercom->I2CS.INTFLAG.bit.SR; } +inline bool SERCOM::isAddressMatch( void ) { return sercom->I2CS.INTFLAG.bit.AMATCH; } + +inline void SERCOM::prepareNackBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 1; } +inline void SERCOM::prepareAckBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 0; } +inline void SERCOM::prepareCommandBitsWire(uint8_t cmd) { - return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; + if (isMasterWIRE()) { + sercom->I2CM.CTRLB.bit.CMD = cmd; + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) + { + // Waiting for synchronization + } + } else { + sercom->I2CS.CTRLB.bit.CMD = cmd; + } } +inline int SERCOM::availableWIRE( void ) { return isMasterWIRE() ? sercom->I2CM.INTFLAG.bit.SB : sercom->I2CS.INTFLAG.bit.DRDY; } inline bool SERCOM::readDataWIRE( void ) { @@ -60,7 +81,7 @@ inline bool SERCOM::readDataWIRE( void ) sercom->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; #ifdef USE_ZERODMA - if (_wire.useDma) { + if (isWireDma()) { if (!_dmaRxActive) dmaStartRx(txn->rxPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); return true; @@ -74,6 +95,38 @@ inline bool SERCOM::readDataWIRE( void ) return true; } +inline uint8_t SERCOM::getINTFLAG( void ) const { return sercom->I2CM.INTFLAG.reg; } +inline uint16_t SERCOM::getSTATUS( void ) const { return sercom->I2CM.STATUS.reg; } +inline void SERCOM::clearINTFLAG( void ) { sercom->I2CM.INTFLAG.reg = 0xFF; } + +inline void SERCOM::setWireTxn(SercomTxn* txn, size_t length, bool useDma) +{ + _wire.currentTxn = txn; + _wire.txnLength = length; + _wire.txnIndex = 0; + _wire.useDma = useDma; +} + +inline void SERCOM::setWireDma(bool useDma) +{ + _wire.useDma = useDma; +} + +inline bool SERCOM::isWireDma(void) const +{ + return _wire.useDma; +} + +inline bool SERCOM::isMasterReadOperationWIRE( void ) +{ + return sercom->I2CS.STATUS.bit.DIR; +} + +inline bool SERCOM::isRXNackReceivedWIRE( void ) +{ + return sercom->I2CM.STATUS.bit.RXNACK; +} + #ifdef USE_ZERODMA inline SERCOM* SERCOM::findDmaOwner(Adafruit_ZeroDMA* dma, bool tx) { @@ -167,7 +220,10 @@ inline void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) SERCOM* inst = findDmaOwner(dma, true); if (!inst) return; - inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + if (inst->isMasterWIRE()) + inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + else + inst->sercom->I2CS.CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3); inst->_wire.returnValue = SercomWireError::SUCCESS; inst->_dmaTxActive = false; SERCOM::setPending((uint8_t)inst->getSercomIndex()); @@ -178,7 +234,10 @@ inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) SERCOM* inst = findDmaOwner(dma, false); if (!inst) return; - inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + if (inst->isMasterWIRE()) + inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); + else + inst->sercom->I2CS.CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3); inst->_wire.txnIndex = inst->_wire.txnLength; inst->_wire.returnValue = SercomWireError::SUCCESS; inst->_dmaRxActive = false; @@ -186,4 +245,6 @@ inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) } #endif +#endif // __cplusplus + #endif // SERCOM_INLINE_H diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index da9732c59..9066f4a97 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -37,12 +37,25 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) this->_uc_pinSDA=pinSDA; this->_uc_pinSCL=pinSCL; transmissionBegun = false; + rxLength = 0; + rxIndex = 0; + txLength = 0; + txIndex = 0; + masterIndex = 0; + awaitingAddressAck = false; + txnDone = false; + txnStatus = 0; + rxBufferPtr = rxBuffer; + rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; + pendingReceive = false; + pendingReceiveLength = 0; } void TwoWire::begin(void) { //Master Mode sercom->initMasterWIRE(TWI_CLOCK); sercom->enableWIRE(); + sercom->registerWireReceive(&TwoWire::onDeferredReceive, this); pinPeripheral(_uc_pinSDA, g_APinDescription[_uc_pinSDA].ulPinType); pinPeripheral(_uc_pinSCL, g_APinDescription[_uc_pinSCL].ulPinType); @@ -52,6 +65,7 @@ void TwoWire::begin(uint8_t address, bool enableGeneralCall) { //Slave mode sercom->initSlaveWIRE(address, enableGeneralCall); sercom->enableWIRE(); + sercom->registerWireReceive(&TwoWire::onDeferredReceive, this); pinPeripheral(_uc_pinSDA, g_APinDescription[_uc_pinSDA].ulPinType); pinPeripheral(_uc_pinSCL, g_APinDescription[_uc_pinSCL].ulPinType); @@ -68,38 +82,54 @@ void TwoWire::end() { } uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) +{ + return requestFrom(address, quantity, static_cast(nullptr), stopBit); +} + +uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit) { if(quantity == 0) { return 0; } - size_t byteRead = 0; - - rxBuffer.clear(); - - if(sercom->startTransmissionWIRE(address, WIRE_READ_FLAG)) - { - // Read first data - rxBuffer.store_char(sercom->readDataWIRE()); + if (rxBuffer != nullptr) { + rxBufferPtr = rxBuffer; + rxBufferCapacity = quantity; + } else { + rxBufferPtr = this->rxBuffer; + rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; + if (quantity > rxBufferCapacity) + quantity = rxBufferCapacity; + } - // Connected to slave - for (byteRead = 1; byteRead < quantity; ++byteRead) - { - sercom->prepareAckBitWIRE(); // Prepare Acknowledge - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_READ); // Prepare the ACK command for the slave - rxBuffer.store_char(sercom->readDataWIRE()); // Read data and send the ACK - } - sercom->prepareNackBitWIRE(); // Prepare NACK to stop slave transmission - //sercom->readDataWIRE(); // Clear data register to send NACK + rxLength = 0; + rxIndex = 0; + + txn.config = I2C_CFG_READ | (stopBit ? I2C_CFG_STOP : 0); + txn.address = address; + txn.length = quantity; + txn.txPtr = nullptr; + txn.rxPtr = rxBufferPtr; + txn.onComplete = &TwoWire::onTxnComplete; + txn.user = this; + txnDone = false; + txnStatus = static_cast(SercomWireError::SUCCESS); + masterIndex = 0; + awaitingAddressAck = true; + + if (!sercom->enqueueWIRE(&txn)) + return 0; - if (stopBit) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); // Send Stop - } + while (!txnDone) { + yield(); } - return byteRead; + if (txnStatus != static_cast(SercomWireError::SUCCESS)) + return 0; + + rxLength = quantity; + return rxLength; } uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity) @@ -110,7 +140,8 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity) void TwoWire::beginTransmission(uint8_t address) { // save address of target and clear buffer txAddress = address; - txBuffer.clear(); + txLength = 0; + txIndex = 0; transmissionBegun = true; } @@ -125,30 +156,38 @@ uint8_t TwoWire::endTransmission(bool stopBit) { transmissionBegun = false ; - // Start I2C transmission - if ( !sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - return 2 ; // Address error + txn.config = stopBit ? I2C_CFG_STOP : 0; + txn.address = txAddress; + txn.length = txLength; + txn.txPtr = txBuffer; + txn.rxPtr = nullptr; + txn.onComplete = &TwoWire::onTxnComplete; + txn.user = this; + txnDone = false; + txnStatus = static_cast(SercomWireError::SUCCESS); + masterIndex = 0; + awaitingAddressAck = true; + + if (!sercom->enqueueWIRE(&txn)) + return static_cast(SercomWireError::QUEUE_FULL); + + while (!txnDone) { + yield(); } - // Send all buffer - while( txBuffer.available() ) - { - // Trying to send data - if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - return 3 ; // Nack or error - } + SercomWireError err = static_cast(txnStatus); + switch (err) { + case SercomWireError::SUCCESS: + return 0; + case SercomWireError::DATA_TOO_LONG: + return 1; + case SercomWireError::NACK_ON_ADDRESS: + return 2; + case SercomWireError::NACK_ON_DATA: + return 3; + default: + return 4; } - - if (stopBit) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - } - - return 0; } uint8_t TwoWire::endTransmission() @@ -159,12 +198,12 @@ uint8_t TwoWire::endTransmission() size_t TwoWire::write(uint8_t ucData) { // No writing, without begun transmission or a full buffer - if ( !transmissionBegun || txBuffer.isFull() ) + if ( !transmissionBegun || txLength >= WIRE_TX_BUFFER_LENGTH ) { return 0 ; } - txBuffer.store_char( ucData ) ; + txBuffer[txLength++] = ucData; return 1 ; } @@ -185,17 +224,21 @@ size_t TwoWire::write(const uint8_t *data, size_t quantity) int TwoWire::available(void) { - return rxBuffer.available(); + return (rxLength > rxIndex) ? (int)(rxLength - rxIndex) : 0; } int TwoWire::read(void) { - return rxBuffer.read_char(); + if (rxIndex >= rxLength) + return -1; + return rxBufferPtr[rxIndex++]; } int TwoWire::peek(void) { - return rxBuffer.peek(); + if (rxIndex >= rxLength) + return -1; + return rxBufferPtr[rxIndex]; } void TwoWire::flush(void) @@ -214,23 +257,143 @@ void TwoWire::onRequest(void(*function)(void)) onRequestCallback = function; } +void TwoWire::setRxBuffer(uint8_t* buffer, size_t length) +{ + if (buffer == nullptr || length == 0) + { + clearRxBuffer(); + return; + } + rxBufferPtr = buffer; + rxBufferCapacity = length; +} + +void TwoWire::clearRxBuffer(void) +{ + if (rxBufferPtr && rxBufferCapacity > 0) + memset(rxBufferPtr, 0, rxBufferCapacity); + rxLength = 0; + rxIndex = 0; +} + +void TwoWire::resetRxBuffer(void) +{ + rxBufferPtr = rxBuffer; + rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; + clearRxBuffer(); +} + +uint8_t* TwoWire::getRxBuffer(void) +{ + return rxBufferPtr; +} + +size_t TwoWire::getRxLength(void) const +{ + return rxLength; +} + void TwoWire::onService(void) { + SercomTxn* t = &txn; + uint8_t flags = (uint8_t)sercom->getINTFLAG(); + + if (flags == 0) + return; + + if (sercom->isRXNackReceivedWIRE()) + { + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + SercomWireError err = awaitingAddressAck ? SercomWireError::NACK_ON_ADDRESS + : SercomWireError::NACK_ON_DATA; + sercom->deferStopWIRE(err); + return; + } + + if (sercom->isMasterWIRE()) + { + if (flags & SERCOM_I2CM_INTFLAG_ERROR) + { + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + uint16_t status = (uint16_t)sercom->getSTATUS(); + uint8_t busState = (status & SERCOM_I2CM_STATUS_BUSSTATE_Msk) >> SERCOM_I2CM_STATUS_BUSSTATE_Pos; + SercomWireError err; + + if (status & SERCOM_I2CM_STATUS_ARBLOST) + err = SercomWireError::ARBITRATION_LOST; + else if (status & SERCOM_I2CM_STATUS_BUSERR) + err = SercomWireError::BUS_ERROR; + else if (status & SERCOM_I2CM_STATUS_MEXTTOUT) + err = SercomWireError::MASTER_TIMEOUT; + else if (status & SERCOM_I2CM_STATUS_SEXTTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + else if (status & SERCOM_I2CM_STATUS_LENERR) + err = SercomWireError::LENGTH_ERROR; + else if (busState == 0x0) + err = SercomWireError::BUS_STATE_UNKNOWN; + else + err = SercomWireError::UNKNOWN_ERROR; + + sercom->clearINTFLAG(); + sercom->deferStopWIRE(err); + return; + } + + bool ok = (t->config & I2C_CFG_READ) ? sercom->readDataWIRE() + : sercom->sendDataWIRE(); + + if (ok){ + awaitingAddressAck = false; +#ifdef USE_ZERODMA + if (t->length > 0 && t->length < 256 && (t->config & I2C_CFG_STOP)) + return; +#endif + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_READ); + return; + } + + // Transaction complete + if (t->config & I2C_CFG_STOP) + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + else + sercom->clearINTFLAG(); + + sercom->deferStopWIRE(SercomWireError::SUCCESS); + return; + } + if ( sercom->isSlaveWIRE() ) { + if (flags & SERCOM_I2CS_INTFLAG_ERROR) + { + uint16_t status = (uint16_t)sercom->getSTATUS(); + SercomWireError err; + + if (status & SERCOM_I2CS_STATUS_BUSERR) + err = SercomWireError::BUS_ERROR; + else if (status & SERCOM_I2CS_STATUS_COLL) + err = SercomWireError::ARBITRATION_LOST; + else if (status & SERCOM_I2CS_STATUS_SEXTTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + else if (status & SERCOM_I2CS_STATUS_LOWTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + else + err = SercomWireError::UNKNOWN_ERROR; + + sercom->clearINTFLAG(); + sercom->deferStopWIRE(err); + return; + } + if(sercom->isStopDetectedWIRE() || (sercom->isAddressMatch() && sercom->isRestartDetectedWIRE() && !sercom->isMasterReadOperationWIRE())) //Stop or Restart detected { sercom->prepareAckBitWIRE(); sercom->prepareCommandBitsWire(0x03); - //Calling onReceiveCallback, if exists - if(onReceiveCallback) - { - onReceiveCallback(available()); - } - - rxBuffer.clear(); + pendingReceive = true; + pendingReceiveLength = available(); + sercom->deferWireReceive(pendingReceiveLength); } else if(sercom->isAddressMatch()) //Address Match { @@ -239,44 +402,89 @@ void TwoWire::onService(void) if(sercom->isMasterReadOperationWIRE()) //Is a request ? { - txBuffer.clear(); + txLength = 0; + txIndex = 0; transmissionBegun = true; - //Calling onRequestCallback, if exists + // onRequestCallback runs in ISR context here. Deferring to PendSV + // would require stalling DRDY or returning 0xFF until the buffer is filled. if(onRequestCallback) - { onRequestCallback(); - } + + t->txPtr = txBuffer; + t->rxPtr = nullptr; + t->length = txLength; + t->config = I2C_CFG_READ; + bool useDma = (txLength > 0 && txLength < 256); + sercom->setWireTxn(t, txLength, useDma); + } else { + rxLength = 0; + rxIndex = 0; + t->txPtr = nullptr; + t->rxPtr = rxBufferPtr; + t->length = rxBufferCapacity; + t->config = 0; + bool useDma = (rxBufferPtr != rxBuffer) && (rxBufferCapacity > 0 && rxBufferCapacity < 256); + sercom->setWireTxn(t, rxBufferCapacity, useDma); + if (useDma) + rxLength = rxBufferCapacity; } } else if(sercom->isDataReadyWIRE()) { - if (sercom->isMasterReadOperationWIRE()) - { - uint8_t c = 0xff; - - if( txBuffer.available() ) { - c = txBuffer.read_char(); - } - - transmissionBegun = sercom->sendDataSlaveWIRE(c); - } else { //Received data - if (rxBuffer.isFull()) { - sercom->prepareNackBitWIRE(); - } else { - //Store data - rxBuffer.store_char(sercom->readDataWIRE()); - - sercom->prepareAckBitWIRE(); - } + bool ok = sercom->isMasterReadOperationWIRE() + ? sercom->sendDataWIRE() + : sercom->readDataWIRE(); + + if (ok) { +#ifdef USE_ZERODMA + if (sercom->isWireDma()) + return; +#endif + if (!sercom->isMasterReadOperationWIRE()) + rxLength++; + sercom->prepareCommandBitsWire(0x03); + return; + } + if (!sercom->isMasterReadOperationWIRE()) { + sercom->prepareNackBitWIRE(); sercom->prepareCommandBitsWire(0x03); + return; } + + sercom->I2CS.DATA.reg = 0xFF; + sercom->prepareCommandBitsWire(0x03); + return; } } } +void TwoWire::onTxnComplete(void* user, int status) +{ + if (!user) + return; + TwoWire* self = static_cast(user); + self->txnStatus = status; + self->txnDone = true; +} + +void TwoWire::onDeferredReceive(void* user, int length) +{ + if (!user) + return; + TwoWire* self = static_cast(user); + if (!self->pendingReceive) + return; + if (self->onReceiveCallback) + self->onReceiveCallback(length); + self->rxLength = 0; + self->rxIndex = 0; + self->pendingReceive = false; + self->pendingReceiveLength = 0; +} + #if WIRE_INTERFACES_COUNT > 0 /* In case new variant doesn't define these macros, * we put here the ones for Arduino Zero. @@ -375,4 +583,3 @@ void TwoWire::onService(void) void WIRE5_IT_HANDLER_3(void) { Wire5.onService(); } #endif // __SAMD51__ #endif - diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index db2ae646e..da2834358 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -25,6 +25,7 @@ #include "SERCOM.h" #include "RingBuffer.h" +#include // WIRE_HAS_END means Wire has end() #define WIRE_HAS_END 1 @@ -43,6 +44,7 @@ class TwoWire : public Stream uint8_t endTransmission(void); uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit); + uint8_t requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit = true); uint8_t requestFrom(uint8_t address, size_t quantity); size_t write(uint8_t data); @@ -54,6 +56,11 @@ class TwoWire : public Stream virtual void flush(void); void onReceive(void(*)(int)); void onRequest(void(*)(void)); + void setRxBuffer(uint8_t* buffer, size_t length); + void clearRxBuffer(void); + void resetRxBuffer(void); + uint8_t* getRxBuffer(void); + size_t getRxLength(void) const; inline size_t write(unsigned long n) { return write((uint8_t)n); } inline size_t write(long n) { return write((uint8_t)n); } @@ -70,17 +77,33 @@ class TwoWire : public Stream bool transmissionBegun; - // RX Buffer - RingBufferN<256> rxBuffer; - - //TX buffer - RingBufferN<256> txBuffer; + // RX/TX buffers (sync compatibility, async staging) + static constexpr size_t WIRE_TX_BUFFER_LENGTH = 255; + static constexpr size_t WIRE_RX_BUFFER_LENGTH = SERIAL_BUFFER_SIZE; + uint8_t rxBuffer[WIRE_RX_BUFFER_LENGTH]; + uint8_t txBuffer[WIRE_TX_BUFFER_LENGTH]; + uint8_t* rxBufferPtr; + size_t rxBufferCapacity; + size_t rxLength; + size_t rxIndex; + size_t txLength; + size_t txIndex; + size_t masterIndex; + bool awaitingAddressAck; uint8_t txAddress; + volatile bool txnDone; + volatile int txnStatus; + bool pendingReceive; + int pendingReceiveLength; + SercomTxn txn; // Callback user functions void (*onRequestCallback)(void); void (*onReceiveCallback)(int); + static void onTxnComplete(void* user, int status); + static void onDeferredReceive(void* user, int length); + // TWI clock frequency static const uint32_t TWI_CLOCK = 100000; }; From 6c58fd558f1783aee3d25988746c2be99f1c67bc Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sat, 31 Jan 2026 21:37:46 -0500 Subject: [PATCH 11/24] refactor SPI to use SERCOM dma implementation --- cores/arduino/SERCOM.cpp | 137 +++++++++++++++ cores/arduino/SERCOM.h | 22 +++ cores/arduino/SERCOM_Txn.h | 8 + cores/arduino/SERCOM_inline.h | 22 +++ libraries/SPI/SPI.cpp | 321 ++++++++++++---------------------- libraries/SPI/SPI.h | 22 +-- 6 files changed, 306 insertions(+), 226 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 914fbaf39..c0df20b29 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -233,6 +233,12 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch resetSPI(); initClockNVIC(); +#ifdef USE_ZERODMA + dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); +#endif + + registerService(getSercomIndex(), &SERCOM::stopTransmissionSPI); + #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode SERCOM_SPI_CTRLA_DOPO(mosi) | @@ -253,6 +259,137 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch while( sercom->SPI.SYNCBUSY.bit.CTRLB == 1 ); } +bool SERCOM::startTransmissionSPI(void) +{ + SercomTxn* txn = nullptr; + if (!_txnQueue.peek(txn) || txn == nullptr) + return false; + + _spi.currentTxn = txn; + _spi.index = 0; + _spi.length = txn->length; + _spi.active = true; + +#ifdef USE_ZERODMA + _spi.useDma = _dmaConfigured; +#else + _spi.useDma = false; +#endif + + if (_spi.useDma) { +#ifdef USE_ZERODMA + void* dataReg = (void*)&sercom->SPI.DATA.reg; + _spi.dmaNeedTx = (txn->txPtr != nullptr); + _spi.dmaNeedRx = (txn->rxPtr != nullptr); + _spi.dmaTxDone = !_spi.dmaNeedTx; + _spi.dmaRxDone = !_spi.dmaNeedRx; + + DmaStatus st = DmaStatus::Ok; + if (_spi.dmaNeedTx && _spi.dmaNeedRx) + st = dmaStartDuplex(txn->txPtr, txn->rxPtr, dataReg, dataReg, txn->length, nullptr); + else if (_spi.dmaNeedTx) + st = dmaStartTx(txn->txPtr, dataReg, txn->length); + else + st = dmaStartDuplex(nullptr, txn->rxPtr, dataReg, dataReg, txn->length, nullptr); + + if (st != DmaStatus::Ok) { + _spi.returnValue = SercomSpiError::UNKNOWN_ERROR; + deferStopSPI(_spi.returnValue); + return false; + } + return true; +#endif + } + + sercom->SPI.INTENSET.reg = SERCOM_SPI_INTENSET_DRE | + SERCOM_SPI_INTENSET_RXC | + SERCOM_SPI_INTENSET_ERROR; + return true; +} + +bool SERCOM::enqueueSPI(SercomTxn* txn) +{ + if (txn == nullptr) + return false; + if (!_txnQueue.store(txn)) + return false; + if (!_spi.active) { + startTransmissionSPI(); + } + return true; +} + +void SERCOM::serviceSPI(void) +{ + if (!_spi.active || _spi.currentTxn == nullptr) + return; + + uint8_t flags = sercom->SPI.INTFLAG.reg; + + if (flags & SERCOM_SPI_INTFLAG_ERROR) + { + _spi.returnValue = SercomSpiError::BUF_OVERFLOW; + sercom->SPI.INTFLAG.reg = SERCOM_SPI_INTFLAG_ERROR; + deferStopSPI(_spi.returnValue); + return; + } + + SercomTxn* txn = _spi.currentTxn; + + if (flags & SERCOM_SPI_INTFLAG_RXC) { + uint8_t rx = sercom->SPI.DATA.reg; + if (txn->rxPtr && _spi.index > 0 && (_spi.index - 1) < _spi.length) + txn->rxPtr[_spi.index - 1] = rx; + if (_spi.index >= _spi.length) { + sercom->SPI.INTENCLR.reg = SERCOM_SPI_INTENCLR_DRE | SERCOM_SPI_INTENCLR_RXC | SERCOM_SPI_INTENCLR_ERROR; + _spi.returnValue = SercomSpiError::SUCCESS; + deferStopSPI(_spi.returnValue); + return; + } + } + + if (flags & SERCOM_SPI_INTFLAG_DRE) { + if (_spi.index < _spi.length) { + uint8_t out = 0xFF; + if (txn->txPtr) + out = txn->txPtr[_spi.index]; + sercom->SPI.DATA.reg = out; + _spi.index++; + return; + } + sercom->SPI.INTENCLR.reg = SERCOM_SPI_INTENCLR_DRE; + } +} + +void SERCOM::deferStopSPI(SercomSpiError error) +{ + _spi.returnValue = error; + setPending((uint8_t)getSercomIndex()); +} + +SercomTxn* SERCOM::stopTransmissionSPI(void) +{ + return stopTransmissionSPI(_spi.returnValue); +} + +SercomTxn* SERCOM::stopTransmissionSPI(SercomSpiError error) +{ + SercomTxn* txn = nullptr; + if (_txnQueue.read(txn) && txn != nullptr) + { + _spi.active = false; + _spi.currentTxn = nullptr; + if (txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); + } + + SercomTxn* next = nullptr; + if (_txnQueue.peek(next) && next) + startTransmissionSPI(); + + return txn; +} + void SERCOM::initSPIClock(SercomSpiClockMode clockMode, uint32_t baudrate) { //Extract data from clockMode diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 2e7e137c9..5ac6b1424 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -218,6 +218,12 @@ class SERCOM bool isDataRegisterEmptySPI( void ) ; bool isTransmitCompleteSPI( void ) ; bool isReceiveCompleteSPI( void ) ; + bool enqueueSPI(SercomTxn* txn); + bool startTransmissionSPI(void); + void serviceSPI(void); + void deferStopSPI(SercomSpiError error); + SercomTxn* stopTransmissionSPI(void); + SercomTxn* stopTransmissionSPI(SercomSpiError error); /* ========== WIRE ========== */ void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; @@ -327,6 +333,9 @@ class SERCOM // --- WIRE DMA callbacks (ISR-safe, PendSV-only completion) --- static inline void dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma); static inline void dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma); + // --- SPI DMA callbacks (protocol-owned) --- + static inline void dmaTxCallbackSPI(Adafruit_ZeroDMA* dma); + static inline void dmaRxCallbackSPI(Adafruit_ZeroDMA* dma); #endif #ifdef SERCOM_STRICT_PADS @@ -401,6 +410,19 @@ class SERCOM size_t txnLength = 0; } _wire; + struct SpiConfig { + bool active = false; + bool useDma = false; + bool dmaNeedTx = false; + bool dmaNeedRx = false; + bool dmaTxDone = false; + bool dmaRxDone = false; + size_t index = 0; + size_t length = 0; + SercomTxn* currentTxn = nullptr; + SercomSpiError returnValue = SercomSpiError::SUCCESS; + } _spi; + RingBufferN _txnQueue; void (*_wireDeferredCb)(void* user, int length) = nullptr; void* _wireDeferredUser = nullptr; diff --git a/cores/arduino/SERCOM_Txn.h b/cores/arduino/SERCOM_Txn.h index 5ce7c5efe..d109abecc 100644 --- a/cores/arduino/SERCOM_Txn.h +++ b/cores/arduino/SERCOM_Txn.h @@ -33,6 +33,14 @@ enum class SercomWireError : uint8_t UNKNOWN_ERROR = 13 // Error flag set but no specific bit matched }; +// SPI error reporting (async callbacks) +enum class SercomSpiError : uint8_t +{ + SUCCESS = 0, + BUF_OVERFLOW = 1, + UNKNOWN_ERROR = 2 +}; + // I2C config flags and helpers enum : uint16_t { I2C_CFG_READ = 1u << 0, diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index 36c32eba0..76a79a69a 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -243,6 +243,28 @@ inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) inst->_dmaRxActive = false; SERCOM::setPending((uint8_t)inst->getSercomIndex()); } + +inline void SERCOM::dmaTxCallbackSPI(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, true); + if (!inst) return; + inst->_spi.dmaTxDone = true; + if (inst->_spi.dmaNeedRx && !inst->_spi.dmaRxDone) + return; + inst->_spi.returnValue = SercomSpiError::SUCCESS; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} + +inline void SERCOM::dmaRxCallbackSPI(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, false); + if (!inst) return; + inst->_spi.dmaRxDone = true; + if (inst->_spi.dmaNeedTx && !inst->_spi.dmaTxDone) + return; + inst->_spi.returnValue = SercomSpiError::SUCCESS; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} #endif #endif // __cplusplus diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 0393752d7..6b1cbfd21 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -58,9 +58,9 @@ void SPIClass::begin() initialized = true; } - if(!use_dma) { - dmaAllocate(); - } +#ifdef USE_ZERODMA + _p_sercom->dmaInit(getDMAC_ID_TX(), getDMAC_ID_RX()); +#endif // PIO init pinPeripheral(_uc_pinMiso, g_APinDescription[_uc_pinMiso].ulPinType); @@ -240,131 +240,8 @@ void SPIClass::transfer(void *buf, size_t count) } } -// DMA-based SPI transfer() function --------------------------------------- - -// IMPORTANT: references to 65535 throughout the DMA code are INTENTIONAL. -// DO NOT try to 'fix' by changing to 65536, or large transfers will fail! -// The BTCNT value of a DMA descriptor is an unsigned 16-bit value with a -// max of 65535. Larger transfers are handled by linked descriptors. - -// Pointer to SPIClass object, one per DMA channel. This allows the -// DMA callback (which has to exist outside the class context) to have -// a reference back to the originating SPIClass object. -static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Legit inits list to NULL - -void SPIClass::dmaCallback(Adafruit_ZeroDMA *dma) { - // dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get - // a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object), - // then locate the originating SPIClass object using array lookup, setting - // the dma_busy element 'false' to indicate end of transfer. Doesn't matter - // if it's a read or write transfer...both channels get pointers to it. - spiPtr[dma->getChannel()]->dma_busy = false; -} - -// For read-only and read+write transfers, a callback is assigned only -// to the read channel to indicate end-of-transfer, and the write channel's -// callback is assigned to this nonsense function (for reasons I'm not -// entirely sure of, setting the callback to NULL doesn't work). -static void dmaDoNothingCallback(Adafruit_ZeroDMA *dma) { (void)dma; } - -// This could've gone in begin(), but for the sake of organization... -void SPIClass::dmaAllocate(void) { - // In order to support fully non-blocking SPI transfers, DMA descriptor - // lists must be created for the input and/or output data. Rather than - // do this dynamically with every transfer, the lists are allocated once - // on SPI init. Maximum list size is finite and knowable -- transfers to - // or from RAM or from flash memory will never exceed the corresponding - // memory size (if they do, you have bigger problems). Descriptors - // aren't large and there's usually only a handful to a dozen, so this - // isn't an excessive burden in exchange for big non-blocking transfers. - uint32_t maxWriteBytes = FLASH_SIZE; // Writes can't exceed all of flash -#if defined(__SAMD51__) - uint32_t maxReadBytes = HSRAM_SIZE; // Reads can't exceed all of RAM -#else - uint32_t maxReadBytes = HMCRAMC0_SIZE; -#endif - if(maxReadBytes > maxWriteBytes) { // I don't think any SAMD devices - maxWriteBytes = maxReadBytes; // have RAM > flash, but just in case - } - - // VITAL to alloc read channel first, assigns it a higher DMA priority! - if(readChannel.allocate() == DMA_STATUS_OK) { - if(writeChannel.allocate() == DMA_STATUS_OK) { - - // Both DMA channels (read and write) allocated successfully, - // set up transfer triggers and other basics... - - // readChannel callback only needs to be set up once. - // Unlike the write callback which may get switched on or off, - // read callback stays put. In certain cases the read DMA job - // just isn't started and the callback is a non-issue then. - readChannel.setTrigger(getDMAC_ID_RX()); - readChannel.setAction(DMA_TRIGGER_ACTON_BEAT); - readChannel.setCallback(dmaCallback); - spiPtr[readChannel.getChannel()] = this; - - writeChannel.setTrigger(getDMAC_ID_TX()); - writeChannel.setAction(DMA_TRIGGER_ACTON_BEAT); - spiPtr[writeChannel.getChannel()] = this; - - // One descriptor per channel has already been allocated - // in Adafruit_ZeroDMA, this just gets pointers to them... - firstReadDescriptor = readChannel.addDescriptor( - (void *)getDataRegister(), // Source address (SPI data reg) - NULL, // Dest address (set later) - 0, // Count (set later) - DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words - false, // Don't increment source address - true); // Increment dest address - firstWriteDescriptor = writeChannel.addDescriptor( - NULL, // Source address (set later) - (void *)getDataRegister(), // Dest (SPI data register) - 0, // Count (set later) - DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words - true, // Increment source address - false); // Don't increment dest address - // This is the number of EXTRA descriptors beyond the first. - int numReadDescriptors = ((maxReadBytes + 65534) / 65535) - 1; - int numWriteDescriptors = ((maxWriteBytes + 65534) / 65535) - 1; - int totalDescriptors = numReadDescriptors + numWriteDescriptors; - - if(totalDescriptors <= 0) { // Don't need extra descriptors, - use_dma = true; // channels are allocated, we're good. - } else { // Else allocate extra descriptor lists... - // Although DMA descriptors are technically a linked list, we just - // allocate a chunk all at once, and finesse the pointers later. - if((extraReadDescriptors = (DmacDescriptor *)malloc( - totalDescriptors * sizeof(DmacDescriptor)))) { - use_dma = true; // Everything allocated successfully - extraWriteDescriptors = &extraReadDescriptors[numReadDescriptors]; - - // Initialize descriptors (copy from first ones) - // cast to void* to suppress warning: with no trivial copy-assignment [-Wclass-memaccess] - for(int i=0; i 65535) { // Limit each descriptor - bytesThisDescriptor = 65535; // to 65535 (not 65536) bytes - } - rDesc->BTCNT.reg = wDesc->BTCNT.reg = bytesThisDescriptor; - if(rxbuf) { // Read-only or read+write - // Auto-inc addresses in DMA descriptors must point to END of data. - // Buf pointers would advance at end of loop anyway, do it now... - rxbuf8 += bytesThisDescriptor; - rDesc->DSTADDR.reg = (uint32_t)rxbuf8; - } - if(txbuf) { // Write-only or read+write - txbuf8 += bytesThisDescriptor; // Same as above - wDesc->SRCADDR.reg = (uint32_t)txbuf8; - wDesc->BTCTRL.bit.SRCINC = 1; // Increment source pointer - } else { // Read-only requires dummy write - wDesc->SRCADDR.reg = (uint32_t)&dum; - wDesc->BTCTRL.bit.SRCINC = 0; // Don't increment source pointer - } - count -= bytesThisDescriptor; - if(count) { // Still more data? - // Link to next descriptors. Extra descriptors are IN ADDITION - // to first, so it's safe and correct that descIdx starts at 0. - rDesc->DESCADDR.reg = (uint32_t)&extraReadDescriptors[descIdx]; - wDesc->DESCADDR.reg = (uint32_t)&extraWriteDescriptors[descIdx]; - rDesc = &extraReadDescriptors[descIdx]; // Update pointers to - wDesc = &extraWriteDescriptors[descIdx]; // next descriptors - descIdx++; - // A write-only transfer doesn't use the read descriptor list, but - // it's quicker to build it (full of nonsense) anyway than to check. - } else { // No more data, end descriptor linked lists - rDesc->DESCADDR.reg = wDesc->DESCADDR.reg = 0; - } - } - - // Set up DMA transfer job(s) ------------------------------------------ - - if(rxbuf) { // Read+write or read-only - // End-of-read callback is already set up, disable write CB, start job - writeChannel.setCallback(dmaDoNothingCallback); - readChannel.startJob(); - } else { // Write-only, use end-of-write callback - writeChannel.setCallback(dmaCallback); - } - - // Run DMA jobs, blocking if requested --------------------------------- + _txn.txPtr = static_cast(txbuf); + _txn.rxPtr = static_cast(rxbuf); + _txn.length = count; + _txn.onComplete = onComplete ? onComplete : &SPIClass::onTxnComplete; + _txn.user = onComplete ? user : this; + txnDone = false; + txnStatus = 0; + + if (!_p_sercom->enqueueSPI(&_txn)) { + if (onComplete) + onComplete(user, static_cast(SercomSpiError::UNKNOWN_ERROR)); + return; + } - dma_busy = true; - writeChannel.startJob(); // All xfers, even read-only, need write job. - if(block) { // If blocking transfer requested, - while(dma_busy); // wait for job to finish - } + if (!onComplete && block) { + while (!txnDone) ; + } +} - } else { // NON-DMA FALLBACK --------------------------------------------- - - if(txbuf8) { - if(rxbuf8) { // Write + read simultaneously - while(count--) { - *rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++); - } - } else { // Write only - while(count--) { - (void)_p_sercom->transferDataSPI(*txbuf8++); - } - } - } else { // Read only - while(count--) { - *rxbuf8++ = _p_sercom->transferDataSPI(0xFF); - } - } +void SPIClass::onService(void) +{ + _p_sercom->serviceSPI(); +} - } // end non-DMA +void SPIClass::onTxnComplete(void* user, int status) +{ + if (!user) + return; + SPIClass* self = static_cast(user); + self->txnStatus = status; + self->txnDone = true; } -// Waits for a prior in-background DMA transfer to complete. +// Waits for a prior in-background transfer to complete. void SPIClass::waitForTransfer(void) { - while(dma_busy); + while(!txnDone); } /* returns the current DMA transfer status to allow non-blocking polling */ bool SPIClass::isBusy(void) { - return dma_busy; + return !txnDone; } @@ -552,19 +374,92 @@ void SPIClass::setClockSource(SercomClockSource clk) { #define PAD_SPI_RX SERCOM_RX_PAD_0 #endif // PERIPH_SPI SPIClass SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX); + + #ifndef SPI_IT_HANDLER + #define SPI_IT_HANDLER SERCOM4_Handler + #endif + void SPI_IT_HANDLER(void) { SPI.onService(); } + + #if defined(__SAMD51__) + #ifndef SPI_IT_HANDLER_0 + #define SPI_IT_HANDLER_0 SERCOM4_0_Handler + #define SPI_IT_HANDLER_1 SERCOM4_1_Handler + #define SPI_IT_HANDLER_2 SERCOM4_2_Handler + #define SPI_IT_HANDLER_3 SERCOM4_3_Handler + #endif + void SPI_IT_HANDLER_0(void) { SPI.onService(); } + void SPI_IT_HANDLER_1(void) { SPI.onService(); } + void SPI_IT_HANDLER_2(void) { SPI.onService(); } + void SPI_IT_HANDLER_3(void) { SPI.onService(); } + #endif #endif #if SPI_INTERFACES_COUNT > 1 SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX); + + #if defined(SPI1_IT_HANDLER) + void SPI1_IT_HANDLER(void) { SPI1.onService(); } + #endif + + #if defined(__SAMD51__) && defined(SPI1_IT_HANDLER_0) + void SPI1_IT_HANDLER_0(void) { SPI1.onService(); } + void SPI1_IT_HANDLER_1(void) { SPI1.onService(); } + void SPI1_IT_HANDLER_2(void) { SPI1.onService(); } + void SPI1_IT_HANDLER_3(void) { SPI1.onService(); } + #endif #endif #if SPI_INTERFACES_COUNT > 2 SPIClass SPI2(&PERIPH_SPI2, PIN_SPI2_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX); + + #if defined(SPI2_IT_HANDLER) + void SPI2_IT_HANDLER(void) { SPI2.onService(); } + #endif + + #if defined(__SAMD51__) && defined(SPI2_IT_HANDLER_0) + void SPI2_IT_HANDLER_0(void) { SPI2.onService(); } + void SPI2_IT_HANDLER_1(void) { SPI2.onService(); } + void SPI2_IT_HANDLER_2(void) { SPI2.onService(); } + void SPI2_IT_HANDLER_3(void) { SPI2.onService(); } + #endif #endif #if SPI_INTERFACES_COUNT > 3 SPIClass SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX); + + #if defined(SPI3_IT_HANDLER) + void SPI3_IT_HANDLER(void) { SPI3.onService(); } + #endif + + #if defined(__SAMD51__) && defined(SPI3_IT_HANDLER_0) + void SPI3_IT_HANDLER_0(void) { SPI3.onService(); } + void SPI3_IT_HANDLER_1(void) { SPI3.onService(); } + void SPI3_IT_HANDLER_2(void) { SPI3.onService(); } + void SPI3_IT_HANDLER_3(void) { SPI3.onService(); } + #endif #endif #if SPI_INTERFACES_COUNT > 4 SPIClass SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX); + + #if defined(SPI4_IT_HANDLER) + void SPI4_IT_HANDLER(void) { SPI4.onService(); } + #endif + + #if defined(__SAMD51__) && defined(SPI4_IT_HANDLER_0) + void SPI4_IT_HANDLER_0(void) { SPI4.onService(); } + void SPI4_IT_HANDLER_1(void) { SPI4.onService(); } + void SPI4_IT_HANDLER_2(void) { SPI4.onService(); } + void SPI4_IT_HANDLER_3(void) { SPI4.onService(); } + #endif #endif #if SPI_INTERFACES_COUNT > 5 SPIClass SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX); + + #if defined(SPI5_IT_HANDLER) + void SPI5_IT_HANDLER(void) { SPI5.onService(); } + #endif + + #if defined(__SAMD51__) && defined(SPI5_IT_HANDLER_0) + void SPI5_IT_HANDLER_0(void) { SPI5.onService(); } + void SPI5_IT_HANDLER_1(void) { SPI5.onService(); } + void SPI5_IT_HANDLER_2(void) { SPI5.onService(); } + void SPI5_IT_HANDLER_3(void) { SPI5.onService(); } + #endif #endif diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 7c719f695..794436e3b 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -21,7 +21,7 @@ #define _SPI_H_INCLUDED #include -#include +#include "SERCOM.h" // SPI_HAS_TRANSACTION means SPI has // - beginTransaction() @@ -118,7 +118,10 @@ class SPIClass { uint16_t transfer16(uint16_t data); void transfer(void *buf, size_t count); void transfer(const void* txbuf, void* rxbuf, size_t count, - bool block = true); + bool block = true, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); + void onService(void); void waitForTransfer(void); bool isBusy(void); @@ -169,17 +172,10 @@ class SPIClass { char interruptSave; uint32_t interruptMask; - // transfer(txbuf, rxbuf, count, block) uses DMA when possible - Adafruit_ZeroDMA readChannel; - Adafruit_ZeroDMA writeChannel; - DmacDescriptor *firstReadDescriptor = NULL; // List entry point - DmacDescriptor *firstWriteDescriptor = NULL; - DmacDescriptor *extraReadDescriptors = NULL; // Add'l descriptors - DmacDescriptor *extraWriteDescriptors = NULL; - bool use_dma = false; // true on successful alloc - volatile bool dma_busy = false; - void dmaAllocate(void); - static void dmaCallback(Adafruit_ZeroDMA *dma); + volatile bool txnDone = false; + volatile int txnStatus = 0; + SercomTxn _txn; + static void onTxnComplete(void* user, int status); }; #if SPI_INTERFACES_COUNT > 0 From 10b86e3cb03b5e6a1316334f5b824186c2509ec7 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sat, 14 Feb 2026 23:50:21 -0600 Subject: [PATCH 12/24] stage more UART DMA structure --- cores/arduino/SERCOM.cpp | 133 +++++++++++++++++++++++++++++++++++---- cores/arduino/SERCOM.h | 24 ++++++- cores/arduino/Uart.cpp | 118 ++++++++++++++++++++++++++++++++++ cores/arduino/Uart.h | 13 ++++ 4 files changed, 276 insertions(+), 12 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index c0df20b29..aa812d93b 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -66,6 +66,27 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint initClockNVIC(); resetUART(); +#ifdef USE_ZERODMA +#ifdef SERCOM0_DMAC_ID_TX + int8_t id = getSercomIndex(); + if (id < 0) + return; + + dmaSetCallbacks(SERCOM::dmaTxCallbackUART, SERCOM::dmaRxCallbackUART); + + if (_dmaConfigured) + return; + + _dmaTxTrigger = SERCOM0_DMAC_ID_TX + (id * 2); + _dmaRxTrigger = SERCOM0_DMAC_ID_RX + (id * 2); + dmaInit(_dmaTxTrigger, _dmaRxTrigger); +#else + (void)0; +#endif // SERCOM0_DMAC_ID_TX +#endif // USE_ZERODMA + + registerService(getSercomIndex(), &SERCOM::stopTransmissionUART); + //Setting the CTRLA register sercom->USART.CTRLA.reg = SERCOM_USART_CTRLA_MODE(mode) | SERCOM_USART_CTRLA_SAMPR(sampleRate); @@ -93,6 +114,7 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); } } + void SERCOM::initFrame(SercomUartCharSize charSize, SercomDataOrder dataOrder, SercomParityMode parityMode, SercomNumberStopBit nbStopBits) { //Setting the CTRLA register @@ -224,14 +246,111 @@ void SERCOM::disableDataRegisterEmptyInterruptUART() sercom->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE; } +bool SERCOM::startTransmissionUART(void) +{ + SercomTxn* txn = nullptr; + if (!_txnQueue.peek(txn) || txn == nullptr) + return false; + + _uart.currentTxn = txn; + _uart.index = 0; + _uart.length = txn->length; + _uart.active = true; + +#ifdef USE_ZERODMA + _uart.useDma = _dmaConfigured; +#else + _uart.useDma = false; +#endif + + if (!_uart.useDma) + return false; + +#ifdef USE_ZERODMA + void* dataReg = (void*)&sercom->USART.DATA.reg; + _uart.dmaNeedTx = (txn->txPtr != nullptr); + _uart.dmaNeedRx = (txn->rxPtr != nullptr); + _uart.dmaTxDone = !_uart.dmaNeedTx; + _uart.dmaRxDone = !_uart.dmaNeedRx; + + DmaStatus st = DmaStatus::Ok; + if (_uart.dmaNeedTx) + st = dmaStartTx(txn->txPtr, dataReg, txn->length); + else if (_uart.dmaNeedRx) + st = dmaStartRx(txn->rxPtr, dataReg, txn->length); + + if (st != DmaStatus::Ok) { + _uart.returnValue = SercomUartError::UNKNOWN_ERROR; + deferStopUART(_uart.returnValue); + return false; + } + return true; +#else + return false; +#endif +} + +bool SERCOM::enqueueUART(SercomTxn* txn) +{ + if (txn == nullptr) + return false; +#ifdef USE_ZERODMA + if (!_dmaConfigured) + return false; +#else + return false; +#endif + if (!_txnQueue.store(txn)) + return false; + if (!_uart.active) { + if (!startTransmissionUART()) { + SercomTxn* tmp = nullptr; + _txnQueue.read(tmp); + if (tmp && tmp->onComplete) + tmp->onComplete(tmp->user, static_cast(SercomUartError::UNKNOWN_ERROR)); + return false; + } + } + return true; +} + +void SERCOM::deferStopUART(SercomUartError error) +{ + _uart.returnValue = error; + setPending((uint8_t)getSercomIndex()); +} + +SercomTxn* SERCOM::stopTransmissionUART(void) +{ + return stopTransmissionUART(_uart.returnValue); +} + +SercomTxn* SERCOM::stopTransmissionUART(SercomUartError error) +{ + SercomTxn* txn = nullptr; + if (_txnQueue.read(txn) && txn != nullptr) + { + _uart.active = false; + _uart.currentTxn = nullptr; + if (txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); + } + + SercomTxn* next = nullptr; + if (_txnQueue.peek(next) && next) + startTransmissionUART(); + + return txn; +} + /* ========================= * ===== Sercom SPI * ========================= */ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize charSize, SercomDataOrder dataOrder) { - resetSPI(); initClockNVIC(); + resetSPI(); #ifdef USE_ZERODMA dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); @@ -497,16 +616,8 @@ uint8_t SERCOM::transferDataSPI(uint8_t data) return sercom->SPI.DATA.bit.DATA; // Reading data } -bool SERCOM::isBufferOverflowErrorSPI() -{ - return sercom->SPI.STATUS.bit.BUFOVF; -} - -bool SERCOM::isDataRegisterEmptySPI() -{ - //DRE : Data Register Empty - return sercom->SPI.INTFLAG.bit.DRE; -} +bool SERCOM::isBufferOverflowErrorSPI() { return sercom->SPI.STATUS.bit.BUFOVF; } +bool SERCOM::isDataRegisterEmptySPI() { return sercom->SPI.INTFLAG.bit.DRE; } //bool SERCOM::isTransmitCompleteSPI() //{ diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 5ac6b1424..f3b797d3b 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -202,6 +202,11 @@ class SERCOM void acknowledgeUARTError() ; void enableDataRegisterEmptyInterruptUART(); void disableDataRegisterEmptyInterruptUART(); + bool enqueueUART(SercomTxn* txn); + bool startTransmissionUART(void); + SercomTxn* stopTransmissionUART(void); + SercomTxn* stopTransmissionUART(SercomUartError error); + void deferStopUART(SercomUartError error); /* ========== SPI ========== */ void initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize charSize, SercomDataOrder dataOrder) ; @@ -336,7 +341,10 @@ class SERCOM // --- SPI DMA callbacks (protocol-owned) --- static inline void dmaTxCallbackSPI(Adafruit_ZeroDMA* dma); static inline void dmaRxCallbackSPI(Adafruit_ZeroDMA* dma); -#endif + // --- UART DMA callbacks (protocol-owned) --- + static inline void dmaTxCallbackUART(Adafruit_ZeroDMA* dma); + static inline void dmaRxCallbackUART(Adafruit_ZeroDMA* dma); +#endif // USE_ZERODMA #ifdef SERCOM_STRICT_PADS enum class PadFunc : uint8_t { @@ -387,6 +395,7 @@ class SERCOM void initClockNVIC( void ) ; #ifdef USE_ZERODMA void initWireDma(void); + void initUartDma(void); #endif void initWIRE(void); @@ -423,6 +432,19 @@ class SERCOM SercomSpiError returnValue = SercomSpiError::SUCCESS; } _spi; + struct UartConfig { + bool active = false; + bool useDma = false; + bool dmaNeedTx = false; + bool dmaNeedRx = false; + bool dmaTxDone = false; + bool dmaRxDone = false; + size_t index = 0; + size_t length = 0; + SercomTxn* currentTxn = nullptr; + SercomUartError returnValue = SercomUartError::SUCCESS; + } _uart; + RingBufferN _txnQueue; void (*_wireDeferredCb)(void* user, int length) = nullptr; void* _wireDeferredUser = nullptr; diff --git a/cores/arduino/Uart.cpp b/cores/arduino/Uart.cpp index dd797da4d..6d0262ace 100644 --- a/cores/arduino/Uart.cpp +++ b/cores/arduino/Uart.cpp @@ -190,6 +190,124 @@ size_t Uart::write(const uint8_t data) return 1; } +size_t Uart::write(const uint8_t* buffer, size_t size) +{ + if (buffer == nullptr || size == 0) + return 0; + +#ifdef USE_ZERODMA + _txn.txPtr = buffer; + _txn.rxPtr = nullptr; + _txn.length = size; + _txn.onComplete = &Uart::onTxnComplete; + _txn.user = this; + txnDone = false; + txnStatus = 0; + + if (sercom->enqueueUART(&_txn)) { + while (!txnDone) ; + return size; + } +#endif + + for (size_t i = 0; i < size; ++i) + write(buffer[i]); + return size; +} + +size_t Uart::writeAsync(const uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user) +{ + if (buffer == nullptr || size == 0) + return 0; + +#ifdef USE_ZERODMA + _txn.txPtr = buffer; + _txn.rxPtr = nullptr; + _txn.length = size; + _txn.onComplete = onComplete ? onComplete : &Uart::onTxnComplete; + _txn.user = onComplete ? user : this; + txnDone = false; + txnStatus = 0; + + if (!sercom->enqueueUART(&_txn)) + return 0; + + return size; +#else + (void)onComplete; + (void)user; + for (size_t i = 0; i < size; ++i) + write(buffer[i]); + return size; +#endif +} + +size_t Uart::read(uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user) +{ + if (buffer == nullptr || size == 0) + return 0; + + if (onComplete == nullptr) { + size_t readCount = 0; + while (readCount < size) { + int c = read(); + if (c >= 0) + buffer[readCount++] = static_cast(c); + } + return readCount; + } + +#ifdef USE_ZERODMA + pendingRxCb = onComplete; + pendingRxUser = user; + rxExternalActive = true; + + sercom->getSercom()->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_RXC; + + _txn.txPtr = nullptr; + _txn.rxPtr = buffer; + _txn.length = size; + _txn.onComplete = &Uart::onTxnComplete; + _txn.user = this; + txnDone = false; + txnStatus = 0; + + if (!sercom->enqueueUART(&_txn)) { + sercom->getSercom()->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + rxExternalActive = false; + pendingRxCb = nullptr; + pendingRxUser = nullptr; + return 0; + } + + return size; +#else + (void)onComplete; + (void)user; + return 0; +#endif +} + +void Uart::onTxnComplete(void* user, int status) +{ + if (!user) + return; + Uart* self = static_cast(user); + self->txnStatus = status; + self->txnDone = true; + if (self->rxExternalActive) { + self->rxExternalActive = false; + self->sercom->getSercom()->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + if (self->pendingRxCb) { + void (*cb)(void*, int) = self->pendingRxCb; + void* cbUser = self->pendingRxUser; + self->pendingRxCb = nullptr; + self->pendingRxUser = nullptr; + cb(cbUser, status); + } + } +} + SercomNumberStopBit Uart::extractNbStopBit(uint16_t config) { switch(config & HARDSER_STOP_BIT_MASK) diff --git a/cores/arduino/Uart.h b/cores/arduino/Uart.h index 8f80ea9ce..9687dbbbb 100644 --- a/cores/arduino/Uart.h +++ b/cores/arduino/Uart.h @@ -36,8 +36,13 @@ class Uart : public HardwareSerial int availableForWrite(); int peek(); int read(); + size_t read(uint8_t* buffer, size_t size, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); void flush(); size_t write(const uint8_t data); + size_t write(const uint8_t* buffer, size_t size); + size_t writeAsync(const uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user); using Print::write; // pull in write(str) and write(buf, size) from Print void IrqHandler(); @@ -59,6 +64,14 @@ class Uart : public HardwareSerial uint32_t ul_pinMaskRTS; uint8_t uc_pinCTS; + volatile bool txnDone = false; + volatile int txnStatus = 0; + SercomTxn _txn; + static void onTxnComplete(void* user, int status); + bool rxExternalActive = false; + void (*pendingRxCb)(void* user, int status) = nullptr; + void* pendingRxUser = nullptr; + SercomNumberStopBit extractNbStopBit(uint16_t config); SercomUartCharSize extractCharSize(uint16_t config); SercomParityMode extractParity(uint16_t config); From 6e6d303b8b27fdb598f3d4de2ae9ad3e726ece96 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 15 Feb 2026 00:03:37 -0600 Subject: [PATCH 13/24] unify structure between different sercoms to mirror each other more consistently --- cores/arduino/RingBuffer.h | 2 +- cores/arduino/SERCOM.cpp | 281 +++++++++++++++++++--------------- cores/arduino/SERCOM.h | 47 +++--- cores/arduino/SERCOM_Txn.h | 15 +- cores/arduino/SERCOM_inline.h | 114 +++----------- libraries/SPI/SPI.cpp | 91 ++++------- libraries/SPI/SPI.h | 4 +- 7 files changed, 256 insertions(+), 298 deletions(-) diff --git a/cores/arduino/RingBuffer.h b/cores/arduino/RingBuffer.h index b6989a5e0..3c6d1b95c 100644 --- a/cores/arduino/RingBuffer.h +++ b/cores/arduino/RingBuffer.h @@ -62,7 +62,7 @@ typedef RingBufferN RingBuffer; template -RingBufferN::RingBufferN( void ) +RingBufferN::RingBufferN( void ) { for (int i = 0; i < N; ++i) _aucBuffer[i] = T{}; diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index aa812d93b..4df26512c 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -57,6 +57,19 @@ SERCOM::SERCOM(Sercom* s) #endif // end __SAMD51__ } +void SERCOM::resetSERCOM() +{ + // UART, SPI, I2CS, and I2CM use the same SWRST and DBGCTRL bits, so this works for all modes + sercom->USART.CTRLA.bit.SWRST = 1 ; + + while ( sercom->USART.CTRLA.bit.SWRST || sercom->USART.SYNCBUSY.bit.SWRST ) + ; // Wait for both bits Software Reset from CTRLA and SYNCBUSY coming back to 0 + + // DBGCTRL is not affected by SWRST, so explicitly clear it here to ensure debug behavior is + // consistent after reset + sercom->USART.DBGCTRL.bit.DBGSTOP = 0; +} + /* ========================= * ===== Sercom UART * ========================= @@ -67,22 +80,11 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint resetUART(); #ifdef USE_ZERODMA -#ifdef SERCOM0_DMAC_ID_TX int8_t id = getSercomIndex(); - if (id < 0) - return; - + if (id >= 0) { dmaSetCallbacks(SERCOM::dmaTxCallbackUART, SERCOM::dmaRxCallbackUART); - - if (_dmaConfigured) - return; - - _dmaTxTrigger = SERCOM0_DMAC_ID_TX + (id * 2); - _dmaRxTrigger = SERCOM0_DMAC_ID_RX + (id * 2); - dmaInit(_dmaTxTrigger, _dmaRxTrigger); -#else - (void)0; -#endif // SERCOM0_DMAC_ID_TX + dmaInit(id); + } #endif // USE_ZERODMA registerService(getSercomIndex(), &SERCOM::stopTransmissionUART); @@ -141,22 +143,7 @@ void SERCOM::initPads(SercomUartTXPad txPad, SercomRXPad rxPad) void SERCOM::resetUART() { - // Start the Software Reset - sercom->USART.CTRLA.bit.SWRST = 1 ; - - while ( sercom->USART.CTRLA.bit.SWRST || sercom->USART.SYNCBUSY.bit.SWRST ) - { - // Wait for both bits Software Reset from CTRLA and SYNCBUSY coming back to 0 - } -} - -void SERCOM::enableUART() -{ - //Setting the enable bit to 1 - sercom->USART.CTRLA.bit.ENABLE = 0x1u; - - //Wait for then enable bit from SYNCBUSY is equal to 0; - while(sercom->USART.SYNCBUSY.bit.ENABLE); + resetSERCOM(); } void SERCOM::flushUART() @@ -353,8 +340,12 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch resetSPI(); #ifdef USE_ZERODMA + int8_t id = getSercomIndex(); + if (id >= 0) { dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); -#endif + dmaInit(id); + } +#endif // USE_ZERODMA registerService(getSercomIndex(), &SERCOM::stopTransmissionSPI); @@ -438,48 +429,6 @@ bool SERCOM::enqueueSPI(SercomTxn* txn) return true; } -void SERCOM::serviceSPI(void) -{ - if (!_spi.active || _spi.currentTxn == nullptr) - return; - - uint8_t flags = sercom->SPI.INTFLAG.reg; - - if (flags & SERCOM_SPI_INTFLAG_ERROR) - { - _spi.returnValue = SercomSpiError::BUF_OVERFLOW; - sercom->SPI.INTFLAG.reg = SERCOM_SPI_INTFLAG_ERROR; - deferStopSPI(_spi.returnValue); - return; - } - - SercomTxn* txn = _spi.currentTxn; - - if (flags & SERCOM_SPI_INTFLAG_RXC) { - uint8_t rx = sercom->SPI.DATA.reg; - if (txn->rxPtr && _spi.index > 0 && (_spi.index - 1) < _spi.length) - txn->rxPtr[_spi.index - 1] = rx; - if (_spi.index >= _spi.length) { - sercom->SPI.INTENCLR.reg = SERCOM_SPI_INTENCLR_DRE | SERCOM_SPI_INTENCLR_RXC | SERCOM_SPI_INTENCLR_ERROR; - _spi.returnValue = SercomSpiError::SUCCESS; - deferStopSPI(_spi.returnValue); - return; - } - } - - if (flags & SERCOM_SPI_INTFLAG_DRE) { - if (_spi.index < _spi.length) { - uint8_t out = 0xFF; - if (txn->txPtr) - out = txn->txPtr[_spi.index]; - sercom->SPI.DATA.reg = out; - _spi.index++; - return; - } - sercom->SPI.INTENCLR.reg = SERCOM_SPI_INTENCLR_DRE; - } -} - void SERCOM::deferStopSPI(SercomSpiError error) { _spi.returnValue = error; @@ -534,33 +483,7 @@ void SERCOM::initSPIClock(SercomSpiClockMode clockMode, uint32_t baudrate) void SERCOM::resetSPI() { - //Setting the Software Reset bit to 1 - sercom->SPI.CTRLA.bit.SWRST = 1; - - //Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0 - while(sercom->SPI.CTRLA.bit.SWRST || sercom->SPI.SYNCBUSY.bit.SWRST); -} - -void SERCOM::enableSPI() -{ - //Setting the enable bit to 1 - sercom->SPI.CTRLA.bit.ENABLE = 1; - - while(sercom->SPI.SYNCBUSY.bit.ENABLE) - { - //Waiting then enable bit from SYNCBUSY is equal to 0; - } -} - -void SERCOM::disableSPI() -{ - while(sercom->SPI.SYNCBUSY.bit.ENABLE) - { - //Waiting then enable bit from SYNCBUSY is equal to 0; - } - - //Setting the enable bit to 0 - sercom->SPI.CTRLA.bit.ENABLE = 0; + resetSERCOM(); } void SERCOM::setDataOrderSPI(SercomDataOrder dataOrder) @@ -953,52 +876,71 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) return txn; } +// Hardware metadata structure for SERCOM peripherals - private to this file #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) - -static const struct { +// SAMD51 has separate core and slow clocks, and extended interrupt array +struct SercomData { Sercom *sercomPtr; uint8_t id_core; uint8_t id_slow; IRQn_Type irq[4]; -} sercomData[] = { + uint8_t dmaTxTrigger; + uint8_t dmaRxTrigger; + void *dataReg; // Pointer to DATA register +}; + +static const SercomData sercomData[] = { { SERCOM0, SERCOM0_GCLK_ID_CORE, SERCOM0_GCLK_ID_SLOW, - SERCOM0_0_IRQn, SERCOM0_1_IRQn, SERCOM0_2_IRQn, SERCOM0_3_IRQn }, + SERCOM0_0_IRQn, SERCOM0_1_IRQn, SERCOM0_2_IRQn, SERCOM0_3_IRQn, + SERCOM0_DMAC_ID_TX, SERCOM0_DMAC_ID_RX, (void*)&SERCOM0->I2CM.DATA.reg }, { SERCOM1, SERCOM1_GCLK_ID_CORE, SERCOM1_GCLK_ID_SLOW, - SERCOM1_0_IRQn, SERCOM1_1_IRQn, SERCOM1_2_IRQn, SERCOM1_3_IRQn }, + SERCOM1_0_IRQn, SERCOM1_1_IRQn, SERCOM1_2_IRQn, SERCOM1_3_IRQn, + SERCOM1_DMAC_ID_TX, SERCOM1_DMAC_ID_RX, (void*)&SERCOM1->I2CM.DATA.reg }, { SERCOM2, SERCOM2_GCLK_ID_CORE, SERCOM2_GCLK_ID_SLOW, - SERCOM2_0_IRQn, SERCOM2_1_IRQn, SERCOM2_2_IRQn, SERCOM2_3_IRQn }, + SERCOM2_0_IRQn, SERCOM2_1_IRQn, SERCOM2_2_IRQn, SERCOM2_3_IRQn, + SERCOM2_DMAC_ID_TX, SERCOM2_DMAC_ID_RX, (void*)&SERCOM2->I2CM.DATA.reg }, { SERCOM3, SERCOM3_GCLK_ID_CORE, SERCOM3_GCLK_ID_SLOW, - SERCOM3_0_IRQn, SERCOM3_1_IRQn, SERCOM3_2_IRQn, SERCOM3_3_IRQn }, + SERCOM3_0_IRQn, SERCOM3_1_IRQn, SERCOM3_2_IRQn, SERCOM3_3_IRQn, + SERCOM3_DMAC_ID_TX, SERCOM3_DMAC_ID_RX, (void*)&SERCOM3->I2CM.DATA.reg }, { SERCOM4, SERCOM4_GCLK_ID_CORE, SERCOM4_GCLK_ID_SLOW, - SERCOM4_0_IRQn, SERCOM4_1_IRQn, SERCOM4_2_IRQn, SERCOM4_3_IRQn }, + SERCOM4_0_IRQn, SERCOM4_1_IRQn, SERCOM4_2_IRQn, SERCOM4_3_IRQn, + SERCOM4_DMAC_ID_TX, SERCOM4_DMAC_ID_RX, (void*)&SERCOM4->I2CM.DATA.reg }, { SERCOM5, SERCOM5_GCLK_ID_CORE, SERCOM5_GCLK_ID_SLOW, - SERCOM5_0_IRQn, SERCOM5_1_IRQn, SERCOM5_2_IRQn, SERCOM5_3_IRQn }, + SERCOM5_0_IRQn, SERCOM5_1_IRQn, SERCOM5_2_IRQn, SERCOM5_3_IRQn, + SERCOM5_DMAC_ID_TX, SERCOM5_DMAC_ID_RX, (void*)&SERCOM5->I2CM.DATA.reg }, #if defined(SERCOM6) { SERCOM6, SERCOM6_GCLK_ID_CORE, SERCOM6_GCLK_ID_SLOW, - SERCOM6_0_IRQn, SERCOM6_1_IRQn, SERCOM6_2_IRQn, SERCOM6_3_IRQn }, + SERCOM6_0_IRQn, SERCOM6_1_IRQn, SERCOM6_2_IRQn, SERCOM6_3_IRQn, + SERCOM6_DMAC_ID_TX, SERCOM6_DMAC_ID_RX, (void*)&SERCOM6->I2CM.DATA.reg }, #endif #if defined(SERCOM7) { SERCOM7, SERCOM7_GCLK_ID_CORE, SERCOM7_GCLK_ID_SLOW, - SERCOM7_0_IRQn, SERCOM7_1_IRQn, SERCOM7_2_IRQn, SERCOM7_3_IRQn }, + SERCOM7_0_IRQn, SERCOM7_1_IRQn, SERCOM7_2_IRQn, SERCOM7_3_IRQn, + SERCOM7_DMAC_ID_TX, SERCOM7_DMAC_ID_RX, (void*)&SERCOM7->I2CM.DATA.reg }, #endif }; #else // end if SAMD51 (prob SAMD21) - -static const struct { +// SAMD21 has unified clock and single interrupt +struct SercomData { Sercom *sercomPtr; uint8_t clock; IRQn_Type irqn; -} sercomData[] = { - SERCOM0, GCM_SERCOM0_CORE, SERCOM0_IRQn, - SERCOM1, GCM_SERCOM1_CORE, SERCOM1_IRQn, - SERCOM2, GCM_SERCOM2_CORE, SERCOM2_IRQn, - SERCOM3, GCM_SERCOM3_CORE, SERCOM3_IRQn, + uint8_t dmaTxTrigger; + uint8_t dmaRxTrigger; + void *dataReg; // Pointer to DATA register +}; + +static const SercomData sercomData[] = { + { SERCOM0, GCM_SERCOM0_CORE, SERCOM0_IRQn, SERCOM0_DMAC_ID_TX, SERCOM0_DMAC_ID_RX, (void*)&SERCOM0->I2CM.DATA.reg }, + { SERCOM1, GCM_SERCOM1_CORE, SERCOM1_IRQn, SERCOM1_DMAC_ID_TX, SERCOM1_DMAC_ID_RX, (void*)&SERCOM1->I2CM.DATA.reg }, + { SERCOM2, GCM_SERCOM2_CORE, SERCOM2_IRQn, SERCOM2_DMAC_ID_TX, SERCOM2_DMAC_ID_RX, (void*)&SERCOM2->I2CM.DATA.reg }, + { SERCOM3, GCM_SERCOM3_CORE, SERCOM3_IRQn, SERCOM3_DMAC_ID_TX, SERCOM3_DMAC_ID_RX, (void*)&SERCOM3->I2CM.DATA.reg }, #if defined(SERCOM4) - SERCOM4, GCM_SERCOM4_CORE, SERCOM4_IRQn, + { SERCOM4, GCM_SERCOM4_CORE, SERCOM4_IRQn, SERCOM4_DMAC_ID_TX, SERCOM4_DMAC_ID_RX, (void*)&SERCOM4->I2CM.DATA.reg }, #endif #if defined(SERCOM5) - SERCOM5, GCM_SERCOM5_CORE, SERCOM5_IRQn, + { SERCOM5, GCM_SERCOM5_CORE, SERCOM5_IRQn, SERCOM5_DMAC_ID_TX, SERCOM5_DMAC_ID_RX, (void*)&SERCOM5->I2CM.DATA.reg }, #endif }; @@ -1045,13 +987,33 @@ bool SERCOM::registerService(uint8_t sercomId, ServiceFn fn) } #ifdef USE_ZERODMA -SERCOM::DmaStatus SERCOM::dmaInit(uint8_t txTrigger, uint8_t rxTrigger) +SERCOM::DmaStatus SERCOM::dmaInit(int8_t sercomId, uint8_t beatSize) { if (_dmaConfigured) return DmaStatus::Ok; - _dmaTxTrigger = txTrigger; - _dmaRxTrigger = rxTrigger; + // Validate beat size: 0=byte, 1=halfword, 2=word + if (beatSize > DMA_BEAT_SIZE_WORD) + beatSize = DMA_BEAT_SIZE_BYTE; + + // Look up DMA triggers from sercomData table +#ifdef SERCOM0_DMAC_ID_TX + if (sercomId >= 0 && sercomId < (int8_t)kSercomCount && sercomData[sercomId].dmaTxTrigger != 0) + { + _dmaTxTrigger = sercomData[sercomId].dmaTxTrigger; + _dmaRxTrigger = sercomData[sercomId].dmaRxTrigger; + } + else +#endif + { + // Fallback: calculate triggers if table lookup unavailable + _dmaTxTrigger = SERCOM0_DMAC_ID_TX + (sercomId * 2); + _dmaRxTrigger = SERCOM0_DMAC_ID_RX + (sercomId * 2); + } + + // DATA register is at the same offset (0x28) for all protocols (I2C, SPI, UART). + // Access via any union member is transparent—just use I2CM as the canonical reference. + void* dataReg = (void*)&sercom->I2CM.DATA.reg; if (!_dmaTx) _dmaTx = new Adafruit_ZeroDMA(); @@ -1088,9 +1050,9 @@ SERCOM::DmaStatus SERCOM::dmaInit(uint8_t txTrigger, uint8_t rxTrigger) _dmaRx->setCallback(_dmaRxCb); if (_dmaTxDesc == nullptr) - _dmaTxDesc = _dmaTx->addDescriptor(&_dmaDummy, (void*)&sercom->I2CM.DATA.reg, 1, DMA_BEAT_SIZE_BYTE, true, false); + _dmaTxDesc = _dmaTx->addDescriptor(&_dmaDummy, dataReg, 0, (dma_beat_size)beatSize, true, false); if (_dmaRxDesc == nullptr) - _dmaRxDesc = _dmaRx->addDescriptor((void*)&sercom->I2CM.DATA.reg, &_dmaDummy, 1, DMA_BEAT_SIZE_BYTE, false, true); + _dmaRxDesc = _dmaRx->addDescriptor(dataReg, &_dmaDummy, 0, (dma_beat_size)beatSize, false, true); if (_dmaTxDesc == nullptr || _dmaRxDesc == nullptr) { _dmaLastError = DmaStatus::DescriptorFailed; @@ -1117,7 +1079,74 @@ void SERCOM::dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb) } } -SERCOM::DmaStatus SERCOM::dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, +SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, volatile void* dstReg, size_t len) +{ + if (!_dmaConfigured || !_dmaTx) { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (src == nullptr || dstReg == nullptr) { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + if (_dmaTxDesc == nullptr) { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + _dmaTx->changeDescriptor(_dmaTxDesc, const_cast(src), + const_cast(dstReg), len); + + if (_dmaTx->startJob() != DMA_STATUS_OK) { + _dmaTx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaTxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, volatile void* srcReg, size_t len) +{ + if (!_dmaConfigured || !_dmaRx) { + _dmaLastError = DmaStatus::NotConfigured; + return _dmaLastError; + } + if (dst == nullptr || srcReg == nullptr) { + _dmaLastError = DmaStatus::NullPtr; + return _dmaLastError; + } + if (len == 0) { + _dmaLastError = DmaStatus::ZeroLen; + return _dmaLastError; + } + if (_dmaRxDesc == nullptr) { + _dmaLastError = DmaStatus::DescriptorFailed; + return _dmaLastError; + } + + _dmaRx->changeDescriptor(_dmaRxDesc, + const_cast(srcReg), + dst, len); + + if (_dmaRx->startJob() != DMA_STATUS_OK) { + _dmaRx->abort(); + _dmaLastError = DmaStatus::StartFailed; + return _dmaLastError; + } + + _dmaRxActive = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; +} + +SERCOM::DmaStatus SERCOM::dmaStartDuplex(const void* txSrc, void* rxDst, volatile void* txReg, volatile void* rxReg, size_t len, const uint8_t* dummyTx) { if (len == 0) diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index f3b797d3b..6d29e021c 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -180,6 +180,14 @@ class SERCOM { public: SERCOM(Sercom* s) ; + void resetSERCOM( void ) ; + inline void enableSERCOM( void ) ; + inline void disableSERCOM( void ) ; + inline void disableInterrupts(uint8_t mask) { sercom->I2CM.INTENCLR.reg = mask; } + inline void enableInterrupts(uint8_t mask) { sercom->I2CM.INTENSET.reg = mask; } + inline uint8_t getINTFLAG( void ) const { return sercom->I2CM.INTFLAG.reg; } + inline uint16_t getSTATUS( void ) const { return sercom->I2CM.STATUS.reg; } + inline void clearINTFLAG( void ) { sercom->I2CM.INTFLAG.reg = 0xFF; } /* ========== UART ========== */ void initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint32_t baudrate=0) ; @@ -187,7 +195,8 @@ class SERCOM void initPads(SercomUartTXPad txPad, SercomRXPad rxPad) ; void resetUART( void ) ; - void enableUART( void ) ; + void enableUART( void ) { enableSERCOM(); } + void disableUART( void ) { disableSERCOM(); } void flushUART( void ) ; void clearStatusUART( void ) ; bool availableDataUART( void ) ; @@ -212,8 +221,8 @@ class SERCOM void initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize charSize, SercomDataOrder dataOrder) ; void initSPIClock(SercomSpiClockMode clockMode, uint32_t baudrate) ; void resetSPI( void ) ; - void enableSPI( void ) ; - void disableSPI( void ) ; + void enableSPI( void ) { enableSERCOM(); } + void disableSPI( void ) { disableSERCOM(); } void setDataOrderSPI(SercomDataOrder dataOrder) ; SercomDataOrder getDataOrderSPI( void ) ; void setBaudrateSPI(uint8_t divider) ; @@ -225,10 +234,18 @@ class SERCOM bool isReceiveCompleteSPI( void ) ; bool enqueueSPI(SercomTxn* txn); bool startTransmissionSPI(void); - void serviceSPI(void); void deferStopSPI(SercomSpiError error); SercomTxn* stopTransmissionSPI(void); SercomTxn* stopTransmissionSPI(SercomSpiError error); + inline SercomTxn* getCurrentTxnSPI(void) { return _spi.currentTxn; } + inline const SercomTxn* getCurrentTxnSPI(void) const { return _spi.currentTxn; } + inline size_t getTxnIndexSPI(void) const { return _spi.index; } + inline size_t getTxnLengthSPI(void) const { return _spi.length; } + inline bool isActiveSPI(void) const { return _spi.active; } + inline void setTxnIndexSPI(size_t index) { _spi.index = index; } + inline void setReturnValueSPI(SercomSpiError err) { _spi.returnValue = err; } + inline bool sendDataSPI(void); + inline bool readDataSPI(void); /* ========== WIRE ========== */ void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; @@ -294,11 +311,10 @@ class SERCOM void setClockSource(int8_t idx, SercomClockSource src, bool core) { (void)idx; (void)src; (void)core; }; SercomClockSource getClockSource(void) { return SERCOM_CLOCK_SOURCE_FCPU; }; uint32_t getFreqRef(void) { return F_CPU; }; -#endif +#endif // SAMD51 vs 21 - // --- Async SERCOM scaffolding (Phase 1) --- enum class Role : uint8_t { None = 0, UART, SPI, I2C }; - using ServiceFn = void (SERCOM::*)(); + using ServiceFn = SercomTxn* (SERCOM::*)(); static bool claim(uint8_t sercomId, Role role); static void release(uint8_t sercomId); @@ -307,7 +323,6 @@ class SERCOM static void dispatchPending(void); #ifdef USE_ZERODMA - // --- SERCOM ZeroDMA helpers (Phase 1) --- using DmaCallback = void (*)(Adafruit_ZeroDMA*); enum class DmaStatus : uint8_t { Ok = 0, @@ -319,11 +334,11 @@ class SERCOM StartFailed }; - DmaStatus dmaInit(uint8_t txTrigger, uint8_t rxTrigger); + DmaStatus dmaInit(int8_t sercomId, uint8_t beatSize = 0); // beatSize: 0=byte (default), 1=halfword, 2=word void dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb); - inline DmaStatus dmaStartTx(const void* src, void* dstReg, size_t len); - inline DmaStatus dmaStartRx(void* dst, void* srcReg, size_t len); - DmaStatus dmaStartDuplex(const void* txSrc, void* rxDst, void* txReg, void* rxReg, size_t len, + DmaStatus dmaStartTx(const void* src, volatile void* dstReg, size_t len); + DmaStatus dmaStartRx(void* dst, volatile void* srcReg, size_t len); + DmaStatus dmaStartDuplex(const void* txSrc, void* rxDst, volatile void* txReg, volatile void* rxReg, size_t len, const uint8_t* dummyTx = nullptr); void dmaRelease(); void dmaResetDescriptors(); @@ -390,14 +405,10 @@ class SERCOM static std::array s_states; static std::array s_instances; static volatile uint32_t s_pendingMask; - uint8_t calculateBaudrateSynchronous(uint32_t baudrate); + uint8_t calculateBaudrateSynchronous(uint32_t baudrate) ; uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; -#ifdef USE_ZERODMA - void initWireDma(void); - void initUartDma(void); -#endif - void initWIRE(void); + void initWIRE(void) ; // Cached I2C master/slave configuration for fast role switching. // This can be expanded to support additional configuration options diff --git a/cores/arduino/SERCOM_Txn.h b/cores/arduino/SERCOM_Txn.h index d109abecc..ca6365c52 100644 --- a/cores/arduino/SERCOM_Txn.h +++ b/cores/arduino/SERCOM_Txn.h @@ -41,15 +41,28 @@ enum class SercomSpiError : uint8_t UNKNOWN_ERROR = 2 }; +// UART error reporting (async callbacks) +enum class SercomUartError : uint8_t +{ + SUCCESS = 0, + UNKNOWN_ERROR = 1 +}; + // I2C config flags and helpers enum : uint16_t { I2C_CFG_READ = 1u << 0, I2C_CFG_STOP = 1u << 1, I2C_CFG_CRC = 1u << 2, I2C_CFG_10BIT = 1u << 3, - I2C_CFG_RESTART = 1u << 4, + I2C_CFG_NODMA = 1u << 4, }; +// I2C STATUS register error-clear masks (write to I2CM.STATUS or I2CS.STATUS) +// Master: LENERR|SEXTTOUT|MEXTTOUT|LOWTOUT|BUSSTATE_IDLE|ARBLOST|BUSERR +static constexpr uint16_t I2CM_STATUS_ERR_CLEAR = 0x753u; +// Slave: SEXTTOUT|LOWTOUT|COLL|BUSERR (preserves HS high-speed detection) +static constexpr uint16_t I2CS_STATUS_ERR_CLEAR = 0x243u; + static inline uint16_t I2C_ADDR(uint16_t addr10) { return addr10 & 0x03FFu; } static inline uint8_t I2C_ADDR7(uint16_t addr) { return (uint8_t)(addr & 0x7Fu); } diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index 76a79a69a..4215e8f50 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -3,22 +3,33 @@ #ifdef __cplusplus -inline void SERCOM::enableWIRE( void ) +inline void SERCOM::enableSERCOM( void ) { - // I2C Master and Slave modes share the ENABLE bit function. + // UART, SPI, I2CS, and I2CM use the same enable bit sercom->I2CM.CTRLA.bit.ENABLE = 1; while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; +} + +inline void SERCOM::disableSERCOM( void ) +{ + // UART, SPI, I2CS, and I2CM use the same enable bit + sercom->I2CM.CTRLA.bit.ENABLE = 0; + while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; +} + +inline void SERCOM::enableWIRE( void ) +{ + enableSERCOM(); // Setting bus idle mode sercom->I2CM.STATUS.bit.BUSSTATE = 1; while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; } -inline void SERCOM::disableWIRE( void ) +inline void SERCOM::deferStopWIRE(SercomWireError error) { - // I2C Master and Slave modes share the ENABLE bit function. - sercom->I2CM.CTRLA.bit.ENABLE = 0; - while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; + _wire.returnValue = error; + setPending((uint8_t)getSercomIndex()); } inline bool SERCOM::sendDataWIRE( void ) @@ -99,7 +110,7 @@ inline uint8_t SERCOM::getINTFLAG( void ) const { return sercom->I2CM.INTFLAG.re inline uint16_t SERCOM::getSTATUS( void ) const { return sercom->I2CM.STATUS.reg; } inline void SERCOM::clearINTFLAG( void ) { sercom->I2CM.INTFLAG.reg = 0xFF; } -inline void SERCOM::setWireTxn(SercomTxn* txn, size_t length, bool useDma) +inline void SERCOM::setTxnWIRE(SercomTxn* txn, size_t length, bool useDma) { _wire.currentTxn = txn; _wire.txnLength = length; @@ -107,31 +118,11 @@ inline void SERCOM::setWireTxn(SercomTxn* txn, size_t length, bool useDma) _wire.useDma = useDma; } -inline void SERCOM::setWireDma(bool useDma) -{ - _wire.useDma = useDma; -} - -inline bool SERCOM::isWireDma(void) const -{ - return _wire.useDma; -} - -inline bool SERCOM::isMasterReadOperationWIRE( void ) -{ - return sercom->I2CS.STATUS.bit.DIR; -} - -inline bool SERCOM::isRXNackReceivedWIRE( void ) -{ - return sercom->I2CM.STATUS.bit.RXNACK; -} - #ifdef USE_ZERODMA inline SERCOM* SERCOM::findDmaOwner(Adafruit_ZeroDMA* dma, bool tx) { - if (dma == nullptr) - return nullptr; + if (dma == nullptr) return nullptr; + for (size_t i = 0; i < kSercomCount; ++i) { SERCOM* inst = s_instances[i]; @@ -148,71 +139,8 @@ inline SERCOM* SERCOM::findDmaOwner(Adafruit_ZeroDMA* dma, bool tx) return inst; } } - return nullptr; -} - -inline SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, void* dstReg, size_t len) -{ - if (!_dmaConfigured || !_dmaTx) { - _dmaLastError = DmaStatus::NotConfigured; - return _dmaLastError; - } - if (src == nullptr || dstReg == nullptr) { - _dmaLastError = DmaStatus::NullPtr; - return _dmaLastError; - } - if (len == 0) { - _dmaLastError = DmaStatus::ZeroLen; - return _dmaLastError; - } - if (_dmaTxDesc == nullptr) { - _dmaLastError = DmaStatus::DescriptorFailed; - return _dmaLastError; - } - - _dmaTx->changeDescriptor(_dmaTxDesc, (void*)src, dstReg, len); - - if (_dmaTx->startJob() != DMA_STATUS_OK) { - _dmaTx->abort(); - _dmaLastError = DmaStatus::StartFailed; - return _dmaLastError; - } - _dmaTxActive = true; - _dmaLastError = DmaStatus::Ok; - return _dmaLastError; -} - -inline SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, void* srcReg, size_t len) -{ - if (!_dmaConfigured || !_dmaRx) { - _dmaLastError = DmaStatus::NotConfigured; - return _dmaLastError; - } - if (dst == nullptr || srcReg == nullptr) { - _dmaLastError = DmaStatus::NullPtr; - return _dmaLastError; - } - if (len == 0) { - _dmaLastError = DmaStatus::ZeroLen; - return _dmaLastError; - } - if (_dmaRxDesc == nullptr) { - _dmaLastError = DmaStatus::DescriptorFailed; - return _dmaLastError; - } - - _dmaRx->changeDescriptor(_dmaRxDesc, srcReg, dst, len); - - if (_dmaRx->startJob() != DMA_STATUS_OK) { - _dmaRx->abort(); - _dmaLastError = DmaStatus::StartFailed; - return _dmaLastError; - } - - _dmaRxActive = true; - _dmaLastError = DmaStatus::Ok; - return _dmaLastError; + return nullptr; } inline void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 6b1cbfd21..4c748a969 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -59,7 +59,7 @@ void SPIClass::begin() } #ifdef USE_ZERODMA - _p_sercom->dmaInit(getDMAC_ID_TX(), getDMAC_ID_RX()); + _p_sercom->dmaInit(_p_sercom->getSercomIndex()); #endif // PIO init @@ -270,7 +270,39 @@ void SPIClass::transfer(const void *txbuf, void *rxbuf, size_t count, void SPIClass::onService(void) { - _p_sercom->serviceSPI(); + // SPI interrupt service handler - moved from SERCOM::serviceSPI() + if (!_p_sercom->isActiveSPI() || _p_sercom->getCurrentTxnSPI() == nullptr) + return; + + uint8_t flags = _p_sercom->getINTFLAG(); + + if (flags & SERCOM_SPI_INTFLAG_ERROR) + { + _p_sercom->setReturnValueSPI(SercomSpiError::BUF_OVERFLOW); + _p_sercom->clearINTFLAG(); + _p_sercom->deferStopSPI(SercomSpiError::BUF_OVERFLOW); + return; + } + + SercomTxn* txn = _p_sercom->getCurrentTxnSPI(); + + if (flags & SERCOM_SPI_INTFLAG_RXC) { + // Read completes after write, so read previous byte + bool hasMore = _p_sercom->readDataSPI(); + + if (!hasMore) { + _p_sercom->disableInterrupts(SERCOM_SPI_INTENCLR_DRE | SERCOM_SPI_INTENCLR_RXC | SERCOM_SPI_INTENCLR_ERROR); + _p_sercom->setReturnValueSPI(SercomSpiError::SUCCESS); + _p_sercom->deferStopSPI(SercomSpiError::SUCCESS); + return; + } + } + + if (flags & SERCOM_SPI_INTFLAG_DRE) { + bool hasMore = _p_sercom->sendDataSPI(); + if (!hasMore) + _p_sercom->disableInterrupts(SERCOM_SPI_INTENCLR_DRE); + } } void SPIClass::onTxnComplete(void* user, int status) @@ -303,61 +335,6 @@ void SPIClass::detachInterrupt() { // Should be disableInterrupt() } -// SPI DMA lookup works on both SAMD21 and SAMD51 - -static const struct { - volatile uint32_t *data_reg; - int dmac_id_tx; - int dmac_id_rx; -} sercomData[] = { - { &SERCOM0->SPI.DATA.reg, SERCOM0_DMAC_ID_TX, SERCOM0_DMAC_ID_RX }, - { &SERCOM1->SPI.DATA.reg, SERCOM1_DMAC_ID_TX, SERCOM1_DMAC_ID_RX }, - { &SERCOM2->SPI.DATA.reg, SERCOM2_DMAC_ID_TX, SERCOM2_DMAC_ID_RX }, - { &SERCOM3->SPI.DATA.reg, SERCOM3_DMAC_ID_TX, SERCOM3_DMAC_ID_RX }, -#if defined(SERCOM4) - { &SERCOM4->SPI.DATA.reg, SERCOM4_DMAC_ID_TX, SERCOM4_DMAC_ID_RX }, -#endif -#if defined(SERCOM5) - { &SERCOM5->SPI.DATA.reg, SERCOM5_DMAC_ID_TX, SERCOM5_DMAC_ID_RX }, -#endif -#if defined(SERCOM6) - { &SERCOM6->SPI.DATA.reg, SERCOM6_DMAC_ID_TX, SERCOM6_DMAC_ID_RX }, -#endif -#if defined(SERCOM7) - { &SERCOM7->SPI.DATA.reg, SERCOM7_DMAC_ID_TX, SERCOM7_DMAC_ID_RX }, -#endif -}; - -volatile uint32_t *SPIClass::getDataRegister(void) { - int8_t idx = _p_sercom->getSercomIndex(); - return (idx >= 0) ? sercomData[idx].data_reg: NULL; -} - -int SPIClass::getDMAC_ID_TX(void) { - int8_t idx = _p_sercom->getSercomIndex(); - return (idx >= 0) ? sercomData[idx].dmac_id_tx : -1; -} - -int SPIClass::getDMAC_ID_RX(void) { - int8_t idx = _p_sercom->getSercomIndex(); - return (idx >= 0) ? sercomData[idx].dmac_id_rx : -1; -} - -#if defined(__SAMD51__) - -// Set the SPI device's SERCOM clock CORE and SLOW clock sources. -// SercomClockSource values are an enumeration in SERCOM.h. -// This works on SAMD51 only. On SAMD21, a dummy function is declared -// in SPI.h which compiles to nothing, so user code doesn't need to check -// and conditionally compile lines for different architectures. -void SPIClass::setClockSource(SercomClockSource clk) { - int8_t idx = _p_sercom->getSercomIndex(); - _p_sercom->setClockSource(idx, clk, true); // true = set core clock - _p_sercom->setClockSource(idx, clk, false); // false = set slow clock -} - -#endif // end __SAMD51__ - #if SPI_INTERFACES_COUNT > 0 /* In case new variant doesn't define these macros, * we put here the ones for Arduino Zero. diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 794436e3b..444f1b9e0 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -42,8 +42,8 @@ // SAMD51 has configurable MAX_SPI, else use peripheral clock default. // Update: changing MAX_SPI via compiler flags is DEPRECATED, because // this affects ALL SPI peripherals including some that should NOT be - // changed (e.g. anything using SD card). Use the setClockSource() - // function instead. This is left here for compatibility with interim code. + // changed (e.g. anything using SD card). Configure SERCOM clock source + // directly via the SERCOM API instead. This is left here for compatibility. #if !defined(MAX_SPI) #define MAX_SPI 24000000 #endif From 18a39244005beeb9ca554e24fc8c622e45699337 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 15 Feb 2026 00:11:49 -0600 Subject: [PATCH 14/24] get sercom's wire protocol working in master mode --- cores/arduino/SERCOM.cpp | 283 +++++++++++++++++++--------------- cores/arduino/SERCOM.h | 83 +++++----- cores/arduino/SERCOM_inline.h | 185 ++++++++++++++-------- 3 files changed, 324 insertions(+), 227 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 4df26512c..0308d0196 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -569,49 +569,53 @@ uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate) */ void SERCOM::resetWIRE() { - //I2CM OR I2CS, no matter SWRST is the same bit. - - //Setting the Software bit to 1 - sercom->I2CM.CTRLA.bit.SWRST = 1; - - //Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0 - while(sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); - + resetSERCOM(); _wire = WireConfig{}; } -void SERCOM::initWIRE(void) + +void SERCOM::clearQueueWIRE(void) { - if (!_wire.inited) { - uint8_t idx = getSercomIndex(); - initClockNVIC(); - registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); - _wire.inited = true; + // Drain all pending transactions from the queue without invoking callbacks + // This is needed for test teardown to ensure no stale transactions carry over + SercomTxn* txn = nullptr; + int drained = 0; + while (_txnQueue.read(txn)) { + drained++; + // Just discard - don't invoke callbacks during reset } -#ifdef USE_ZERODMA - initWireDma(); -#endif + + // Ensure wire state is completely clean + _wire.active = false; + _wire.currentTxn = nullptr; + _wire.txnIndex = 0; + _wire.txnLength = 0; + _wire.returnValue = SercomWireError::SUCCESS; + _wire.retryCount = 0; + + // Clear deferred callbacks (from slave/receive operations) + _wireDeferredCb = nullptr; + _wireDeferredUser = nullptr; + _wireDeferredLength = 0; + _wireDeferredPending = false; } -#ifdef USE_ZERODMA -void SERCOM::initWireDma(void) +void SERCOM::initWIRE(void) { -#ifdef SERCOM0_DMAC_ID_TX - if (_dmaConfigured) + if (_wire.inited) // If already initialized, return return; - int8_t id = getSercomIndex(); - if (id < 0) - return; + uint8_t idx = getSercomIndex(); + initClockNVIC(); + registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); - _dmaTxTrigger = SERCOM0_DMAC_ID_TX + (id * 2); - _dmaRxTrigger = SERCOM0_DMAC_ID_RX + (id * 2); +#ifdef USE_ZERODMA dmaSetCallbacks(SERCOM::dmaTxCallbackWIRE, SERCOM::dmaRxCallbackWIRE); - dmaInit(_dmaTxTrigger, _dmaRxTrigger); -#else - (void)0; -#endif + if (idx >= 0) + dmaInit(idx); +#endif // USE_ZERODMA + + _wire.inited = true; // Mark as initialized last } -#endif void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall, uint8_t speed ) { @@ -639,13 +643,13 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) setMasterWIRE(); } -void SERCOM::registerWireReceive(void (*cb)(void* user, int length), void* user) +void SERCOM::registerReceiveWIRE(void (*cb)(void* user, int length), void* user) { _wireDeferredCb = cb; _wireDeferredUser = user; } -void SERCOM::deferWireReceive(int length) +void SERCOM::deferReceiveWIRE(int length) { _wireDeferredLength = length; _wireDeferredPending = true; @@ -654,50 +658,36 @@ void SERCOM::deferWireReceive(int length) void SERCOM::setMasterWIRE(void) { + // Errata: do not enable QCEN when SCLSM=1 (bus error). Hs-mode requires SCLSM=1, + // so master Hs-mode must be DMA-only and STOP-only (no repeated starts). disableWIRE(); - - sercom->I2CM.CTRLB.reg = _wire.ctrlb |SERCOM_I2CM_CTRLB_QCEN; - sercom->I2CM.BAUD.reg = _wire.baud; bool sclsm = (_wire.masterSpeed == 0x2); - - // Set master mode and clock settings - sercom->I2CM.CTRLA.reg = _wire.ctrla | + sercom->I2CM.CTRLA.reg = _wire.ctrla | SERCOM_I2CM_CTRLA_MODE(I2C_MASTER_OPERATION) | - SERCOM_I2CM_CTRLA_SPEED(_wire.masterSpeed) | + SERCOM_I2CM_CTRLA_SPEED(_wire.masterSpeed) | (sclsm ? SERCOM_I2CM_CTRLA_SCLSM : 0 ); - - while (sercom->I2CM.SYNCBUSY.bit.ENABLE != 0) ; - - // Setting bus idle mode - sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (sercom->I2CM.SYNCBUSY.bit.SYSOP != 0) ; - - // Disable slave interrupts: address match, data ready, stop/restart. - sercom->I2CS.INTENCLR.reg = SERCOM_I2CS_INTENSET_AMATCH | - SERCOM_I2CS_INTENSET_DRDY | + sercom->I2CM.CTRLB.reg = _wire.ctrlb; + sercom->I2CM.BAUD.reg = _wire.baud; + enableWIRE(); + // Disable slave interrupts. + // Master interrupts are set in startTransmissionWIRE() when the transaction is enqueued, + // so we don't want to enable them here. + sercom->I2CS.INTENCLR.reg = SERCOM_I2CS_INTENSET_ERROR | + SERCOM_I2CS_INTENSET_AMATCH | + SERCOM_I2CS_INTENSET_DRDY | SERCOM_I2CS_INTENSET_PREC; } void SERCOM::setSlaveWIRE(void) { disableWIRE(); - - sercom->I2CS.ADDR.reg = _wire.addr; - sercom->I2CS.CTRLB.reg = _wire.ctrlb; bool sclsm = (_wire.slaveSpeed == 0x2); - - // Set master mode and clock settings - sercom->I2CS.CTRLA.reg = _wire.ctrla | - SERCOM_I2CS_CTRLA_MODE(I2C_SLAVE_OPERATION) | - SERCOM_I2CS_CTRLA_SPEED(_wire.slaveSpeed) | + sercom->I2CS.CTRLA.reg = SERCOM_I2CS_CTRLA_MODE(I2C_SLAVE_OPERATION) | + SERCOM_I2CS_CTRLA_SPEED(_wire.slaveSpeed) | (sclsm ? SERCOM_I2CS_CTRLA_SCLSM : 0 ); - - while (sercom->I2CS.SYNCBUSY.bit.ENABLE != 0) ; - - // Setting bus idle mode - sercom->I2CS.STATUS.bit.BUSSTATE = 1 ; - while (sercom->I2CS.SYNCBUSY.bit.SYSOP != 0) ; - + sercom->I2CS.CTRLB.reg = _wire.ctrlb | SERCOM_I2CS_CTRLB_AACKEN; + sercom->I2CS.ADDR.reg = _wire.addr; + enableWIRE(); // Enable slave interrupts: address match, data ready, stop/restart. sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_ERROR | // Error SERCOM_I2CS_INTENSET_PREC | // Stop @@ -710,19 +700,13 @@ void SERCOM::setBaudrateWIRE(uint32_t baudrate) // Determine speed mode based on requested baudrate const uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)} uint8_t speedBit; - uint8_t clockStretchMode; // See: 28.6.2.4.6 (SERCOM I2C Highspeed mode) - if (baudrate <= topSpeeds[0]) { + if (baudrate <= topSpeeds[0]) speedBit = 0; // Standard/Fast mode up to 400 khz - clockStretchMode = 0; - } else if (baudrate <= topSpeeds[1]) { + else if (baudrate <= topSpeeds[1]) speedBit = 1; // Fast mode+ up to 1 Mhz - clockStretchMode = 0; - } else { - // High speed up to 3.4 Mhz - speedBit = 2; - clockStretchMode = 1; - } + else + speedBit = 2; // High speed up to 3.4 Mhz _wire.masterSpeed = speedBit; @@ -742,48 +726,73 @@ void SERCOM::setBaudrateWIRE(uint32_t baudrate) } -SercomTxn* SERCOM::startTransmissionWIRE(void) +SercomTxn* SERCOM::startTransmissionWIRE( void ) { + // Writing ADDR.ADDR drives different behavior based on BUSSTATE: + // UNKNOWN: MB and BUSERR assert and the transfer aborts. + // BUSY: The host waits until the bus is IDLE. + // IDLE: A START is generated, the address is sent, and on ACK the host holds SCL low with CLKHOLD + // set and MB asserted. + // OWNER: A repeated START is generated; if the prior transaction was a read, the ACK/NACK for the + // read is sent before the repeated START. The repeated START ADDR write must occur while MB or SB + // is set. + // Writing ADDR also clears BUSERR, ARBLOST, MB, and SB. + + if (isBusUnknownWIRE()) { + stopTransmissionWIRE(SercomWireError::BUS_STATE_UNKNOWN); + return nullptr; + } + SercomTxn* txn = nullptr; - if (!_txnQueue.peek(txn) || txn == nullptr) + + if (!_txnQueue.peek(txn)) return nullptr; + if (txn != _wire.currentTxn) + _wire.retryCount = 0; + _wire.currentTxn = txn; _wire.txnIndex = 0; _wire.txnLength = txn->length; + setDmaWIRE(false); // Reset DMA mode - let code below decide if DMA is used - const bool read = (txn->config & I2C_CFG_READ) != 0; + const bool read = txn->config & I2C_CFG_READ; uint16_t addr = (txn->config & I2C_CFG_10BIT) ? I2C_ADDR(txn->address) : I2C_ADDR7(txn->address); addr = (uint16_t)((addr << 1) | (read ? 1u : 0u)); + bool hsMode = (_wire.masterSpeed == 0x2); + uint32_t addrReg = SERCOM_I2CM_ADDR_ADDR(addr) | + ((txn->config & I2C_CFG_10BIT) ? SERCOM_I2CM_ADDR_TENBITEN : 0) | + (hsMode ? SERCOM_I2CM_ADDR_HS : 0); - // If another master owns the bus or the last bus owner has not properly - // sent a stop, return failure early. This will prevent some misbehaved - // devices from deadlocking here at the cost of the caller being responsible - // for retrying the failed transmission. See SercomWireBusState for the - // possible bus states. - if(!isBusOwnerWIRE()) - { - if (isArbLostWIRE() && !isBusIdleWIRE()) { - stopTransmissionWIRE(SercomWireError::ARBITRATION_LOST); + if (hsMode || sercom->I2CM.CTRLA.bit.SCLSM) { +#ifndef USE_ZERODMA + stopTransmissionWIRE(SercomWireError::OTHER); + return nullptr; +#endif + if (txn->length >255) { + stopTransmissionWIRE(SercomWireError::DATA_TOO_LONG); return nullptr; } - if (isBusUnknownWIRE()) { - stopTransmissionWIRE(SercomWireError::BUS_STATE_UNKNOWN); + + if (sercom->I2CM.CTRLB.bit.QCEN) { + stopTransmissionWIRE(SercomWireError::OTHER); return nullptr; } - } - - uint32_t addrReg = SERCOM_I2CM_ADDR_ADDR(addr) | - ((txn->config & I2C_CFG_10BIT) ? SERCOM_I2CM_ADDR_TENBITEN : 0) | - ((_wire.masterSpeed == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); + txn->config |= I2C_CFG_STOP; + setDmaWIRE(true); + } #ifdef USE_ZERODMA - setWireDma(txn->length > 0 && txn->length < 256 && (txn->config & I2C_CFG_STOP)); + else { + setDmaWIRE(txn->length > 0 && txn->length < 256 && + (txn->config & I2C_CFG_STOP) && + !(txn->config & I2C_CFG_NODMA)); + } - if (isWireDma()) + if (isDmaWIRE()) { if (!_dmaConfigured) - dmaInit(_dmaTxTrigger, _dmaRxTrigger); + dmaInit(getSercomIndex()); if (!_dmaConfigured || !_dmaTx || !_dmaRx) { stopTransmissionWIRE(SercomWireError::OTHER); @@ -794,10 +803,10 @@ SercomTxn* SERCOM::startTransmissionWIRE(void) } #endif - // Send start and address (non-blocking; ISR handles MB/SB) + // Send address (non-blocking; ISR handles ERROR/MB/SB) _wire.active = true; - sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB; - sercom->I2CM.ADDR.reg = addrReg; + sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_MB; + sercom->I2CM.ADDR.reg = addrReg; // ADDR is write synchronized so just wait for the MB/SB to know when synced return txn; } @@ -813,12 +822,6 @@ bool SERCOM::enqueueWIRE(SercomTxn* txn) return true; } -void SERCOM::deferStopWIRE(SercomWireError error) -{ - _wire.returnValue = error; - setPending((uint8_t)getSercomIndex()); -} - SercomTxn* SERCOM::stopTransmissionWIRE( void ) { return stopTransmissionWIRE( _wire.returnValue ); @@ -831,46 +834,76 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) // Retry/backoff policy is intentionally deferred; a future change may add // a retry budget or tick-based delay if needed. - SercomTxn* txn = nullptr; - _txnQueue.peek(txn); + SercomTxn* txn = _wire.currentTxn; + SercomTxn* next = nullptr; + + constexpr uint8_t kMaxWireRetries = 3; if (error == SercomWireError::BUS_STATE_UNKNOWN) { + if (_wire.retryCount < kMaxWireRetries) { + ++_wire.retryCount; sercom->I2CM.STATUS.bit.BUSSTATE = 1; - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; - - if (txn) startTransmissionWIRE(); - return txn; + } } if (error == SercomWireError::ARBITRATION_LOST || error == SercomWireError::BUS_ERROR) { + if (_wire.retryCount < kMaxWireRetries) { + ++_wire.retryCount; sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; - - if (txn) startTransmissionWIRE(); - return txn; + } } - // Callbacks are expected to run in non-ISR context (main loop/PendSV). - if (_txnQueue.read(txn) && txn != nullptr) { - _wire.active = false; - _wire.currentTxn = nullptr; - if (txn->onComplete) - txn->onComplete(txn->user, static_cast(error)); + if (error == SercomWireError::BUS_STATE_UNKNOWN || + error == SercomWireError::ARBITRATION_LOST || + error == SercomWireError::BUS_ERROR) { + _wire.retryCount = 0; } - SercomTxn* next = nullptr; - - if (_txnQueue.peek(next) && next) - startTransmissionWIRE(); + // Callbacks are expected to run in non-ISR context (main loop/PendSV). + if (txn &&txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); + if(isMasterWIRE()) + _txnQueue.read(txn); // remove the completed transaction from the queue + else { + // Deliver deferred WIRE callback outside the SERCOM ISR (from PendSV). + // This avoids running user code in the hardware interrupt context. if (_wireDeferredPending && _wireDeferredCb) { _wireDeferredPending = false; _wireDeferredCb(_wireDeferredUser, _wireDeferredLength); + } + } + + _wire.retryCount = 0; + _wire.active = false; + _wire.currentTxn = nullptr; + + bool isMaster = isMasterWIRE(); + + if (_txnQueue.peek(next) && isMaster) { + // Undocumented HW limitation: DMA transfers must terminate with STOP and bus release. + // After a DMA write, the host holds the bus ~7.33 us before the next transfer (Sr window). + // Writing ADDR during that window leaves the bus in an undefined state and breaks + // subsequent DMA/non-DMA transactions. To avoid this, we must wait for BUSSTATE + // to return to IDLE after a STOP returning the hardware to a known state. + // At the tested 48 MHz, this busy-wait is ~350 cycles corresponding to a 3.5 us delay + // at 100 MHz. Thus, we only wait when needed for the next transaction and not on every STOP. + if (txn) { + if (txn->config & I2C_CFG_STOP) + while (sercom->I2CM.STATUS.bit.BUSSTATE > 0x1) ; + } + + startTransmissionWIRE(); + } else if (isMaster) { + sercom->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENCLR_ERROR | + SERCOM_I2CM_INTENCLR_MB | + SERCOM_I2CM_INTENCLR_SB; } return txn; diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 6d29e021c..d489339f0 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -251,49 +251,53 @@ class SERCOM void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; void initSlaveWIRE(uint16_t address, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false) ; void initMasterWIRE(uint32_t baudrate) ; - inline void setWireTxn(SercomTxn* txn, size_t length, bool useDma); - inline void setWireDma(bool useDma); - inline bool isWireDma(void) const; - void registerWireReceive(void (*cb)(void* user, int length), void* user); - void deferWireReceive(int length); + inline void setTxnWIRE(SercomTxn* txn, size_t length, bool useDma); + inline void setDmaWIRE(bool useDma) { _wire.useDma = useDma; } + inline bool isDmaWIRE(void) const { return _wire.useDma; } + void registerReceiveWIRE(void (*cb)(void* user, int length), void* user); + void deferReceiveWIRE(int length); void setSlaveWIRE( void ) ; void setMasterWIRE( void ) ; void resetWIRE( void ) ; + void clearQueueWIRE( void ) ; inline void enableWIRE( void ) ; - inline void disableWIRE( void ) ; + inline void disableWIRE( void ) { disableSERCOM(); } void setBaudrateWIRE(uint32_t baudrate) ; - inline void prepareNackBitWIRE( void ) ; - inline void prepareAckBitWIRE( void ) ; - inline void prepareCommandBitsWire(uint8_t cmd) ; - bool startTransmissionWIRE( void ) ; + inline void prepareNackBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 1; } + inline void prepareAckBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 0; } + inline void prepareCommandBitsWIRE(uint8_t cmd) ; + SercomTxn* startTransmissionWIRE( void ) ; bool startTransmissionWIRE( uint8_t address, SercomWireReadWriteFlag flag ) = delete ; SercomTxn* stopTransmissionWIRE( void ) ; SercomTxn* stopTransmissionWIRE( SercomWireError error ) ; bool enqueueWIRE(SercomTxn* txn); void deferStopWIRE(SercomWireError error); - inline uint8_t getINTFLAG( void ) const; - inline uint16_t getSTATUS( void ) const; - inline void clearINTFLAG( void ); + inline bool sendDataWIRE( void ) ; - inline bool isMasterWIRE( void ) ; - inline bool isSlaveWIRE( void ) ; - inline bool isBusIdleWIRE( void ) ; - inline bool isBusOwnerWIRE( void ) ; - inline bool isBusUnknownWIRE( void ) ; - inline bool isArbLostWIRE( void ) ; - inline bool isBusBusyWIRE( void ) ; - inline bool isDataReadyWIRE( void ) ; - inline bool isStopDetectedWIRE( void ) ; - inline bool isRestartDetectedWIRE( void ) ; - inline bool isAddressMatch( void ) ; - inline bool isMasterReadOperationWIRE( void ) ; - inline bool isRXNackReceivedWIRE( void ) ; - inline int availableWIRE( void ) ; + inline bool isMasterWIRE( void ) { return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; } + inline bool isSlaveWIRE( void ) { return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; } + inline bool isBusIdleWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; } + inline bool isBusOwnerWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_OWNER_STATE; } + inline bool isBusUnknownWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_UNKNOWN_STATE; } + inline bool isArbLostWIRE( void ) { return sercom->I2CM.STATUS.bit.ARBLOST == 1; } + inline bool isBusBusyWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_BUSY_STATE; } + inline bool isDataReadyWIRE( void ) { return sercom->I2CS.INTFLAG.bit.DRDY; } + inline bool isStopDetectedWIRE( void ) { return sercom->I2CS.INTFLAG.bit.PREC; } + inline bool isRestartDetectedWIRE( void ) { return sercom->I2CS.STATUS.bit.SR; } + inline bool isAddressMatch( void ) { return sercom->I2CS.INTFLAG.bit.AMATCH; } + inline bool isMasterReadOperationWIRE( void ) { return sercom->I2CS.STATUS.bit.DIR; } + inline bool isRXNackReceivedWIRE( void ) { return sercom->I2CM.STATUS.bit.RXNACK; } + inline int availableWIRE( void ) { return isMasterWIRE() ? sercom->I2CM.INTFLAG.bit.SB : sercom->I2CS.INTFLAG.bit.DRDY; } inline bool readDataWIRE( void ); - inline void writeDataWIRE(uint8_t data); - inline uint8_t readDataWIREByte(void); - + inline SercomTxn* getCurrentTxnWIRE(void) { return _wire.currentTxn; } + inline const SercomTxn* getCurrentTxnWIRE(void) const { return _wire.currentTxn; } + inline size_t getTxnIndexWIRE(void) const { return _wire.txnIndex; } + inline size_t getTxnLengthWIRE(void) const { return _wire.txnLength; } + + inline bool isDBGSTOP( void ) const { return sercom->I2CM.DBGCTRL.bit.DBGSTOP; } + inline void setDBGSTOP( bool stop ) { sercom->I2CM.DBGCTRL.bit.DBGSTOP = stop; } + inline Sercom* getSercom() const { return sercom; } int8_t getSercomIndex(void) ; uint32_t getSercomFreqRef(void) ; @@ -415,15 +419,16 @@ class SERCOM // as needed in the future. For now, it just provides default support // for (hs) mode and DMA. struct WireConfig { - uint32_t ctrla = 0x00000002; // default CTRLA value: auto ENABLE - uint32_t ctrlb = 0x00000500; // default CTRLB value: SMEN (both) | AACKEN (Slave only) - uint32_t baud = 0x000000FF; // default to lowest supported speed - uint32_t addr = 0x00000000; // default address no GCEN, no ADDRMASK, 7-bit address only - uint8_t masterSpeed = 0x0; // default to lowest speed - uint8_t slaveSpeed = 0x0; // default to lowest speed - bool inited = false; // whether initMaster/SlaveWIRE has been called - bool useDma = false; // per transaction DMA use flag for Host/Client modes - bool active = false; // active transaction in progress + uint32_t ctrla = 0x00000000; // default CTRLA value: auto ENABLE + uint32_t ctrlb = SERCOM_I2CM_CTRLB_SMEN; // default CTRLB value: SMEN + uint32_t baud = 0x000000FF; // default to lowest supported speed + uint32_t addr = 0x00000000; // default address no GCEN, no ADDRMASK, 7-bit address only + uint8_t masterSpeed = 0x0; // default to lowest speed + uint8_t slaveSpeed = 0x0; // default to lowest speed + bool inited = false; // whether initMaster/SlaveWIRE has been called + bool useDma = false; // per transaction DMA use flag for Host/Client modes + bool active = false; // active transaction in progress + uint8_t retryCount = 0; // retry count for recoverable bus errors SercomWireError returnValue = SercomWireError::SUCCESS; SercomTxn* currentTxn = nullptr; size_t txnIndex = 0; diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index 4215e8f50..c278d7090 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -35,80 +35,111 @@ inline void SERCOM::deferStopWIRE(SercomWireError error) inline bool SERCOM::sendDataWIRE( void ) { SercomTxn* txn = _wire.currentTxn; - if (txn && txn->txPtr == nullptr) return false; + if (txn == nullptr || txn->txPtr == nullptr) return false; #ifdef USE_ZERODMA - if (isWireDma()) { - if (!_dmaTxActive) - dmaStartTx(txn->txPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); - return true; + if (isDmaWIRE()) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaTxActive && !_dmaRxActive) + value = dmaStartTx(txn->txPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); + return value == DmaStatus::Ok; } #endif - if (_wire.txnIndex < _wire.txnLength) { - sercom->I2CM.DATA.bit.DATA = txn->txPtr[_wire.txnIndex++]; - return true; - } - - return false; -} -inline bool SERCOM::isMasterWIRE( void ) { return sercom->I2CM.CTRLA.bit.MODE == I2C_MASTER_OPERATION; } -inline bool SERCOM::isSlaveWIRE( void ) { return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; } + sercom->I2CM.DATA.reg = txn->txPtr[_wire.txnIndex++]; -inline bool SERCOM::isBusIdleWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; } -inline bool SERCOM::isBusOwnerWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_OWNER_STATE; } -inline bool SERCOM::isBusUnknownWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_UNKNOWN_STATE; } -inline bool SERCOM::isArbLostWIRE( void ) { return sercom->I2CM.STATUS.bit.ARBLOST == 1; } -inline bool SERCOM::isBusBusyWIRE( void ) { return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_BUSY_STATE; } -inline bool SERCOM::isDataReadyWIRE( void ) { return sercom->I2CS.INTFLAG.bit.DRDY; } -inline bool SERCOM::isStopDetectedWIRE( void ) { return sercom->I2CS.INTFLAG.bit.PREC; } -inline bool SERCOM::isRestartDetectedWIRE( void ) { return sercom->I2CS.INTFLAG.bit.SR; } -inline bool SERCOM::isAddressMatch( void ) { return sercom->I2CS.INTFLAG.bit.AMATCH; } + if(isMasterWIRE()) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync and clear MB + + // Return false when the last byte has been consumed so the caller can + // issue STOP / complete the transaction without waiting for another SB. + return (_wire.txnIndex < _wire.txnLength); +} -inline void SERCOM::prepareNackBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 1; } -inline void SERCOM::prepareAckBitWIRE( void ) { sercom->I2CM.CTRLB.bit.ACKACT = 0; } -inline void SERCOM::prepareCommandBitsWire(uint8_t cmd) +inline void SERCOM::prepareCommandBitsWIRE(uint8_t cmd) { - if (isMasterWIRE()) { sercom->I2CM.CTRLB.bit.CMD = cmd; - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) - { - // Waiting for synchronization + if (isMasterWIRE()) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Waiting for synchronization } - } else { - sercom->I2CS.CTRLB.bit.CMD = cmd; - } -} -inline int SERCOM::availableWIRE( void ) { return isMasterWIRE() ? sercom->I2CM.INTFLAG.bit.SB : sercom->I2CS.INTFLAG.bit.DRDY; } inline bool SERCOM::readDataWIRE( void ) { SercomTxn* txn = _wire.currentTxn; - if (txn && txn->rxPtr == nullptr) return false; + if (txn == nullptr || txn->rxPtr == nullptr) return false; + +#ifdef USE_ZERODMA + if (isDmaWIRE()) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaRxActive && !_dmaTxActive) + value = dmaStartRx(txn->rxPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); + return value == DmaStatus::Ok; + } +#endif + + bool isMaster = isMasterWIRE(); + + if (isMaster) { + if (_wire.txnIndex == (_wire.txnLength - 1)) { + uint8_t cmd = txn->config & I2C_CFG_STOP ? WIRE_MASTER_ACT_STOP : WIRE_MASTER_ACT_NO_ACTION; + sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(cmd); // NACK the last byte and send STOP if requested + if (cmd == WIRE_MASTER_ACT_STOP) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for CMD to sync and clear SB + } + else + prepareAckBitWIRE(); // ACK bytes otherwise for non-SCLSM mode + } + + // Read DATA register (clears SB in Smart Mode) + txn->rxPtr[_wire.txnIndex++] = sercom->I2CM.DATA.reg; - if (isMasterWIRE() && (_wire.txnIndex + 1 >= _wire.txnLength)) - sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; - else - sercom->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; + if(isMaster) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync and clear SB + + return (_wire.txnIndex < _wire.txnLength); +} + +inline bool SERCOM::sendDataSPI(void) +{ + SercomTxn* txn = _spi.currentTxn; + if (txn == nullptr || txn->txPtr == nullptr) return false; #ifdef USE_ZERODMA - if (isWireDma()) { - if (!_dmaRxActive) - dmaStartRx(txn->rxPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); - return true; + if (_spi.useDma) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaTxActive && !_dmaRxActive) + value = dmaStartTx(txn->txPtr, &sercom->SPI.DATA.reg, _spi.length); + return value == DmaStatus::Ok; } #endif - if ( _wire.txnIndex >= _wire.txnLength) - return false; + // Byte-by-byte: Write DATA register + sercom->SPI.DATA.bit.DATA = txn->txPtr[_spi.index++]; - txn->rxPtr[_wire.txnIndex++] = sercom->I2CM.DATA.bit.DATA; - return true; + // Return false when last byte consumed so caller can complete transaction + return (_spi.index < _spi.length); } -inline uint8_t SERCOM::getINTFLAG( void ) const { return sercom->I2CM.INTFLAG.reg; } -inline uint16_t SERCOM::getSTATUS( void ) const { return sercom->I2CM.STATUS.reg; } -inline void SERCOM::clearINTFLAG( void ) { sercom->I2CM.INTFLAG.reg = 0xFF; } +inline bool SERCOM::readDataSPI(void) +{ + SercomTxn* txn = _spi.currentTxn; + if (txn == nullptr || txn->rxPtr == nullptr) return false; + +#ifdef USE_ZERODMA + if (_spi.useDma) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaRxActive && !_dmaTxActive) + value = dmaStartRx(txn->rxPtr, &sercom->SPI.DATA.reg, _spi.length); + return value == DmaStatus::Ok; + } +#endif + + // Byte-by-byte: Read DATA register + txn->rxPtr[_spi.index - 1] = sercom->SPI.DATA.bit.DATA; + + // Return false when all bytes consumed + return (_spi.index < _spi.length); +} inline void SERCOM::setTxnWIRE(SercomTxn* txn, size_t length, bool useDma) { @@ -148,13 +179,15 @@ inline void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) SERCOM* inst = findDmaOwner(dma, true); if (!inst) return; - if (inst->isMasterWIRE()) - inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); - else - inst->sercom->I2CS.CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3); - inst->_wire.returnValue = SercomWireError::SUCCESS; + // When using ADDR.LENEN mode, the hardware automatically generates STOP + // after ADDR.LEN bytes are transferred (datasheet §28.6.4.1.2). + // If a NACK TOPis received by the client for a host write transaction before + // ADDR.LEN bytes, a STOP will be automatically generated and the length error + // (STATUS.LENERR) will be raised along with the INTFLAG.ERROR interrupt.re + + inst->_wire.txnIndex = inst->_wire.txnLength; inst->_dmaTxActive = false; - SERCOM::setPending((uint8_t)inst->getSercomIndex()); + inst->deferStopWIRE(SercomWireError::SUCCESS); } inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) @@ -162,14 +195,14 @@ inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) SERCOM* inst = findDmaOwner(dma, false); if (!inst) return; - if (inst->isMasterWIRE()) - inst->sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(WIRE_MASTER_ACT_STOP); - else - inst->sercom->I2CS.CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3); + // When using ADDR.LENEN mode, the hardware automatically generates NACK+STOP + // after ADDR.LEN bytes are transferred (datasheet §28.6.4.1.2). + // Do NOT issue a manual STOP command - it conflicts with the automatic sequence. + // The STOP is generated by hardware, not by software CMD write. + inst->_wire.txnIndex = inst->_wire.txnLength; - inst->_wire.returnValue = SercomWireError::SUCCESS; inst->_dmaRxActive = false; - SERCOM::setPending((uint8_t)inst->getSercomIndex()); + inst->deferStopWIRE(SercomWireError::SUCCESS); } inline void SERCOM::dmaTxCallbackSPI(Adafruit_ZeroDMA* dma) @@ -179,6 +212,7 @@ inline void SERCOM::dmaTxCallbackSPI(Adafruit_ZeroDMA* dma) inst->_spi.dmaTxDone = true; if (inst->_spi.dmaNeedRx && !inst->_spi.dmaRxDone) return; + inst->_dmaTxActive = false; inst->_spi.returnValue = SercomSpiError::SUCCESS; SERCOM::setPending((uint8_t)inst->getSercomIndex()); } @@ -190,9 +224,34 @@ inline void SERCOM::dmaRxCallbackSPI(Adafruit_ZeroDMA* dma) inst->_spi.dmaRxDone = true; if (inst->_spi.dmaNeedTx && !inst->_spi.dmaTxDone) return; + inst->_dmaRxActive = false; inst->_spi.returnValue = SercomSpiError::SUCCESS; SERCOM::setPending((uint8_t)inst->getSercomIndex()); } + +inline void SERCOM::dmaTxCallbackUART(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, true); + if (!inst) return; + inst->_uart.dmaTxDone = true; + if (inst->_uart.dmaNeedRx && !inst->_uart.dmaRxDone) + return; + inst->_dmaTxActive = false; + inst->_uart.returnValue = SercomUartError::SUCCESS; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} + +inline void SERCOM::dmaRxCallbackUART(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, false); + if (!inst) return; + inst->_uart.dmaRxDone = true; + if (inst->_uart.dmaNeedTx && !inst->_uart.dmaTxDone) + return; + inst->_dmaRxActive = false; + inst->_uart.returnValue = SercomUartError::SUCCESS; + SERCOM::setPending((uint8_t)inst->getSercomIndex()); +} #endif #endif // __cplusplus From 66c1ff7d518ac27c44a31f6280d8a74be3b7b304 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 15 Feb 2026 00:12:51 -0600 Subject: [PATCH 15/24] clean up SPI --- cores/arduino/SERCOM_inline.h | 8 ++++---- libraries/SPI/SPI.h | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index c278d7090..0a713614d 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -58,10 +58,10 @@ inline bool SERCOM::sendDataWIRE( void ) inline void SERCOM::prepareCommandBitsWIRE(uint8_t cmd) { - sercom->I2CM.CTRLB.bit.CMD = cmd; + sercom->I2CM.CTRLB.bit.CMD = cmd; if (isMasterWIRE()) while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Waiting for synchronization - } +} inline bool SERCOM::readDataWIRE( void ) { @@ -193,8 +193,8 @@ inline void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) { SERCOM* inst = findDmaOwner(dma, false); - if (!inst) return; - + if (!inst) return; + // When using ADDR.LENEN mode, the hardware automatically generates NACK+STOP // after ADDR.LEN bytes are transferred (datasheet §28.6.4.1.2). // Do NOT issue a manual STOP command - it conflicts with the automatic sequence. diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 444f1b9e0..d0afa90b9 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -142,20 +142,6 @@ class SPIClass { void setDataMode(uint8_t uc_mode); void setClockDivider(uint8_t uc_div); - // SERCOM lookup functions are available on both SAMD51 and 21. - volatile uint32_t *getDataRegister(void); - int getDMAC_ID_TX(void); - int getDMAC_ID_RX(void); - uint8_t getSercomIndex(void) { return _p_sercom->getSercomIndex(); }; -#if defined(__SAMD51__) - // SERCOM clock source override is available only on SAMD51. - void setClockSource(SercomClockSource clk); -#else - // On SAMD21, this compiles to nothing, so user code doesn't need to - // check and conditionally compile lines for different architectures. - void setClockSource(SercomClockSource clk) { (void)clk; }; -#endif // end __SAMD51__ - private: void config(SPISettings settings); From 430e8bfd412c003d7d6f74f42efa1bf6cf40a78e Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 15 Feb 2026 14:25:53 -0600 Subject: [PATCH 16/24] finish refactor of Wire to fully use async SERCOM --- cores/arduino/SERCOM.cpp | 3 + cores/arduino/SERCOM.h | 2 +- cores/arduino/SERCOM_inline.h | 41 +++-- libraries/Wire/Wire.cpp | 293 +++++++++------------------------- libraries/Wire/Wire.h | 181 ++++++++++++++++++++- 5 files changed, 287 insertions(+), 233 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 0308d0196..88b02bcdd 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -865,6 +865,9 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) _wire.retryCount = 0; } + if(isMasterWIRE()) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync from last transaction + // Callbacks are expected to run in non-ISR context (main loop/PendSV). if (txn &&txn->onComplete) txn->onComplete(txn->user, static_cast(error)); diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index d489339f0..17d54f9a5 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -251,7 +251,7 @@ class SERCOM void initSlaveWIRE(uint8_t address, bool enableGeneralCall = false, uint8_t speed = 0x0) ; void initSlaveWIRE(uint16_t address, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false) ; void initMasterWIRE(uint32_t baudrate) ; - inline void setTxnWIRE(SercomTxn* txn, size_t length, bool useDma); + inline void setTxnWIRE(SercomTxn* txn); inline void setDmaWIRE(bool useDma) { _wire.useDma = useDma; } inline bool isDmaWIRE(void) const { return _wire.useDma; } void registerReceiveWIRE(void (*cb)(void* user, int length), void* user); diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h index 0a713614d..9f6502ff9 100644 --- a/cores/arduino/SERCOM_inline.h +++ b/cores/arduino/SERCOM_inline.h @@ -46,10 +46,8 @@ inline bool SERCOM::sendDataWIRE( void ) } #endif + // Wait for DATA to sync out of the ISR and clear MB sercom->I2CM.DATA.reg = txn->txPtr[_wire.txnIndex++]; - - if(isMasterWIRE()) - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync and clear MB // Return false when the last byte has been consumed so the caller can // issue STOP / complete the transaction without waiting for another SB. @@ -83,19 +81,21 @@ inline bool SERCOM::readDataWIRE( void ) if (_wire.txnIndex == (_wire.txnLength - 1)) { uint8_t cmd = txn->config & I2C_CFG_STOP ? WIRE_MASTER_ACT_STOP : WIRE_MASTER_ACT_NO_ACTION; sercom->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(cmd); // NACK the last byte and send STOP if requested - if (cmd == WIRE_MASTER_ACT_STOP) - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for CMD to sync and clear SB } else prepareAckBitWIRE(); // ACK bytes otherwise for non-SCLSM mode } + else { + // Slave mode: set ACKACT BEFORE reading DATA (SMEN auto-receives based on ACKACT) + if (_wire.txnIndex >= _wire.txnLength) + prepareNackBitWIRE(); // NACK if buffer full + else + prepareAckBitWIRE(); // ACK if room available + } - // Read DATA register (clears SB in Smart Mode) + // Read DATA register (accesses auto-trigger bus operation based on ACKACT/SMEN) txn->rxPtr[_wire.txnIndex++] = sercom->I2CM.DATA.reg; - if(isMaster) - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync and clear SB - return (_wire.txnIndex < _wire.txnLength); } @@ -141,12 +141,29 @@ inline bool SERCOM::readDataSPI(void) return (_spi.index < _spi.length); } -inline void SERCOM::setTxnWIRE(SercomTxn* txn, size_t length, bool useDma) +inline void SERCOM::setTxnWIRE(SercomTxn* txn) { _wire.currentTxn = txn; - _wire.txnLength = length; _wire.txnIndex = 0; - _wire.useDma = useDma; + + if (txn) + _wire.txnLength = txn->length; +#ifdef USE_ZERODMA + setDmaWIRE((txn && txn->length > 0 && txn->length < 256) || sercom->I2CM.CTRLA.bit.SCLSM); + + if (isDmaWIRE()) + _wire.txnLength = (_wire.txnLength < 255u) ? _wire.txnLength : 255u; +#else + _wire.useDma = false; + + if (sercom->I2CM.CTRLA.bit.SCLSM) + _wire.currentTxn = nullptr; // SCLSM requires DMA for proper operation (true for master; I think for slave) +#endif + if (!_wire.active) + _wire.retryCount = 0; + + _wire.returnValue = SercomWireError::SUCCESS; + _wire.active = true; } #ifdef USE_ZERODMA diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 9066f4a97..9b33ab869 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -41,6 +41,9 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) rxIndex = 0; txLength = 0; txIndex = 0; + txExternalPtr = nullptr; + txExternalLength = 0; + txExternalActive = false; masterIndex = 0; awaitingAddressAck = false; txnDone = false; @@ -53,40 +56,37 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) void TwoWire::begin(void) { //Master Mode + pinPeripheral(_uc_pinSDA, g_APinDescription[_uc_pinSDA].ulPinType); + pinPeripheral(_uc_pinSCL, g_APinDescription[_uc_pinSCL].ulPinType); + sercom->initMasterWIRE(TWI_CLOCK); sercom->enableWIRE(); - sercom->registerWireReceive(&TwoWire::onDeferredReceive, this); +} +void TwoWire::begin(uint16_t address, bool enableGeneralCall, uint8_t speed, bool enable10Bit) { + //Slave mode pinPeripheral(_uc_pinSDA, g_APinDescription[_uc_pinSDA].ulPinType); pinPeripheral(_uc_pinSCL, g_APinDescription[_uc_pinSCL].ulPinType); -} -void TwoWire::begin(uint8_t address, bool enableGeneralCall) { - //Slave mode - sercom->initSlaveWIRE(address, enableGeneralCall); + sercom->initSlaveWIRE(address, enableGeneralCall, speed, enable10Bit); sercom->enableWIRE(); - sercom->registerWireReceive(&TwoWire::onDeferredReceive, this); + sercom->registerReceiveWIRE(&TwoWire::onDeferredReceive, this); +} - pinPeripheral(_uc_pinSDA, g_APinDescription[_uc_pinSDA].ulPinType); - pinPeripheral(_uc_pinSCL, g_APinDescription[_uc_pinSCL].ulPinType); +void TwoWire::begin(uint8_t address, bool enableGeneralCall) { + begin(static_cast(address), enableGeneralCall); } void TwoWire::setClock(uint32_t baudrate) { - sercom->disableWIRE(); - sercom->initMasterWIRE(baudrate); - sercom->enableWIRE(); + sercom->setBaudrateWIRE(baudrate); } void TwoWire::end() { sercom->disableWIRE(); } -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) -{ - return requestFrom(address, quantity, static_cast(nullptr), stopBit); -} - -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit) +uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit, + void (*onComplete)(void* user, int status), void* user) { if(quantity == 0) { @@ -111,8 +111,8 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer txn.length = quantity; txn.txPtr = nullptr; txn.rxPtr = rxBufferPtr; - txn.onComplete = &TwoWire::onTxnComplete; - txn.user = this; + txn.onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; + txn.user = onComplete ? user : this; txnDone = false; txnStatus = static_cast(SercomWireError::SUCCESS); masterIndex = 0; @@ -121,20 +121,24 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer if (!sercom->enqueueWIRE(&txn)) return 0; - while (!txnDone) { - yield(); - } + if (!onComplete) { + while (!txnDone) { + yield(); + } - if (txnStatus != static_cast(SercomWireError::SUCCESS)) - return 0; + if (txnStatus != static_cast(SercomWireError::SUCCESS)) + return 0; - rxLength = quantity; - return rxLength; + rxLength = quantity; + return rxLength; + } + + return quantity; } uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity) { - return requestFrom(address, quantity, true); + return requestFrom(address, quantity, nullptr, true); } void TwoWire::beginTransmission(uint8_t address) { @@ -142,6 +146,9 @@ void TwoWire::beginTransmission(uint8_t address) { txAddress = address; txLength = 0; txIndex = 0; + txExternalPtr = nullptr; + txExternalLength = 0; + txExternalActive = false; transmissionBegun = true; } @@ -152,17 +159,17 @@ void TwoWire::beginTransmission(uint8_t address) { // 2 : NACK on transmit of address // 3 : NACK on transmit of data // 4 : Other error -uint8_t TwoWire::endTransmission(bool stopBit) +uint8_t TwoWire::endTransmission(bool stopBit, void (*onComplete)(void* user, int status), void* user) { transmissionBegun = false ; txn.config = stopBit ? I2C_CFG_STOP : 0; txn.address = txAddress; - txn.length = txLength; - txn.txPtr = txBuffer; + txn.length = txExternalActive ? txExternalLength : txLength; + txn.txPtr = txExternalActive ? txExternalPtr : txBuffer; txn.rxPtr = nullptr; - txn.onComplete = &TwoWire::onTxnComplete; - txn.user = this; + txn.onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; + txn.user = onComplete ? user : this; txnDone = false; txnStatus = static_cast(SercomWireError::SUCCESS); masterIndex = 0; @@ -171,23 +178,31 @@ uint8_t TwoWire::endTransmission(bool stopBit) if (!sercom->enqueueWIRE(&txn)) return static_cast(SercomWireError::QUEUE_FULL); - while (!txnDone) { - yield(); - } + txExternalPtr = nullptr; + txExternalLength = 0; + txExternalActive = false; - SercomWireError err = static_cast(txnStatus); - switch (err) { - case SercomWireError::SUCCESS: - return 0; - case SercomWireError::DATA_TOO_LONG: - return 1; - case SercomWireError::NACK_ON_ADDRESS: - return 2; - case SercomWireError::NACK_ON_DATA: - return 3; - default: - return 4; + if (!onComplete) { + while (!txnDone) { + yield(); + } + + SercomWireError err = static_cast(txnStatus); + switch (err) { + case SercomWireError::SUCCESS: + return 0; + case SercomWireError::DATA_TOO_LONG: + return 1; + case SercomWireError::NACK_ON_ADDRESS: + return 2; + case SercomWireError::NACK_ON_DATA: + return 3; + default: + return 4; + } } + + return 0; } uint8_t TwoWire::endTransmission() @@ -198,7 +213,7 @@ uint8_t TwoWire::endTransmission() size_t TwoWire::write(uint8_t ucData) { // No writing, without begun transmission or a full buffer - if ( !transmissionBegun || txLength >= WIRE_TX_BUFFER_LENGTH ) + if ( !transmissionBegun || txExternalActive || txLength >= WIRE_TX_BUFFER_LENGTH ) { return 0 ; } @@ -210,6 +225,20 @@ size_t TwoWire::write(uint8_t ucData) size_t TwoWire::write(const uint8_t *data, size_t quantity) { + if (!transmissionBegun) + return 0; + + if (txExternalActive) + return 0; + + if (txLength == 0 && quantity > 0) { + txExternalPtr = data; + txExternalLength = quantity; + txExternalActive = true; + txLength = quantity; + return quantity; + } + //Try to store all data for(size_t i = 0; i < quantity; ++i) { @@ -293,174 +322,6 @@ size_t TwoWire::getRxLength(void) const return rxLength; } -void TwoWire::onService(void) -{ - SercomTxn* t = &txn; - uint8_t flags = (uint8_t)sercom->getINTFLAG(); - - if (flags == 0) - return; - - if (sercom->isRXNackReceivedWIRE()) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - SercomWireError err = awaitingAddressAck ? SercomWireError::NACK_ON_ADDRESS - : SercomWireError::NACK_ON_DATA; - sercom->deferStopWIRE(err); - return; - } - - if (sercom->isMasterWIRE()) - { - if (flags & SERCOM_I2CM_INTFLAG_ERROR) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - uint16_t status = (uint16_t)sercom->getSTATUS(); - uint8_t busState = (status & SERCOM_I2CM_STATUS_BUSSTATE_Msk) >> SERCOM_I2CM_STATUS_BUSSTATE_Pos; - SercomWireError err; - - if (status & SERCOM_I2CM_STATUS_ARBLOST) - err = SercomWireError::ARBITRATION_LOST; - else if (status & SERCOM_I2CM_STATUS_BUSERR) - err = SercomWireError::BUS_ERROR; - else if (status & SERCOM_I2CM_STATUS_MEXTTOUT) - err = SercomWireError::MASTER_TIMEOUT; - else if (status & SERCOM_I2CM_STATUS_SEXTTOUT) - err = SercomWireError::SLAVE_TIMEOUT; - else if (status & SERCOM_I2CM_STATUS_LENERR) - err = SercomWireError::LENGTH_ERROR; - else if (busState == 0x0) - err = SercomWireError::BUS_STATE_UNKNOWN; - else - err = SercomWireError::UNKNOWN_ERROR; - - sercom->clearINTFLAG(); - sercom->deferStopWIRE(err); - return; - } - - bool ok = (t->config & I2C_CFG_READ) ? sercom->readDataWIRE() - : sercom->sendDataWIRE(); - - if (ok){ - awaitingAddressAck = false; -#ifdef USE_ZERODMA - if (t->length > 0 && t->length < 256 && (t->config & I2C_CFG_STOP)) - return; -#endif - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_READ); - return; - } - - // Transaction complete - if (t->config & I2C_CFG_STOP) - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - else - sercom->clearINTFLAG(); - - sercom->deferStopWIRE(SercomWireError::SUCCESS); - return; - } - - if ( sercom->isSlaveWIRE() ) - { - if (flags & SERCOM_I2CS_INTFLAG_ERROR) - { - uint16_t status = (uint16_t)sercom->getSTATUS(); - SercomWireError err; - - if (status & SERCOM_I2CS_STATUS_BUSERR) - err = SercomWireError::BUS_ERROR; - else if (status & SERCOM_I2CS_STATUS_COLL) - err = SercomWireError::ARBITRATION_LOST; - else if (status & SERCOM_I2CS_STATUS_SEXTTOUT) - err = SercomWireError::SLAVE_TIMEOUT; - else if (status & SERCOM_I2CS_STATUS_LOWTOUT) - err = SercomWireError::SLAVE_TIMEOUT; - else - err = SercomWireError::UNKNOWN_ERROR; - - sercom->clearINTFLAG(); - sercom->deferStopWIRE(err); - return; - } - - if(sercom->isStopDetectedWIRE() || - (sercom->isAddressMatch() && sercom->isRestartDetectedWIRE() && !sercom->isMasterReadOperationWIRE())) //Stop or Restart detected - { - sercom->prepareAckBitWIRE(); - sercom->prepareCommandBitsWire(0x03); - - pendingReceive = true; - pendingReceiveLength = available(); - sercom->deferWireReceive(pendingReceiveLength); - } - else if(sercom->isAddressMatch()) //Address Match - { - sercom->prepareAckBitWIRE(); - sercom->prepareCommandBitsWire(0x03); - - if(sercom->isMasterReadOperationWIRE()) //Is a request ? - { - txLength = 0; - txIndex = 0; - - transmissionBegun = true; - - // onRequestCallback runs in ISR context here. Deferring to PendSV - // would require stalling DRDY or returning 0xFF until the buffer is filled. - if(onRequestCallback) - onRequestCallback(); - - t->txPtr = txBuffer; - t->rxPtr = nullptr; - t->length = txLength; - t->config = I2C_CFG_READ; - bool useDma = (txLength > 0 && txLength < 256); - sercom->setWireTxn(t, txLength, useDma); - } else { - rxLength = 0; - rxIndex = 0; - t->txPtr = nullptr; - t->rxPtr = rxBufferPtr; - t->length = rxBufferCapacity; - t->config = 0; - bool useDma = (rxBufferPtr != rxBuffer) && (rxBufferCapacity > 0 && rxBufferCapacity < 256); - sercom->setWireTxn(t, rxBufferCapacity, useDma); - if (useDma) - rxLength = rxBufferCapacity; - } - } - else if(sercom->isDataReadyWIRE()) - { - bool ok = sercom->isMasterReadOperationWIRE() - ? sercom->sendDataWIRE() - : sercom->readDataWIRE(); - - if (ok) { -#ifdef USE_ZERODMA - if (sercom->isWireDma()) - return; -#endif - if (!sercom->isMasterReadOperationWIRE()) - rxLength++; - sercom->prepareCommandBitsWire(0x03); - return; - } - - if (!sercom->isMasterReadOperationWIRE()) { - sercom->prepareNackBitWIRE(); - sercom->prepareCommandBitsWire(0x03); - return; - } - - sercom->I2CS.DATA.reg = 0xFF; - sercom->prepareCommandBitsWire(0x03); - return; - } - } -} - void TwoWire::onTxnComplete(void* user, int status) { if (!user) diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index da2834358..f313c5e99 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -30,21 +30,37 @@ // WIRE_HAS_END means Wire has end() #define WIRE_HAS_END 1 +// NOTE: SAMD21/SAMD51 silicon errata: when I2C master uses SCLSM=1, CTRLB.CMD +// (STOP/RESTART) is ignored, so interrupt-driven byte mode cannot reliably end +// transfers or issue repeated starts. Hs-mode requires SCLSM=1, therefore Hs-mode +// is DMA-only and STOP-only (no repeated starts). The non-DMA Wire path should +// not enable Hs-mode. Also per errata, do not enable QCEN when SCLSM=1 (bus error). + class TwoWire : public Stream { public: TwoWire(SERCOM *s, uint8_t pinSDA, uint8_t pinSCL); void begin(); + void begin(uint16_t, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false); void begin(uint8_t, bool enableGeneralCall = false); void end(); void setClock(uint32_t); void beginTransmission(uint8_t); - uint8_t endTransmission(bool stopBit); + // If onComplete is nullptr, this blocks for legacy sync behavior. + // If onComplete is non-null, this enqueues and returns immediately (async). + uint8_t endTransmission(bool stopBit = true, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); uint8_t endTransmission(void); - uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit); - uint8_t requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit = true); + // If onComplete is nullptr, this blocks for legacy sync behavior. + // If onComplete is non-null, this enqueues and returns immediately (async). + // If rxBuffer is nullptr, the internal buffer is used; otherwise rxBuffer is used. + uint8_t requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer = nullptr, + bool stopBit = true, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); uint8_t requestFrom(uint8_t address, size_t quantity); size_t write(uint8_t data); @@ -68,7 +84,7 @@ class TwoWire : public Stream inline size_t write(int n) { return write((uint8_t)n); } using Print::write; - void onService(void); + inline void onService(void); private: SERCOM * sercom; @@ -88,6 +104,9 @@ class TwoWire : public Stream size_t rxIndex; size_t txLength; size_t txIndex; + const uint8_t* txExternalPtr; + size_t txExternalLength; + bool txExternalActive; size_t masterIndex; bool awaitingAddressAck; uint8_t txAddress; @@ -127,4 +146,158 @@ class TwoWire : public Stream extern TwoWire Wire5; #endif +inline void TwoWire::onService(void) +{ + uint8_t flags = (uint8_t)sercom->getINTFLAG(); + uint16_t status = (uint16_t)sercom->getSTATUS(); + bool isMaster = sercom->isMasterWIRE(); + + if ((!isMaster && !sercom->isSlaveWIRE()) || flags == 0) { + sercom->clearINTFLAG(); + return; + } + + if (status & SERCOM_I2CM_STATUS_RXNACK) { + sercom->prepareCommandBitsWIRE(WIRE_MASTER_ACT_STOP); + SercomWireError err = awaitingAddressAck ? SercomWireError::NACK_ON_ADDRESS + : SercomWireError::NACK_ON_DATA; + sercom->deferStopWIRE(err); + return; + } + + if (isMaster) { + SercomTxn* txn = sercom->getCurrentTxnWIRE(); + if (!txn) { + sercom->clearINTFLAG(); + return; + } + + if (flags & SERCOM_I2CM_INTFLAG_ERROR) { + sercom->prepareCommandBitsWIRE(WIRE_MASTER_ACT_STOP); + uint8_t busState = (status & SERCOM_I2CM_STATUS_BUSSTATE_Msk) >> SERCOM_I2CM_STATUS_BUSSTATE_Pos; + SercomWireError err = SercomWireError::UNKNOWN_ERROR; + + if (status & SERCOM_I2CM_STATUS_ARBLOST) + err = SercomWireError::ARBITRATION_LOST; + if (status & SERCOM_I2CM_STATUS_BUSERR) + err = SercomWireError::BUS_ERROR; + if (status & SERCOM_I2CM_STATUS_MEXTTOUT) + err = SercomWireError::MASTER_TIMEOUT; + if (status & SERCOM_I2CM_STATUS_SEXTTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + if (status & SERCOM_I2CM_STATUS_LENERR) + err = SercomWireError::LENGTH_ERROR; + if (busState == 0x0) + err = SercomWireError::BUS_STATE_UNKNOWN; + + sercom->clearINTFLAG(); + sercom->deferStopWIRE(err); + return; + } + + bool isRead = (txn->config & I2C_CFG_READ); + + if (sercom->getTxnIndexWIRE() < sercom->getTxnLengthWIRE()) { + isRead ? sercom->readDataWIRE() : sercom->sendDataWIRE(); + awaitingAddressAck = false; + return; + } + + if ((txn->config & I2C_CFG_STOP) && !isRead) + sercom->prepareCommandBitsWIRE(WIRE_MASTER_ACT_STOP); + else + sercom->clearINTFLAG(); + + awaitingAddressAck = true; + sercom->deferStopWIRE(SercomWireError::SUCCESS); + return; + } + else { + if (flags & SERCOM_I2CS_INTFLAG_ERROR) { + SercomWireError err = SercomWireError::UNKNOWN_ERROR;; + + if (status & SERCOM_I2CS_STATUS_BUSERR) + err = SercomWireError::BUS_ERROR; + if (status & SERCOM_I2CS_STATUS_COLL) + err = SercomWireError::ARBITRATION_LOST; + if (status & SERCOM_I2CS_STATUS_SEXTTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + if (status & SERCOM_I2CS_STATUS_LOWTOUT) + err = SercomWireError::SLAVE_TIMEOUT; + + sercom->clearINTFLAG(); + sercom->deferStopWIRE(err); + return; + } + + // To avoid unnecessary clock cycles for register reads, avoid using inline getters + bool isMasterRead = (status & SERCOM_I2CS_STATUS_DIR); // Master Read / Slave Transmit + bool sr = (status & SERCOM_I2CS_STATUS_SR); // Repeated Start detected + bool prec = (flags & SERCOM_I2CS_INTFLAG_PREC); // Stop detected + bool amatch = (flags & SERCOM_I2CS_INTFLAG_AMATCH); // Address Match detected + bool drdy = (flags & SERCOM_I2CS_INTFLAG_DRDY); // Data Ready detected + + // Stop or Restart detected - defer receive callback + if(prec || (amatch && sr && !isMasterRead)) + { + pendingReceive = true; + pendingReceiveLength = available(); + sercom->deferReceiveWIRE(pendingReceiveLength); + return; + } + + // Address Match - setup transaction + // AACKEN enabled: address ACK is automatic, no manual ACK/clear needed + else if(amatch) + { + if(isMasterRead) // Master Read / Slave TX + { + // onRequestCallback runs in ISR context here. Deferring to PendSV + // would require stalling DRDY or returning 0xFF until the buffer is filled. + // onRequestCallback is what will set TwoWire::txn for the transaction. + if(onRequestCallback) + onRequestCallback(); + + // Ensure callback actually set txn.length; if not, stall with 0-length txn + if (txn.length == 0) + return; + + if (!(txn.config & I2C_CFG_READ)) + txn.config |= I2C_CFG_READ; + } + else // Master Write / Slave RX + { + // rxLength needs to be set in the rxCallback so the user has runtime control + // setting rxLength to 0 will default to the internal buffer + rxIndex = 0; + txn.txPtr = nullptr; + txn.rxPtr = rxLength ? rxBufferPtr : rxBuffer; + txn.length = rxLength ? rxLength : WIRE_RX_BUFFER_LENGTH; + txn.config = 0; + } + + sercom->setTxnWIRE(&txn); + + // SCLSM=0 (Smart Mode disabled): AMATCH and DRDY never fire together + // → return now, DRDY will fire in next interrupt + // SCLSM=1 (Smart Mode enabled) + Master Read: AMATCH+DRDY fire together + // → fall through to handle data immediately + // SCLSM=1 + Master Write: DRDY not set yet + // → return now, DRDY fires later + if (!drdy) + return; + // else: DRDY is set (SCLSM=1 Master Read case), fall through + } + + // Data Ready - handle byte transfer + if(drdy) + { + isMasterRead ? sercom->sendDataWIRE() : sercom->readDataWIRE(); + + if (!isMasterRead) + rxLength = sercom->getTxnIndexWIRE(); + } + } +} + #endif From 51e0dd592143f35475dbe75b8cc9c680908e0015 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 19:41:06 -0600 Subject: [PATCH 17/24] master wire fully tested sync/async --- cores/arduino/SERCOM.cpp | 62 ++++++----- cores/arduino/SERCOM.h | 3 +- libraries/Wire/Wire.cpp | 234 +++++++++++++++++++++++++-------------- libraries/Wire/Wire.h | 58 +++++----- 4 files changed, 216 insertions(+), 141 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 88b02bcdd..cb53055f5 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -82,7 +82,7 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint #ifdef USE_ZERODMA int8_t id = getSercomIndex(); if (id >= 0) { - dmaSetCallbacks(SERCOM::dmaTxCallbackUART, SERCOM::dmaRxCallbackUART); + dmaSetCallbacks(SERCOM::dmaTxCallbackUART, SERCOM::dmaRxCallbackUART); dmaInit(id); } #endif // USE_ZERODMA @@ -342,7 +342,7 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch #ifdef USE_ZERODMA int8_t id = getSercomIndex(); if (id >= 0) { - dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); + dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); dmaInit(id); } #endif // USE_ZERODMA @@ -569,8 +569,9 @@ uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate) */ void SERCOM::resetWIRE() { - resetSERCOM(); - _wire = WireConfig{}; + clearQueueWIRE(); // Drain pending transactions from queue + resetSERCOM(); // SWRST: hardware reset to default state + _wire = WireConfig{}; // Reset software state } void SERCOM::clearQueueWIRE(void) @@ -604,10 +605,10 @@ void SERCOM::initWIRE(void) if (_wire.inited) // If already initialized, return return; - uint8_t idx = getSercomIndex(); - initClockNVIC(); - registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); - + uint8_t idx = getSercomIndex(); + initClockNVIC(); + registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); + #ifdef USE_ZERODMA dmaSetCallbacks(SERCOM::dmaTxCallbackWIRE, SERCOM::dmaRxCallbackWIRE); if (idx >= 0) @@ -802,7 +803,7 @@ SercomTxn* SERCOM::startTransmissionWIRE( void ) addrReg |= SERCOM_I2CM_ADDR_LENEN | SERCOM_I2CM_ADDR_LEN((uint8_t)txn->length); } #endif - + // Send address (non-blocking; ISR handles ERROR/MB/SB) _wire.active = true; sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_MB; @@ -842,9 +843,11 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if (error == SercomWireError::BUS_STATE_UNKNOWN) { if (_wire.retryCount < kMaxWireRetries) { ++_wire.retryCount; + sercom->I2CM.STATUS.bit.BUSSTATE = 1; while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; startTransmissionWIRE(); + return txn; } } @@ -852,9 +855,11 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if (error == SercomWireError::ARBITRATION_LOST || error == SercomWireError::BUS_ERROR) { if (_wire.retryCount < kMaxWireRetries) { ++_wire.retryCount; + sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; startTransmissionWIRE(); + return txn; } } @@ -868,18 +873,30 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if(isMasterWIRE()) while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Wait for DATA to sync from last transaction + // Undocumented HW limitation: DMA transfers must terminate with STOP and bus release. + // After a DMA write, the host holds the bus ~7.33 us before the next transfer (Sr window). + // Writing ADDR during that window leaves the bus in an undefined state and breaks + // subsequent DMA/non-DMA transactions. To avoid this, we must wait for BUSSTATE + // to return to IDLE after a STOP returning the hardware to a known state. + // At the tested 48 MHz, this busy-wait is ~350 cycles corresponding to a 3.5 us delay + // at 100 MHz. This wait must occur BEFORE the callback to ensure the bus is stable + // before user code can enqueue the next transaction. + if (isMasterWIRE() && txn && (txn->config & I2C_CFG_STOP)) { + while (sercom->I2CM.STATUS.bit.BUSSTATE > 0x1) ; + } + // Callbacks are expected to run in non-ISR context (main loop/PendSV). - if (txn &&txn->onComplete) - txn->onComplete(txn->user, static_cast(error)); + if (txn && txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); if(isMasterWIRE()) _txnQueue.read(txn); // remove the completed transaction from the queue else { // Deliver deferred WIRE callback outside the SERCOM ISR (from PendSV). // This avoids running user code in the hardware interrupt context. - if (_wireDeferredPending && _wireDeferredCb) { - _wireDeferredPending = false; - _wireDeferredCb(_wireDeferredUser, _wireDeferredLength); + if (_wireDeferredPending && _wireDeferredCb) { + _wireDeferredPending = false; + _wireDeferredCb(_wireDeferredUser, _wireDeferredLength); } } @@ -889,25 +906,12 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) bool isMaster = isMasterWIRE(); - if (_txnQueue.peek(next) && isMaster) { - // Undocumented HW limitation: DMA transfers must terminate with STOP and bus release. - // After a DMA write, the host holds the bus ~7.33 us before the next transfer (Sr window). - // Writing ADDR during that window leaves the bus in an undefined state and breaks - // subsequent DMA/non-DMA transactions. To avoid this, we must wait for BUSSTATE - // to return to IDLE after a STOP returning the hardware to a known state. - // At the tested 48 MHz, this busy-wait is ~350 cycles corresponding to a 3.5 us delay - // at 100 MHz. Thus, we only wait when needed for the next transaction and not on every STOP. - if (txn) { - if (txn->config & I2C_CFG_STOP) - while (sercom->I2CM.STATUS.bit.BUSSTATE > 0x1) ; - } - + if (_txnQueue.peek(next) && isMaster) startTransmissionWIRE(); - } else if (isMaster) { + else if (isMaster) sercom->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENCLR_ERROR | SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; - } return txn; } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 17d54f9a5..eeadf05f4 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -294,6 +294,7 @@ class SERCOM inline const SercomTxn* getCurrentTxnWIRE(void) const { return _wire.currentTxn; } inline size_t getTxnIndexWIRE(void) const { return _wire.txnIndex; } inline size_t getTxnLengthWIRE(void) const { return _wire.txnLength; } + inline bool isActiveWIRE(void) const { return _wire.active; } inline bool isDBGSTOP( void ) const { return sercom->I2CM.DBGCTRL.bit.DBGSTOP; } inline void setDBGSTOP( bool stop ) { sercom->I2CM.DBGCTRL.bit.DBGSTOP = stop; } @@ -343,7 +344,7 @@ class SERCOM DmaStatus dmaStartTx(const void* src, volatile void* dstReg, size_t len); DmaStatus dmaStartRx(void* dst, volatile void* srcReg, size_t len); DmaStatus dmaStartDuplex(const void* txSrc, void* rxDst, volatile void* txReg, volatile void* rxReg, size_t len, - const uint8_t* dummyTx = nullptr); + const uint8_t* dummyTx = nullptr); void dmaRelease(); void dmaResetDescriptors(); void dmaAbortTx(); diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 9b33ab869..2fdf8d0a8 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -39,19 +39,16 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) transmissionBegun = false; rxLength = 0; rxIndex = 0; - txLength = 0; - txIndex = 0; - txExternalPtr = nullptr; - txExternalLength = 0; - txExternalActive = false; masterIndex = 0; awaitingAddressAck = false; txnDone = false; txnStatus = 0; rxBufferPtr = rxBuffer; - rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; + rxBufferCapacity = WIRE_BUFFER_LENGTH; + txBufferCapacity = WIRE_BUFFER_LENGTH; pendingReceive = false; pendingReceiveLength = 0; + txnPoolHead = 0; } void TwoWire::begin(void) { @@ -82,74 +79,92 @@ void TwoWire::setClock(uint32_t baudrate) { } void TwoWire::end() { - sercom->disableWIRE(); + sercom->resetWIRE(); // SWRST: resets hardware + clears state + drains queue } -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer, bool stopBit, +uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit, uint8_t* rxBuffer, void (*onComplete)(void* user, int status), void* user) { if(quantity == 0) - { return 0; - } + + loader = SercomTxn{}; if (rxBuffer != nullptr) { - rxBufferPtr = rxBuffer; - rxBufferCapacity = quantity; + loader.rxPtr = rxBuffer; + loader.length = quantity; } else { - rxBufferPtr = this->rxBuffer; - rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; - if (quantity > rxBufferCapacity) - quantity = rxBufferCapacity; + loader.rxPtr = this->rxBuffer; + loader.length = ( quantity > WIRE_BUFFER_LENGTH) ? WIRE_BUFFER_LENGTH : quantity; } - rxLength = 0; - rxIndex = 0; - - txn.config = I2C_CFG_READ | (stopBit ? I2C_CFG_STOP : 0); - txn.address = address; - txn.length = quantity; - txn.txPtr = nullptr; - txn.rxPtr = rxBufferPtr; - txn.onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; - txn.user = onComplete ? user : this; - txnDone = false; - txnStatus = static_cast(SercomWireError::SUCCESS); - masterIndex = 0; + loader.config = I2C_CFG_READ | (stopBit ? I2C_CFG_STOP : 0); + loader.address = address; + loader.onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; + + // Allocate fresh transaction from pool and copy loader data + SercomTxn* txn = allocateTxn(); + *txn = loader; + + // For async callbacks, pass txn as user so callback can access txn->rxPtr/length directly + // For sync calls, pass 'this' so onTxnComplete can update txnStatus/txnDone + if (onComplete) + txn->user = (user == nullptr) ? txn : user; + else + txn->user = this; + awaitingAddressAck = true; + txnDone = false; - if (!sercom->enqueueWIRE(&txn)) + // Enqueue the pool transaction, not the loader + if (!sercom->enqueueWIRE(txn)) return 0; if (!onComplete) { + // Wait for transaction to complete (onTxnComplete sets txnDone) with timeout + uint32_t startMillis = millis(); + const uint32_t timeout = 1000; // 1 second timeout while (!txnDone) { + if (millis() - startMillis > timeout) + return 0; + yield(); } if (txnStatus != static_cast(SercomWireError::SUCCESS)) return 0; + // Set up pointers for Wire.available()/read() to access the data + // txn->rxPtr already points to this->rxBuffer (from loader copy) + rxBufferPtr = loader.rxPtr; rxLength = quantity; + rxIndex = 0; return rxLength; } return quantity; } -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity) -{ - return requestFrom(address, quantity, nullptr, true); +SercomTxn* TwoWire::allocateTxn() { + // Simple round-robin allocation from pool + SercomTxn* txn = &txnPool[txnPoolHead]; + txnPoolHead = (txnPoolHead + 1) % TXN_POOL_SIZE; + *txn = SercomTxn{}; // Clear the transaction + return txn; } -void TwoWire::beginTransmission(uint8_t address) { - // save address of target and clear buffer - txAddress = address; - txLength = 0; - txIndex = 0; - txExternalPtr = nullptr; - txExternalLength = 0; - txExternalActive = false; +void TwoWire::freeTxn(SercomTxn* txn) { + // Transactions are freed when removed from SERCOM queue + // Pool allocation is round-robin, so no explicit free needed + (void)txn; +} +void TwoWire::beginTransmission(uint8_t address) { + // Initialize loader as staging area for building transaction + loader = SercomTxn{}; + loader.txPtr = nullptr; + loader.address = address; + txBufferCapacity = WIRE_BUFFER_LENGTH; transmissionBegun = true; } @@ -163,27 +178,30 @@ uint8_t TwoWire::endTransmission(bool stopBit, void (*onComplete)(void* user, in { transmissionBegun = false ; - txn.config = stopBit ? I2C_CFG_STOP : 0; - txn.address = txAddress; - txn.length = txExternalActive ? txExternalLength : txLength; - txn.txPtr = txExternalActive ? txExternalPtr : txBuffer; - txn.rxPtr = nullptr; - txn.onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; - txn.user = onComplete ? user : this; - txnDone = false; - txnStatus = static_cast(SercomWireError::SUCCESS); - masterIndex = 0; + // Allocate a fresh transaction from the pool and copy staged data from loader + SercomTxn* txn = allocateTxn(); + *txn = loader; // Copy staged transaction data + + // Set parameters that weren't known during beginTransmission/write + txn->config = stopBit ? I2C_CFG_STOP : 0; + txn->onComplete = onComplete ? onComplete : &TwoWire::onTxnComplete; + txn->user = (user == nullptr) ? this : user; + awaitingAddressAck = true; + txnDone = false; - if (!sercom->enqueueWIRE(&txn)) + // Enqueue the pool transaction, not the loader + if (!sercom->enqueueWIRE(txn)) return static_cast(SercomWireError::QUEUE_FULL); - txExternalPtr = nullptr; - txExternalLength = 0; - txExternalActive = false; - if (!onComplete) { + // Wait for transaction to complete (onTxnComplete sets txnDone) with timeout + uint32_t startMillis = millis(); + const uint32_t timeout = 1000; // 1 second timeout while (!txnDone) { + if (millis() - startMillis > timeout) + return 4; // OTHER error + yield(); } @@ -197,7 +215,7 @@ uint8_t TwoWire::endTransmission(bool stopBit, void (*onComplete)(void* user, in return 2; case SercomWireError::NACK_ON_DATA: return 3; - default: + default: // OTHER return 4; } } @@ -205,49 +223,81 @@ uint8_t TwoWire::endTransmission(bool stopBit, void (*onComplete)(void* user, in return 0; } -uint8_t TwoWire::endTransmission() -{ - return endTransmission(true); -} - size_t TwoWire::write(uint8_t ucData) { - // No writing, without begun transmission or a full buffer - if ( !transmissionBegun || txExternalActive || txLength >= WIRE_TX_BUFFER_LENGTH ) - { - return 0 ; - } + if (!transmissionBegun) + return 0; + + // Check buffer full + if (loader.length >= txBufferCapacity) + return 0; + + // Initialize to internal buffer if first write + if (loader.txPtr == nullptr) + loader.txPtr = txBuffer; - txBuffer[txLength++] = ucData; + // Append to current buffer (internal or external) + if (loader.txPtr == txBuffer) + txBuffer[loader.length++] = ucData; + else + const_cast(loader.txPtr)[loader.length++] = ucData; - return 1 ; + return 1; } -size_t TwoWire::write(const uint8_t *data, size_t quantity) +size_t TwoWire::write(const uint8_t *data, size_t quantity, bool setExternal) { if (!transmissionBegun) return 0; - if (txExternalActive) + if (quantity == 0) return 0; - if (txLength == 0 && quantity > 0) { - txExternalPtr = data; - txExternalLength = quantity; - txExternalActive = true; - txLength = quantity; - return quantity; + // External path: require external buffer (zero-copy) + if (setExternal) { + if (loader.txPtr == nullptr) { + loader.txPtr = data; + txBufferCapacity = quantity; // Treat quantity as both length and capacity + loader.length = quantity; + return quantity; + } + + // Prevent switching from internal buffer to external mid-transaction + if (loader.txPtr == txBuffer) + return 0; } - //Try to store all data - for(size_t i = 0; i < quantity; ++i) - { - //Return the number of data stored, when the buffer is full (if write return 0) - if(!write(data[i])) - return i; + // Sync path: prefer internal buffer unless it overflows + if (loader.txPtr == nullptr) { + if (quantity <= WIRE_BUFFER_LENGTH) { + loader.txPtr = txBuffer; + txBufferCapacity = WIRE_BUFFER_LENGTH; + memcpy(txBuffer, data, quantity); + loader.length = quantity; + return quantity; + } + + // Large write: require external buffer + loader.txPtr = data; + txBufferCapacity = quantity; + loader.length = quantity; + return quantity; } - //All data stored + // Appending to existing buffer + size_t available = txBufferCapacity - loader.length; + if (quantity > available) + quantity = available; + + if (quantity == 0) + return 0; + + if (loader.txPtr == txBuffer) + memcpy(txBuffer + loader.length, data, quantity); + else + memcpy(const_cast(loader.txPtr) + loader.length, data, quantity); + + loader.length += quantity; return quantity; } @@ -297,6 +347,20 @@ void TwoWire::setRxBuffer(uint8_t* buffer, size_t length) rxBufferCapacity = length; } +void TwoWire::setTxBuffer(uint8_t* buffer, size_t length) +{ + if (buffer == nullptr || length == 0) { + loader.txPtr = nullptr; + txBufferCapacity = WIRE_BUFFER_LENGTH; + loader.length = 0; + return; + } + + loader.txPtr = buffer; + txBufferCapacity = length; + loader.length = 0; +} + void TwoWire::clearRxBuffer(void) { if (rxBufferPtr && rxBufferCapacity > 0) @@ -308,7 +372,7 @@ void TwoWire::clearRxBuffer(void) void TwoWire::resetRxBuffer(void) { rxBufferPtr = rxBuffer; - rxBufferCapacity = WIRE_RX_BUFFER_LENGTH; + rxBufferCapacity = WIRE_BUFFER_LENGTH; clearRxBuffer(); } diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index f313c5e99..02636fafa 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -52,19 +52,21 @@ class TwoWire : public Stream uint8_t endTransmission(bool stopBit = true, void (*onComplete)(void* user, int status) = nullptr, void* user = nullptr); - uint8_t endTransmission(void); // If onComplete is nullptr, this blocks for legacy sync behavior. // If onComplete is non-null, this enqueues and returns immediately (async). // If rxBuffer is nullptr, the internal buffer is used; otherwise rxBuffer is used. - uint8_t requestFrom(uint8_t address, size_t quantity, uint8_t* rxBuffer = nullptr, - bool stopBit = true, + uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit = true, + uint8_t* rxBuffer = nullptr, void (*onComplete)(void* user, int status) = nullptr, void* user = nullptr); - uint8_t requestFrom(uint8_t address, size_t quantity); size_t write(uint8_t data); - size_t write(const uint8_t * data, size_t quantity); + // 3-arg write: when setExternal=true, data is used directly (zero-copy) and + // quantity is treated as both length and capacity; subsequent write() calls return 0. + // For streaming > WIRE_BUFFER_LENGTH or async usage, call setTxBuffer() before write() + // on every transaction. + size_t write(const uint8_t * data, size_t quantity, bool setExternal = false); virtual int available(void); virtual int read(void); @@ -73,6 +75,7 @@ class TwoWire : public Stream void onReceive(void(*)(int)); void onRequest(void(*)(void)); void setRxBuffer(uint8_t* buffer, size_t length); + void setTxBuffer(uint8_t* buffer, size_t length); void clearRxBuffer(void); void resetRxBuffer(void); uint8_t* getRxBuffer(void); @@ -94,27 +97,30 @@ class TwoWire : public Stream bool transmissionBegun; // RX/TX buffers (sync compatibility, async staging) - static constexpr size_t WIRE_TX_BUFFER_LENGTH = 255; - static constexpr size_t WIRE_RX_BUFFER_LENGTH = SERIAL_BUFFER_SIZE; - uint8_t rxBuffer[WIRE_RX_BUFFER_LENGTH]; - uint8_t txBuffer[WIRE_TX_BUFFER_LENGTH]; + static constexpr size_t WIRE_BUFFER_LENGTH = 255; + uint8_t rxBuffer[WIRE_BUFFER_LENGTH]; + uint8_t txBuffer[WIRE_BUFFER_LENGTH]; uint8_t* rxBufferPtr; size_t rxBufferCapacity; size_t rxLength; size_t rxIndex; - size_t txLength; - size_t txIndex; - const uint8_t* txExternalPtr; - size_t txExternalLength; - bool txExternalActive; + size_t txBufferCapacity; size_t masterIndex; bool awaitingAddressAck; - uint8_t txAddress; volatile bool txnDone; volatile int txnStatus; bool pendingReceive; int pendingReceiveLength; - SercomTxn txn; + SercomTxn slaveTxn; + SercomTxn loader; // Staging area for building transactions + + // Transaction pool for async operations (matches SERCOM queue depth) + static constexpr size_t TXN_POOL_SIZE = 8; + SercomTxn txnPool[TXN_POOL_SIZE]; + uint8_t txnPoolHead; + + SercomTxn* allocateTxn(); + void freeTxn(SercomTxn* txn); // Callback user functions void (*onRequestCallback)(void); @@ -254,29 +260,29 @@ inline void TwoWire::onService(void) { // onRequestCallback runs in ISR context here. Deferring to PendSV // would require stalling DRDY or returning 0xFF until the buffer is filled. - // onRequestCallback is what will set TwoWire::txn for the transaction. + // onRequestCallback is what will set TwoWire::slaveTxn for the transaction. if(onRequestCallback) onRequestCallback(); - // Ensure callback actually set txn.length; if not, stall with 0-length txn - if (txn.length == 0) + // Ensure callback actually set slaveTxn.length; if not, stall with 0-length txn + if (slaveTxn.length == 0) return; - if (!(txn.config & I2C_CFG_READ)) - txn.config |= I2C_CFG_READ; + if (!(slaveTxn.config & I2C_CFG_READ)) + slaveTxn.config |= I2C_CFG_READ; } else // Master Write / Slave RX { // rxLength needs to be set in the rxCallback so the user has runtime control // setting rxLength to 0 will default to the internal buffer rxIndex = 0; - txn.txPtr = nullptr; - txn.rxPtr = rxLength ? rxBufferPtr : rxBuffer; - txn.length = rxLength ? rxLength : WIRE_RX_BUFFER_LENGTH; - txn.config = 0; + slaveTxn.txPtr = nullptr; + slaveTxn.rxPtr = rxLength ? rxBufferPtr : rxBuffer; + slaveTxn.length = rxLength ? rxLength : WIRE_BUFFER_LENGTH; + slaveTxn.config = 0; } - sercom->setTxnWIRE(&txn); + sercom->setTxnWIRE(&slaveTxn); // SCLSM=0 (Smart Mode disabled): AMATCH and DRDY never fire together // → return now, DRDY will fire in next interrupt From d5d0430c482e181ec7ad7e9ca8e281903236504a Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 20:10:07 -0600 Subject: [PATCH 18/24] test SPI sync and async operations --- libraries/SPI/SPI.cpp | 23 +++++++++++++++++------ libraries/SPI/SPI.h | 8 +++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 4c748a969..be50ba042 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -47,6 +47,9 @@ SPIClass::SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint // SERCOM pads _padTx=PadTx; _padRx=PadRx; + + // Transaction pool initialization + txnPoolHead = 0; } void SPIClass::begin() @@ -249,15 +252,16 @@ void SPIClass::transfer(const void *txbuf, void *rxbuf, size_t count, // OK to assume now that txbuf and/or rxbuf are non-NULL, an if/else is // often sufficient, don't need else-ifs for everything buffer related. - _txn.txPtr = static_cast(txbuf); - _txn.rxPtr = static_cast(rxbuf); - _txn.length = count; - _txn.onComplete = onComplete ? onComplete : &SPIClass::onTxnComplete; - _txn.user = onComplete ? user : this; + SercomTxn* txn = allocateTxn(); + txn->txPtr = static_cast(txbuf); + txn->rxPtr = static_cast(rxbuf); + txn->length = count; + txn->onComplete = onComplete ? onComplete : &SPIClass::onTxnComplete; + txn->user = onComplete ? user : this; txnDone = false; txnStatus = 0; - if (!_p_sercom->enqueueSPI(&_txn)) { + if (!_p_sercom->enqueueSPI(txn)) { if (onComplete) onComplete(user, static_cast(SercomSpiError::UNKNOWN_ERROR)); return; @@ -305,6 +309,13 @@ void SPIClass::onService(void) } } +SercomTxn* SPIClass::allocateTxn() { + // Simple round-robin allocation from pool + SercomTxn* txn = &txnPool[txnPoolHead]; + txnPoolHead = (txnPoolHead + 1) % TXN_POOL_SIZE; + return txn; +} + void SPIClass::onTxnComplete(void* user, int status) { if (!user) diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index d0afa90b9..1991fb9d2 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -160,7 +160,13 @@ class SPIClass { volatile bool txnDone = false; volatile int txnStatus = 0; - SercomTxn _txn; + + // Transaction pool for async operations (matches SERCOM queue depth) + static constexpr size_t TXN_POOL_SIZE = 8; + SercomTxn txnPool[TXN_POOL_SIZE]; + uint8_t txnPoolHead; + + SercomTxn* allocateTxn(); static void onTxnComplete(void* user, int status); }; From 6bb67bcbc7e09351762a4eb725af9f42ec667943 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 20:57:41 -0600 Subject: [PATCH 19/24] high level testing no hardware testing done on UART --- cores/arduino/Uart.cpp | 107 +++++++++++++++++++++++------------------ cores/arduino/Uart.h | 15 ++++-- 2 files changed, 73 insertions(+), 49 deletions(-) diff --git a/cores/arduino/Uart.cpp b/cores/arduino/Uart.cpp index 6d0262ace..8e8e03796 100644 --- a/cores/arduino/Uart.cpp +++ b/cores/arduino/Uart.cpp @@ -38,6 +38,7 @@ Uart::Uart(SERCOM *_s, uint8_t _pinRX, uint8_t _pinTX, SercomRXPad _padRX, Serco uc_padTX = _padTX; uc_pinRTS = _pinRTS; uc_pinCTS = _pinCTS; + txnPoolHead = 0; } void Uart::begin(unsigned long baudrate) @@ -190,56 +191,58 @@ size_t Uart::write(const uint8_t data) return 1; } -size_t Uart::write(const uint8_t* buffer, size_t size) +size_t Uart::write(const uint8_t* buffer, size_t size, + void (*onComplete)(void* user, int status), + void* user) { if (buffer == nullptr || size == 0) return 0; + if (onComplete == nullptr) { + // Synchronous path: block until complete #ifdef USE_ZERODMA - _txn.txPtr = buffer; - _txn.rxPtr = nullptr; - _txn.length = size; - _txn.onComplete = &Uart::onTxnComplete; - _txn.user = this; - txnDone = false; - txnStatus = 0; - - if (sercom->enqueueUART(&_txn)) { - while (!txnDone) ; - return size; - } + SercomTxn* txn = allocateTxn(); + txn->txPtr = buffer; + txn->rxPtr = nullptr; + txn->length = size; + txn->onComplete = &Uart::onTxnComplete; + txn->user = this; + txnDone = false; + txnStatus = 0; + + if (sercom->enqueueUART(txn)) { + while (!txnDone) ; + return size; + } #endif - - for (size_t i = 0; i < size; ++i) - write(buffer[i]); - return size; -} - -size_t Uart::writeAsync(const uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user) -{ - if (buffer == nullptr || size == 0) - return 0; - + // Fallback: byte-by-byte + for (size_t i = 0; i < size; ++i) + write(buffer[i]); + return size; + } else { + // Asynchronous path: enqueue and return immediately #ifdef USE_ZERODMA - _txn.txPtr = buffer; - _txn.rxPtr = nullptr; - _txn.length = size; - _txn.onComplete = onComplete ? onComplete : &Uart::onTxnComplete; - _txn.user = onComplete ? user : this; - txnDone = false; - txnStatus = 0; + SercomTxn* txn = allocateTxn(); + txn->txPtr = buffer; + txn->rxPtr = nullptr; + txn->length = size; + txn->onComplete = onComplete; + txn->user = user; + txnDone = false; + txnStatus = 0; + + if (!sercom->enqueueUART(txn)) + return 0; - if (!sercom->enqueueUART(&_txn)) - return 0; - - return size; + return size; #else - (void)onComplete; - (void)user; - for (size_t i = 0; i < size; ++i) - write(buffer[i]); - return size; + (void)onComplete; + (void)user; + for (size_t i = 0; i < size; ++i) + write(buffer[i]); + return size; #endif + } } size_t Uart::read(uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user) @@ -248,6 +251,7 @@ size_t Uart::read(uint8_t* buffer, size_t size, void (*onComplete)(void* user, i return 0; if (onComplete == nullptr) { + // Synchronous path: read from ring buffer size_t readCount = 0; while (readCount < size) { int c = read(); @@ -258,21 +262,25 @@ size_t Uart::read(uint8_t* buffer, size_t size, void (*onComplete)(void* user, i } #ifdef USE_ZERODMA + // Asynchronous path: use DMA pendingRxCb = onComplete; pendingRxUser = user; rxExternalActive = true; + // Disable RXC interrupt; DMA takes over sercom->getSercom()->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_RXC; - _txn.txPtr = nullptr; - _txn.rxPtr = buffer; - _txn.length = size; - _txn.onComplete = &Uart::onTxnComplete; - _txn.user = this; + SercomTxn* txn = allocateTxn(); + txn->txPtr = nullptr; + txn->rxPtr = buffer; + txn->length = size; + txn->onComplete = &Uart::onTxnComplete; + txn->user = this; txnDone = false; txnStatus = 0; - if (!sercom->enqueueUART(&_txn)) { + if (!sercom->enqueueUART(txn)) { + // Enqueue failed; restore RXC interrupt and clear pending state sercom->getSercom()->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; rxExternalActive = false; pendingRxCb = nullptr; @@ -288,6 +296,13 @@ size_t Uart::read(uint8_t* buffer, size_t size, void (*onComplete)(void* user, i #endif } +SercomTxn* Uart::allocateTxn() { + // Simple round-robin allocation from pool + SercomTxn* txn = &txnPool[txnPoolHead]; + txnPoolHead = (txnPoolHead + 1) % TXN_POOL_SIZE; + return txn; +} + void Uart::onTxnComplete(void* user, int status) { if (!user) diff --git a/cores/arduino/Uart.h b/cores/arduino/Uart.h index 9687dbbbb..6f59fddf9 100644 --- a/cores/arduino/Uart.h +++ b/cores/arduino/Uart.h @@ -41,8 +41,11 @@ class Uart : public HardwareSerial void* user = nullptr); void flush(); size_t write(const uint8_t data); - size_t write(const uint8_t* buffer, size_t size); - size_t writeAsync(const uint8_t* buffer, size_t size, void (*onComplete)(void* user, int status), void* user); + // If onComplete is nullptr, blocks (sync); otherwise enqueues and returns (async) + // Transparently uses DMA when available, falls back to byte-by-byte otherwise + size_t write(const uint8_t* buffer, size_t size, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); using Print::write; // pull in write(str) and write(buf, size) from Print void IrqHandler(); @@ -66,7 +69,13 @@ class Uart : public HardwareSerial volatile bool txnDone = false; volatile int txnStatus = 0; - SercomTxn _txn; + + // Transaction pool for async operations (matches SERCOM queue depth) + static constexpr size_t TXN_POOL_SIZE = 8; + SercomTxn txnPool[TXN_POOL_SIZE]; + uint8_t txnPoolHead = 0; + + SercomTxn* allocateTxn(); static void onTxnComplete(void* user, int status); bool rxExternalActive = false; void (*pendingRxCb)(void* user, int status) = nullptr; From 9370d6a958d981f1f58df521ae613bab6f3cb2f9 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 22:10:27 -0600 Subject: [PATCH 20/24] refactor to use async sercom api's --- variants/mkrgsm1400/variant.cpp | 33 +++++++++++++++++++--------- variants/mkrnb1500/variant.cpp | 37 +++++++++++++++++++++----------- variants/mkrwifi1010/variant.cpp | 35 ++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/variants/mkrgsm1400/variant.cpp b/variants/mkrgsm1400/variant.cpp index c534367c2..d137b4aab 100644 --- a/variants/mkrgsm1400/variant.cpp +++ b/variants/mkrgsm1400/variant.cpp @@ -174,6 +174,7 @@ SERCOM sercom5(SERCOM5); #if defined(USE_BQ24195L_PMIC) #include "wiring_private.h" +#include "SERCOM_Txn.h" #define PMIC_ADDRESS 0x6B #define PMIC_REG01 0x01 @@ -185,12 +186,17 @@ static inline void enable_battery_charging() { pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG01); - PERIPH_WIRE.sendDataMasterWIRE(0x1B); // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + static SercomTxn txn; + static uint8_t txData[2] = {PMIC_REG01, 0x1B}; // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.disableWIRE(); + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } static inline void disable_battery_fet(bool disabled) { @@ -199,14 +205,21 @@ static inline void disable_battery_fet(bool disabled) { pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG07); + static SercomTxn txn; + static uint8_t txData[2]; + txData[0] = PMIC_REG07; // No D+/D– detection + Safety timer not slowed by 2X during input DPM or thermal regulation + // BAT fet disabled/enabled + charge and bat fault INT - PERIPH_WIRE.sendDataMasterWIRE(0x0B | (disabled ? 0x20 : 0x00)); - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + txData[1] = 0x0B | (disabled ? 0x20 : 0x00); - PERIPH_WIRE.disableWIRE(); + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } #endif diff --git a/variants/mkrnb1500/variant.cpp b/variants/mkrnb1500/variant.cpp index 3cc0a5921..0a45fe372 100644 --- a/variants/mkrnb1500/variant.cpp +++ b/variants/mkrnb1500/variant.cpp @@ -174,23 +174,29 @@ SERCOM sercom5(SERCOM5); #if defined(USE_BQ24195L_PMIC) #include "wiring_private.h" +#include "SERCOM_Txn.h" #define PMIC_ADDRESS 0x6B #define PMIC_REG01 0x01 #define PMIC_REG07 0x07 -static inline void enable_battery_charging() { +static inline void enable_battery_charging() { PERIPH_WIRE.initMasterWIRE(100000); PERIPH_WIRE.enableWIRE(); pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG01); - PERIPH_WIRE.sendDataMasterWIRE(0x1B); // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + static SercomTxn txn; + static uint8_t txData[2] = {PMIC_REG01, 0x1B}; // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.disableWIRE(); + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } static inline void disable_battery_fet(bool disabled) { @@ -199,14 +205,21 @@ static inline void disable_battery_fet(bool disabled) { pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG07); + static SercomTxn txn; + static uint8_t txData[2]; + txData[0] = PMIC_REG07; // No D+/D– detection + Safety timer not slowed by 2X during input DPM or thermal regulation + // BAT fet disabled/enabled + charge and bat fault INT - PERIPH_WIRE.sendDataMasterWIRE(0x0B | (disabled ? 0x20 : 0x00)); - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - - PERIPH_WIRE.disableWIRE(); + txData[1] = 0x0B | (disabled ? 0x20 : 0x00); + + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } #endif diff --git a/variants/mkrwifi1010/variant.cpp b/variants/mkrwifi1010/variant.cpp index f1938506b..7634df70a 100644 --- a/variants/mkrwifi1010/variant.cpp +++ b/variants/mkrwifi1010/variant.cpp @@ -174,6 +174,7 @@ SERCOM sercom5(SERCOM5); #if defined(USE_BQ24195L_PMIC) #include "wiring_private.h" +#include "SERCOM_Txn.h" #define PMIC_ADDRESS 0x6B #define PMIC_REG01 0x01 @@ -185,12 +186,17 @@ static inline void enable_battery_charging() { pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG01); - PERIPH_WIRE.sendDataMasterWIRE(0x1B); // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + static SercomTxn txn; + static uint8_t txData[2] = {PMIC_REG01, 0x1B}; // Charge Battery + Minimum System Voltage 3.5V - PERIPH_WIRE.disableWIRE(); + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } static inline void disable_battery_fet(bool disabled) { @@ -199,14 +205,21 @@ static inline void disable_battery_fet(bool disabled) { pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); - PERIPH_WIRE.startTransmissionWIRE( PMIC_ADDRESS, WIRE_WRITE_FLAG ); - PERIPH_WIRE.sendDataMasterWIRE(PMIC_REG07); + static SercomTxn txn; + static uint8_t txData[2]; + txData[0] = PMIC_REG07; // No D+/D– detection + Safety timer not slowed by 2X during input DPM or thermal regulation + // BAT fet disabled/enabled + charge and bat fault INT - PERIPH_WIRE.sendDataMasterWIRE(0x0B | (disabled ? 0x20 : 0x00)); - PERIPH_WIRE.prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - - PERIPH_WIRE.disableWIRE(); + txData[1] = 0x0B | (disabled ? 0x20 : 0x00); + + txn = SercomTxn{}; + txn.config = I2C_CFG_STOP; + txn.address = PMIC_ADDRESS; + txn.length = 2; + txn.txPtr = txData; + + PERIPH_WIRE.enqueueWIRE(&txn); + PERIPH_WIRE.startTransmissionWIRE(); } #endif From c4bc1bdad206d87a7db14098895cb300e74cb673 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 22:25:25 -0600 Subject: [PATCH 21/24] samd datasheet multiplexing tables as csv --- .../wirepinmux/SAMD21_Device_I2C_Config.csv | 161 ++++++++++++++++++ extras/wirepinmux/SAMD21_Table_2-1.csv | 31 ++++ extras/wirepinmux/SAMD21_Table_2-2.csv | 11 ++ extras/wirepinmux/SAMD21_Table_7-1.csv | 53 ++++++ extras/wirepinmux/SAMD21_Table_7-2.csv | 39 +++++ extras/wirepinmux/SAMD21_Table_7-5.csv | 4 + .../wirepinmux/SAMD51_Device_I2C_Config.csv | 26 +++ extras/wirepinmux/SAMD51_Table_1-1.csv | 14 ++ extras/wirepinmux/SAMD51_Table_1-2.csv | 21 +++ extras/wirepinmux/SAMD51_Table_6-1.csv | 101 +++++++++++ extras/wirepinmux/SAMD51_Table_6-8.csv | 10 ++ 11 files changed, 471 insertions(+) create mode 100644 extras/wirepinmux/SAMD21_Device_I2C_Config.csv create mode 100644 extras/wirepinmux/SAMD21_Table_2-1.csv create mode 100644 extras/wirepinmux/SAMD21_Table_2-2.csv create mode 100644 extras/wirepinmux/SAMD21_Table_7-1.csv create mode 100644 extras/wirepinmux/SAMD21_Table_7-2.csv create mode 100644 extras/wirepinmux/SAMD21_Table_7-5.csv create mode 100644 extras/wirepinmux/SAMD51_Device_I2C_Config.csv create mode 100644 extras/wirepinmux/SAMD51_Table_1-1.csv create mode 100644 extras/wirepinmux/SAMD51_Table_1-2.csv create mode 100644 extras/wirepinmux/SAMD51_Table_6-1.csv create mode 100644 extras/wirepinmux/SAMD51_Table_6-8.csv diff --git a/extras/wirepinmux/SAMD21_Device_I2C_Config.csv b/extras/wirepinmux/SAMD21_Device_I2C_Config.csv new file mode 100644 index 000000000..93463cb34 --- /dev/null +++ b/extras/wirepinmux/SAMD21_Device_I2C_Config.csv @@ -0,0 +1,161 @@ +Device,Pin Count,Variant,I2C Pins,SERCOM Configs +SAMD21E14,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E14A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E14B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E14C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E14D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E14L,32,L,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E15L,32,L,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E16L,32,L,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E17L,32,L,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21E18L,32,L,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G14L,48,L,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G15L,48,L,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G16L,48,L,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G17L,48,L,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21G18L,48,L,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMD21J14,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J14A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J14B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J14C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J14D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J15,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J15A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J15B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J15C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J15D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J16,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J16A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J16B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J16C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J16D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J17,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J17A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J17B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J17C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J17D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J18,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J18A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J18B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J18C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMD21J18D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1E14,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E14A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E14B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E14C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E14D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E15,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E15A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E15B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E15C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E15D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E16,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E16A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E16B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E16C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E16D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E17,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E17A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E17B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E17C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E17D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E18,32,base,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E18A,32,A,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E18B,32,B,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E18C,32,C,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1E18D,32,D,"PA08,PA09,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G14,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G14A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G14B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G14C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G14D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G15,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G15A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G15B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G15C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G15D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G16,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G16A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G16B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G16C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G16D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G17,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G17A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G17B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G17C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G17D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G18,48,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G18A,48,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G18B,48,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G18C,48,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1G18D,48,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]" +SAMDA1J14,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J14A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J14B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J14C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J14D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J15,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J15A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J15B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J15C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J15D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J16,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J16A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J16B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J16C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J16D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J17,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J17A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J17B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J17C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J17D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J18,64,base,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J18A,64,A,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J18B,64,B,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J18C,64,C,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" +SAMDA1J18D,64,D,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PB12,PB13,PB16,PB17,PB30,PB31","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[0]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[1]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[0]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[1]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[0]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[1]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[0]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[1]]|PB12:[SERCOM4/PAD[0]]|PB13:[SERCOM4/PAD[1]]|PB16:[SERCOM5/PAD[0]]|PB17:[SERCOM5/PAD[1]]|PB30:[SERCOM5/PAD[0]]|PB31:[SERCOM5/PAD[1]]" diff --git a/extras/wirepinmux/SAMD21_Table_2-1.csv b/extras/wirepinmux/SAMD21_Table_2-1.csv new file mode 100644 index 000000000..6b5f41f29 --- /dev/null +++ b/extras/wirepinmux/SAMD21_Table_2-1.csv @@ -0,0 +1,31 @@ +Table 2-1. SAM D21 E/G/J and SAM D21 EL/GL Product Family Features,,,,,,,,,,,,,,,,,,,,,, +,,,,,Oscillators,,Peripherals,,,,,,,,,,,,Analog,,, +Device,Program Memory (KB),Data Memory (KB),Pins,Packages,Internal,External,USB,SERCOM,TC-Waveform /PWM Output/Capture Input Channels per TCx Instance,TCC,Waveform /PWM Output Channels per TCCx Instance,I2S,DMA Channels,RTC,WDT,Event System (Channels),External Interrupt Lines,I/O Pins,ADC Channels,Analog Comparator,DAC,"PTC (Mutual/Self-capacitance Channels)""" +ATSAMD21E15A,32,4,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E16A,64,8,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E17A,128,16,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E18A,256,32,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E15B,32,4,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E16B,64,8,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E15C,32,4,35,WLCSP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21E16C,64,8,35,WLCSP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,4,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,26,10,2,Y,"60/6" +ATSAMD21G15A,32,4,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21G16A,64,8,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21G17A,128,16,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21G18A,256,32,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21G15B,32,4,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21G16B,64,8,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",3,"8/4/2",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21J15A,32,4,64,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21J16A,64,8,64,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21J17A,128,16,64,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21J18A,256,32,64,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21J15B,32,4,64,TQFP/QFN/UFBGA,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21J16B,64,8,64,TQFP/QFN/UFBGA,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",3,"8/4/2",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21E15L,32,4,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC,N,4,"3-2",3,"6/4/2",N,12,Y,Y,12,16,26,14,4,Y,N +ATSAMD21E16L,64,8,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC,N,4,"3-2",3,"6/4/2",N,12,Y,Y,12,16,26,14,4,Y,N +ATSAMD21G16L,64,8,48,QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC,Y,6,"5-2",3,"8/4/2",N,12,Y,Y,12,16,38,18,4,Y,N +ATSAMD21E17D,128,16,32,TQFP/QFN/WLCSP,OSC32K/OSCULP32K/OSC8M/DFLL48M,XOSC32K/XOSC,Y,4,"3-2",4,"6/4/2/6",Y,12,Y,Y,12,16,26,10,2,Y,"30/6" +ATSAMD21G17D,128,16,48,QFN/TQFP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"3-2",4,"8/4/2/8",Y,12,Y,Y,12,16,38,14,2,Y,"120/10" +ATSAMD21J17D,128,16,64,QFN/TQFP/UFBGA,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,Y,6,"5-2",4,"8/4/2/8",Y,12,Y,Y,12,16,52,20,2,Y,"256/16" +ATSAMD21E17L,128,16,32,QFN/TQFP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC,N,4,"3-2",4,"6/4/2/6",N,12,Y,Y,12,16,26,14,4,Y,N +ATSAMD21G17L,128,16,48,QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC,N,6,"5-2",4,"8/4/2/8",N,12,Y,Y,12,16,38,18,4,Y,N \ No newline at end of file diff --git a/extras/wirepinmux/SAMD21_Table_2-2.csv b/extras/wirepinmux/SAMD21_Table_2-2.csv new file mode 100644 index 000000000..999c3f468 --- /dev/null +++ b/extras/wirepinmux/SAMD21_Table_2-2.csv @@ -0,0 +1,11 @@ +Table 2-2. SAM DA1,E/G/J Product Family Features,,,,,,,,,,,,,,,,,,,,, +Device,Program Memory (KB),Data Memory (KB),Pins,Packages,Internal,External,USB,SERCOM,TC-Waveform/PWM Output/Capture Input Channels per TCx Instance,TCC,Waveform/PWM Output Channels per TCCx Instance,I2S,DMA Channels,RTC,WDT,EventSystem (Channels),External Interrupt Lines,I/O Pins,ADC Channels,2,1,PTC (Mutual/Self-capacitance Channels) +ATSAMDA1E14B,16,4,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,4,"3-2",3,"6/4/2",1,12,Y,Y,12,16,26,10,2,1,"60/6" +ATSAMDA1E15B,32,4,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,4,"3-2",3,"6/4/2",1,12,Y,Y,12,16,26,10,2,1,"60/6" +ATSAMDA1E16B,64,8,32,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,4,"3-2",3,"6/4/2",1,12,Y,Y,12,16,26,10,2,1,"60/6" +ATSAMDA1G14B,16,4,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"3-2",3,"8/4/2",1,12,Y,Y,12,16,38,14,2,1,"120/10" +ATSAMDA1G15B,32,4,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"3-2",3,"8/4/2",1,12,Y,Y,12,16,38,14,2,1,"120/10" +ATSAMDA1G16B,64,8,48,TQFP/QFN,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"3-2",3,"8/4/2",1,12,Y,Y,12,16,38,14,2,1,"120/10" +ATSAMDA1J14B,16,4,64,TQFP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"5-2",3,"8/4/2",1,12,Y,Y,12,16,52,20,2,1,"256/16" +ATSAMDA1J15B,32,4,64,TQFP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"5-2",3,"8/4/2",1,12,Y,Y,12,16,52,20,2,1,"256/16" +ATSAMDA1J16B,64,8,64,TQFP,OSC32K/OSCULP32K/OSC8M/DFLL48M/FDPLL96M,XOSC32K/XOSC,1,6,"5-2",3,"8/4/2",1,12,Y,Y,12,16,52,20,2,1,"256/16" \ No newline at end of file diff --git a/extras/wirepinmux/SAMD21_Table_7-1.csv b/extras/wirepinmux/SAMD21_Table_7-1.csv new file mode 100644 index 000000000..6c4c1b681 --- /dev/null +++ b/extras/wirepinmux/SAMD21_Table_7-1.csv @@ -0,0 +1,53 @@ +SAMD2xE,SAMD2xG,SAMD2xJ,I/O Pin,Supply,A,B1,B2,B3,B4,B5,C,D,E,F,G,H +1,1,1,PA00,VDDANA,EXTINT[0],,,,,,,SERCOM1/PAD[0],TCC2/WO[0],,, +2,2,2,PA01,VDDANA,EXTINT[1],,,,,,,SERCOM1/PAD[1],TCC2/WO[1],,, +3,3,3,PA02,VDDANA,EXTINT[2],,AIN[0],,Y[0],VOUT,,,,TCC3/WO[0],, +4,4,4,PA03,VDDANA,EXTINT[3],ADC/VREFA DAC/VREFA,AIN[1],,Y[1],,,,,TCC3/WO[1],, +,,5,PB04,VDDANA,EXTINT[4],,AIN[12],,Y[10],,,,,,, +,,6,PB05,VDDANA,EXTINT[5],,AIN[13],,Y[11],,,,,,, +,,9,PB06,VDDANA,EXTINT[6],,AIN[14],,Y[12],,,,,,, +,,10,PB07,VDDANA,EXTINT[7],,AIN[15],,Y[13],,,,,,, +,7,11,PB08,VDDANA,EXTINT[8],,AIN[2],,Y[14],,,SERCOM4/PAD[0],TC4/WO[0],TCC3/WO[6],, +,8,12,PB09,VDDANA,EXTINT[9],,AIN[3],,Y[15],,,SERCOM4/PAD[1],TC4/WO[1],TCC3/WO[7],, +5,9,13,PA04,VDDANA,EXTINT[4],ADC/VREFB,AIN[4],AIN[0],Y[2],,,SERCOM0/PAD[0],TCC0/WO[0],TCC3/WO[2],, +6,10,14,PA05,VDDANA,EXTINT[5],,AIN[5],AIN[1],Y[3],,,SERCOM0/PAD[1],TCC0/WO[1],TCC3/WO[3],, +7,11,15,PA06,VDDANA,EXTINT[6],,AIN[6],AIN[2],Y[4],,,SERCOM0/PAD[2],TCC1/WO[0],TCC3/WO[4],, +8,12,16,PA07,VDDANA,EXTINT[7],,AIN[7],AIN[3],Y[5],,,SERCOM0/PAD[3],TCC1/WO[1],TCC3/WO[5],I2S/SD[0], +11,13,17,PA08,VDDIO,NMI,,AIN[16],,X[0],,SERCOM0/PAD[0],SERCOM2/PAD[0],TCC0/WO[0],TCC1/WO[2],I2S/SD[1], +12,14,18,PA09,VDDIO,EXTINT[9],,AIN[17],,X[1],,SERCOM0/PAD[1],SERCOM2/PAD[1],TCC0/WO[1],TCC1/WO[3],I2S/MCK[0], +13,15,19,PA10,VDDIO,EXTINT[10],,AIN[18],,X[2],,SERCOM0/PAD[2],SERCOM2/PAD[2],TCC1/WO[0],TCC0/WO[2],I2S/SCK[0],GCLK_IO[4] +14,16,20,PA11,VDDIO,EXTINT[11],,AIN[19],,X[3],,SERCOM0/PAD[3],SERCOM2/PAD[3],TCC1/WO[1],TCC0/WO[3],I2S/FS[0],GCLK_IO[5] +,19,23,PB10,VDDIO,EXTINT[10],,,,,,,SERCOM4/PAD[2],TC5/WO[0],TCC0/WO[4],I2S/MCK[1],GCLK_IO[4] +,20,24,PB11,VDDIO,EXTINT[11],,,,,,,SERCOM4/PAD[3],TC5/WO[1],TCC0/WO[5],I2S/SCK[1],GCLK_IO[5] +,,25,PB12,VDDIO,EXTINT[12],,,,X[12],,SERCOM4/PAD[0],,TC4/WO[0],TCC0/WO[6],I2S/FS[1],GCLK_IO[6] +,,26,PB13,VDDIO,EXTINT[13],,,,X[13],,SERCOM4/PAD[1],,TC4/WO[1],TCC0/WO[7],,GCLK_IO[7] +,,27,PB14,VDDIO,EXTINT[14],,,,X[14],,SERCOM4/PAD[2],,TC5/WO[0],,,GCLK_IO[0] +,,28,PB15,VDDIO,EXTINT[15],,,,X[15],,SERCOM4/PAD[3],,TC5/WO[1],,,GCLK_IO[1] +,21,29,PA12,VDDIO,EXTINT[12],,,,,,SERCOM2/PAD[0],SERCOM4/PAD[0],TCC2/WO[0],TCC0/WO[6],,AC/CMP[0] +,22,30,PA13,VDDIO,EXTINT[13],,,,,,SERCOM2/PAD[1],SERCOM4/PAD[1],TCC2/WO[1],TCC0/WO[7],,AC/CMP[1] +15,23,31,PA14,VDDIO,EXTINT[14],,,,,,SERCOM2/PAD[2],SERCOM4/PAD[2],TC3/WO[0],TCC0/WO[4],,GCLK_IO[0] +16,24,32,PA15,VDDIO,EXTINT[15],,,,,,SERCOM2/PAD[3],SERCOM4/PAD[3],TC3/WO[1],TCC0/WO[5],,GCLK_IO[1] +17,25,35,PA16,VDDIO,EXTINT[0],,,,X[4],,SERCOM1/PAD[0],SERCOM3/PAD[0],TCC2/WO[0],TCC0/WO[6],,GCLK_IO[2] +18,26,36,PA17,VDDIO,EXTINT[1],,,,X[5],,SERCOM1/PAD[1],SERCOM3/PAD[1],TCC2/WO[1],TCC0/WO[7],,GCLK_IO[3] +19,27,37,PA18,VDDIO,EXTINT[2],,,,X[6],,SERCOM1/PAD[2],SERCOM3/PAD[2],TC3/WO[0],TCC0/WO[2],,AC/CMP[0] +20,28,38,PA19,VDDIO,EXTINT[3],,,,X[7],,SERCOM1/PAD[3],SERCOM3/PAD[3],TC3/WO[1],TCC0/WO[3],I2S/SD[0],AC/CMP[1] +,,39,PB16,VDDIO,EXTINT[0],,,,,,SERCOM5/PAD[0],,TC6/WO[0],TCC0/WO[4],I2S/SD[1],GCLK_IO[2] +,,40,PB17,VDDIO,EXTINT[1],,,,,,SERCOM5/PAD[1],,TC6/WO[1],TCC0/WO[5],I2S/MCK[0],GCLK_IO[3] +,29,41,PA20,VDDIO,EXTINT[4],,,,X[8],,SERCOM5/PAD[2],SERCOM3/PAD[2],TC7/WO[0],TCC0/WO[6],I2S/SCK[0],GCLK_IO[4] +,30,42,PA21,VDDIO,EXTINT[5],,,,X[9],,SERCOM5/PAD[3],SERCOM3/PAD[3],TC7/WO[1],TCC0/WO[7],I2S/FS[0],GCLK_IO[5] +21,31,43,PA22,VDDIO,EXTINT[6],,,,X[10],,SERCOM3/PAD[0],SERCOM5/PAD[0],TC4/WO[0],TCC0/WO[4],,GCLK_IO[6] +22,32,44,PA23,VDDIO,EXTINT[7],,,,X[11],,SERCOM3/PAD[1],SERCOM5/PAD[1],TC4/WO[1],TCC0/WO[5],USB/SOF 1kHz,GCLK_IO[7] +23,33,45,PA24,VDDIO,EXTINT[12],,,,,,SERCOM3/PAD[2],SERCOM5/PAD[2],TC5/WO[0],TCC1/WO[2],USB/DM, +24,34,46,PA25,VDDIO,EXTINT[13],,,,,,SERCOM3/PAD[3],SERCOM5/PAD[3],TC5/WO[1],TCC1/WO[3],USB/DP, +,37,49,PB22,VDDIO,EXTINT[6],,,,,,,SERCOM5/PAD[2],TC7/WO[0],TCC3/WO[0],,GCLK_IO[0] +,38,50,PB23,VDDIO,EXTINT[7],,,,,,,SERCOM5/PAD[3],TC7/WO[1],TCC3/WO[1],,GCLK_IO[1] +25,39,51,PA27,VDDIO,EXTINT[15],,,,,,,,,TCC3/WO[6],,GCLK_IO[0] +27,41,53,PA28,VDDIO,EXTINT[8],,,,,,,,,TCC3/WO[7],,GCLK_IO[0] +31,45,57,PA30,VDDIO,EXTINT[10],,,,,,,SERCOM1/PAD[2],TCC1/WO[0],TCC3/WO[4],SWCLK,GCLK_IO[0] +32,46,58,PA31,VDDIO,EXTINT[11],,,,,,,SERCOM1/PAD[3],TCC1/WO[1],TCC3/WO[5],SWDIO, +,,59,PB30,VDDIO,EXTINT[14],,,,,,,SERCOM5/PAD[0],TCC0/WO[0],TCC1/WO[2],, +,,60,PB31,VDDIO,EXTINT[15],,,,,,,SERCOM5/PAD[1],TCC0/WO[1],TCC1/WO[3],, +,,61,PB00,VDDANA,EXTINT[0],,AIN[8],,Y[6],,,SERCOM5/PAD[2],TC7/WO[0],,, +,,62,PB01,VDDANA,EXTINT[1],,AIN[9],,Y[7],,,SERCOM5/PAD[3],TC7/WO[1],,, +,47,63,PB02,VDDANA,EXTINT[2],,AIN[10],,Y[8],,,SERCOM5/PAD[0],TC6/WO[0],TCC3/WO[2],, +,48,64,PB03,VDDANA,EXTINT[3],,AIN[11],,Y[9],,,SERCOM5/PAD[1],TC6/WO[1],TCC3/WO[3],, \ No newline at end of file diff --git a/extras/wirepinmux/SAMD21_Table_7-2.csv b/extras/wirepinmux/SAMD21_Table_7-2.csv new file mode 100644 index 000000000..e6029598a --- /dev/null +++ b/extras/wirepinmux/SAMD21_Table_7-2.csv @@ -0,0 +1,39 @@ +SAMD21ExL,SAMD21GxL,I/O Pin,Supply,A,B1,B2,B3,B4,B5,C,D,E,F,G,H +1,1,PA02,VDDANA,EXTINT[2],,AIN[0],,,VOUT,,,,TCC3/WO[0],, +2,2,PA03,VDDANA,EXTINT[3],DAC/VREFA,AIN[1],,,,,,,TCC3/WO[1],, +3,3,PB04,VDDANA,EXTINT[4],,AIN[12],,AIN[0],,,,,,, +4,4,PB05,VDDANA,EXTINT[5],,AIN[13],,AIN[1],,,,,,, +,7,PB08,VDDANA,EXTINT[8],,AIN[2],,,,,SERCOM4/PAD[0],TC4/WO[0],TCC3/WO[6],, +,8,PB09,VDDANA,EXTINT[9],,AIN[3],,,,,SERCOM4/PAD[1],TC4/WO[1],TCC3/WO[7],, +5,9,PA04,VDDANA,EXTINT[4],ADC/VREFB,AIN[4],AIN[0],,,,SERCOM0/PAD[0],TCC0/WO[0],TCC3/WO[2],, +6,10,PA05,VDDANA,EXTINT[5],,AIN[5],AIN[1],,,,SERCOM0/PAD[1],TCC0/WO[1],TCC3/WO[3],, +7,11,PA06,VDDANA,EXTINT[6],,AIN[6],AIN[2],,,,SERCOM0/PAD[2],TCC1/WO[0],TCC3/WO[4],, +8,12,PA07,VDDANA,EXTINT[7],,AIN[7],AIN[3],,,,SERCOM0/PAD[3],TCC1/WO[1],TCC3/WO[5],, +11,13,PA08,VDDIO,NMI,,AIN[16],,,,SERCOM0/PAD[0],SERCOM2/PAD[0],TCC0/WO[0],TCC1/WO[2],, +12,14,PA09,VDDIO,EXTINT[9],,AIN[17],,,,SERCOM0/PAD[1],SERCOM2/PAD[1],TCC0/WO[1],TCC1/WO[3],, +13,15,PA10,VDDIO,EXTINT[10],,AIN[18],,,,SERCOM0/PAD[2],SERCOM2/PAD[2],TCC1/WO[0],TCC0/WO[2],,GCLK_IO[4] +14,16,PA11,VDDIO,EXTINT[11],,AIN[19],,,,SERCOM0/PAD[3],SERCOM2/PAD[3],TCC1/WO[1],TCC0/WO[3],,GCLK_IO[5] +,19,PB10,VDDIO,EXTINT[10],,,,,,,SERCOM4/PAD[2],TC5/WO[0],TCC0/WO[4],,GCLK_IO[4] +,20,PB11,VDDIO,EXTINT[11],,,,,,,SERCOM4/PAD[3],TC5/WO[1],TCC0/WO[5],,GCLK_IO[5] +,21,PA12,VDDIO,EXTINT[12],,,,,,SERCOM2/PAD[0],SERCOM4/PAD[0],TCC2/WO[0],TCC0/WO[6],,AC/CMP[0] +,22,PA13,VDDIO,EXTINT[13],,,,,,SERCOM2/PAD[1],SERCOM4/PAD[1],TCC2/WO[1],TCC0/WO[7],,AC/CMP[1] +15,23,PA14,VDDIO,EXTINT[14],,,,,,SERCOM2/PAD[2],SERCOM4/PAD[2],TC3/WO[0],TCC0/WO[4],,GCLK_IO[0] +16,24,PA15,VDDIO,EXTINT[15],,,,,,SERCOM2/PAD[3],SERCOM4/PAD[3],TC3/WO[1],TCC0/WO[5],,GCLK_IO[1] +17,25,PA16,VDDIO,EXTINT[0],,,,,,SERCOM1/PAD[0],SERCOM3/PAD[0],TCC2/WO[0],TCC0/WO[6],,GCLK_IO[2] +18,26,PA17,VDDIO,EXTINT[1],,,,,,SERCOM1/PAD[1],SERCOM3/PAD[1],TCC2/WO[1],TCC0/WO[7],,GCLK_IO[3] +19,27,PA18,VDDIO,EXTINT[2],,,,,,SERCOM1/PAD[2],SERCOM3/PAD[2],TC3/WO[0],TCC0/WO[2],,AC/CMP[0] +20,28,PA19,VDDIO,EXTINT[3],,,,,,SERCOM1/PAD[3],SERCOM3/PAD[3],TC3/WO[1],TCC0/WO[3],,AC/CMP[1] +,29,PA20,VDDIO,EXTINT[4],,,,,,SERCOM5/PAD[2],SERCOM3/PAD[2],TC7/WO[0],TCC0/WO[6],,GCLK_IO[4] +,30,PA21,VDDIO,EXTINT[5],,,,,,SERCOM5/PAD[3],SERCOM3/PAD[3],TC7/WO[1],TCC0/WO[7],,GCLK_IO[5] +21,31,PA22,VDDIO,EXTINT[6],,,,,,SERCOM3/PAD[0],SERCOM5/PAD[0],TC4/WO[0],TCC0/WO[4],,GCLK_IO[6] +22,32,PA23,VDDIO,EXTINT[7],,,,,,SERCOM3/PAD[1],SERCOM5/PAD[1],TC4/WO[1],TCC0/WO[5],,GCLK_IO[7] +23,33,PA24,VDDIO,EXTINT[12],,,,,,SERCOM3/PAD[2],SERCOM5/PAD[2],TC5/WO[0],TCC1/WO[2],,AC1/CMP[0] +24,34,PA25,VDDIO,EXTINT[13],,,,,,SERCOM3/PAD[3],SERCOM5/PAD[3],TC5/WO[1],TCC1/WO[3],,AC1/CMP[1] +,37,PA27,VDDIO,EXTINT[15],,,,,,,,,TCC3/WO[6],,GCLK_IO[0] +,39,PA28,VDDIO,EXTINT[8],,,,,,,,,TCC3/WO[7],,GCLK_IO[0] +29,43,PA30,VDDIO,EXTINT[10],,,,,,,SERCOM1/PAD[2],TCC1/WO[0],TCC3/WO[4],SWCLK,GCLK_IO[0] +30,44,PA31,VDDIO,EXTINT[11],,,,,,,SERCOM1/PAD[3],TCC1/WO[1],TCC3/WO[5],SWDIO, +,45,PB00,,,,AIN[8],,,,,,,,, +,46,PB01,,,,AIN[9],,,,,,,,, +31,47,PB02,VDDANA,EXTINT[2],,AIN[10],,AIN[2],,,SERCOM5/PAD[0],TC6/WO[0],TCC3/WO[2],, +32,48,PB03,VDDANA,EXTINT[3],,AIN[11],,AIN[3],,,SERCOM5/PAD[1],TC6/WO[1],TCC3/WO[3],, \ No newline at end of file diff --git a/extras/wirepinmux/SAMD21_Table_7-5.csv b/extras/wirepinmux/SAMD21_Table_7-5.csv new file mode 100644 index 000000000..4344f607f --- /dev/null +++ b/extras/wirepinmux/SAMD21_Table_7-5.csv @@ -0,0 +1,4 @@ +Device,I/O Pin +32, PA08, PA09, PA16, PA17, PA22, PA23 +48, PA08, PA09, PA12, PA13, PA16, PA17, PA22, PA23 +64, PA08, PA09, PA12, PA13, PA16, PA17, PA22, PA23, PB12, PB13, PB16, PB17, PB30, PB31 \ No newline at end of file diff --git a/extras/wirepinmux/SAMD51_Device_I2C_Config.csv b/extras/wirepinmux/SAMD51_Device_I2C_Config.csv new file mode 100644 index 000000000..cbf74e6b5 --- /dev/null +++ b/extras/wirepinmux/SAMD51_Device_I2C_Config.csv @@ -0,0 +1,26 @@ +Device,Pin Count,I2C Pins,SERCOM Configs +SAMD51G18,48,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51G19,48,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51J18,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51J19,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51J20,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51N19,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51N20,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAMD51P19,120,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PD08,PD09","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]|PD08:[SERCOM7/PAD[0],SERCOM6/PAD[1]]|PD09:[SERCOM7/PAD[1],SERCOM6/PAD[0]]" +SAMD51P20,120,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PD08,PD09","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]|PD08:[SERCOM7/PAD[0],SERCOM6/PAD[1]]|PD09:[SERCOM7/PAD[1],SERCOM6/PAD[0]]" +SAME51G18,48,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51G19,48,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51J18,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51J19,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51J20,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51N19,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME51N20,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME53J18,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME53J19,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME53J20,64,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME53N19,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME53N20,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME54N19,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME54N20,100,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]" +SAME54P19,120,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PD08,PD09","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]|PD08:[SERCOM7/PAD[0],SERCOM6/PAD[1]]|PD09:[SERCOM7/PAD[1],SERCOM6/PAD[0]]" +SAME54P20,120,"PA08,PA09,PA12,PA13,PA16,PA17,PA22,PA23,PD08,PD09","PA08:[SERCOM0/PAD[0],SERCOM2/PAD[1]]|PA09:[SERCOM0/PAD[1],SERCOM2/PAD[0]]|PA12:[SERCOM2/PAD[0],SERCOM4/PAD[1]]|PA13:[SERCOM2/PAD[1],SERCOM4/PAD[0]]|PA16:[SERCOM1/PAD[0],SERCOM3/PAD[1]]|PA17:[SERCOM1/PAD[1],SERCOM3/PAD[0]]|PA22:[SERCOM3/PAD[0],SERCOM5/PAD[1]]|PA23:[SERCOM3/PAD[1],SERCOM5/PAD[0]]|PD08:[SERCOM7/PAD[0],SERCOM6/PAD[1]]|PD09:[SERCOM7/PAD[1],SERCOM6/PAD[0]]" diff --git a/extras/wirepinmux/SAMD51_Table_1-1.csv b/extras/wirepinmux/SAMD51_Table_1-1.csv new file mode 100644 index 000000000..490edd9a4 --- /dev/null +++ b/extras/wirepinmux/SAMD51_Table_1-1.csv @@ -0,0 +1,14 @@ +Table 1-1. SAM E53/E54 Family Features with Ethernet,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,Peripherals,,,,,,,,,,,,,,,,,,,Analog,,,,,Security,,,, +Device,Flash (KB),SRAM (KB) / Backup RAM (KB),Pins,Packages,Ethernet Controller,CAN-FD,SERCOM,TC/Compare,TCC (24-bit/16-bit),I2S,USB,QSPI,SDHC,DMA Channels,PCC (data size),CCL,Position Decoder,RTC,WDT,Frequency Measurement,Event System (Channels),External Interrupt Lines,I/O Pins,ADC (Channels ADC0/ADC1),Analog Comparators (Channels),DAC (Channels),PTC (Mutual/Self-capacitance Channels),Temperature Sensor,AES,TRNG,Public Key Cryptography (PUKCC),Integrity Check Monitor,Tamper Pins +SAME53N20,1024,256 / 8,100,TQFP,Y,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME53N19,512,192 / 8,100,TQFP,Y,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME53J20,1024,256 / 8,64,"TQFP,VQFN",Y,N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,"256/32",Y,Y,Y,Y,Y,3 +SAME53J19,512,192 / 8,64,"TQFP,VQFN",Y,N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,"256/32",Y,Y,Y,Y,Y,3 +SAME53J18,256,128 / 8,64,"TQFP,VQFN",Y,N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,"256/32",Y,Y,Y,Y,Y,3 +SAME54P20,1024,256 / 8,128,TQFP,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME54P20,1024,256 / 8,120,TFBGA,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME54P19,512,192 / 8,128,TQFP,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME54P19,512,192 / 8,120,TFBGA,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME54N20,1024,256 / 8,100,TQFP,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,"256/32",Y,Y,Y,Y,Y,5 +SAME54N19,512,192 / 8,100,TQFP,Y,Y,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,"256/32",Y,Y,Y,Y,Y,5 \ No newline at end of file diff --git a/extras/wirepinmux/SAMD51_Table_1-2.csv b/extras/wirepinmux/SAMD51_Table_1-2.csv new file mode 100644 index 000000000..800a7bd2f --- /dev/null +++ b/extras/wirepinmux/SAMD51_Table_1-2.csv @@ -0,0 +1,21 @@ +Table 1-2. SAM D51/E51 Family Features without Ethernet,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,Peripherals,,,,,,,,,,,,,,,,,,Analog,,,,,Security,,,, +Device,Flash (KB),SRAM (KB) / Backup RAM (KB),Pins,Packages,CAN-FD,SERCOM,TC/Compare,TCC (24-bit/16-bit),I2S,USB,QSPI,SDHC,DMA Channels,PCC (data size),CCL,Position Decoder,RTC,WDT,Frequency Measurement,Event System (Channels),External Interrupt Lines,I/O Pins,ADC (Channels ADC0/ADC1),Analog Comparators (Channels),DAC (Channels),PTC (Mutual/Self-capacitance Channels),Temperature Sensor,AES,TRNG,Public Key Cryptography (PUKCC),Integrity Check Monitor,Tamper Pins +SAMD51P20,1024,256,128,TQFP,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51P20,1024,256,120,TFBGA,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51P19,512,192,128,TQFP,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51P19,512,192,120,TFBGA,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,99,"16/16",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51N20,1024,256,100,TQFP,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51N19,512,192,100,TQFP,N,8,"8/2","2/3",Y,Y,Y,2,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,256/32,Y,Y,Y,Y,Y,5 +SAMD51J20,1024,256,64,"TQFP,VQFN,WLCSP",N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAMD51J19,512,192,64,"TQFP,VQFN,WLCSP",N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAMD51J18,256,128,64,"TQFP,VQFN",N,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAMD51G19,512,192,48,VQFN,N,6,"4/2","2/1",N,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,37,"16/4",4,2,121/22,Y,Y,Y,Y,Y,2 +SAMD51G18,256,128,48,VQFN,N,6,"4/2","2/1",N,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,37,"16/4",4,2,121/22,Y,Y,Y,Y,Y,2 +SAME51N20,1024,256,100,TQFP,Y,8,"8/2","2/3",Y,Y,Y,1,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,256/32,Y,Y,Y,Y,Y,5 +SAME51N19,512,192,100,TQFP,Y,8,"8/2","2/3",Y,Y,Y,1,32,14,4,Y,Y,Y,Y,32,16,81,"16/12",4,2,256/32,Y,Y,Y,Y,Y,5 +SAME51J20,1024,256,64,"TQFP,VQFN",Y,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAME51J19,512,192,64,"TQFP,VQFN",Y,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAME51J18,256,128,64,"TQFP,VQFN",Y,6,"6/2","2/3",Y,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,51,"16/8",4,2,256/32,Y,Y,Y,Y,Y,3 +SAME51G18,256,128,48,VQFN,Y,6,"4/2","2/1",N,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,37,"16/4",4,2,121/22,Y,Y,Y,Y,Y,2 +SAME51G19,512,192,48,VQFN,Y,6,"4/2","2/1",N,Y,Y,1,32,10,4,Y,Y,Y,Y,32,16,37,"16/4",4,2,121/22,Y,Y,Y,Y,Y,2 \ No newline at end of file diff --git a/extras/wirepinmux/SAMD51_Table_6-1.csv b/extras/wirepinmux/SAMD51_Table_6-1.csv new file mode 100644 index 000000000..b9171d493 --- /dev/null +++ b/extras/wirepinmux/SAMD51_Table_6-1.csv @@ -0,0 +1,101 @@ +48,64,100,120,128,Pad Name,A,B1,B2,B3,B4,B5,B6,C,D,E,F,G,H,I,J,K,L,M,N +48,64/C6,100,B2,128,PB03,EIC/EXTINT[3],-,ADC0/AIN[15],-,-,-,X21/Y21,-,SERCOM5/PAD[1],TC6/WO[1],-,-,-,-,-,-,-,-,- +1,01/B8,1,A1,1,PA00,EIC/EXTINT[0],-,-,-,-,-,,-,SERCOM1/PAD[0],TC2/WO[0],-,-,-,-,-,-,-,-,- +2,02/C8,2,B1,2,PA01,EIC/EXTINT[1],-,-,-,-,-,,-,SERCOM1/PAD[1],TC2/WO[1],-,-,-,-,-,-,-,-,- +,,3,C1,3,PC00,EIC/EXTINT[0],-,-,ADC1/AIN[10],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,,4,C2,4,PC01,EIC/EXTINT[1],-,-,ADC1/AIN[11],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,,5,D1,7,PC02,EIC/EXTINT[2],-,-,ADC1/AIN[4],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,,6,E2,8,PC03,EIC/EXTINT[3],-,-,ADC1/AIN[5],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +3,03/C7,7,E1,9,PA02,EIC/EXTINT[2],-,ADC0/AIN[0],-,-,DAC/VOUT[0],,-,-,-,-,-,-,-,-,-,-,-,- +4,04/D6,8,F2,10,PA03,EIC/EXTINT[3],ANAREF/VREFA,ADC0/AIN[1],-,-,-,X0/Y0,-,-,-,-,-,-,-,-,-,-,-,- +,05/D7,9,F1,11,PB04,EIC/EXTINT[4],-,-,ADC1/AIN[6],-,-,X22/Y22,-,-,-,-,-,-,-,-,-,-,-,- +,06/D8,10,G1,12,PB05,EIC/EXTINT[5],-,-,ADC1/AIN[7],-,-,X23/Y23,-,-,-,-,-,-,-,-,-,-,-,- +,,-,G2,13,PD00,EIC/EXTINT[0],-,-,ADC1/AIN[14],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,,-,H1,16,PD01,EIC/EXTINT[1],-,-,ADC1/AIN[15],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,09/E7,13,H2,17,PB06,EIC/EXTINT[6],-,-,ADC1/AIN[8],-,-,X24/Y24,-,-,-,-,-,-,-,-,-,-,-,CCL/IN[6] +,10/E6,14,J1,18,PB07,EIC/EXTINT[7],-,-,ADC1/AIN[9],-,-,X25/Y25,-,-,-,-,-,-,-,-,-,-,-,CCL/IN[7] +7,11/F5,15,J2,19,PB08,EIC/EXTINT[8],-,ADC0/AIN[2],ADC1/AIN[0],-,-,X1/Y1,-,SERCOM4/PAD[0],TC4/WO[0],-,-,-,-,-,-,-,-,CCL/IN[8] +8,12/F8,16,K1,20,PB09,EIC/EXTINT[9],-,ADC0/AIN[3],ADC1/AIN[1],-,-,X2/Y2,-,SERCOM4/PAD[1],TC4/WO[1],-,-,-,-,-,-,-,-,CCL/OUT[2] +9,13/F7,17,K2,21,PA04,EIC/EXTINT[4],ANAREF/VREFB,ADC0/AIN[4],-,AC/AIN[0],-,X3/Y3,-,SERCOM0/PAD[0],TC0/WO[0],-,-,-,-,-,-,-,-,CCL/IN[0] +10,14/F6,18,L1,22,PA05,EIC/EXTINT[5],-,ADC0/AIN[5],-,AC/AIN[1],DAC/VOUT[1],,-,SERCOM0/PAD[1],TC0/WO[1],-,-,-,-,-,-,-,-,CCL/IN[1] +11,15/G7,19,L2,23,PA06,EIC/EXTINT[6],ANAREF/VREFC,ADC0/AIN[6],-,AC/AIN[2],-,X4/Y4,-,SERCOM0/PAD[2],TC1/WO[0],-,-,-,SDHC0/SDCD,-,-,-,-,CCL/IN[2] +12,16/G8,20,M1,24,PA07,EIC/EXTINT[7],-,ADC0/AIN[7],-,AC/AIN[3],-,X5/Y5,-,SERCOM0/PAD[3],TC1/WO[1],-,-,-,SDHC0/SDWP,-,-,-,-,CCL/OUT[0] +,,-,N1,27,PC04,EIC/EXTINT[4],-,-,-,-,-,,SERCOM6/PAD[0],-,-,TCC0/WO[0],-,-,-,-,-,-,-,- +,,21,N2,28,PC05,EIC/EXTINT[5],-,-,-,-,-,,SERCOM6/PAD[1],-,-,-,-,-,-,-,-,-,-,- +,,22,P1,29,PC06,EIC/EXTINT[6],-,-,-,-,-,,SERCOM6/PAD[2],-,-,-,-,-,SDHC0/SDCD,-,-,-,-,- +,,23,P2,30,PC07,EIC/EXTINT[9],-,-,-,-,-,,SERCOM6/PAD[3],-,-,-,-,-,SDHC0/SDWP,-,-,-,-,- +13,17/H8,26,R1,33,PA08,EIC/NMI,-,ADC0/AIN[8],ADC1/AIN[2],-,-,X6/Y6,SERCOM0/PAD[0],SERCOM2/PAD[1],TC0/WO[0],TCC0/WO[0],TCC1/WO[4],QSPI/DATA[0],SDHC0/SDCMD,I2S/MCK[0],-,-,-,CCL/IN[3] +14,18/G6,27,P3,34,PA09,EIC/EXTINT[9],-,ADC0/AIN[9],ADC1/AIN[3],-,-,X7/Y7,SERCOM0/PAD[1],SERCOM2/PAD[0],TC0/WO[1],TCC0/WO[1],TCC1/WO[5],QSPI/DATA[1],SDHC0/SDDAT[0],I2S/FS[0],-,-,-,CCL/IN[4] +15,19/H7,28,R2,35,PA10,EIC/EXTINT[10],-,ADC0/-AIN[10],,-,-,X8/Y8,SERCOM0/PAD[2],SERCOM2/PAD[2],TC1/WO[0],TCC0/WO[2],TCC1/WO[6],QSPI/DATA[2],SDHC0/SDDAT[1],I2S/SCK[0],-,-,GCLK/IO[4],CCL/IN[5] +16,20/G5,29,P4,36,PA11,EIC/EXTINT[11],-,ADC0/-AIN[11],,-,-,X9/Y9,SERCOM0/PAD[3],SERCOM2/PAD[3],TC1/WO[1],TCC0/WO[3],TCC1/WO[7],QSPI/DATA[3],SDHC0/SDDAT[2],I2S/SDO,-,-,GCLK/IO[5],CCL/OUT[1] +19,23/H6,32,R3,39,PB10,EIC/EXTINT[10],-,-,-,-,-,,-,SERCOM4/PAD[2],TC5/WO[0],TCC0/WO[4],TCC1/WO[0],QSPI/SCK,SDHC0/SDDAT[3],I2S/SDI,-,-,GCLK/IO[4],CCL/IN[11] +20,24/G4,33,P5,40,PB11,EIC/EXTINT[11],-,-,-,-,-,,-,SERCOM4/PAD[3],TC5/WO[1],TCC0/WO[5],TCC1/WO[1],QSPI/CS,SDHC0/SDCK,I2S/FS[1],-,-,GCLK/IO[5],CCL/OUT[1] +,25/H5,34,R4,41,PB12,EIC/EXTINT[12],-,-,-,-,-,X26/Y26,SERCOM4/PAD[0],-,TC4/WO[0],TCC3/WO[0],TCC0/WO[0],CAN1/TX,SDHC0/SDCD,I2S/SCK[1],-,-,GCLK/IO[6],- +,26/H4,35,P6,42,PB13,EIC/EXTINT[13],-,-,-,-,-,X27/Y27,SERCOM4/PAD[1],-,TC4/WO[1],TCC3/WO[1],TCC0/WO[1],CAN1/RX,SDHC0/SDWP,I2S/MCK[1],-,-,GCLK/IO[7],- +,27/G3,36,R5,43,PB14,EIC/EXTINT[14],-,-,-,-,-,X28/Y28,SERCOM4/PAD[2],-,TC5/WO[0],TCC4/WO[0],TCC0/WO[2],CAN1/TX,-,-,PCC/DATA[8],GMAC/GMDC,GCLK/IO[0],CCL/IN[9] +,28/H3,37,P7,44,PB15,EIC/EXTINT[15],-,-,-,-,-,X29/Y29,SERCOM4/PAD[3],-,TC5/WO[1],TCC4/WO[1],TCC0/WO[3],CAN1/RX,-,-,PCC/DATA[9],GMAC/GMDIO,GCLK/IO[1],CCL/IN[10] +,,-,R6,47,PD08,EIC/EXTINT[3],-,-,-,-,-,,SERCOM7/PAD[0],SERCOM6/PAD[1],-,TCC0/WO[1],-,-,-,-,-,-,-,- +,,-,P8,48,PD09,EIC/EXTINT[4],-,-,-,-,-,,SERCOM7/PAD[1],SERCOM6/PAD[0],-,TCC0/WO[2],-,-,-,-,-,-,-,- +,,-,R7,49,PD10,EIC/EXTINT[5],-,-,-,-,-,,SERCOM7/PAD[2],SERCOM6/PAD[2],-,TCC0/WO[3],-,-,-,-,-,-,-,- +,,-,P9,50,PD11,EIC/EXTINT[6],-,-,-,-,-,,SERCOM7/PAD[3],SERCOM6/PAD[3],-,TCC0/WO[4],-,-,-,-,-,-,-,- +,,-,R8,51,PD12,EIC/EXTINT[7],-,-,-,-,-,,-,-,-,TCC0/WO[5],-,-,-,-,-,-,-,- +,,40,P10,52,PC10,EIC/EXTINT[10],-,-,-,-,-,,SERCOM6/PAD[2],SERCOM7/PAD[2],-,TCC0/WO[0],TCC1/WO[4],-,-,-,-,-,-,- +,,41,R9,55,PC11,EIC/EXTINT[11],-,-,-,-,-,,SERCOM6/PAD[3],SERCOM7/PAD[3],-,TCC0/WO[1],TCC1/WO[5],-,-,-,-,GMAC/GMDC,-,- +,,42,R10,56,PC12,EIC/EXTINT[12],-,-,-,-,-,,SERCOM7/PAD[0],SERCOM6/PAD[1],-,TCC0/WO[2],TCC1/WO[6],-,-,-,PCC/DATA[10],GMAC/GMDIO,-,- +,,43,P11,57,PC13,EIC/EXTINT[13],-,-,-,-,-,,SERCOM7/PAD[1],SERCOM6/PAD[0],-,TCC0/WO[3],TCC1/WO[7],-,-,-,PCC/DATA[11],-,-,- +,,44,R11,58,PC14,EIC/EXTINT[14],-,-,-,-,-,,SERCOM7/PAD[2],SERCOM6/PAD[2],-,TCC0/WO[4],TCC1/WO[0],-,-,-,PCC/DATA[12],GMAC/GRX[3],-,- +,,45,P12,59,PC15,EIC/EXTINT[15],-,-,-,-,-,,SERCOM7/PAD[3],SERCOM6/PAD[3],-,TCC0/WO[5],TCC1/WO[1],-,-,-,PCC/DATA[13],GMAC/GRX[2],-,- +21,29/F2,46,R12,60,PA12,EIC/EXTINT[12],-,-,-,-,-,,SERCOM2/PAD[0],SERCOM4/PAD[1],TC2/WO[0],TCC0/WO[6],TCC1/WO[2],-,SDHC0/SDCD,-,PCC/DEN1,GMAC/GRX[1],AC/CMP[0],- +22,30/G2,47,P13,61,PA13,EIC/EXTINT[13],-,-,-,-,-,,SERCOM2/PAD[1],SERCOM4/PAD[0],TC2/WO[1],TCC0/WO[7],TCC1/WO[3],-,SDHC0/SDWP,-,PCC/DEN2,GMAC/GRX[0],AC/CMP[1],- +23,31/H1,48,R13,62,PA14,EIC/EXTINT[14],-,-,-,-,-,,SERCOM2/PAD[2],SERCOM4/PAD[2],TC3/WO[0],TCC2/WO[0],TCC1/WO[2],-,-,-,PCC/CLK,GMAC/GTXCK,GCLK/IO[0],- +24,32/H2,49,R14,63,PA15,EIC/EXTINT[15],-,-,-,-,-,,SERCOM2/PAD[3],SERCOM4/PAD[3],TC3/WO[1],TCC2/WO[1],TCC1/WO[3],-,-,-,-,GMAC/GRXER,GCLK/IO[1],- +25,35/G1,52,R15,66,PA16,EIC/EXTINT[0],-,-,-,-,-,X10/Y10,SERCOM1/PAD[0],SERCOM3/PAD[1],TC2/WO[0],TCC1/WO[0],TCC0/WO[4],-,-,-,PCC/DATA[0],GMAC/GCRS/GRXDV(6),GCLK/IO[2],CCL/IN[0] +26,36/F1,53,P14,67,PA17,EIC/EXTINT[1],-,-,-,-,-,X11/Y11,SERCOM1/PAD[1],SERCOM3/PAD[0],TC2/WO[1],TCC1/WO[1],TCC0/WO[5],-,-,-,PCC/DATA[1],GMAC/GTXEN,GCLK/IO[3],CCL/IN[1] +27,37/E1,54,P15,68,PA18,EIC/EXTINT[2],-,-,-,-,-,X12/Y12,SERCOM1/PAD[2],SERCOM3/PAD[2],TC3/WO[0],TCC1/WO[2],TCC0/WO[6],-,-,-,PCC/DATA[2],GMAC/GTX[0],AC/CMP[0],CCL/IN[2] +28,38/E2,55,N14,69,PA19,EIC/EXTINT[3],-,-,-,-,-,X13/Y13,SERCOM1/PAD[3],SERCOM3/PAD[3],TC3/WO[1],TCC1/WO[3],TCC0/WO[7],-,-,-,PCC/DATA[3],GMAC/GTX[1],AC/CMP[1],CCL/OUT[0] +,,56,N15,70,PC16,EIC/EXTINT[0],-,-,-,-,-,,SERCOM6/PAD[0],SERCOM0/PAD[1],-,TCC0/WO[0],PDEC/PDEC[0],-,-,-,-,GMAC/GTX[2],-,- +,,57,M14,71,PC17,EIC/EXTINT[1],-,-,-,-,-,,SERCOM6/PAD[1],SERCOM0/PAD[0],-,TCC0/WO[1],PDEC/PDEC[1],-,-,-,-,GMAC/GTX[3],-,- +,,58,M15,72,PC18,EIC/EXTINT[2],-,-,-,-,-,,SERCOM6/PAD[2],SERCOM0/PAD[2],-,TCC0/WO[2],PDEC/PDEC[2],-,-,-,-,GMAC/GRXCK,-,- +,,59,L14,73,PC19,EIC/EXTINT[3],-,-,-,-,-,,SERCOM6/PAD[3],SERCOM0/PAD[3],-,TCC0/WO[3],-,-,-,-,-,GMAC/GTXER,-,- +,,60,L15,74,PC20,EIC/EXTINT[4],-,-,-,-,-,,-,-,-,TCC0/WO[4],-,-,SDHC1/SDCD,-,-,GMAC/GRXDV,-,CCL/IN[9] +,,61,K14,75,PC21,EIC/EXTINT[5],-,-,-,-,-,,-,-,-,TCC0/WO[5],-,-,SDHC1/SDWP,-,-,GMAC/GCOL,-,CCL/IN[10] +,,-,K15,76,PC22,EIC/EXTINT[6],-,-,-,-,-,,SERCOM1/PAD[0],SERCOM3/PAD[1],-,TCC0/WO[6],-,-,-,-,-,GMAC/GMDC,-,- +,,-,J14,77,PC23,EIC/EXTINT[7],-,-,-,-,-,,SERCOM1/PAD[1],SERCOM3/PAD[0],-,TCC0/WO[7],-,-,-,-,-,GMAC/GMDIO,-,- +,,-,J15,80,PD20,EIC/EXTINT[10],-,-,-,-,-,,SERCOM1/PAD[2],SERCOM3/PAD[2],-,TCC1/WO[0],-,-,SDHC1/SDCD,-,-,-,-,- +,,-,H14,81,PD21,EIC/EXTINT[11],-,-,-,-,-,,SERCOM1/PAD[3],SERCOM3/PAD[3],-,TCC1/WO[1],-,-,SDHC1/SDWP,-,-,-,-,- +,39/D4,64,H15,82,PB16,EIC/EXTINT[0],-,-,-,-,-,,SERCOM5/PAD[0],-,TC6/WO[0],TCC3/WO[0],TCC0/WO[4],-,SDHC1/SDCD,I2S/SCK[0],-,-,GCLK/IO[2],CCL/IN[11] +,40/D1,65,G15,83,PB17,EIC/EXTINT[1],-,-,-,-,-,,SERCOM5/PAD[1],-,TC6/WO[1],TCC3/WO[1],TCC0/WO[5],-,SDHC1/SDWP,I2S/MCK[0],-,-,GCLK/IO[3],CCL/OUT[3] +,,66,G14,84,PB18,EIC/EXTINT[2],-,-,-,-,-,,SERCOM5/PAD[2],SERCOM7/PAD[2],-,TCC1/WO[0],PDEC/PDEC[0],-,SDHC1/SDDAT[0],-,-,-,GCLK/IO[4],- +,,67,F15,85,PB19,EIC/EXTINT[3],-,-,-,-,-,,SERCOM5/PAD[3],SERCOM7/PAD[3],-,TCC1/WO[1],PDEC/PDEC[1],-,SDHC1/SDDAT[1],-,-,-,GCLK/IO[5],- +,,68,F14,86,PB20,EIC/EXTINT[4],-,-,-,-,-,,SERCOM3/PAD[0],SERCOM7/PAD[1],-,TCC1/WO[2],PDEC/PDEC[2],-,SDHC1/SDDAT[2],-,-,-,GCLK/IO[6],- +,,69,E15,87,PB21,EIC/EXTINT[5],-,-,-,-,-,,SERCOM3/PAD[1],SERCOM7/PAD[0],-,TCC1/WO[3],-,-,SDHC1/SDDAT[3],-,-,-,GCLK/IO[7],- +29,41/D2,70,E14,88,PA20,EIC/EXTINT[4],-,-,-,-,-,X14/Y14,SERCOM5/PAD[2],SERCOM3/PAD[2],TC7/WO[0],TCC1/WO[4],TCC0/WO[0],-,SDHC1/SDCMD,I2S/FS[0],PCC/DATA[4],GMAC/GMDC,-,- +30,42/D3,71,D15,89,PA21,EIC/EXTINT[5],-,-,-,-,-,X15/Y15,SERCOM5/PAD[3],SERCOM3/PAD[3],TC7/WO[1],TCC1/WO[5],TCC0/WO[1],-,SDHC1/SDCK,I2S/SDO,PCC/DATA[5],GMAC/GMDIO,-,- +31,43/C1,72,D14,92,PA22,EIC/EXTINT[6],-,-,-,-,-,X16/Y16,SERCOM3/PAD[0],SERCOM5/PAD[1],TC4/WO[0],TCC1/WO[6],TCC0/WO[2],-,CAN0/TX,I2S/SDI,PCC/DATA[6],-,-,CCL/IN[6] +32,44/C2,73,C14,93,PA23,EIC/EXTINT[7],-,-,-,-,-,X17/Y17,SERCOM3/PAD[1],SERCOM5/PAD[0],TC4/WO[1],TCC1/WO[7],TCC0/WO[3],USB/SOF_1KHZ,CAN0/RX,I2S/FS[1],PCC/DATA[7],-,-,CCL/IN[7] +33,45/B1,74,C15,94,PA24,EIC/EXTINT[8],-,-,-,-,-,,SERCOM3/PAD[2],SERCOM5/PAD[2],TC5/WO[0],TCC2/WO[2],PDEC/PDEC[0],USB/DM,CAN0/TX,-,-,-,-,CCL/IN[8] +34,46/A1,75,B15,95,PA25,EIC/EXTINT[9],-,-,-,-,-,,SERCOM3/PAD[3],SERCOM5/PAD[3],TC5/WO[1],-,PDEC/PDEC[1],USB/DP,CAN0/RX,-,-,-,-,CCL/OUT[2] +37,49/A2,78,A15,98,PB22,EIC/EXTINT[6],-,-,-,-,-,,SERCOM1/PAD[2],SERCOM5/PAD[2],TC7/WO[0],-,PDEC/PDEC[2],USB/SOF_1KHZ,-,-,-,-,GCLK/IO[0],CCL/IN[0] +38,50/A3,79,A14,99,PB23,EIC/EXTINT[7],-,-,-,-,-,,SERCOM1/PAD[3],SERCOM5/PAD[3],TC7/WO[1],-,PDEC/PDEC[0],-,-,-,-,-,GCLK/IO[1],CCL/OUT[0] +,,80,B14,100,PB24,EIC/EXTINT[8],-,-,-,-,-,,SERCOM0/PAD[0],SERCOM2/PAD[1],-,-,PDEC/PDEC[1],-,-,-,-,-,AC/CMP[0],- +,,81,B13,101,PB25,EIC/EXTINT[9],-,-,-,-,-,,SERCOM0/PAD[1],SERCOM2/PAD[0],-,-,PDEC/PDEC[2],-,-,-,-,-,AC/CMP[1],- +,,-,A13,102,PB26,EIC/EXTINT[12],-,-,-,-,-,,SERCOM2/PAD[0],SERCOM4/PAD[1],-,TCC1/WO[2],-,-,-,-,-,-,-,- +,,-,B12,103,PB27,EIC/EXTINT[13],-,-,-,-,-,,SERCOM2/PAD[1],SERCOM4/PAD[0],-,TCC1/WO[3],-,-,-,-,-,-,-,- +,,-,A12,104,PB28,EIC/EXTINT[14],-,-,-,-,-,,SERCOM2/PAD[2],SERCOM4/PAD[2],-,TCC1/WO[4],-,-,-,I2S/SCK[1],-,-,-,- +,,-,B11,105,PB29,EIC/EXTINT[15],-,-,-,-,-,,SERCOM2/PAD[3],SERCOM4/PAD[3],-,TCC1/WO[5],-,-,-,I2S/MCK[1],-,-,-,- +,,82,A11,108,PC24,EIC/EXTINT[8],-,-,-,-,-,,SERCOM0/PAD[2],SERCOM2/PAD[2],-,-,-,CORTEX_CM4/TRACEDATA[3],-,-,-,-,-,- +,,83,B10,109,PC25,EIC/EXTINT[9],-,-,-,-,-,,SERCOM0/PAD[3],SERCOM2/PAD[3],-,-,-,CORTEX_CM4/TRACEDATA[2],-,-,-,-,-,- +,,84,A10,110,PC26,EIC/EXTINT[10],-,-,-,-,-,,-,-,-,-,-,CORTEX_CM4/TRACEDATA[1],-,-,-,-,-,- +,,85,A9,111,PC27,EIC/EXTINT[11],-,-,-,-,-,,SERCOM1/PAD[0],-,-,-,-,CORTEX_CM4/TRACECLK,-,-,-,-,CORTEX_M4/SWO,CCL/IN[4] +,,86,B9,112,PC28,EIC/EXTINT[12],-,-,-,-,-,,SERCOM1/PAD[1],-,-,-,-,CORTEX_CM4/TRACEDATA[0],-,-,-,-,-,CCL/IN[5] +39,51/B3,87,B8,113,PA27,EIC/EXTINT[11],-,-,-,-,-,X18/Y18,-,-,-,-,-,-,-,-,-,GCLK/IO[1],-, +40,52/B4,88,A8,114,RESET_N,-,-,-,-,-,-,,-,-,-,-,-,-,-,-,-,-,-,- +45,57/C5,93,B7,119,PA30,EIC/EXTINT[14],-,-,-,-,-,X19/Y19,SERCOM7/PAD[2],SERCOM1/PAD[2],TC6/WO[0],TCC2/WO[0],-,CORTEX_CM4/SWCLK,-,-,-,-,GCLK/IO[0],CCL/IN[3] +46,58/D5,94,B6,120,PA31,EIC/EXTINT[15],-,-,-,-,-,,SERCOM7/PAD[3],SERCOM1/PAD[3],TC6/WO[1],TCC2/WO[1],--,CORTEX_CM4/SWDIO,-,-,-,-,-,CCL/OUT[1] +,59/A6,95,A5,121,PB30,EIC/EXTINT[14],-,-,-,-,-,,SERCOM7/PAD[0],SERCOM5/PAD[1],TC0/WO[0],TCC4/WO[0],TCC0/WO[6],CORTEX_CM4/SWO,-,-,-,-,-,- +,60/B6,96,B5,122,PB31,EIC/EXTINT[15],-,-,-,-,-,,SERCOM7/PAD[1],SERCOM5/PAD[0],TC0/WO[1],TCC4/WO[1],TCC0/WO[7],,-,-,-,-,-,- +,,-,A4,123,PC30,EIC/EXTINT[14],-,-,ADC1/AIN[12],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,,-,B4,124,PC31,EIC/EXTINT[15],-,-,ADC1/AIN[13],-,-,,-,-,-,-,-,-,-,-,-,-,-,- +,61/A7,97,A3,125,PB00,EIC/EXTINT[0],-,ADC0/AIN[12],-,-,-,X30/Y30,-,SERCOM5/PAD[2],TC7/WO[0],-,-,-,-,-,-,-,-,CCL/IN[1] +,62/B7,98,B3,126,PB01,EIC/EXTINT[1],-,ADC0/AIN[13],-,-,-,X31/Y31,-,SERCOM5/PAD[3],TC7/WO[1],-,-,-,-,-,-,-,-,CCL/IN[2] +47,63/A8,99,A2,127,PB02,EIC/EXTINT[2],-,ADC0/AIN[14],-,-,-,X20/Y20,-,SERCOM5/PAD[0],TC6/WO[0],TCC2/WO[2],-,-,-,-,-,-,-,CCL/OUT[0] \ No newline at end of file diff --git a/extras/wirepinmux/SAMD51_Table_6-8.csv b/extras/wirepinmux/SAMD51_Table_6-8.csv new file mode 100644 index 000000000..85923703f --- /dev/null +++ b/extras/wirepinmux/SAMD51_Table_6-8.csv @@ -0,0 +1,10 @@ +Device,Supply,I/O Pin +128,VDDIOB, PA08, PA09 +128,VDDIO,PA12, PA13, PA16, PA17, PA22, PA23, PD08, PD09 +120,VDDIOB, PA08, PA09 +120,VDDIO, PA12, PA13, PA16, PA17, PA22, PA23, PD08, PD09 +100,VDDIOB, PA08, PA09 +100,VDDIO, PA12, PA13, PA16, PA17, PA22, PA23 +64,VDDIOB, PA08, PA09 +64,VDDIO, PA12, PA13, PA16, PA17, PA22, PA23 +48,VDDIO, PA08, PA09, PA12, PA13, PA16, PA17, PA22, PA23 \ No newline at end of file From f4fc47a5e410f35e3efc17bc5c6757cc37064fc8 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 22:27:28 -0600 Subject: [PATCH 22/24] script to generate wire sercom specific pin assignments can be used for pin assignment validation --- tools/generate_wirepinmux_inc.py | 570 +++++++++++++++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 tools/generate_wirepinmux_inc.py diff --git a/tools/generate_wirepinmux_inc.py b/tools/generate_wirepinmux_inc.py new file mode 100644 index 000000000..f47df3f38 --- /dev/null +++ b/tools/generate_wirepinmux_inc.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python3 +""" +Generate WirePinMux.inc from SAMD21/DA1 and SAMD51/E5x device tables. + +Workflow: +1. Load device-to-package mappings from Tables 2-1, 2-2 (SAMD21/DA1) and 1-1, 1-2 (SAMD51/E5x) +2. Load I2C pin configurations from Tables 7-5 (SAMD21/DA1) and 6-8 (SAMD5x/E5x) +3. For each pin, parse columns C and D to extract primary/alternate SERCOM+PAD +4. Group devices by series and emit guard blocks with I2C_PIN macros + +Emits: + libraries/Wire/WirePinMux.inc + +Each pin entry: + I2C_PIN(PA08, 0, 0, 2, 1) +where arguments are (pin, primary_sercom, primary_pad, alt_sercom, alt_pad). +If an alternate SERCOM is missing, alt_sercom/pad are set to 255. +""" +from __future__ import annotations + +import csv +import re +from pathlib import Path +from typing import Dict, List, Set, Tuple + +ROOT = Path(__file__).resolve().parent.parent +DATA_DIR = ROOT / "extras" / "wirepinmux" +OUTPUT = ROOT / "libraries" / "Wire" / "WirePinMux.inc" + +# Device-to-package mapping CSVs +SAMD21_TABLE_2_1 = DATA_DIR / "SAMD21_Table_2-1.csv" +SAMD21_TABLE_2_2 = DATA_DIR / "SAMD21_Table_2-2.csv" +SAMD51_TABLE_1_1 = DATA_DIR / "SAMD51_Table_1-1.csv" +SAMD51_TABLE_1_2 = DATA_DIR / "SAMD51_Table_1-2.csv" + +# I2C pin configuration CSVs +SAMD21_TABLE_7_5 = DATA_DIR / "SAMD21_Table_7-5.csv" +SAMD51_TABLE_6_8 = DATA_DIR / "SAMD51_Table_6-8.csv" + +# Regex patterns +SERCOM_PAD_RE = re.compile(r"SERCOM(\d+)/PAD\[(\d+)\]") + +# Fallback when alt is missing +NO_ALT = (255, 255) + + +def load_csv(path: Path, skip_rows: int = 0) -> List[Dict[str, str]]: + """Load CSV file and return list of row dictionaries.""" + with path.open(newline="", encoding="utf-8-sig") as f: # utf-8-sig removes BOM + # Skip header rows if needed + for _ in range(skip_rows): + next(f) + return list(csv.DictReader(f)) + + +def parse_sercom_pad(text: str) -> Tuple[int, int] | None: + """Parse 'SERCOM0/PAD[0]' -> (0, 0), return None if not found.""" + if not text or text.strip() == "": + return None + m = SERCOM_PAD_RE.search(text) + if m: + return (int(m.group(1)), int(m.group(2))) + return None + + +def load_device_pin_map( + csv_paths: List[Tuple[Path, int]], +) -> Dict[str, Set[str]]: + """ + Load device-to-package tables and return device -> set of pins mapping. + + For SAMD21/DA1 (Tables 2-1, 2-2): 'Pins' column indicates package size (32, 48, 64). + For SAMD51/E5x (Tables 1-1, 1-2): 'Pins' column indicates package size (48, 64, 100, 120/128). + + Args: + csv_paths: List of (path, skip_rows) tuples + + We'll cross-reference with the I2C pin tables (7-5, 6-8) which list pins by package size. + """ + device_to_pins: Dict[str, Set[str]] = {} + + for csv_path, skip_rows in csv_paths: + rows = load_csv(csv_path, skip_rows=skip_rows) + for row in rows: + device = row.get("Device", "").strip() + pins_str = row.get("Pins", "").strip() + + if not device or not pins_str: + continue + + # Extract numeric pin count + try: + pin_count = int(pins_str) + except ValueError: + continue + + device_to_pins[device] = {str(pin_count)} + + return device_to_pins + + +def load_i2c_pins_samd21(csv_path: Path) -> Dict[str, List[str]]: + """Load I2C pin list by package from Table 7-5 (SAMD21/DA1).""" + package_to_pins: Dict[str, List[str]] = {} + + rows = load_csv(csv_path) + for row in rows: + package = row.get("Device", "").strip() + if not package: + continue + + pin_tokens: List[str] = [] + for key, val in row.items(): + if key == "Device": + continue + if val is None: + continue + values = val if isinstance(val, list) else [val] + for v in values: + token = v.strip() + if not token: + continue + pin_tokens.extend(t.strip() for t in token.split(",") if t.strip()) + + if not pin_tokens: + continue + + package_to_pins[package] = pin_tokens + + return package_to_pins + + +def load_i2c_pins_samd51(csv_path: Path) -> Dict[str, List[str]]: + """ + Load I2C pin list by package from Table 6-8 (SAMD5x/E5x). + + Each device can have multiple rows (different supplies). Merge pin lists per device: + - VDDIOB rows (if any) come first + - then other supplies (e.g., VDDIO) + Pins are de-duplicated preserving first-seen order. + Returns: package_size -> [pin_names] + """ + package_to_pins: Dict[str, List[str]] = {} + rows = load_csv(csv_path) + + # Collect per-device rows keyed by (device, supply_order) + per_device: Dict[str, List[Tuple[int, List[str]]]] = {} + for row in rows: + device = row.get("Device", "").strip() + supply = row.get("Supply", "").strip().upper() + # Gather pin tokens from all remaining columns (some rows spread pins across columns) + pin_tokens: List[str] = [] + for key, val in row.items(): + if key in {"Device", "Supply"}: + continue + if val is None: + continue + values = val if isinstance(val, list) else [val] + for v in values: + token = v.strip() + if not token: + continue + pin_tokens.extend(t.strip() for t in token.split(",") if t.strip()) + + if not device or not pin_tokens: + continue + pin_names = pin_tokens + + # Order supplies: VDDIOB first (0), others after (1) + supply_order = 0 if supply == "VDDIOB" else 1 + per_device.setdefault(device, []).append((supply_order, pin_names)) + + # Merge per device + for device, pin_lists in per_device.items(): + # Sort by supply order so VDDIOB pins precede + pin_lists.sort(key=lambda x: x[0]) + merged: List[str] = [] + seen: Set[str] = set() + for _, pins in pin_lists: + for pin in pins: + if pin not in seen: + seen.add(pin) + merged.append(pin) + package_to_pins[device] = merged + + return package_to_pins + + +def load_pin_mux_configs( + csv_path: Path, is_samd51: bool = False +) -> Dict[str, Tuple[int, int, int, int]]: + """ + Load pin multiplexing configuration from Table 7-1 (SAMD21) or 6-1 (SAMD51). + + Returns: pin_name -> (primary_sercom, primary_pad, alt_sercom, alt_pad) + + For SAMD21 Table 7-1: columns are organized with column C (primary) and D (alt) for SERCOM + For SAMD51 Table 6-1: columns C (primary) and D (alt) for SERCOM + + Special case: Some pins only have alternate SERCOM (column D only, no column C). + For these, we store them with 255,255 as primary and the SERCOM from D as alternate. + """ + pin_configs: Dict[str, Tuple[int, int, int, int]] = {} + + rows = load_csv(csv_path) + for row in rows: + # Pin name is in 'I/O Pin' column for SAMD21, 'Pad Name' for SAMD51 + pin_name = row.get("Pad Name" if is_samd51 else "I/O Pin", "").strip() + + if not pin_name or not pin_name.startswith("P"): + continue + + # Column C is primary SERCOM, column D is alternate SERCOM + col_c = row.get("C", "").strip() + col_d = row.get("D", "").strip() + + primary = parse_sercom_pad(col_c) + alt = parse_sercom_pad(col_d) + + if primary: + # Pin has primary SERCOM (column C) + primary_sercom, primary_pad = primary + alt_sercom, alt_pad = alt if alt else NO_ALT + pin_configs[pin_name] = (primary_sercom, primary_pad, alt_sercom, alt_pad) + elif alt: + # Pin only has alternate SERCOM (column D only, no column C) + # Store as: no primary (255,255), alternate from column D + alt_sercom, alt_pad = alt + pin_configs[pin_name] = (NO_ALT[0], NO_ALT[1], alt_sercom, alt_pad) + + return pin_configs + + +def macro_candidates(dev: str) -> List[str]: + """ + Return the canonical sam.h device macro for a device name. + + sam.h defines exactly one macro per device with pattern __DEVICENAME__ + where DEVICENAME is the datasheet name with "AT" prefix stripped. + + Examples: + ATSAMD21E18A → __SAMD21E18A__ + ATSAMDA1E14B → __SAMDA1E14B__ + SAMD51J19A → __SAMD51J19A__ + SAME53N20 → __SAME53N20__ + """ + dev_upper = dev.upper() + # Strip "AT" prefix if present (ATSAMD21E18A → SAMD21E18A) + if dev_upper.startswith("AT"): + dev_upper = dev_upper[2:] + return [f"__{dev_upper}__"] + + +def classify_device_series(device: str, pin_count: str) -> str: + """ + Classify device into series based on family and package. + + Returns series macro name like "SAMD21E_SERIES", "SAMD51_120_SERIES", etc. + """ + dev_upper = device.upper() + + # Remove "AT" prefix if present + if dev_upper.startswith("AT"): + dev_upper = dev_upper[2:] + + # SAMD21/DA1 classification by letter (E, G, J) + if dev_upper.startswith("SAMD21E"): + return "SAMD21E_SERIES" + elif dev_upper.startswith("SAMD21G"): + return "SAMD21G_SERIES" + elif dev_upper.startswith("SAMD21J"): + return "SAMD21J_SERIES" + elif dev_upper.startswith("SAMDA1"): + return "SAMDA1_SERIES" + + # SAMD51/E5x classification by pin count + # 120/128-pin packages have 5 SERCOMs (with PD08/PD09) + # Others have 4 SERCOMs + pin_num = int(pin_count) if pin_count.isdigit() else 0 + + if dev_upper.startswith("SAMD51"): + if pin_num >= 120: + return "SAMD51_120_SERIES" + else: + return "SAMD51_SERIES" + elif dev_upper.startswith("SAME51"): + return "SAME51_SERIES" + elif dev_upper.startswith("SAME53"): + return "SAME53_SERIES" + elif dev_upper.startswith("SAME54"): + if pin_num >= 120: + return "SAME54_120_SERIES" + else: + return "SAME54_SERIES" + + return "UNKNOWN_SERIES" + + +def build_series_map() -> ( + Dict[str, Tuple[List[str], List[Tuple[str, Tuple[int, int, int, int]]]]] +): + """ + Build series -> (devices, pins) mapping. + + Returns: { + "SAMD21E_SERIES": ([device_names], [(pin, (s0, p0, s1, p1))]) + } + """ + series_map: Dict[ + str, Tuple[List[str], List[Tuple[str, Tuple[int, int, int, int]]]] + ] = {} + + # Load device-to-package mappings (with skip_rows for header lines) + device_pins_samd21 = load_device_pin_map( + [ + (SAMD21_TABLE_2_1, 2), # Skip 2 header rows + (SAMD21_TABLE_2_2, 1), # Skip 1 header row + ] + ) + device_pins_samd51 = load_device_pin_map( + [ + (SAMD51_TABLE_1_1, 2), # Skip 2 header rows (table title + subheader) + (SAMD51_TABLE_1_2, 2), # Skip 2 header rows (table title + subheader) + ] + ) + + # Load I2C pin configurations by package + i2c_pins_samd21 = load_i2c_pins_samd21(SAMD21_TABLE_7_5) + i2c_pins_samd51 = load_i2c_pins_samd51(SAMD51_TABLE_6_8) + + # Load pin mux configurations (SERCOM mappings from columns C/D) + pin_mux_samd21 = load_pin_mux_configs( + ROOT / "docs" / "SAMD21_Table_7-1.csv", is_samd51=False + ) + pin_mux_samd51 = load_pin_mux_configs( + ROOT / "docs" / "SAMD51_Table_6-1.csv", is_samd51=True + ) + + # Process SAMD21/DA1 devices + for device, pin_counts in device_pins_samd21.items(): + pin_count = list(pin_counts)[0] # Get the pin count + series = classify_device_series(device, pin_count) + + if series not in series_map: + series_map[series] = ([], []) + + series_map[series][0].append(device) + + # Get I2C pins for this package size + if pin_count in i2c_pins_samd21: + pin_list = i2c_pins_samd21[pin_count] + # Resolve SERCOM configs from mux table + resolved_pins = [] + for pin_name in pin_list: + if pin_name in pin_mux_samd21: + resolved_pins.append((pin_name, pin_mux_samd21[pin_name])) + + # Only set pins for the first device in series (all same series share pins) + if not series_map[series][1]: + series_map[series] = (series_map[series][0], resolved_pins) + + # Process SAMD51/E5x devices + for device, pin_counts in device_pins_samd51.items(): + pin_count = list(pin_counts)[0] + series = classify_device_series(device, pin_count) + + if series not in series_map: + series_map[series] = ([], []) + + series_map[series][0].append(device) + + # Get I2C pins for this package size + if pin_count in i2c_pins_samd51: + pin_list = i2c_pins_samd51[pin_count] + # Resolve SERCOM configs from mux table + resolved_pins = [] + for pin_name in pin_list: + if pin_name in pin_mux_samd51: + resolved_pins.append((pin_name, pin_mux_samd51[pin_name])) + + # Only set pins for the first device in series + if not series_map[series][1]: + series_map[series] = (series_map[series][0], resolved_pins) + + return series_map + + +def format_series_macro(series_name: str, device_macros: List[str]) -> List[str]: + """ + Format a series macro with intelligent grouping. + + For SAMD21, group by numeric suffix (15, 16, 17, 18). + For SAMD51/E5x, group by letter prefix (G, J, N, P). + + Returns list of lines including #ifndef, #define, content lines, #endif. + """ + lines: List[str] = [] + lines.append(f"#ifndef {series_name}") + + if len(device_macros) == 1: + lines.append(f"#define {series_name} (defined({device_macros[0]}))") + else: + # Group devices by their variant (numeric for SAMD21, letter for SAMD51/E5x) + groups: Dict[str, List[str]] = {} + + for macro in sorted(device_macros): + # Extract the device name from __DEVICENAME__ + device_name = macro.strip("_") + + # For SAMD21/DA1: extract numeric suffix (15, 16, 17, 18, 14, etc.) + # Examples: SAMD21E15A -> "15", SAMDA1E14B -> "14", SAMD21J18A -> "18" + # Match: (SAMD21|SAMDA1)([DEGJ]?)(\d+) + if "SAMD21" in device_name or "SAMDA1" in device_name: + # Extract the number that appears after the family letter (E, G, J) + match = re.search(r"(SAMD21|SAMDA1)([DEGJ]?)(\d+)", device_name) + if match: + suffix = match.group(3) # "15", "16", "14", etc. + else: + suffix = "unknown" + # For SAMD51/E5x: extract letter prefix (G, J, N, P) + # Examples: SAMD51G18 -> "G", SAME51J19 -> "J" + else: + match = re.search(r"(SAM[DE]\d+)([A-Z])\d+", device_name) + if match: + suffix = match.group(2) # "G", "J", "N", etc. + else: + suffix = "unknown" + + if suffix not in groups: + groups[suffix] = [] + groups[suffix].append(macro) + + # Sort groups by their key (numeric or letter order) + sorted_groups = sorted(groups.items(), key=lambda x: x[0]) + + # Build macro output with proper line continuations + output_lines = [] + for group_idx, (suffix, macros) in enumerate(sorted_groups): + is_last = group_idx == len(sorted_groups) - 1 + group_line = " || ".join(f"defined({m})" for m in macros) + + if group_idx == 0: + # First group: start the macro + if is_last: + # Only one group + output_lines.append(f"#define {series_name} ({group_line})") + else: + # Multiple groups + output_lines.append(f"#define {series_name} ({group_line} || \\") + else: + # Continuation lines + if is_last: + # Last group + output_lines.append(f" {group_line})") + else: + # Middle group + output_lines.append(f" {group_line} || \\") + + lines.extend(output_lines) + + lines.append("#endif") + return lines + + +def emit_inc( + series_map: Dict[ + str, Tuple[List[str], List[Tuple[str, Tuple[int, int, int, int]]]] + ], +) -> str: + """Generate the WirePinMux.inc file content.""" + lines: List[str] = [] + lines.append( + "// Auto-generated from SAMD21/SAMD51 device tables. Do not edit manually.\n" + ) + + # Define custom ordering for series (moved here to use in sorting) + series_order = { + "SAMD21E_SERIES": 0, + "SAMD21G_SERIES": 1, + "SAMD21J_SERIES": 2, + "SAMDA1_SERIES": 3, + "SAMD51_120_SERIES": 4, + "SAMD51_SERIES": 5, + "SAME51_SERIES": 5, + "SAME53_SERIES": 5, + "SAME54_SERIES": 5, + "SAME54_120_SERIES": 6, + } + + # Emit series macro definitions in custom order + for series_name in sorted( + series_map.keys(), key=lambda s: (series_order.get(s, 999), s) + ): + devices, _ = series_map[series_name] + + # Build the macro definition + device_checks = [] + for dev in sorted(set(devices)): + device_checks.extend(macro_candidates(dev)) + + # Remove duplicates while preserving order + seen = set() + unique_checks = [] + for check in device_checks: + if check not in seen: + seen.add(check) + unique_checks.append(check) + + # Use the intelligent grouping formatter + lines.extend(format_series_macro(series_name, unique_checks)) + lines.append("") # Add blank line between series + + lines.append("") + + # Fallback: if a TARGET_* flag is set but no series macro matches, default to the base series + lines.append( + "#if defined(TARGET_SAMD21) && !(SAMD21E_SERIES || SAMD21G_SERIES || SAMD21J_SERIES || SAMDA1_SERIES)" + ) + lines.append("#define SAMD21E_SERIES 1") + lines.append("#endif") + lines.append( + "#if defined(TARGET_SAMD51) && !(SAMD51_SERIES || SAMD51_120_SERIES || SAME51_SERIES || SAME53_SERIES || SAME54_SERIES || SAME54_120_SERIES)" + ) + lines.append("#define SAMD51_SERIES 1") + lines.append("#endif") + lines.append("") + + # Group series by their pin configuration + pin_config_groups: Dict[Tuple, List[str]] = {} + for series_name, (_, pins) in series_map.items(): + pin_tuple = tuple((p, s0, p0, s1, p1) for p, (s0, p0, s1, p1) in pins) + if pin_tuple not in pin_config_groups: + pin_config_groups[pin_tuple] = [] + pin_config_groups[pin_tuple].append(series_name) + + # Sort by series order + sorted_groups = sorted( + pin_config_groups.items(), + key=lambda x: (series_order.get(sorted(x[1])[0], 999), sorted(x[1])[0]), + ) + + # Emit pin configurations with guards + for pin_config, series_list in sorted_groups: + # Build guard expression + guard_expr = " || ".join(sorted(series_list)) + lines.append(f"#if {guard_expr}") + + # Emit I2C_PIN macros + for pin_name, s0, p0, s1, p1 in pin_config: + lines.append(f"I2C_PIN({pin_name}, {s0}, {p0}, {s1}, {p1})") + + lines.append("#endif\n") + + return "\n".join(lines) + + +def main() -> None: + """Main entry point.""" + series_map = build_series_map() + inc_text = emit_inc(series_map) + OUTPUT.write_text(inc_text) + + total_devices = sum(len(devices) for devices, _ in series_map.values()) + print( + f"Wrote {len(series_map)} series macros covering {total_devices} devices to {OUTPUT}" + ) + + +if __name__ == "__main__": + main() From 12f886b564e66e5fad3430bb47e926597fc5df33 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 23:33:44 -0600 Subject: [PATCH 23/24] Fix CI build errors - 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() --- cores/arduino/SERCOM.cpp | 14 +++++++------- libraries/SPI/SPI.cpp | 2 -- libraries/Wire/Wire.h | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index cb53055f5..dac063e72 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -844,11 +844,11 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if (_wire.retryCount < kMaxWireRetries) { ++_wire.retryCount; - sercom->I2CM.STATUS.bit.BUSSTATE = 1; - while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; + sercom->I2CM.STATUS.bit.BUSSTATE = 1; + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; startTransmissionWIRE(); - return txn; + return txn; } } @@ -856,11 +856,11 @@ SercomTxn* SERCOM::stopTransmissionWIRE( SercomWireError error ) if (_wire.retryCount < kMaxWireRetries) { ++_wire.retryCount; - sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag - sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; + sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag + sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; startTransmissionWIRE(); - return txn; + return txn; } } @@ -1338,7 +1338,7 @@ void SERCOM::clearPads(uint8_t sercomId) void SERCOM::setPending(uint8_t sercomId) { - if (sercomId >= kSercomCount || sercomId < 0) + if (sercomId >= kSercomCount) return; __disable_irq(); diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index be50ba042..b9d646ac2 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -288,8 +288,6 @@ void SPIClass::onService(void) return; } - SercomTxn* txn = _p_sercom->getCurrentTxnSPI(); - if (flags & SERCOM_SPI_INTFLAG_RXC) { // Read completes after write, so read previous byte bool hasMore = _p_sercom->readDataSPI(); diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 02636fafa..730bf17c0 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -41,8 +41,8 @@ class TwoWire : public Stream public: TwoWire(SERCOM *s, uint8_t pinSDA, uint8_t pinSCL); void begin(); - void begin(uint16_t, bool enableGeneralCall = false, uint8_t speed = 0x0, bool enable10Bit = false); void begin(uint8_t, bool enableGeneralCall = false); + void begin(uint16_t, bool enableGeneralCall, uint8_t speed = 0x0, bool enable10Bit = false); void end(); void setClock(uint32_t); From 694b1d2fdd02ed1f9a6cb86199762c7bb88805dc Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Mon, 16 Feb 2026 23:40:05 -0600 Subject: [PATCH 24/24] Make SPI interrupt handlers weak and fix example ambiguity - 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 --- libraries/SPI/SPI.cpp | 10 ++++++++++ .../Wire/examples/slave_receiver/slave_receiver.ino | 2 +- libraries/Wire/examples/slave_sender/slave_sender.ino | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index b9d646ac2..216941e66 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -364,6 +364,7 @@ void SPIClass::detachInterrupt() { #ifndef SPI_IT_HANDLER #define SPI_IT_HANDLER SERCOM4_Handler #endif + void SPI_IT_HANDLER(void) __attribute__ ((weak)); void SPI_IT_HANDLER(void) { SPI.onService(); } #if defined(__SAMD51__) @@ -373,6 +374,10 @@ void SPIClass::detachInterrupt() { #define SPI_IT_HANDLER_2 SERCOM4_2_Handler #define SPI_IT_HANDLER_3 SERCOM4_3_Handler #endif + void SPI_IT_HANDLER_0(void) __attribute__ ((weak)); + void SPI_IT_HANDLER_1(void) __attribute__ ((weak)); + void SPI_IT_HANDLER_2(void) __attribute__ ((weak)); + void SPI_IT_HANDLER_3(void) __attribute__ ((weak)); void SPI_IT_HANDLER_0(void) { SPI.onService(); } void SPI_IT_HANDLER_1(void) { SPI.onService(); } void SPI_IT_HANDLER_2(void) { SPI.onService(); } @@ -383,10 +388,15 @@ void SPIClass::detachInterrupt() { SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX); #if defined(SPI1_IT_HANDLER) + void SPI1_IT_HANDLER(void) __attribute__ ((weak)); void SPI1_IT_HANDLER(void) { SPI1.onService(); } #endif #if defined(__SAMD51__) && defined(SPI1_IT_HANDLER_0) + void SPI1_IT_HANDLER_0(void) __attribute__ ((weak)); + void SPI1_IT_HANDLER_1(void) __attribute__ ((weak)); + void SPI1_IT_HANDLER_2(void) __attribute__ ((weak)); + void SPI1_IT_HANDLER_3(void) __attribute__ ((weak)); void SPI1_IT_HANDLER_0(void) { SPI1.onService(); } void SPI1_IT_HANDLER_1(void) { SPI1.onService(); } void SPI1_IT_HANDLER_2(void) { SPI1.onService(); } diff --git a/libraries/Wire/examples/slave_receiver/slave_receiver.ino b/libraries/Wire/examples/slave_receiver/slave_receiver.ino index a3103f8b9..5660ac678 100644 --- a/libraries/Wire/examples/slave_receiver/slave_receiver.ino +++ b/libraries/Wire/examples/slave_receiver/slave_receiver.ino @@ -14,7 +14,7 @@ void setup() { - Wire.begin(4); // join i2c bus with address #4 + Wire.begin((uint8_t)4); // join i2c bus with address #4 Wire.onReceive(receiveEvent); // register event Serial.begin(9600); // start serial for output } diff --git a/libraries/Wire/examples/slave_sender/slave_sender.ino b/libraries/Wire/examples/slave_sender/slave_sender.ino index d3b238af9..34f44be9d 100644 --- a/libraries/Wire/examples/slave_sender/slave_sender.ino +++ b/libraries/Wire/examples/slave_sender/slave_sender.ino @@ -14,7 +14,7 @@ void setup() { - Wire.begin(2); // join i2c bus with address #2 + Wire.begin((uint8_t)2); // join i2c bus with address #2 Wire.onRequest(requestEvent); // register event }