Skip to content

Commit

Permalink
Add SPIClass::getSettings to precalculate SPI settings
Browse files Browse the repository at this point in the history
This allows specifying the clock rate in Mhz, instead of having to use a
clock divider constant. The code will automatically select the fastest
clock speed supported that is not faster than the specified clock rate
(so callers can just pass in the maximum supported clock rate of the SPI
slave, even when they know the maximum speed of their AVR chip is a lot
less).

Care is taken to ensure that when a constant clock is passed, the
calculation is done at compiletime without any overhead.
  • Loading branch information
matthijskooijman committed Apr 24, 2014
1 parent 0451bdb commit d29a0dc
Showing 1 changed file with 76 additions and 5 deletions.
81 changes: 76 additions & 5 deletions hardware/arduino/avr/libraries/SPI/SPI.h
Expand Up @@ -24,6 +24,13 @@
#define SPI_CLOCK_MASK 0x03 // SPR1 = bit 1, SPR0 = bit 0 on SPCR
#define SPI_2XCLOCK_MASK 0x01 // SPI2X = bit 0 on SPSR

class SPISettings {
private:
uint8_t spcr;
uint8_t spsr;
friend class SPIClass;
};

class SPIClass {
public:
// Initialize the SPI library
Expand All @@ -39,7 +46,72 @@ class SPIClass {
// Before using SPI.transfer() or asserting chip select pins,
// this function is used to gain exclusive access to the SPI bus
// and configure the correct settings.
inline static void beginTransaction(uint8_t clockDiv, uint8_t bitOrder, uint8_t dataMode) {
inline static SPISettings getSettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) {
// Clock settings are defined as follows. Note that this shows SPI2X
// inverted, so the bits form increasing numbers. Also note that
// fosc/64 appears twice
//
// SPR1 SPR0 ~SPI2X Freq
// 0 0 0 fosc/2
// 0 0 1 fosc/4
// 0 1 0 fosc/8
// 0 1 1 fosc/16
// 1 0 0 fosc/32
// 1 0 1 fosc/64
// 1 1 0 fosc/64
// 1 1 1 fosc/128

// We find the fastest clock that is less than or equal to the
// given clock rate. The clock divider that results in clock_setting
// is 2 ^^ (clock_div + 1). If nothing is slow enough, we'll use the
// slowest (128 == 2 ^^ 7, so clock_div = 6).
uint8_t clockDiv;

// When the clock is known at compiletime, use this if-then-else
// cascade, which the compiler knows how to completely optimize
// away. When clock is not know, use a loop instead, which generates
// shorter code.
if (__builtin_constant_p(clock)) {
if (clock >= F_CPU / 2)
clockDiv = 0;
else if (clock >= F_CPU / 4)
clockDiv = 1;
else if (clock >= F_CPU / 8)
clockDiv = 2;
else if (clock >= F_CPU / 16)
clockDiv = 3;
else if (clock >= F_CPU / 32)
clockDiv = 4;
else if (clock >= F_CPU / 64)
clockDiv = 5;
else
clockDiv = 6;
} else {
uint32_t clockSetting = F_CPU / 2;
clockDiv = 0;
while (clockDiv < 6 && clock < clockSetting) {
clockSetting /= 2;
clockDiv++;
}
}

// Compensate for the duplicate fosc/64
if (clockDiv == 6)
clockDiv = 7;

// Invert the SPI2X bit
clockDiv ^= 0x1;

SPISettings res;

res.spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) |
(dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK);
res.spsr = clockDiv & SPI_2XCLOCK_MASK;

return res;
}

inline static void beginTransaction(SPISettings settings ) {
if (interruptMode > 0) {
if (interruptMode == 1) {
getExternalInterruptsMask(&interruptSave); //interruptSave = SPI_EIMSK;
Expand All @@ -49,10 +121,9 @@ class SPIClass {
noInterrupts();
}
}
// these long expressions each compile to only 2 instructions
SPCR = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) |
(dataMode & SPI_MODE_MASK) | (clockDiv & SPI_CLOCK_MASK);
SPSR = (clockDiv >> 2) & SPI_2XCLOCK_MASK;

SPCR = settings.spcr;
SPSR = settings.spsr;
}

// Write a byte to the SPI bus (MOSI pin) and also receive (MISO pin)
Expand Down

0 comments on commit d29a0dc

Please sign in to comment.