Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,67 @@ http://www.pjrc.com/teensy/td_libs_SPI.html

![](http://www.pjrc.com/teensy/td_libs_SPI_1.jpg)

SPI Async Extension Proposal
=====================

This branch contains an API to allow async SPI requests on Arduinos. It is just a proof-of-concept and still requires work.

This extension uses interrupts to transfer an entire buffer via SPI asynchronously while the main Arduino loop runs. It interacts cleanly with beginTransaction()/endTransaction() and interrupt handlers can safely queue additional transfers. It is not currently safe to use transfer() outside of a transaction block, but protections can be added for that case.

Usage
--------

Here's an example program that kicks off three SPI transactions:

```C++
#include "SPI.h"
uint8_t buf1[] = { 0x01, 0x02 },
buf2[] = { 0x03, 0x04 },
buf3[] = { 0x05, 0x06 };

SPISettings theSettings = SPISettings(100000, MSBFIRST, SPI_MODE0);

SPIQueuedTransfer t1(buf1, 2, theSettings, 0, 0),
t2(buf2, 2, theSettings, 0, 0),
t3(buf3, 2, theSettings, 0, 0);

void setup() {
pinMode(13, OUTPUT); //High while transfers are queued
pinMode(14, OUTPUT); //High while SPI ISR is running
pinMode(15, OUTPUT); //High while workTransferQueue

digitalWrite(13, 0);
SPI.begin();

SPI.queueTransfer(&t1);
SPI.queueTransfer(&t2);
SPI.queueTransfer(&t3);
digitalWrite(13, 1);

//Do some things...

//Hold up execution until all transfers are complete
while(!t1.is_completed && !t2.is_completed && !t3.is_completed);
}
```

Here's a trace of the output from the above program running on a Teensy 2.0 at 16Mhz with an SPI bus speed of 125kBps:

![screen shot 2014-12-28 at 10 46 07 pm](https://cloud.githubusercontent.com/assets/17287/5566620/60398f4c-8ee3-11e4-9b1b-b143adb34447.png)

Implementation Notes
-------------------------
* No ordering is guaranteed across queued transfers. You can see in the trace that they are processed in a LIFO order if the SPI module is busy
* The inTransaction flag becomes mandatory in order to interoperate gracefully with begin/endTransaction()
* Memory overhead is approximately 5-bytes + 8-bytes per queued transfer
* Interrupt latency is ~2.5µS per byte + ~10µS per transfer

Performance
----------------

Using the ISR consumes more cycles than polling when the SPI bus is running very fast. In the trace above, the per-queued-transfer cost is about 19µS, implying that async transfers are advantageous for transfers when the SPI bus is running slower than about 2Mbps. This may not seem very useful, but the gains should be quite marked on the Teensy 3.x and other ARM-based Arduinos where the CPU runs at 48+Mhz and the FIFO queues allow transfers up to 8 bytes between interrupts.

TODO
-------

I have only implemented a proof-of-concept for AVR; there is no ARM implementation yet. The naming can probably also use a pass. endTransaction() still needs a few lines of code to kick off any queued transfers.
46 changes: 45 additions & 1 deletion SPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@

#if defined(__AVR__)

#include <avr/interrupt.h>

SPIClass SPI;

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;

#ifdef SPI_QUEUED_TRANSFERS

SPIQueuedTransfer *SPIClass::nextTransfer = NULL; //Stores the next transfer, or NULL if there aren't any
SPIQueuedTransfer *SPIClass::curTransfer = NULL; //Currently in-progress transfer, or NULL if there isn't any
uint8_t SPIClass::curTransferPos = 0; //Position in the current byte being read/written

#endif

void SPIClass::begin()
Expand Down Expand Up @@ -138,6 +146,42 @@ void SPIClass::usingInterrupt(uint8_t interruptNumber)
SREG = stmp;
}

#ifdef SPI_QUEUED_TRANSFERS

inline void spi_isr(void) __attribute__((always_inline));
/*
* ISR to keep pumping queued transfers out the SPI interface
*/
ISR(SPI_STC_vect) {
spi_isr();
}

inline void spi_isr(void) {
digitalWrite(14, 1);
uint8_t *buf = SPI.curTransfer->buffer;
uint8_t pos = SPI.curTransferPos;
uint8_t len = SPI.curTransfer->len;

buf[pos++] = SPDR;
if(pos < len) {
//More bytes to transfer
SPDR = buf[pos];
SPI.curTransferPos = pos;
} else {
//Complete this transfer, move on if there's another

//Deassert Chip
digitalWrite(SPI.curTransfer->cs_pin, !SPI.curTransfer->assert_state);

//2. Look for next queued transfer
SPI.inTransactionFlag = 0;
SPI.workTransferQueue();
}
digitalWrite(14,0);
}


#endif

/**********************************************************/
/* 32 bit Teensy 3.0 and 3.1 */
Expand Down
96 changes: 92 additions & 4 deletions SPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,47 @@
/* 8 bit AVR-based boards */
/**********************************************************/

#ifdef SPI_QUEUED_TRANSFERS

class SPISettings;

/*
* Allows an SPI transfer to occurr asynchronously while the main loop
* continues to run. The is_completed flag is set to true once the transfer is
* complete. The CS pin is automatically asserted before the transfer starts,
* and deasserted at the completion of the transfer. New transfers may be
* safely queued from an ISR.
*
* buffer - pointer to the buffer for the transfer. The contents will be replaced with received values from the chip
* len - number of bytes to transfer, stored as a 7 bit quantity, 127 bytes is the max transfer size
* settings - a reference to an SPISettings structure
* cs_pin - the number value of the Chip-select pin for this transfer
* assert_state - 0 or 1 (low or high) representing the state of the CS pin that activates the device
*
*/
class SPIQueuedTransfer {
public:
SPIQueuedTransfer(uint8_t *buffer, uint8_t len, SPISettings &settings, uint8_t cs_pin, uint8_t assert_state) :
buffer(buffer), len(len), cs_pin(cs_pin), assert_state(assert_state), is_completed(0), settings(settings), next(NULL) {}


private:
uint8_t *buffer;
uint8_t len : 7;

uint8_t cs_pin : 6;
uint8_t assert_state : 1;
public:
uint8_t is_completed : 1;
private:
SPISettings &settings;
SPIQueuedTransfer *next;

friend class SPIClass;
friend void spi_isr(void); //So that the ISR can acess all the private variables
};
#endif

#if defined(__AVR__)

// define SPI_AVR_EIMSK for AVR boards with external interrupt pins
Expand Down Expand Up @@ -187,8 +228,8 @@ class SPIClass {
pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
}
inTransactionFlag = 1;
#endif
inTransactionFlag = 1;
SPCR = settings.spcr;
SPSR = settings.spsr;
}
Expand Down Expand Up @@ -247,8 +288,8 @@ class SPIClass {
pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
}
inTransactionFlag = 0;
#endif
inTransactionFlag = 0;
if (interruptMode > 0) {
#ifdef SPI_AVR_EIMSK
if (interruptMode == 1) {
Expand All @@ -259,7 +300,49 @@ class SPIClass {
SREG = interruptSave;
}
}
//TODO: Kick off a queued transfer immediately if there is one
}

#ifdef SPI_QUEUED_TRANSFERS
inline static void queueTransfer(SPIQueuedTransfer *transfer) {
uint8_t tmp = SREG;
cli();
transfer->next = nextTransfer;
nextTransfer = transfer;

if(!inTransactionFlag)
workTransferQueue();
SREG = tmp;
}

//NOTE: Interrupts MUST be disabled when calling this method!
inline static void workTransferQueue() {
digitalWrite(15, 1);
inTransactionFlag = 1;

SPIQueuedTransfer *t = nextTransfer;
if(!t) {
inTransactionFlag = 0;
digitalWrite(15, 0);
return;
}

nextTransfer = t->next;

//1. Configure SPI as requested
SPCR = t->settings.spcr | _BV(SPIE); //Config + interrupts
SPSR = t->settings.spsr;

//2. Assert Chip-Select
digitalWrite(t->cs_pin, t->assert_state);

//3. Begin transfer of first byte
curTransfer = t;
curTransferPos = 0; //This is the index of the byte we're sending
SPDR = t->buffer[0];
digitalWrite(15, 0);
}
#endif

// Disable the SPI bus
static void end();
Expand Down Expand Up @@ -291,9 +374,14 @@ class SPIClass {
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
#ifdef SPI_QUEUED_TRANSFERS
static SPIQueuedTransfer *nextTransfer; //Stores the next transfer, or NULL if there aren't any
static SPIQueuedTransfer *curTransfer; //Currently in-progress transfer, or NULL if there isn't any
static uint8_t curTransferPos; //Position in the current byte being read/written

friend void spi_isr(void); //So that the ISR can acess all the private variables
#endif
static uint8_t inTransactionFlag;
#endif
};


Expand Down