SPI / SERCOM library sets SCK rate higher than maxSpeed specified in SPISetting
Edit: This issue was for 1.8.11. The problem of SCK rate higher than SPISettings is still in 1.8.13 but max clock is limited to 12 MHz. A requested max clock of 9 MHz still results in 12 MHz.
I found this problem while using a scope to investigate SPI signals on an SD card. I was surprised to see 24 MHz SCK whenever clock was greater than 12 MHz in SPISetting.
SPISettings(uint32_t clock, BitOrder bitOrder, SPIMode dataMode)
The problem appears to be this function at about line 362 of samd/1.8.11/cores/arduino/SERCOM.cpp.
uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate)
{
return SERCOM_FREQ_REF / (2 * baudrate) - 1;
}
I replaced it with this function which seems give the best rate less or equal to the clock value in SPISettings.
uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate)
{
return (SERCOM_FREQ_REF + 2*baudrate - 1)/(2*baudrate) - 1;
}
This function is based on ceil((double)a/b) for integer a, b is (a + b - 1)/b.
Here are values of speedMax requested in SPISetting, speedOld for the current function and speedNew for the new function.
speedMax,speedOld,speedNew 1000000,1000000,1000000 2000000,2000000,2000000 3000000,3000000,3000000 4000000,4000000,4000000 5000000,6000000,4800000 6000000,6000000,6000000 7000000,8000000,6000000 8000000,8000000,8000000 9000000,12000000,8000000 10000000,12000000,8000000 11000000,12000000,8000000 12000000,12000000,12000000
Here is a sketch that calculated the above table:
#ifndef SERCOM_FREQ_REF
#define SERCOM_FREQ_REF 48000000UL
#endif // SERCOM_FREQ_REF
// Current uint8_t SERCOM::calculateBaudrateSynchronous(uint32_t baudrate)
// from about line 362 of samd/1.8.11/cores/arduino/SERCOM.cpp
uint8_t calculateBaudrateSynchronous(uint32_t baudrate)
{
return SERCOM_FREQ_REF / (2 * baudrate) - 1;
}
// New based on ceil((double)a/b) for integer a, b is (a + b - 1)/b
uint8_t newCalculateBaudrateSynchronous(uint32_t baudrate)
{
return (SERCOM_FREQ_REF + 2*baudrate - 1)/(2*baudrate) - 1;
}
// Convert baud register value to frequency.
uint32_t baudToFreq(uint8_t baud) {
return SERCOM_FREQ_REF/(2*(baud + 1));
}
void setup() {
Serial.begin(9600);
while (!Serial) {}
Serial.println("\nspeedMax,speedOld,speedNew");
for (uint8_t mhz = 1; mhz < 25; mhz++) {
// frequency used in SPISettings
uint32_t speedMaximum = 1000000UL*mhz;
Serial.print(speedMaximum);
Serial.write(',');
uint8_t baud = calculateBaudrateSynchronous(speedMaximum);
Serial.print(baudToFreq(baud));
Serial.write(',');
baud = newCalculateBaudrateSynchronous(speedMaximum);
Serial.println(baudToFreq(baud));
}
}
void loop() {}