Skip to content

Commit 015acfc

Browse files
authored
Merge pull request #381 from crabel99/master
Resolve a set of critical bugs in `SERCOM` and unify SPI/UART/I2C baudrate calculations
2 parents 2917083 + 262507f commit 015acfc

2 files changed

Lines changed: 106 additions & 54 deletions

File tree

cores/arduino/SERCOM.cpp

Lines changed: 95 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ SERCOM::SERCOM(Sercom* s)
3131
{
3232
sercom = s;
3333

34-
#if defined(__SAMD51__)
34+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
3535
// A briefly-available but now deprecated feature had the SPI clock source
3636
// set via a compile-time setting (MAX_SPI)...problem was this affected
3737
// ALL SERCOMs, whereas some (anything read/write, e.g. SD cards) should
@@ -40,8 +40,8 @@ SERCOM::SERCOM(Sercom* s)
4040
// per-peripheral basis. Nonetheless, we check SERCOM_SPI_FREQ_REF here
4141
// (MAX_SPI * 2) to retain compatibility with any interim projects that
4242
// might have relied on the compile-time setting. But please, don't.
43-
#if SERCOM_SPI_FREQ_REF == F_CPU // F_CPU clock = GCLK0
44-
clockSource = SERCOM_CLOCK_SOURCE_FCPU;
43+
#if SERCOM_SPI_FREQ_REF == F_CPU // F_CPU clock = GCLK0
44+
clockSource = SERCOM_CLOCK_SOURCE_100M;
4545
#elif SERCOM_SPI_FREQ_REF == 48000000 // 48 MHz clock = GCLK1 (standard)
4646
clockSource = SERCOM_CLOCK_SOURCE_48M;
4747
#elif SERCOM_SPI_FREQ_REF == 100000000 // 100 MHz clock = GCLK2
@@ -80,11 +80,7 @@ void SERCOM::initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint
8080
// Asynchronous fractional mode (Table 24-2 in datasheet)
8181
// BAUD = fref / (sampleRateValue * fbaud)
8282
// (multiply by 8, to calculate fractional piece)
83-
#if defined(__SAMD51__)
84-
uint32_t baudTimes8 = (SERCOM_FREQ_REF * 8) / (sampleRateValue * baudrate);
85-
#else
86-
uint32_t baudTimes8 = (SystemCoreClock * 8) / (sampleRateValue * baudrate);
87-
#endif
83+
uint32_t baudTimes8 = (freqRef * 8) / (sampleRateValue * baudrate);
8884

8985
sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8);
9086
sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8);
@@ -230,8 +226,8 @@ void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize ch
230226
resetSPI();
231227
initClockNVIC();
232228

233-
#if defined(__SAMD51__)
234-
sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode
229+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
230+
sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3) | // master mode
235231
SERCOM_SPI_CTRLA_DOPO(mosi) |
236232
SERCOM_SPI_CTRLA_DIPO(miso) |
237233
dataOrder << SERCOM_SPI_CTRLA_DORD_Pos;
@@ -322,13 +318,7 @@ SercomDataOrder SERCOM::getDataOrderSPI()
322318
void SERCOM::setBaudrateSPI(uint8_t divider)
323319
{
324320
disableSPI(); // Register is enable-protected
325-
326-
#if defined(__SAMD51__)
327321
sercom->SPI.BAUD.reg = calculateBaudrateSynchronous(freqRef / divider);
328-
#else
329-
sercom->SPI.BAUD.reg = calculateBaudrateSynchronous(SERCOM_SPI_FREQ_REF / divider);
330-
#endif
331-
332322
enableSPI();
333323
}
334324

@@ -386,13 +376,11 @@ bool SERCOM::isDataRegisterEmptySPI()
386376
// return sercom->SPI.INTFLAG.bit.RXC;
387377
//}
388378

389-
uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate) {
390-
#if defined(__SAMD51__)
379+
uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate)
380+
{
391381
uint16_t b = freqRef / (2 * baudrate);
392-
#else
393-
uint16_t b = SERCOM_SPI_FREQ_REF / (2 * baudrate);
394-
#endif
395-
if(b > 0) b--; // Don't -1 on baud calc if already at 0
382+
if (b > 0)
383+
b--; // Don't -1 on baud calc if already at 0
396384
return b;
397385
}
398386

@@ -488,14 +476,37 @@ void SERCOM::initMasterWIRE( uint32_t baudrate )
488476

489477

490478
// Enable all interrupts
491-
// sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ;
479+
// sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ;
480+
481+
// Determine speed mode based on requested baudrate
482+
const uint32_t topSpeeds[3] = {400000, 1000000, 3400000}; // {(sm/fm), (fm+), (hs)}
483+
uint8_t speedBit;
484+
uint8_t clockStretchMode; // See: 28.6.2.4.6 (SERCOM I2C Highspeed mode)
485+
486+
if (baudrate <= topSpeeds[0]) {
487+
speedBit = 0; // Standard/Fast mode up to 400 khz
488+
clockStretchMode = 0;
489+
} else if (baudrate <= topSpeeds[1]) {
490+
speedBit = 1; // Fast mode+ up to 1 Mhz
491+
clockStretchMode = 0;
492+
} else {
493+
// High speed up to 3.4 Mhz
494+
speedBit = 2;
495+
clockStretchMode = 1;
496+
}
492497

493-
// Synchronous arithmetic baudrate
494-
#if defined(__SAMD51__)
495-
sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ;
496-
#else
497-
sercom->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
498-
#endif
498+
sercom->I2CM.CTRLA.bit.SPEED = speedBit;
499+
sercom->I2CM.CTRLA.bit.SCLSM = clockStretchMode;
500+
501+
uint32_t minBaudrate = freqRef / 512; // BAUD = 255: SAMD51(@100MHz) ~195kHz, SAMD21 ~94kHz
502+
uint32_t maxBaudrate = topSpeeds[speedBit];
503+
baudrate = max(minBaudrate, min(baudrate, maxBaudrate));
504+
505+
if (speedBit == 0x2)
506+
sercom->I2CM.BAUD.bit.HSBAUD = freqRef / (2 * baudrate) - 1;
507+
else
508+
sercom->I2CM.BAUD.bit.BAUD = freqRef / (2 * baudrate) - 5 -
509+
(freqRef/1000000ul * WIRE_RISE_TIME_NANOSECONDS) / 2000;
499510
}
500511

501512
void SERCOM::prepareNackBitWIRE( void )
@@ -551,7 +562,8 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag
551562
}
552563

553564
// Send start and address
554-
sercom->I2CM.ADDR.bit.ADDR = address;
565+
sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) |
566+
((sercom->I2CM.CTRLA.bit.SPEED == 0x2) ? SERCOM_I2CM_ADDR_HS : 0);
555567

556568
// Address Transmitted
557569
if ( flag == WIRE_WRITE_FLAG ) // Write mode
@@ -727,7 +739,7 @@ uint8_t SERCOM::readDataWIRE( void )
727739
}
728740
}
729741

730-
#if defined(__SAMD51__)
742+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
731743

732744
static const struct {
733745
Sercom *sercomPtr;
@@ -785,7 +797,47 @@ int8_t SERCOM::getSercomIndex(void) {
785797
return -1;
786798
}
787799

788-
#if defined(__SAMD51__)
800+
uint32_t SERCOM::getSercomFreqRef(void)
801+
{
802+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
803+
int8_t idx = getSercomIndex();
804+
uint8_t gen = 1; // default to GCLK1 (48 MHz) if we can't resolve
805+
806+
if (idx >= 0)
807+
{
808+
uint8_t pch = sercomData[idx].id_core;
809+
gen = GCLK->PCHCTRL[pch].bit.GEN;
810+
}
811+
812+
switch (gen)
813+
{
814+
case 0:
815+
freqRef = 100000000UL;
816+
break;
817+
case 1:
818+
freqRef = 48000000UL;
819+
break;
820+
case 2:
821+
freqRef = 100000000UL;
822+
break;
823+
case 3:
824+
freqRef = 32768UL;
825+
break;
826+
case 4:
827+
freqRef = 12000000UL;
828+
break;
829+
default:
830+
freqRef = 48000000UL;
831+
break;
832+
}
833+
#else
834+
freqRef = SystemCoreClock;
835+
#endif
836+
837+
return freqRef;
838+
}
839+
840+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
789841
// This is currently for overriding an SPI SERCOM's clock source only --
790842
// NOT for UART or WIRE SERCOMs, where it will have unintended consequences.
791843
// It does not check.
@@ -804,16 +856,19 @@ void SERCOM::setClockSource(int8_t idx, SercomClockSource src, bool core) {
804856
if(core) clockSource = src; // Save SercomClockSource value
805857

806858
// From cores/arduino/startup.c:
807-
// GCLK0 = F_CPU
859+
// GCLK0 = F_CPU (this is 120 MHz and exceeds SERCOM maximum)
808860
// GCLK1 = 48 MHz
809861
// GCLK2 = 100 MHz
810862
// GCLK3 = XOSC32K
811863
// GCLK4 = 12 MHz
812864
if(src == SERCOM_CLOCK_SOURCE_FCPU) {
813865
GCLK->PCHCTRL[clk_id].reg =
814-
GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
815-
if(core) freqRef = F_CPU; // Save clock frequency value
816-
} else if(src == SERCOM_CLOCK_SOURCE_48M) {
866+
GCLK_PCHCTRL_GEN_GCLK2_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); // Guard Sercom from exceeding 100 MHz maximum
867+
if (core)
868+
freqRef = 100000000; // Save clock frequency value
869+
}
870+
else if (src == SERCOM_CLOCK_SOURCE_48M)
871+
{
817872
GCLK->PCHCTRL[clk_id].reg =
818873
GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
819874
if(core) freqRef = 48000000;
@@ -840,21 +895,15 @@ void SERCOM::initClockNVIC( void )
840895
int8_t idx = getSercomIndex();
841896
if(idx < 0) return; // We got a problem here
842897

843-
#if defined(__SAMD51__)
898+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
844899

845900
for(uint8_t i=0; i<4; i++) {
846901
NVIC_ClearPendingIRQ(sercomData[idx].irq[i]);
847902
NVIC_SetPriority(sercomData[idx].irq[i], SERCOM_NVIC_PRIORITY);
848903
NVIC_EnableIRQ(sercomData[idx].irq[i]);
849904
}
850905

851-
// SPI DMA speed is dictated by the "slow clock" (I think...maybe) so
852-
// BOTH are set to the same clock source (clk_slow isn't sourced from
853-
// XOSC32K as in prior versions of SAMD core).
854-
// This might have power implications for sleep code.
855-
856-
setClockSource(idx, clockSource, true); // true = core clock
857-
setClockSource(idx, clockSource, false); // false = slow clock
906+
setClockSource(idx, clockSource, true); // true = core clock
858907

859908
#else // end if SAMD51 (prob SAMD21)
860909

@@ -875,4 +924,6 @@ void SERCOM::initClockNVIC( void )
875924
while(GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); // Wait for synchronization
876925

877926
#endif // end !SAMD51
927+
928+
getSercomFreqRef();
878929
}

cores/arduino/SERCOM.h

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@ class SERCOM
214214

215215
void resetWIRE( void ) ;
216216
void enableWIRE( void ) ;
217-
void disableWIRE( void );
218-
void prepareNackBitWIRE( void ) ;
219-
void prepareAckBitWIRE( void ) ;
220-
void prepareCommandBitsWire(uint8_t cmd);
217+
void disableWIRE( void );
218+
void prepareNackBitWIRE( void ) ;
219+
void prepareAckBitWIRE( void ) ;
220+
void prepareCommandBitsWire(uint8_t cmd);
221221
bool startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) ;
222222
bool sendDataMasterWIRE(uint8_t data) ;
223223
bool sendDataSlaveWIRE(uint8_t data) ;
@@ -233,11 +233,12 @@ class SERCOM
233233
bool isRestartDetectedWIRE( void ) ;
234234
bool isAddressMatch( void ) ;
235235
bool isMasterReadOperationWIRE( void ) ;
236-
bool isRXNackReceivedWIRE( void ) ;
236+
bool isRXNackReceivedWIRE( void ) ;
237237
int availableWIRE( void ) ;
238238
uint8_t readDataWIRE( void ) ;
239239
int8_t getSercomIndex(void);
240-
#if defined(__SAMD51__)
240+
uint32_t getSercomFreqRef(void);
241+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
241242
// SERCOM clock source override is only available on
242243
// SAMD51 (not 21) ... but these functions are declared
243244
// regardless so user code doesn't need ifdefs or lengthy
@@ -253,11 +254,11 @@ class SERCOM
253254
uint32_t getFreqRef(void) { return F_CPU; };
254255
#endif
255256

256-
private:
257-
Sercom* sercom;
258-
#if defined(__SAMD51__)
257+
private:
258+
Sercom *sercom;
259+
uint32_t freqRef = 48000000ul; // Frequency corresponding to clockSource
260+
#if defined(__SAMD51__) || defined(__SAME51__) || defined(__SAME53__) || defined(__SAME54__)
259261
SercomClockSource clockSource;
260-
uint32_t freqRef; // Frequency corresponding to clockSource
261262
#endif
262263
uint8_t calculateBaudrateSynchronous(uint32_t baudrate);
263264
uint32_t division(uint32_t dividend, uint32_t divisor) ;

0 commit comments

Comments
 (0)