diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 5e48073f731..ecd8bf6bfe7 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -1,5 +1,8 @@ /* * Copyright (c) 2010 by Cristian Maglie + * Copyright (c) 2014 by Paul Stoffregen (Transaction API) + * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) + * Copyright (c) 2014 by Andrew J. Kroll (atomicity fixes) * SPI Master library for arduino. * * This file is free software; you can redistribute it and/or modify @@ -8,59 +11,185 @@ * published by the Free Software Foundation. */ -#include "pins_arduino.h" #include "SPI.h" SPIClass SPI; +SPIflags_t SPIClass::modeFlags = {false, false, 0}; +uint8_t SPIClass::interruptMask = 0; +uint8_t SPIClass::interruptSave = 0; void SPIClass::begin() { + uint8_t sreg = SREG; + noInterrupts(); // Protect from a scheduler and prevent transactionBegin + if(!modeFlags.initialized) { + modeFlags.initialized = true; + // Set SS to high so a connected chip will be "deselected" by default + digitalWrite(SS, HIGH); - // Set SS to high so a connected chip will be "deselected" by default - digitalWrite(SS, HIGH); + // When the SS pin is set as OUTPUT, it can be used as + // a general purpose output port (it doesn't influence + // SPI operations). + pinMode(SS, OUTPUT); - // When the SS pin is set as OUTPUT, it can be used as - // a general purpose output port (it doesn't influence - // SPI operations). - pinMode(SS, OUTPUT); + // Warning: if the SS pin ever becomes a LOW INPUT then SPI + // automatically switches to Slave, so the data direction of + // the SS pin MUST be kept as OUTPUT. + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); - // Warning: if the SS pin ever becomes a LOW INPUT then SPI - // automatically switches to Slave, so the data direction of - // the SS pin MUST be kept as OUTPUT. - SPCR |= _BV(MSTR); - SPCR |= _BV(SPE); - - // Set direction register for SCK and MOSI pin. - // MISO pin automatically overrides to INPUT. - // By doing this AFTER enabling SPI, we avoid accidentally - // clocking in a single bit since the lines go directly - // from "input" to SPI control. - // http://code.google.com/p/arduino/issues/detail?id=888 - pinMode(SCK, OUTPUT); - pinMode(MOSI, OUTPUT); + // Set direction register for SCK and MOSI pin. + // MISO pin automatically overrides to INPUT. + // By doing this AFTER enabling SPI, we avoid accidentally + // clocking in a single bit since the lines go directly + // from "input" to SPI control. + // http://code.google.com/p/arduino/issues/detail?id=888 + pinMode(SCK, OUTPUT); + pinMode(MOSI, OUTPUT); + } + SREG = sreg; } - void SPIClass::end() { - SPCR &= ~_BV(SPE); + uint8_t sreg = SREG; + noInterrupts(); // Protect from a scheduler and prevent transactionBegin + if(!modeFlags.interruptMode && modeFlags.initialized) { + SPCR &= ~_BV(SPE); + modeFlags.initialized = false; +#ifdef SPI_TRANSACTION_MISMATCH_LED + modeFlags.inTransactionFlag = false; +#endif + } + SREG = sreg; } -void SPIClass::setBitOrder(uint8_t bitOrder) -{ - if(bitOrder == LSBFIRST) { - SPCR |= _BV(DORD); - } else { - SPCR &= ~(_BV(DORD)); - } -} +// mapping of interrupt numbers to bits within SPI_AVR_EIMSK +#if defined(__AVR_ATmega32U4__) +#define SPI_INT0_MASK (1<> 2) & SPI_2XCLOCK_MASK); +void SPIClass::notUsingInterrupt(uint8_t interruptNumber) { + uint8_t mask = 0; + uint8_t sreg = SREG; + noInterrupts(); // Protect from a scheduler and prevent transactionBegin + switch(interruptNumber) { +#ifdef SPI_INT0_MASK + case 0: mask = SPI_INT0_MASK; + break; +#endif +#ifdef SPI_INT1_MASK + case 1: mask = SPI_INT1_MASK; + break; +#endif +#ifdef SPI_INT2_MASK + case 2: mask = SPI_INT2_MASK; + break; +#endif +#ifdef SPI_INT3_MASK + case 3: mask = SPI_INT3_MASK; + break; +#endif +#ifdef SPI_INT4_MASK + case 4: mask = SPI_INT4_MASK; + break; +#endif +#ifdef SPI_INT5_MASK + case 5: mask = SPI_INT5_MASK; + break; +#endif +#ifdef SPI_INT6_MASK + case 6: mask = SPI_INT6_MASK; + break; +#endif +#ifdef SPI_INT7_MASK + case 7: mask = SPI_INT7_MASK; + break; +#endif + default: + if(interruptMask) { + modeFlags.interruptMode = 1; + } else { + modeFlags.interruptMode = 0; + } + break; + } + interruptMask &= ~mask; + SREG = sreg; } - diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index f647d5c8918..a4ef74d13f0 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -1,5 +1,8 @@ /* * Copyright (c) 2010 by Cristian Maglie + * Copyright (c) 2014 by Paul Stoffregen (Transaction API) + * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) + * Copyright (c) 2014 by Andrew J. Kroll (atomicity fixes) * SPI Master library for arduino. * * This file is free software; you can redistribute it and/or modify @@ -11,9 +14,37 @@ #ifndef _SPI_H_INCLUDED #define _SPI_H_INCLUDED -#include #include -#include +#include + +// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(), +// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode) +#define SPI_HAS_TRANSACTION 1 + +// SPI_ATOMIC_VERSION means that SPI has atomicity fixes and what version. +// This way when there is a bug fix you can check this define to alert users +// of your code if it uses better version of this library. +// This also implies everything that SPI_HAS_TRANSACTION as documented above is +// available too. +#define SPI_ATOMIC_VERSION 1 + +// Uncomment this line to add detection of mismatched begin/end transactions. +// A mismatch occurs if other libraries fail to use SPI.endTransaction() for +// each SPI.beginTransaction(). Connect an LED to this pin. The LED will turn +// on if any mismatch is ever detected. +//#define SPI_TRANSACTION_MISMATCH_LED 5 + +#ifndef LSBFIRST +#define LSBFIRST 0 +#endif +#ifndef MSBFIRST +#define MSBFIRST 1 +#endif + +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x04 +#define SPI_MODE2 0x08 +#define SPI_MODE3 0x0C #define SPI_CLOCK_DIV4 0x00 #define SPI_CLOCK_DIV16 0x01 @@ -22,49 +53,297 @@ #define SPI_CLOCK_DIV2 0x04 #define SPI_CLOCK_DIV8 0x05 #define SPI_CLOCK_DIV32 0x06 -//#define SPI_CLOCK_DIV64 0x07 - -#define SPI_MODE0 0x00 -#define SPI_MODE1 0x04 -#define SPI_MODE2 0x08 -#define SPI_MODE3 0x0C #define SPI_MODE_MASK 0x0C // CPOL = bit 3, CPHA = bit 2 on SPCR #define SPI_CLOCK_MASK 0x03 // SPR1 = bit 1, SPR0 = bit 0 on SPCR #define SPI_2XCLOCK_MASK 0x01 // SPI2X = bit 0 on SPSR -class SPIClass { +// Flags for the state of SPI, used as needed. +// Normally inTransaction is not used. + +typedef struct SPIflags { + bool initialized : 1; // tells us that begin() was called + bool inTransaction : 1; + uint8_t interruptMode : 6; // 0=none, 1=mask, 2=global (more can be added) +} __attribute__((packed)) SPIflags_t; + +#define SPI_HAS_NOTUSINGINTERRUPT 1 + +// define SPI_AVR_EIMSK for AVR boards with external interrupt pins +#if defined(EIMSK) +#define SPI_AVR_EIMSK EIMSK +#elif defined(GICR) +#define SPI_AVR_EIMSK GICR +#elif defined(GIMSK) +#define SPI_AVR_EIMSK GIMSK +#endif + +class SPISettings { public: - inline static byte transfer(byte _data); - // SPI Configuration methods + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if(__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + // 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 known, 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++; + } + } - inline static void attachInterrupt(); - inline static void detachInterrupt(); // Default + // Compensate for the duplicate fosc/64 + if(clockDiv == 6) + clockDiv = 7; - static void begin(); // Default - static void end(); + // Invert the SPI2X bit + clockDiv ^= 0x1; - static void setBitOrder(uint8_t); - static void setDataMode(uint8_t); - static void setClockDivider(uint8_t); + // Pack into the SPISettings class + spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) | + (dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK); + spsr = clockDiv & SPI_2XCLOCK_MASK; + } + uint8_t spcr; + uint8_t spsr; + friend class SPIClass; }; -extern SPIClass SPI; +class SPIClass { +public: + + // Initialize the SPI library + static void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + static void usingInterrupt(uint8_t interruptNumber); + // And this does the opposite. + static void notUsingInterrupt(uint8_t interruptNumber); + + // 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(SPISettings settings) { + uint8_t sreg = SREG; + noInterrupts(); +#ifdef SPI_TRANSACTION_MISMATCH_LED + if(modeFlags.inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + modeFlags.inTransactionFlag = true; +#endif + +#ifndef SPI_AVR_EIMSK + if(modeFlags.interruptMode) { + interruptSave = sreg; + } +#else + if(modeFlags.interruptMode == 2) { + interruptSave = sreg; + } else if(modeFlags.interruptMode == 1) { + interruptSave = SPI_AVR_EIMSK; + SPI_AVR_EIMSK &= ~interruptMask; + SREG = sreg; + } +#endif + else { + SREG = sreg; + } + + SPCR = settings.spcr; + SPSR = settings.spsr; + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + + inline static uint8_t transfer(uint8_t data) { + SPDR = data; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); // wait + return SPDR; + } + + inline static uint16_t transfer16(uint16_t data) { -byte SPIClass::transfer(byte _data) { - SPDR = _data; - while (!(SPSR & _BV(SPIF))) - ; - return SPDR; -} + union { + uint16_t val; -void SPIClass::attachInterrupt() { - SPCR |= _BV(SPIE); -} + struct { + uint8_t lsb; + uint8_t msb; + }; + } in, out; + in.val = data; + if(!(SPCR & _BV(DORD))) { + SPDR = in.msb; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); + out.msb = SPDR; + SPDR = in.lsb; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); + out.lsb = SPDR; + } else { + SPDR = in.lsb; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); + out.lsb = SPDR; + SPDR = in.msb; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); + out.msb = SPDR; + } + return out.val; + } -void SPIClass::detachInterrupt() { - SPCR &= ~_BV(SPIE); -} + inline static void transfer(void *buf, size_t count) { + if(count) { + uint8_t *p = (uint8_t *)buf; + SPDR = *p; + while(--count > 0) { + uint8_t out = *(p + 1); + while(!(SPSR & _BV(SPIF))); + uint8_t in = SPDR; + SPDR = out; + *p++ = in; + } + while(!(SPSR & _BV(SPIF))); + *p = SPDR; + } + } + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + + inline static void endTransaction(void) { + uint8_t sreg = SREG; + noInterrupts(); +#ifdef SPI_TRANSACTION_MISMATCH_LED + if(!modeFlags.inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + modeFlags.inTransactionFlag = false; +#endif +#ifndef SPI_AVR_EIMSK + if(modeFlags.interruptMode) { + SREG = interruptSave; + } else { + SREG = sreg; + } +#else + if(modeFlags.interruptMode == 2) { + SREG = interruptSave; + } else { + if(modeFlags.interruptMode == 1) { + SPI_AVR_EIMSK = interruptSave; + } + SREG = sreg; + } +#endif + } + + // Disable the SPI bus + static void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + + inline static void setBitOrder(uint8_t bitOrder) { + if(bitOrder == LSBFIRST) SPCR |= _BV(DORD); + else SPCR &= ~(_BV(DORD)); + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + + inline static void setDataMode(uint8_t dataMode) { + SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + + inline static void setClockDivider(uint8_t clockDiv) { + SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK); + SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK); + } + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + + inline static void attachInterrupt() { + SPCR |= _BV(SPIE); + } + + inline static void detachInterrupt() { + SPCR &= ~_BV(SPIE); + } + +private: + static SPIflags_t modeFlags; // Flags for the state and mode of SPI + static uint8_t interruptMask; // which interrupts to mask + static uint8_t interruptSave; // temp storage, to restore state +}; #endif