aboutsummaryrefslogtreecommitdiff
path: root/libraries/SPI
diff options
context:
space:
mode:
authorPaulStoffregen <paul@pjrc.com>2014-08-01 05:38:27 -0700
committerPaulStoffregen <paul@pjrc.com>2014-08-01 05:38:27 -0700
commit48f5256789369bd5a4d8d9b9a663982aea717c7d (patch)
tree62f016b657c81b94e25ebc6fbfba971c8b9fff0c /libraries/SPI
parente057236dd6d37e80afe8345111e16df12832143d (diff)
SPI Transactions for AVR
Diffstat (limited to 'libraries/SPI')
-rw-r--r--libraries/SPI/SPI.cpp102
-rw-r--r--libraries/SPI/SPI.h266
2 files changed, 324 insertions, 44 deletions
diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp
index 5e48073..9a7a400 100644
--- a/libraries/SPI/SPI.cpp
+++ b/libraries/SPI/SPI.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2010 by Cristian Maglie <c.maglie@bug.st>
+ * Copyright (c) 2014 by Paul Stoffregen <paul@pjrc.com> (Transaction API)
* SPI Master library for arduino.
*
* This file is free software; you can redistribute it and/or modify
@@ -8,13 +9,20 @@
* published by the Free Software Foundation.
*/
-#include "pins_arduino.h"
#include "SPI.h"
+#include "pins_arduino.h"
SPIClass SPI;
-void SPIClass::begin() {
+uint8_t SPIClass::interruptMode = 0;
+uint8_t SPIClass::interruptMask = 0;
+uint8_t SPIClass::interruptSave = 0;
+#ifdef SPI_TRANSACTION_MISMATCH_LED
+uint8_t SPIClass::inTransactionFlag = 0;
+#endif
+void SPIClass::begin()
+{
// Set SS to high so a connected chip will be "deselected" by default
digitalWrite(SS, HIGH);
@@ -39,28 +47,86 @@ void SPIClass::begin() {
pinMode(MOSI, OUTPUT);
}
-
void SPIClass::end() {
SPCR &= ~_BV(SPE);
}
-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<<INT0)
+ #define SPI_INT1_MASK (1<<INT1)
+ #define SPI_INT2_MASK (1<<INT2)
+ #define SPI_INT3_MASK (1<<INT3)
+ #define SPI_INT4_MASK (1<<INT6)
+#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
+ #define SPI_INT0_MASK (1<<INT0)
+ #define SPI_INT1_MASK (1<<INT1)
+ #define SPI_INT2_MASK (1<<INT2)
+ #define SPI_INT3_MASK (1<<INT3)
+ #define SPI_INT4_MASK (1<<INT4)
+ #define SPI_INT5_MASK (1<<INT5)
+ #define SPI_INT6_MASK (1<<INT6)
+ #define SPI_INT7_MASK (1<<INT7)
+#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
+ #define SPI_INT0_MASK (1<<INT4)
+ #define SPI_INT1_MASK (1<<INT5)
+ #define SPI_INT2_MASK (1<<INT0)
+ #define SPI_INT3_MASK (1<<INT1)
+ #define SPI_INT4_MASK (1<<INT2)
+ #define SPI_INT5_MASK (1<<INT3)
+ #define SPI_INT6_MASK (1<<INT6)
+ #define SPI_INT7_MASK (1<<INT7)
+#else
+ #ifdef INT0
+ #define SPI_INT0_MASK (1<<INT0)
+ #endif
+ #ifdef INT1
+ #define SPI_INT1_MASK (1<<INT1)
+ #endif
+ #ifdef INT2
+ #define SPI_INT2_MASK (1<<INT2)
+ #endif
+#endif
-void SPIClass::setDataMode(uint8_t mode)
+void SPIClass::usingInterrupt(uint8_t interruptNumber)
{
- SPCR = (SPCR & ~SPI_MODE_MASK) | mode;
-}
+ uint8_t mask;
-void SPIClass::setClockDivider(uint8_t rate)
-{
- SPCR = (SPCR & ~SPI_CLOCK_MASK) | (rate & SPI_CLOCK_MASK);
- SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((rate >> 2) & SPI_2XCLOCK_MASK);
+ if (interruptMode > 1) return;
+
+ noInterrupts();
+ 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:
+ interruptMode = 2;
+ interrupts();
+ return;
+ }
+ interruptMode = 1;
+ interruptMask |= mask;
+ interrupts();
}
diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h
index f647d5c..962100b 100644
--- a/libraries/SPI/SPI.h
+++ b/libraries/SPI/SPI.h
@@ -1,5 +1,7 @@
/*
* Copyright (c) 2010 by Cristian Maglie <c.maglie@bug.st>
+ * Copyright (c) 2014 by Paul Stoffregen <paul@pjrc.com> (Transaction API)
+ * Copyright (c) 2014 by Matthijs Kooijman <matthijs@stdin.nl> (SPISettings AVR)
* SPI Master library for arduino.
*
* This file is free software; you can redistribute it and/or modify
@@ -11,9 +13,24 @@
#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED
-#include <stdio.h>
#include <Arduino.h>
-#include <avr/pgmspace.h>
+
+// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(),
+// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode)
+#define SPI_HAS_TRANSACTION 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 a 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_CLOCK_DIV4 0x00
#define SPI_CLOCK_DIV16 0x01
@@ -22,7 +39,6 @@
#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
@@ -33,38 +49,236 @@
#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 {
+// 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);
+ 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
- // SPI Configuration methods
+ // 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;
- inline static void attachInterrupt();
- inline static void detachInterrupt(); // Default
+ // 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++;
+ }
+ }
- static void begin(); // Default
- static void end();
+ // Compensate for the duplicate fosc/64
+ if (clockDiv == 6)
+ clockDiv = 7;
- static void setBitOrder(uint8_t);
- static void setDataMode(uint8_t);
- static void setClockDivider(uint8_t);
+ // Invert the SPI2X bit
+ clockDiv ^= 0x1;
+
+ // 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;
-byte SPIClass::transfer(byte _data) {
- SPDR = _data;
- while (!(SPSR & _BV(SPIF)))
- ;
- return SPDR;
-}
+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);
+
+ // 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) {
+ if (interruptMode > 0) {
+ #ifdef SPI_AVR_EIMSK
+ if (interruptMode == 1) {
+ interruptSave = SPI_AVR_EIMSK;
+ SPI_AVR_EIMSK &= ~interruptMask;
+ } else
+ #endif
+ {
+ interruptSave = SREG;
+ cli();
+ }
+ }
+ #ifdef SPI_TRANSACTION_MISMATCH_LED
+ if (inTransactionFlag) {
+ pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
+ digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
+ }
+ inTransactionFlag = 1;
+ #endif
+ 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) {
+ union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out;
+ in.val = data;
+ if (!(SPCR & _BV(DORD))) {
+ SPDR = in.msb;
+ while (!(SPSR & _BV(SPIF))) ;
+ out.msb = SPDR;
+ SPDR = in.lsb;
+ while (!(SPSR & _BV(SPIF))) ;
+ out.lsb = SPDR;
+ } else {
+ SPDR = in.lsb;
+ while (!(SPSR & _BV(SPIF))) ;
+ out.lsb = SPDR;
+ SPDR = in.msb;
+ while (!(SPSR & _BV(SPIF))) ;
+ out.msb = SPDR;
+ }
+ return out.val;
+ }
+ inline static void transfer(void *buf, size_t count) {
+ if (count == 0) return;
+ 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) {
+ #ifdef SPI_TRANSACTION_MISMATCH_LED
+ if (!inTransactionFlag) {
+ pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
+ digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
+ }
+ inTransactionFlag = 0;
+ #endif
+ if (interruptMode > 0) {
+ #ifdef SPI_AVR_EIMSK
+ if (interruptMode == 1) {
+ SPI_AVR_EIMSK = interruptSave;
+ } else
+ #endif
+ {
+ SREG = interruptSave;
+ }
+ }
+ }
-void SPIClass::attachInterrupt() {
- SPCR |= _BV(SPIE);
-}
+ // 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); }
-void SPIClass::detachInterrupt() {
- SPCR &= ~_BV(SPIE);
-}
+private:
+ static uint8_t interruptMode; // 0=none, 1=mask, 2=global
+ static uint8_t interruptMask; // which interrupts to mask
+ static uint8_t interruptSave; // temp storage, to restore state
+ #ifdef SPI_TRANSACTION_MISMATCH_LED
+ static uint8_t inTransactionFlag;
+ #endif
+};
+
+extern SPIClass SPI;
#endif