SPI

transfer(void*, void*, size_t, std::function)

For transferring a large number of bytes, this form of transfer() uses DMA to speed up SPI data transfer and at the same time allows you to run code in parallel to the data transmission. The function initializes, configures and enables the DMA peripheral’s channel and stream for the selected SPI peripheral for both outgoing and incoming data and initiates the data transfer. If a user callback function is passed then it will be called after completion of the DMA transfer. This results in asynchronous filling of RX buffer after which the DMA transfer is disabled till the transfer function is called again. If NULL is passed as a callback then the result is synchronous i.e. the function will only return once the DMA transfer is complete.

Note: The SPI protocol is based on a one byte OUT / one byte IN interface. For every byte expected to be received, one (dummy, typically 0x00 or 0xFF) byte must be sent.

// PROTOTYPE
void transfer(const void* tx_buffer, void* rx_buffer, size_t length, wiring_spi_dma_transfercomplete_callback_t user_callback);
typedef void (*wiring_spi_dma_transfercomplete_callback_t)(void);

// SYNTAX
SPI.transfer(tx_buffer, rx_buffer, length, myFunction);

Parameters:

  • tx_buffer: array of Tx bytes that is filled by the user before starting the SPI transfer. If NULL, default dummy 0xFF bytes will be clocked out.
  • rx_buffer: array of Rx bytes that will be filled by the slave during the SPI transfer. If NULL, the received data will be discarded.
  • length: number of data bytes that are to be transferred
  • myFunction: user specified function callback to be called after completion of the SPI DMA transfer. It takes no argument and returns nothing, e.g.: void myHandler(). Can be NULL if not using a callback.

NOTE: tx_buffer and rx_buffer sizes MUST be identical (of size length)

Since 0.5.0 When SPI peripheral is configured in slave mode, the transfer will be canceled when the master deselects this slave device. The user application can check the actual number of bytes received/transmitted by calling available().

Note that you must use the same SPI object as used with SPI.begin() so if you used SPI1.begin() also use SPI1.transfer().

If you are using the callback, it is called as an interrupt service routine (ISR) and there are many restrictions when calling from an interrupt context. Specifically for SPI, you cannot SPI.endTransaction() or start another DMA transaction using SPI.transfer().

You should not set the CS pin high from the callback ISR. The callback is called when the DMA transaction is completed, which can be before the data is actually transmitted out of the SPI FIFO.

Thus in practice it's often better to perform your SPI operations from a worker thread instead of chained interrupts if you want asynchronous execution.

Things you should not do from an ISR:

  • Any memory allocation or free: new, delete, malloc, free, strdup, etc.
  • Any Particle class function like Particle.publish, Particle.subscribe, etc.
  • Most API functions, with the exception of pinSetFast, pinResetFast, and analogRead.
  • delay or other functions that block.
  • Log.info, Log.error, etc.
  • sprintf, Serial.printlnf, etc. with a %f (float) value.
  • attachInterrupt and detachInterrupt cannot be called within the ISR.
  • Mutex locks. This includes SPI transactions and I2C lock and unlock.
  • Start an SPI.transaction with DMA.

Since 3.0.0:

On Gen 3 devices with Device OS 3.0.0 and later you can pass byte arrays stored in the program flash in tx_buffer. The nRF52 DMA controller does not support transferring directly out of flash memory, but in Device OS 3.0.0 the data will be copied in chunks to a temporary buffer in RAM automatically. Prior to 3.0.0 you had to do this manually in your code.