From e0a883c22f844802ad5b3d6ab9e020eac269e511 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Tue, 13 Jan 2026 12:51:34 -0600 Subject: [PATCH 1/4] Unify SERCOM clock frequency handling across UART/SPI/WIRE --- cores/arduino/SERCOM.cpp | 109 ++++++++++++++++++++++++--------------- cores/arduino/SERCOM.h | 7 +-- 2 files changed, 71 insertions(+), 45 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 52b9ce9c7..5e2bf4d98 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -31,7 +31,7 @@ SERCOM::SERCOM(Sercom* s) { sercom = s; -#if defined(__SAMD51__) +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) // A briefly-available but now deprecated feature had the SPI clock source // set via a compile-time setting (MAX_SPI)...problem was this affected // ALL SERCOMs, whereas some (anything read/write, e.g. SD cards) should @@ -40,8 +40,8 @@ SERCOM::SERCOM(Sercom* s) // per-peripheral basis. Nonetheless, we check SERCOM_SPI_FREQ_REF here // (MAX_SPI * 2) to retain compatibility with any interim projects that // might have relied on the compile-time setting. But please, don't. - #if SERCOM_SPI_FREQ_REF == F_CPU // F_CPU clock = GCLK0 - clockSource = SERCOM_CLOCK_SOURCE_FCPU; +#if SERCOM_SPI_FREQ_REF == F_CPU // F_CPU clock = GCLK0 + clockSource = SERCOM_CLOCK_SOURCE_100M; #elif SERCOM_SPI_FREQ_REF == 48000000 // 48 MHz clock = GCLK1 (standard) clockSource = SERCOM_CLOCK_SOURCE_48M; #elif SERCOM_SPI_FREQ_REF == 100000000 // 100 MHz clock = GCLK2 @@ -80,11 +80,7 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint // Asynchronous fractional mode (Table 24-2 in datasheet) // BAUD = fref / (sampleRateValue * fbaud) // (multiply by 8, to calculate fractional piece) -#if defined(__SAMD51__) - uint32_t baudTimes8 = (SERCOM_FREQ_REF * 8) / (sampleRateValue * baudrate); -#else - uint32_t baudTimes8 = (SystemCoreClock * 8) / (sampleRateValue * baudrate); -#endif + uint32_t baudTimes8 = (freqRef * 8) / (sampleRateValue * baudrate); sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); @@ -230,8 +226,8 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch resetSPI(); initClockNVIC(); -#if defined(__SAMD51__) - sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) + sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode SERCOM_SPI_CTRLA_DOPO(mosi) | SERCOM_SPI_CTRLA_DIPO(miso) | dataOrder << SERCOM_SPI_CTRLA_DORD_Pos; @@ -322,13 +318,7 @@ SercomDataOrder SERCOM::getDataOrderSPI() void SERCOM::setBaudrateSPI(uint8_t divider) { disableSPI(); // Register is enable-protected - -#if defined(__SAMD51__) sercom->SPI.BAUD.reg = calculateBaudrateSynchronous(freqRef / divider); -#else - sercom->SPI.BAUD.reg = calculateBaudrateSynchronous(SERCOM_SPI_FREQ_REF / divider); -#endif - enableSPI(); } @@ -386,13 +376,11 @@ bool SERCOM::isDataRegisterEmptySPI() // return sercom->SPI.INTFLAG.bit.RXC; //} -uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate) { -#if defined(__SAMD51__) +uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate) +{ uint16_t b = freqRef / (2 * baudrate); -#else - uint16_t b = SERCOM_SPI_FREQ_REF / (2 * baudrate); -#endif - if(b > 0) b--; // Don't -1 on baud calc if already at 0 + if (b > 0) + b--; // Don't -1 on baud calc if already at 0 return b; } @@ -490,12 +478,10 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) // Enable all interrupts // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; - // Synchronous arithmetic baudrate -#if defined(__SAMD51__) - sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ; -#else - sercom->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); -#endif + if (sercom->I2CM.CTRLA.bit.SPEED == 0x2) + sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1; + else + sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - freqRef * WIRE_RISE_TIME_NANOSECONDS / (2 * 1e9f); } void SERCOM::prepareNackBitWIRE( void ) @@ -727,7 +713,7 @@ uint8_t SERCOM::readDataWIRE( void ) } } -#if defined(__SAMD51__) +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) static const struct { Sercom *sercomPtr; @@ -785,7 +771,47 @@ int8_t SERCOM::getSercomIndex(void) { return -1; } -#if defined(__SAMD51__) +uint32_t SERCOM::getSercomFreqRef(void) +{ +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) + int8_t idx = getSercomIndex(); + uint8_t gen = 1; // default to GCLK1 (48 MHz) if we can't resolve + + if (idx >= 0) + { + uint8_t pch = sercomData[idx].id_core; + gen = GCLK->PCHCTRL[pch].bit.GEN; + } + + switch (gen) + { + case 0: + freqRef = 100000000UL; + break; + case 1: + freqRef = 48000000UL; + break; + case 2: + freqRef = 100000000UL; + break; + case 3: + freqRef = 32768UL; + break; + case 4: + freqRef = 12000000UL; + break; + default: + freqRef = 48000000UL; + break; + } +#else + freqRef = SystemCoreClock; +#endif + + return freqRef; +} + +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) // This is currently for overriding an SPI SERCOM's clock source only -- // NOT for UART or WIRE SERCOMs, where it will have unintended consequences. // It does not check. @@ -804,16 +830,19 @@ void SERCOM::setClockSource(int8_t idx, SercomClockSource src, bool core) { if(core) clockSource = src; // Save SercomClockSource value // From cores/arduino/startup.c: - // GCLK0 = F_CPU + // GCLK0 = F_CPU (this is 120 MHz and exceeds SERCOM maximum) // GCLK1 = 48 MHz // GCLK2 = 100 MHz // GCLK3 = XOSC32K // GCLK4 = 12 MHz if(src == SERCOM_CLOCK_SOURCE_FCPU) { GCLK->PCHCTRL[clk_id].reg = - GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); - if(core) freqRef = F_CPU; // Save clock frequency value - } else if(src == SERCOM_CLOCK_SOURCE_48M) { + GCLK_PCHCTRL_GEN_GCLK2_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); // Guard Sercom from exceeding 100 MHz maximum + if (core) + freqRef = 100000000; // Save clock frequency value + } + else if (src == SERCOM_CLOCK_SOURCE_48M) + { GCLK->PCHCTRL[clk_id].reg = GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); if(core) freqRef = 48000000; @@ -840,7 +869,7 @@ void SERCOM::initClockNVIC( void ) int8_t idx = getSercomIndex(); if(idx < 0) return; // We got a problem here -#if defined(__SAMD51__) +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) for(uint8_t i=0; i<4; i++) { NVIC_ClearPendingIRQ(sercomData[idx].irq[i]); @@ -848,13 +877,7 @@ void SERCOM::initClockNVIC( void ) NVIC_EnableIRQ(sercomData[idx].irq[i]); } - // SPI DMA speed is dictated by the "slow clock" (I think...maybe) so - // BOTH are set to the same clock source (clk_slow isn't sourced from - // XOSC32K as in prior versions of SAMD core). - // This might have power implications for sleep code. - - setClockSource(idx, clockSource, true); // true = core clock - setClockSource(idx, clockSource, false); // false = slow clock + setClockSource(idx, clockSource, true); // true = core clock #else // end if SAMD51 (prob SAMD21) @@ -875,4 +898,6 @@ void SERCOM::initClockNVIC( void ) while(GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); // Wait for synchronization #endif // end !SAMD51 + + getSercomFreqRef(); } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index c717e78b6..aa2909a8e 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -237,6 +237,7 @@ class SERCOM int availableWIRE( void ) ; uint8_t readDataWIRE( void ) ; int8_t getSercomIndex(void); + uint32_t getSercomFreqRef(void); #if defined(__SAMD51__) // SERCOM clock source override is only available on // SAMD51 (not 21) ... but these functions are declared @@ -254,10 +255,10 @@ class SERCOM #endif private: - Sercom* sercom; -#if defined(__SAMD51__) + Sercom *sercom; + uint32_t freqRef = 48000000; // Frequency corresponding to clockSource +#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) SercomClockSource clockSource; - uint32_t freqRef; // Frequency corresponding to clockSource #endif uint8_t calculateBaudrateSynchronous(uint32_t baudrate); uint32_t division(uint32_t dividend, uint32_t divisor) ; From d2772f8cbef12bb86d5a61017d9516de740ac280 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Tue, 13 Jan 2026 14:09:30 -0600 Subject: [PATCH 2/4] bug: resolve low speed I2C BAUD register overflow --- cores/arduino/SERCOM.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 5e2bf4d98..4fa3f2f23 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -478,7 +478,13 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) // Enable all interrupts // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; - if (sercom->I2CM.CTRLA.bit.SPEED == 0x2) + uint8_t speedBit = sercom->I2CM.CTRLA.bit.SPEED; + uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)} + const uint32_t minBaudrate = freqRef / 512; // BAUD = 255: SAMD51 ~195kHz, SAMD21 ~94kHz + const uint32_t maxBaudrate = topSpeeds[speedBit]; + baudrate = max(minBaudrate, min(baudrate, maxBaudrate)); + + if (speedBit == 0x2) sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1; else sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - freqRef * WIRE_RISE_TIME_NANOSECONDS / (2 * 1e9f); From eebc1ea91ba1aac4382e5362bb641c228174476b Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 20 Jan 2026 13:03:05 +0700 Subject: [PATCH 3/4] avoid float division --- cores/arduino/SERCOM.cpp | 14 ++++++++++---- cores/arduino/SERCOM.h | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 4fa3f2f23..7b66168ee 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -479,7 +479,7 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; uint8_t speedBit = sercom->I2CM.CTRLA.bit.SPEED; - uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)} + const uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)} const uint32_t minBaudrate = freqRef / 512; // BAUD = 255: SAMD51 ~195kHz, SAMD21 ~94kHz const uint32_t maxBaudrate = topSpeeds[speedBit]; baudrate = max(minBaudrate, min(baudrate, maxBaudrate)); @@ -487,7 +487,8 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) if (speedBit == 0x2) sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1; else - sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - freqRef * WIRE_RISE_TIME_NANOSECONDS / (2 * 1e9f); + sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - + (freqRef/2000000ul * WIRE_RISE_TIME_NANOSECONDS) / 1000; } void SERCOM::prepareNackBitWIRE( void ) @@ -789,10 +790,15 @@ uint32_t SERCOM::getSercomFreqRef(void) gen = GCLK->PCHCTRL[pch].bit.GEN; } + // GCLK0 = F_CPU + // GCLK1 = 48 MHz + // GCLK2 = 100 MHz + // GCLK3 = XOSC32K + // GCLK4 = 12 MHz switch (gen) { case 0: - freqRef = 100000000UL; + freqRef = 100000000UL; // F_CPU but limit at 100 Mhz for SERCOM ref clock break; case 1: freqRef = 48000000UL; @@ -836,7 +842,7 @@ void SERCOM::setClockSource(int8_t idx, SercomClockSource src, bool core) { if(core) clockSource = src; // Save SercomClockSource value // From cores/arduino/startup.c: - // GCLK0 = F_CPU (this is 120 MHz and exceeds SERCOM maximum) + // GCLK0 = F_CPU (this is 120 MHz and exceeds SERCOM maximum, limit to 100Mhz) // GCLK1 = 48 MHz // GCLK2 = 100 MHz // GCLK3 = XOSC32K diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index aa2909a8e..35c90f1c4 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -214,10 +214,10 @@ class SERCOM void resetWIRE( void ) ; void enableWIRE( void ) ; - void disableWIRE( void ); - void prepareNackBitWIRE( void ) ; - void prepareAckBitWIRE( void ) ; - void prepareCommandBitsWire(uint8_t cmd); + 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) ; @@ -233,12 +233,12 @@ class SERCOM bool isRestartDetectedWIRE( void ) ; bool isAddressMatch( void ) ; bool isMasterReadOperationWIRE( void ) ; - bool isRXNackReceivedWIRE( void ) ; + bool isRXNackReceivedWIRE( void ) ; int availableWIRE( void ) ; uint8_t readDataWIRE( void ) ; int8_t getSercomIndex(void); - uint32_t getSercomFreqRef(void); -#if defined(__SAMD51__) + 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 // regardless so user code doesn't need ifdefs or lengthy @@ -254,9 +254,9 @@ class SERCOM uint32_t getFreqRef(void) { return F_CPU; }; #endif - private: - Sercom *sercom; - uint32_t freqRef = 48000000; // Frequency corresponding to clockSource + private: + Sercom *sercom; + uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource #if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__) SercomClockSource clockSource; #endif From 7941541436caf2b350a9d3c5f542dba846a06874 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 23 Jan 2026 17:42:45 +0700 Subject: [PATCH 4/4] avoid float division 2 --- cores/arduino/SERCOM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 7b66168ee..357bfe5e2 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -488,7 +488,7 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1; else sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 - - (freqRef/2000000ul * WIRE_RISE_TIME_NANOSECONDS) / 1000; + (freqRef/1000000ul * WIRE_RISE_TIME_NANOSECONDS) / 2000; } void SERCOM::prepareNackBitWIRE( void )