diff --git a/cores/arduino/RingBuffer.h b/cores/arduino/RingBuffer.h index deecb733c..3c6d1b95c 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 -RingBufferN::RingBufferN( void ) +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); } diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index f945a847f..dac063e72 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 @@ -30,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 @@ -50,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 * ========================= @@ -59,6 +79,16 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint initClockNVIC(); resetUART(); +#ifdef USE_ZERODMA + int8_t id = getSercomIndex(); + if (id >= 0) { + dmaSetCallbacks(SERCOM::dmaTxCallbackUART, SERCOM::dmaRxCallbackUART); + dmaInit(id); + } +#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); @@ -86,6 +116,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 @@ -112,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() @@ -217,14 +233,121 @@ 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 + int8_t id = getSercomIndex(); + if (id >= 0) { + dmaSetCallbacks(SERCOM::dmaTxCallbackSPI, SERCOM::dmaRxCallbackSPI); + dmaInit(id); + } +#endif // USE_ZERODMA + + registerService(getSercomIndex(), &SERCOM::stopTransmissionSPI); #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode @@ -246,6 +369,95 @@ 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::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 @@ -271,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) @@ -353,16 +539,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() //{ @@ -391,408 +569,810 @@ 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); + clearQueueWIRE(); // Drain pending transactions from queue + resetSERCOM(); // SWRST: hardware reset to default state + _wire = WireConfig{}; // Reset software state } -void SERCOM::enableWIRE() +void SERCOM::clearQueueWIRE(void) { - // 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; - } - - // 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 + // 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 } + + // 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; } -void SERCOM::disableWIRE() +void SERCOM::initWIRE(void) { - // I2C Master and Slave modes share the ENABLE bit function. + if (_wire.inited) // If already initialized, return + return; - // Enable the I2C master mode - sercom->I2CM.CTRLA.bit.ENABLE = 0 ; + uint8_t idx = getSercomIndex(); + initClockNVIC(); + registerService(idx, static_cast(&SERCOM::stopTransmissionWIRE)); + +#ifdef USE_ZERODMA + dmaSetCallbacks(SERCOM::dmaTxCallbackWIRE, SERCOM::dmaRxCallbackWIRE); + if (idx >= 0) + dmaInit(idx); +#endif // USE_ZERODMA - while ( sercom->I2CM.SYNCBUSY.bit.ENABLE != 0 ) - { - // Waiting the enable bit from SYNCBUSY is equal to 0; - } + _wire.inited = true; // Mark as initialized last } -void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall ) +void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall, uint8_t speed ) { - // Initialize the peripheral clock and interruption - initClockNVIC() ; - resetWIRE() ; - - // Set slave mode - sercom->I2CS.CTRLA.bit.MODE = I2C_SLAVE_OPERATION; - - 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 the interrupt register - sercom->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_PREC | // Stop - SERCOM_I2CS_INTENSET_AMATCH | // Address Match - SERCOM_I2CS_INTENSET_DRDY ; // Data Ready + initSlaveWIRE( ucAddress & 0x7Fu, enableGeneralCall, speed, false ); +} - while ( sercom->I2CM.SYNCBUSY.bit.SYSOP != 0 ) - { - // Wait the SYSOP bit from SYNCBUSY to come back to 0 - } +void SERCOM::initSlaveWIRE( uint16_t ucAddress, bool enableGeneralCall, uint8_t speed, bool enable10Bit ) +{ + 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 + (enable10Bit ? SERCOM_I2CS_ADDR_TENBITEN : 0) | // 10-bit addressing + enableGeneralCall; // enable general call (address 0x00) + setSlaveWIRE(); } void SERCOM::initMasterWIRE( uint32_t baudrate ) { - // Initialize the peripheral clock and interruption - initClockNVIC() ; + initWIRE(); - resetWIRE() ; + setBaudrateWIRE(baudrate); + setMasterWIRE(); +} - // 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*/ ; +void SERCOM::registerReceiveWIRE(void (*cb)(void* user, int length), void* user) +{ + _wireDeferredCb = cb; + _wireDeferredUser = user; +} - // Enable Smart mode and Quick Command - //sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN /*| SERCOM_I2CM_CTRLB_QCEN*/ ; +void SERCOM::deferReceiveWIRE(int length) +{ + _wireDeferredLength = length; + _wireDeferredPending = true; + setPending((uint8_t)getSercomIndex()); +} +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(); + bool sclsm = (_wire.masterSpeed == 0x2); + 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 ); + 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; +} - // Enable all interrupts - // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; +void SERCOM::setSlaveWIRE(void) +{ + disableWIRE(); + bool sclsm = (_wire.slaveSpeed == 0x2); + sercom->I2CS.CTRLA.reg = SERCOM_I2CS_CTRLA_MODE(I2C_SLAVE_OPERATION) | + SERCOM_I2CS_CTRLA_SPEED(_wire.slaveSpeed) | + (sclsm ? SERCOM_I2CS_CTRLA_SCLSM : 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 + 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) - 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 - 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 ) + +SercomTxn* SERCOM::startTransmissionWIRE( void ) { - if(isMasterWIRE()) { - // Send a NACK - sercom->I2CM.CTRLB.bit.ACKACT = 1; - } else { - sercom->I2CS.CTRLB.bit.ACKACT = 1; + // 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; } -} -void SERCOM::prepareAckBitWIRE( void ) -{ - if(isMasterWIRE()) { - // Send an ACK - sercom->I2CM.CTRLB.bit.ACKACT = 0; - } else { - sercom->I2CS.CTRLB.bit.ACKACT = 0; + SercomTxn* 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; + 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 (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 (sercom->I2CM.CTRLB.bit.QCEN) { + stopTransmissionWIRE(SercomWireError::OTHER); + return nullptr; + } + + txn->config |= I2C_CFG_STOP; + setDmaWIRE(true); + } +#ifdef USE_ZERODMA + else { + setDmaWIRE(txn->length > 0 && txn->length < 256 && + (txn->config & I2C_CFG_STOP) && + !(txn->config & I2C_CFG_NODMA)); } -} -void SERCOM::prepareCommandBitsWire(uint8_t cmd) -{ - if(isMasterWIRE()) { - sercom->I2CM.CTRLB.bit.CMD = cmd; + if (isDmaWIRE()) + { + if (!_dmaConfigured) + dmaInit(getSercomIndex()); - while(sercom->I2CM.SYNCBUSY.bit.SYSOP) - { - // Waiting for synchronization + if (!_dmaConfigured || !_dmaTx || !_dmaRx) { + stopTransmissionWIRE(SercomWireError::OTHER); + return nullptr; } - } else { - sercom->I2CS.CTRLB.bit.CMD = cmd; + + 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; + sercom->I2CM.ADDR.reg = addrReg; // ADDR is write synchronized so just wait for the MB/SB to know when synced + + return txn; } -bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) +bool SERCOM::enqueueWIRE(SercomTxn* txn) { - // 7-bits address + 1-bits R/W - address = (address << 0x1ul) | flag; + if (txn == nullptr) + return false; + if (!_txnQueue.store(txn)) + return false; + if (!_wire.active) + return startTransmissionWIRE() != nullptr; + return true; +} - // 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( isBusBusyWIRE() || (isArbLostWIRE() && !isBusIdleWIRE()) || isBusUnknownWIRE() ) - { - return false; +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 = _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) ; + startTransmissionWIRE(); + + return txn; } } - // Send start and address - sercom->I2CM.INTFLAG.bit.ERROR = 1; - sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) | - ((sercom->I2CM.CTRLA.bit.SPEED == 0x2) ? SERCOM_I2CM_ADDR_HS : 0); + if (error == SercomWireError::ARBITRATION_LOST || error == SercomWireError::BUS_ERROR) { + if (_wire.retryCount < kMaxWireRetries) { + ++_wire.retryCount; - // Address Transmitted - if ( flag == WIRE_WRITE_FLAG ) // Write mode - { - 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; - } + sercom->I2CM.STATUS.bit.ARBLOST = 1; // Clear arbitration lost flag + sercom->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; + startTransmissionWIRE(); + + return txn; } } - else // Read mode - { - while( !sercom->I2CM.INTFLAG.bit.SB ) { - // Wait transmission complete - - // 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; - } - } - // Clean the 'Slave on Bus' flag, for further usage. - //sercom->I2CM.INTFLAG.bit.SB = 0x1ul; + if (error == SercomWireError::BUS_STATE_UNKNOWN || + error == SercomWireError::ARBITRATION_LOST || + error == SercomWireError::BUS_ERROR) { + _wire.retryCount = 0; } - //ACK received (0: ACK, 1: NACK) - if(sercom->I2CM.STATUS.bit.RXNACK) - { - return false; - } - else - { - return true; + 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) ; } -} -bool SERCOM::sendDataMasterWIRE(uint8_t data) -{ - //Send data - sercom->I2CM.INTFLAG.bit.ERROR = 1; - sercom->I2CM.DATA.bit.DATA = data; + // Callbacks are expected to run in non-ISR context (main loop/PendSV). + if (txn && txn->onComplete) + txn->onComplete(txn->user, static_cast(error)); - //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. - // 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(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); } } - //Problems on line? nack received? - if(sercom->I2CM.STATUS.bit.RXNACK) - return false; - else - return true; + _wire.retryCount = 0; + _wire.active = false; + _wire.currentTxn = nullptr; + + bool isMaster = isMasterWIRE(); + + if (_txnQueue.peek(next) && isMaster) + startTransmissionWIRE(); + else if (isMaster) + sercom->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENCLR_ERROR | + SERCOM_I2CM_INTENCLR_MB | + SERCOM_I2CM_INTENCLR_SB; + + return txn; } -bool SERCOM::sendDataSlaveWIRE(uint8_t data) +// Hardware metadata structure for SERCOM peripherals - private to this file +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) +// 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]; + 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_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_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_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_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_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_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_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_DMAC_ID_TX, SERCOM7_DMAC_ID_RX, (void*)&SERCOM7->I2CM.DATA.reg }, +#endif +}; + +#else // end if SAMD51 (prob SAMD21) +// SAMD21 has unified clock and single interrupt +struct SercomData { + Sercom *sercomPtr; + uint8_t clock; + IRQn_Type 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_DMAC_ID_TX, SERCOM4_DMAC_ID_RX, (void*)&SERCOM4->I2CM.DATA.reg }, +#endif +#if defined(SERCOM5) + { SERCOM5, GCM_SERCOM5_CORE, SERCOM5_IRQn, SERCOM5_DMAC_ID_TX, SERCOM5_DMAC_ID_RX, (void*)&SERCOM5->I2CM.DATA.reg }, +#endif +}; + +#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) { - //Send data - sercom->I2CS.DATA.bit.DATA = data; + if (sercomId >= kSercomCount) + return false; - //Problems on line? nack received? - if(!sercom->I2CS.INTFLAG.bit.DRDY || sercom->I2CS.STATUS.bit.RXNACK) + SercomState &state = s_states[sercomId]; + if (state.role != Role::None && state.role != role) return false; - else - return true; + + state.role = role; + return true; } -bool SERCOM::isMasterWIRE( void ) +void SERCOM::release(uint8_t sercomId) { - return sercom->I2CS.CTRLA.bit.MODE == I2C_MASTER_OPERATION; + if (sercomId >= kSercomCount) + return; + + 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); } -bool SERCOM::isSlaveWIRE( void ) +bool SERCOM::registerService(uint8_t sercomId, ServiceFn fn) { - return sercom->I2CS.CTRLA.bit.MODE == I2C_SLAVE_OPERATION; + if (sercomId >= kSercomCount) + return false; + + s_states[sercomId].service = fn; + return true; } -bool SERCOM::isBusIdleWIRE( void ) +#ifdef USE_ZERODMA +SERCOM::DmaStatus SERCOM::dmaInit(int8_t sercomId, uint8_t beatSize) { - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_IDLE_STATE; + if (_dmaConfigured) + return DmaStatus::Ok; + + // 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(); + 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); + + if (_dmaTxDesc == nullptr) + _dmaTxDesc = _dmaTx->addDescriptor(&_dmaDummy, dataReg, 0, (dma_beat_size)beatSize, true, false); + if (_dmaRxDesc == nullptr) + _dmaRxDesc = _dmaRx->addDescriptor(dataReg, &_dmaDummy, 0, (dma_beat_size)beatSize, false, true); + if (_dmaTxDesc == nullptr || _dmaRxDesc == nullptr) + { + _dmaLastError = DmaStatus::DescriptorFailed; + dmaRelease(); + return _dmaLastError; + } + + _dmaConfigured = true; + _dmaLastError = DmaStatus::Ok; + return _dmaLastError; } -bool SERCOM::isBusOwnerWIRE( void ) +void SERCOM::dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb) { - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_OWNER_STATE; + _dmaTxCb = txCb; + _dmaRxCb = rxCb; + + if (_dmaConfigured) + { + if (_dmaTxCb) + _dmaTx->setCallback(_dmaTxCb); + if (_dmaRxCb) + _dmaRx->setCallback(_dmaRxCb); + } } -bool SERCOM::isBusUnknownWIRE( void ) +SERCOM::DmaStatus SERCOM::dmaStartTx(const void* src, volatile void* dstReg, size_t len) { - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_UNKNOWN_STATE; + 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; } -bool SERCOM::isArbLostWIRE( void ) +SERCOM::DmaStatus SERCOM::dmaStartRx(void* dst, volatile void* srcReg, size_t len) { - return sercom->I2CM.STATUS.bit.ARBLOST == 1; + 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; } -bool SERCOM::isBusBusyWIRE( void ) +SERCOM::DmaStatus SERCOM::dmaStartDuplex(const void* txSrc, void* rxDst, volatile void* txReg, volatile void* rxReg, size_t len, + const uint8_t* dummyTx) { - return sercom->I2CM.STATUS.bit.BUSSTATE == WIRE_BUSY_STATE; + 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; } -bool SERCOM::isDataReadyWIRE( void ) +void SERCOM::dmaAbortTx() { - return sercom->I2CS.INTFLAG.bit.DRDY; + if (_dmaTx) + _dmaTx->abort(); + _dmaTxActive = false; } -bool SERCOM::isStopDetectedWIRE( void ) +void SERCOM::dmaAbortRx() { - return sercom->I2CS.INTFLAG.bit.PREC; + if (_dmaRx) + _dmaRx->abort(); + _dmaRxActive = false; } -bool SERCOM::isRestartDetectedWIRE( void ) +void SERCOM::dmaRelease() { - return sercom->I2CS.STATUS.bit.SR; + 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; } -bool SERCOM::isAddressMatch( void ) +void SERCOM::dmaResetDescriptors() { - return sercom->I2CS.INTFLAG.bit.AMATCH; + _dmaTxDesc = nullptr; + _dmaRxDesc = nullptr; } -bool SERCOM::isMasterReadOperationWIRE( void ) +bool SERCOM::dmaTxBusy() const { - return sercom->I2CS.STATUS.bit.DIR; + return _dmaTxActive; } -bool SERCOM::isRXNackReceivedWIRE( void ) +bool SERCOM::dmaRxBusy() const { - return sercom->I2CM.STATUS.bit.RXNACK; + return _dmaRxActive; } -int SERCOM::availableWIRE( void ) +SERCOM::DmaStatus SERCOM::dmaLastError() const { - if(isMasterWIRE()) - return sercom->I2CM.INTFLAG.bit.SB; - else - return sercom->I2CS.INTFLAG.bit.DRDY; + return _dmaLastError; } +#endif -uint8_t SERCOM::readDataWIRE( void ) +#ifdef SERCOM_STRICT_PADS +bool SERCOM::registerPads(uint8_t sercomId, const PadFunc (&pads)[4], bool muxFunctionD) { - if(isMasterWIRE()) + if (sercomId >= kSercomCount) + return false; + + SercomState &state = s_states[sercomId]; + for (size_t i = 0; i < 4; ++i) { - 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; - } - } + 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; - return sercom->I2CM.DATA.bit.DATA ; + 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; } - else + if (any) { - return sercom->I2CS.DATA.reg ; + state.padsConfigured = true; + state.muxFunctionD = muxFunctionD; } + return true; } -#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) +void SERCOM::clearPads(uint8_t sercomId) +{ + if (sercomId >= kSercomCount) + return; -static const struct { - Sercom *sercomPtr; - uint8_t id_core; - uint8_t id_slow; - IRQn_Type irq[4]; -} sercomData[] = { - { SERCOM0, SERCOM0_GCLK_ID_CORE, SERCOM0_GCLK_ID_SLOW, - SERCOM0_0_IRQn, SERCOM0_1_IRQn, SERCOM0_2_IRQn, SERCOM0_3_IRQn }, - { SERCOM1, SERCOM1_GCLK_ID_CORE, SERCOM1_GCLK_ID_SLOW, - SERCOM1_0_IRQn, SERCOM1_1_IRQn, SERCOM1_2_IRQn, SERCOM1_3_IRQn }, - { SERCOM2, SERCOM2_GCLK_ID_CORE, SERCOM2_GCLK_ID_SLOW, - SERCOM2_0_IRQn, SERCOM2_1_IRQn, SERCOM2_2_IRQn, SERCOM2_3_IRQn }, - { SERCOM3, SERCOM3_GCLK_ID_CORE, SERCOM3_GCLK_ID_SLOW, - SERCOM3_0_IRQn, SERCOM3_1_IRQn, SERCOM3_2_IRQn, SERCOM3_3_IRQn }, - { SERCOM4, SERCOM4_GCLK_ID_CORE, SERCOM4_GCLK_ID_SLOW, - SERCOM4_0_IRQn, SERCOM4_1_IRQn, SERCOM4_2_IRQn, SERCOM4_3_IRQn }, - { SERCOM5, SERCOM5_GCLK_ID_CORE, SERCOM5_GCLK_ID_SLOW, - SERCOM5_0_IRQn, SERCOM5_1_IRQn, SERCOM5_2_IRQn, SERCOM5_3_IRQn }, -#if defined(SERCOM6) - { SERCOM6, SERCOM6_GCLK_ID_CORE, SERCOM6_GCLK_ID_SLOW, - SERCOM6_0_IRQn, SERCOM6_1_IRQn, SERCOM6_2_IRQn, SERCOM6_3_IRQn }, -#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 }, -#endif -}; + 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 -#else // end if SAMD51 (prob SAMD21) +void SERCOM::setPending(uint8_t sercomId) +{ + if (sercomId >= kSercomCount) + return; -static const struct { - 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, -#if defined(SERCOM4) - SERCOM4, GCM_SERCOM4_CORE, SERCOM4_IRQn, -#endif -#if defined(SERCOM5) - SERCOM5, GCM_SERCOM5_CORE, SERCOM5_IRQn, -#endif -}; + __disable_irq(); + s_pendingMask |= (1u << sercomId); + __enable_irq(); + __DMB(); + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; +} -#endif // end !SAMD51 +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; + SERCOM* inst = s_instances[i]; + if (fn && inst) + (inst->*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++) { diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 35c90f1c4..eeadf05f4 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -20,6 +20,13 @@ #define _SERCOM_CLASS_ #include "sam.h" +#include "SERCOM_Txn.h" +#include "RingBuffer.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 @@ -35,6 +42,10 @@ #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, @@ -169,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) ; @@ -176,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 ) ; @@ -191,13 +211,18 @@ 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) ; 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) ; @@ -207,37 +232,76 @@ class SERCOM bool isDataRegisterEmptySPI( void ) ; bool isTransmitCompleteSPI( void ) ; bool isReceiveCompleteSPI( void ) ; + bool enqueueSPI(SercomTxn* txn); + bool startTransmissionSPI(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) ; + 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); + 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 enableWIRE( void ) ; - void disableWIRE( void ); - 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) ; - bool isMasterWIRE( void ) ; - 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 ) ; - uint8_t readDataWIRE( void ) ; - int8_t getSercomIndex(void); - uint32_t getSercomFreqRef(void); + void clearQueueWIRE( void ) ; + inline void enableWIRE( void ) ; + inline void disableWIRE( void ) { disableSERCOM(); } + void setBaudrateWIRE(uint32_t baudrate) ; + 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 bool sendDataWIRE( 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 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 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; } + inline Sercom* getSercom() const { return sercom; } + 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 // SAMD51 (not 21) ... but these functions are declared @@ -252,17 +316,175 @@ 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 - - private: - Sercom *sercom; - uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource +#endif // SAMD51 vs 21 + + enum class Role : uint8_t { None = 0, UART, SPI, I2C }; + using ServiceFn = SercomTxn* (SERCOM::*)(); + + 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); + +#ifdef USE_ZERODMA + using DmaCallback = void (*)(Adafruit_ZeroDMA*); + enum class DmaStatus : uint8_t { + Ok = 0, + NotConfigured, + NullPtr, + ZeroLen, + AllocateFailed, + DescriptorFailed, + StartFailed + }; + + DmaStatus dmaInit(int8_t sercomId, uint8_t beatSize = 0); // beatSize: 0=byte (default), 1=halfword, 2=word + void dmaSetCallbacks(DmaCallback txCb, DmaCallback rxCb); + 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(); + void dmaAbortTx(); + void dmaAbortRx(); + 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); + // --- SPI DMA callbacks (protocol-owned) --- + static inline void dmaTxCallbackSPI(Adafruit_ZeroDMA* dma); + static inline void dmaRxCallbackSPI(Adafruit_ZeroDMA* dma); + // --- 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 { + 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 - uint8_t calculateBaudrateSynchronous(uint32_t baudrate); + +#if defined(SERCOM_INST_NUM) && (SERCOM_INST_NUM > 0) + static constexpr size_t kSercomCount = SERCOM_INST_NUM; +#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 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 ) ; + void initWIRE(void) ; + + // 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 = 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; + 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; + + 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; + int _wireDeferredLength = 0; + bool _wireDeferredPending = false; + +#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 _dmaDummy = 0; + uint8_t _dmaTxTrigger = 0; + uint8_t _dmaRxTrigger = 0; + bool _dmaConfigured = false; + bool _dmaTxActive = false; + bool _dmaRxActive = false; + DmaStatus _dmaLastError = DmaStatus::Ok; +#endif }; +#include "SERCOM_inline.h" + #endif diff --git a/cores/arduino/SERCOM_Txn.h b/cores/arduino/SERCOM_Txn.h new file mode 100644 index 000000000..ca6365c52 --- /dev/null +++ b/cores/arduino/SERCOM_Txn.h @@ -0,0 +1,106 @@ +#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; +}; + +// 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 +}; + +// SPI error reporting (async callbacks) +enum class SercomSpiError : uint8_t +{ + SUCCESS = 0, + BUF_OVERFLOW = 1, + 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_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); } + +// 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 diff --git a/cores/arduino/SERCOM_inline.h b/cores/arduino/SERCOM_inline.h new file mode 100644 index 000000000..9f6502ff9 --- /dev/null +++ b/cores/arduino/SERCOM_inline.h @@ -0,0 +1,276 @@ +#ifndef SERCOM_INLINE_H +#define SERCOM_INLINE_H + +#ifdef __cplusplus + +inline void SERCOM::enableSERCOM( void ) +{ + // 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::deferStopWIRE(SercomWireError error) +{ + _wire.returnValue = error; + setPending((uint8_t)getSercomIndex()); +} + +inline bool SERCOM::sendDataWIRE( void ) +{ + SercomTxn* txn = _wire.currentTxn; + if (txn == nullptr || txn->txPtr == nullptr) return false; + +#ifdef USE_ZERODMA + if (isDmaWIRE()) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaTxActive && !_dmaRxActive) + value = dmaStartTx(txn->txPtr, &sercom->I2CM.DATA.reg, _wire.txnLength); + return value == DmaStatus::Ok; + } +#endif + + // Wait for DATA to sync out of the ISR and clear MB + sercom->I2CM.DATA.reg = txn->txPtr[_wire.txnIndex++]; + + // 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::prepareCommandBitsWIRE(uint8_t cmd) +{ + sercom->I2CM.CTRLB.bit.CMD = cmd; + if (isMasterWIRE()) + while (sercom->I2CM.SYNCBUSY.bit.SYSOP) ; // Waiting for synchronization +} + +inline bool SERCOM::readDataWIRE( void ) +{ + SercomTxn* txn = _wire.currentTxn; + 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 + } + 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 (accesses auto-trigger bus operation based on ACKACT/SMEN) + txn->rxPtr[_wire.txnIndex++] = sercom->I2CM.DATA.reg; + + 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 (_spi.useDma) { + DmaStatus value = DmaStatus::StartFailed; + if (!_dmaTxActive && !_dmaRxActive) + value = dmaStartTx(txn->txPtr, &sercom->SPI.DATA.reg, _spi.length); + return value == DmaStatus::Ok; + } +#endif + + // Byte-by-byte: Write DATA register + sercom->SPI.DATA.bit.DATA = txn->txPtr[_spi.index++]; + + // Return false when last byte consumed so caller can complete transaction + return (_spi.index < _spi.length); +} + +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) +{ + _wire.currentTxn = txn; + _wire.txnIndex = 0; + + 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 +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 void SERCOM::dmaTxCallbackWIRE(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, true); + if (!inst) return; + + // 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; + inst->deferStopWIRE(SercomWireError::SUCCESS); +} + +inline void SERCOM::dmaRxCallbackWIRE(Adafruit_ZeroDMA* dma) +{ + SERCOM* inst = findDmaOwner(dma, false); + 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. + // The STOP is generated by hardware, not by software CMD write. + + inst->_wire.txnIndex = inst->_wire.txnLength; + inst->_dmaRxActive = false; + inst->deferStopWIRE(SercomWireError::SUCCESS); +} + +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->_dmaTxActive = false; + 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->_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 + +#endif // SERCOM_INLINE_H diff --git a/cores/arduino/Uart.cpp b/cores/arduino/Uart.cpp index dd797da4d..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,6 +191,138 @@ size_t Uart::write(const uint8_t data) return 1; } +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 + 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 + // 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 + 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; + + 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) { + // Synchronous path: read from ring buffer + size_t readCount = 0; + while (readCount < size) { + int c = read(); + if (c >= 0) + buffer[readCount++] = static_cast(c); + } + return readCount; + } + +#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; + + 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)) { + // Enqueue failed; restore RXC interrupt and clear pending state + 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 +} + +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) + 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..6f59fddf9 100644 --- a/cores/arduino/Uart.h +++ b/cores/arduino/Uart.h @@ -36,8 +36,16 @@ 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); + // 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(); @@ -59,6 +67,20 @@ class Uart : public HardwareSerial uint32_t ul_pinMaskRTS; uint8_t uc_pinCTS; + volatile bool txnDone = false; + volatile int txnStatus = 0; + + // 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; + void* pendingRxUser = nullptr; + SercomNumberStopBit extractNbStopBit(uint16_t config); SercomUartCharSize extractCharSize(uint16_t config); SercomParityMode extractParity(uint16_t config); 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 diff --git a/libraries/Adafruit_TinyUSB_Arduino b/libraries/Adafruit_TinyUSB_Arduino index 3e5369cd4..c953968c4 160000 --- a/libraries/Adafruit_TinyUSB_Arduino +++ b/libraries/Adafruit_TinyUSB_Arduino @@ -1 +1 @@ -Subproject commit 3e5369cd4720c63d9caf31db6ca4c18ce7bcd0b7 +Subproject commit c953968c468218d8968138b5ed8482fa565373df diff --git a/libraries/Adafruit_ZeroDMA b/libraries/Adafruit_ZeroDMA index 5b48867c1..acc5dadb4 160000 --- a/libraries/Adafruit_ZeroDMA +++ b/libraries/Adafruit_ZeroDMA @@ -1 +1 @@ -Subproject commit 5b48867c1d3015ec63cd8f31def45f5a2d8e49bd +Subproject commit acc5dadb458b2c329757a61dc4f18dda945a0c36 diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 0393752d7..216941e66 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() @@ -58,9 +61,9 @@ void SPIClass::begin() initialized = true; } - if(!use_dma) { - dmaAllocate(); - } +#ifdef USE_ZERODMA + _p_sercom->dmaInit(_p_sercom->getSercomIndex()); +#endif // PIO init pinPeripheral(_uc_pinMiso, g_APinDescription[_uc_pinMiso].ulPinType); @@ -240,131 +243,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; - } - } + 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 (onComplete) + onComplete(user, static_cast(SercomSpiError::UNKNOWN_ERROR)); + return; + } - // Set up DMA transfer job(s) ------------------------------------------ + if (!onComplete && block) { + while (!txnDone) ; + } +} - 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); - } +void SPIClass::onService(void) +{ + // SPI interrupt service handler - moved from SERCOM::serviceSPI() + if (!_p_sercom->isActiveSPI() || _p_sercom->getCurrentTxnSPI() == nullptr) + return; - // Run DMA jobs, blocking if requested --------------------------------- + uint8_t flags = _p_sercom->getINTFLAG(); - 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 (flags & SERCOM_SPI_INTFLAG_ERROR) + { + _p_sercom->setReturnValueSPI(SercomSpiError::BUF_OVERFLOW); + _p_sercom->clearINTFLAG(); + _p_sercom->deferStopSPI(SercomSpiError::BUF_OVERFLOW); + return; + } - } 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); - } + 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); + } +} - } // end non-DMA +SercomTxn* SPIClass::allocateTxn() { + // Simple round-robin allocation from pool + SercomTxn* txn = &txnPool[txnPoolHead]; + txnPoolHead = (txnPoolHead + 1) % TXN_POOL_SIZE; + return txn; } -// Waits for a prior in-background DMA transfer to complete. +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 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; } @@ -481,61 +344,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. @@ -552,19 +360,102 @@ 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) __attribute__ ((weak)); + 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) __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(); } + 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) __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(); } + 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..1991fb9d2 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() @@ -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 @@ -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); @@ -139,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); @@ -169,17 +158,16 @@ 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; + + // 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); }; #if SPI_INTERFACES_COUNT > 0 diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index da9732c59..2fdf8d0a8 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -37,81 +37,134 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) this->_uc_pinSDA=pinSDA; this->_uc_pinSCL=pinSCL; transmissionBegun = false; + rxLength = 0; + rxIndex = 0; + masterIndex = 0; + awaitingAddressAck = false; + txnDone = false; + txnStatus = 0; + rxBufferPtr = rxBuffer; + rxBufferCapacity = WIRE_BUFFER_LENGTH; + txBufferCapacity = WIRE_BUFFER_LENGTH; + pendingReceive = false; + pendingReceiveLength = 0; + txnPoolHead = 0; } 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(); +} +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->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(); + sercom->resetWIRE(); // SWRST: resets hardware + clears state + drains queue } -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, 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) { + loader.rxPtr = rxBuffer; + loader.length = quantity; + } else { + loader.rxPtr = this->rxBuffer; + loader.length = ( quantity > WIRE_BUFFER_LENGTH) ? WIRE_BUFFER_LENGTH : quantity; } - size_t byteRead = 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; - rxBuffer.clear(); + // Enqueue the pool transaction, not the loader + if (!sercom->enqueueWIRE(txn)) + return 0; - if(sercom->startTransmissionWIRE(address, WIRE_READ_FLAG)) - { - // Read first data - rxBuffer.store_char(sercom->readDataWIRE()); - - // 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 + 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; - if (stopBit) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); // Send Stop + 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 byteRead; + return quantity; } -uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity) -{ - return requestFrom(address, quantity, 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; - txBuffer.clear(); +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; } @@ -121,81 +174,150 @@ 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 ; - // Start I2C transmission - if ( !sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - return 2 ; // Address error - } + // 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; + + // Enqueue the pool transaction, not the loader + if (!sercom->enqueueWIRE(txn)) + return static_cast(SercomWireError::QUEUE_FULL); + + 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(); + } - // 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: // OTHER + return 4; } } - - if (stopBit) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - } 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 || txBuffer.isFull() ) - { - return 0 ; - } + if (!transmissionBegun) + return 0; - txBuffer.store_char( ucData ) ; + // Check buffer full + if (loader.length >= txBufferCapacity) + return 0; - return 1 ; + // Initialize to internal buffer if first write + if (loader.txPtr == nullptr) + loader.txPtr = txBuffer; + + // 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; } -size_t TwoWire::write(const uint8_t *data, size_t quantity) +size_t TwoWire::write(const uint8_t *data, size_t quantity, bool setExternal) { - //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; + if (!transmissionBegun) + return 0; + + if (quantity == 0) + return 0; + + // 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; + } + + // 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; } 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,67 +336,78 @@ void TwoWire::onRequest(void(*function)(void)) onRequestCallback = function; } -void TwoWire::onService(void) +void TwoWire::setRxBuffer(uint8_t* buffer, size_t length) { - if ( sercom->isSlaveWIRE() ) + if (buffer == nullptr || length == 0) { - 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(); - } - else if(sercom->isAddressMatch()) //Address Match - { - sercom->prepareAckBitWIRE(); - sercom->prepareCommandBitsWire(0x03); - - if(sercom->isMasterReadOperationWIRE()) //Is a request ? - { - txBuffer.clear(); - - transmissionBegun = true; - - //Calling onRequestCallback, if exists - if(onRequestCallback) - { - onRequestCallback(); - } - } - } - 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(); - } - - sercom->prepareCommandBitsWire(0x03); - } - } + clearRxBuffer(); + return; + } + rxBufferPtr = buffer; + 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) + memset(rxBufferPtr, 0, rxBufferCapacity); + rxLength = 0; + rxIndex = 0; +} + +void TwoWire::resetRxBuffer(void) +{ + rxBufferPtr = rxBuffer; + rxBufferCapacity = WIRE_BUFFER_LENGTH; + clearRxBuffer(); +} + +uint8_t* TwoWire::getRxBuffer(void) +{ + return rxBufferPtr; +} + +size_t TwoWire::getRxLength(void) const +{ + return rxLength; +} + +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 @@ -375,4 +508,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..730bf17c0 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -25,28 +25,48 @@ #include "SERCOM.h" #include "RingBuffer.h" +#include // 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(uint8_t, bool enableGeneralCall = false); + void begin(uint16_t, bool enableGeneralCall, uint8_t speed = 0x0, bool enable10Bit = false); void end(); void setClock(uint32_t); void beginTransmission(uint8_t); - uint8_t endTransmission(bool stopBit); - 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). + uint8_t endTransmission(bool stopBit = true, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); - uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit); - uint8_t requestFrom(uint8_t address, size_t quantity); + // 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, bool stopBit = true, + uint8_t* rxBuffer = nullptr, + void (*onComplete)(void* user, int status) = nullptr, + void* user = nullptr); 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); @@ -54,6 +74,12 @@ 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 setTxBuffer(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); } @@ -61,7 +87,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; @@ -70,17 +96,39 @@ class TwoWire : public Stream bool transmissionBegun; - // RX Buffer - RingBufferN<256> rxBuffer; - - //TX buffer - RingBufferN<256> txBuffer; - uint8_t txAddress; + // RX/TX buffers (sync compatibility, async staging) + 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 txBufferCapacity; + size_t masterIndex; + bool awaitingAddressAck; + volatile bool txnDone; + volatile int txnStatus; + bool pendingReceive; + int pendingReceiveLength; + 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); 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; }; @@ -104,4 +152,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::slaveTxn for the transaction. + if(onRequestCallback) + onRequestCallback(); + + // Ensure callback actually set slaveTxn.length; if not, stall with 0-length txn + if (slaveTxn.length == 0) + return; + + 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; + slaveTxn.txPtr = nullptr; + slaveTxn.rxPtr = rxLength ? rxBufferPtr : rxBuffer; + slaveTxn.length = rxLength ? rxLength : WIRE_BUFFER_LENGTH; + slaveTxn.config = 0; + } + + sercom->setTxnWIRE(&slaveTxn); + + // 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 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 } 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() 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