Mbed OS Reference
Loading...
Searching...
No Matches
SPI Class Reference

An SPI Master, used for communicating with SPI slave devices. More...

#include <SPI.h>

Inheritance diagram for SPI:
NonCopyable< SPI > EMW3080B_SPI

Public Member Functions

 SPI (PinName mosi, PinName miso, PinName sclk, PinName ssel=NC)
 Create a SPI master connected to the specified pins. More...
 
 SPI (PinName mosi, PinName miso, PinName sclk, PinName ssel, use_gpio_ssel_t)
 Create a SPI master connected to the specified pins. More...
 
 SPI (const spi_pinmap_t &static_pinmap)
 Create a SPI master connected to the specified pins. More...
 
 SPI (const spi_pinmap_t &static_pinmap, PinName ssel)
 Create a SPI master connected to the specified pins. More...
 
void format (int bits, int mode=0)
 Configure the data transmission format. More...
 
void frequency (int hz=1000000)
 Set the SPI bus clock frequency. More...
 
virtual int write (int value)
 Write to the SPI Slave and return the response. More...
 
template<typename WordT >
std::enable_if< std::is_integral< WordT >::value, int >::type write (const WordT *tx_buffer, int tx_length, WordT *rx_buffer, int rx_length)
 Write to the SPI Slave and obtain the response. More...
 
virtual void lock (void)
 Acquire exclusive access to this SPI bus. More...
 
virtual void unlock (void)
 Release exclusive access to this SPI bus. More...
 
void select (void)
 Assert the Slave Select line and acquire exclusive access to this SPI bus. More...
 
void deselect (void)
 Deassert the Slave Select line, releasing exclusive access to this SPI bus. More...
 
void set_default_write_value (char data)
 Set default write data. More...
 
template<typename WordT >
std::enable_if< std::is_integral< WordT >::value, int >::type transfer (const WordT *tx_buffer, int tx_length, CacheAlignedBuffer< WordT > &rx_buffer, int rx_length, const event_callback_t &callback, int event=SPI_EVENT_COMPLETE)
 Start non-blocking SPI transfer. More...
 
template<typename WordT >
std::enable_if< std::is_integral< WordT >::value, int >::type transfer_and_wait (const WordT *tx_buffer, int tx_length, CacheAlignedBuffer< WordT > &rx_buffer, int rx_length, rtos::Kernel::Clock::duration_u32 timeout=rtos::Kernel::wait_for_u32_forever)
 Start SPI transfer and wait until it is complete. More...
 
void abort_transfer ()
 Abort the on-going SPI transfer, if any, and continue with transfers in the queue, if any. More...
 
void clear_transfer_buffer ()
 Clear the queue of transfers. More...
 
void abort_all_transfers ()
 Clear the queue of transfers and abort any on-going transfer. More...
 
int set_dma_usage (DMAUsage usage)
 Configure DMA usage suggestion for non-blocking transfers. More...
 

Detailed Description

An SPI Master, used for communicating with SPI slave devices.

The default format is set to 8-bits, mode 0, and a clock frequency of 1MHz.

Note
Synchronization level: Thread safe

SPI allows you to transfer data to and from peripheral devices using single-word transfers, transactions, or an asynchronous API.

Here's how to talk to a chip using the single-word API:

#include "mbed.h"
SPI device(SPI_MOSI, SPI_MISO, SPI_SCLK, SPI_CS, use_gpio_ssel)
int main() {
device.format(8, 0);
device.lock();
device.select();
int response = device.write(0x0A);
int response2 = device.write(0x0B);
device.deselect();
device.unlock();
}
An SPI Master, used for communicating with SPI slave devices.
Definition: SPI.h:263
void format(int bits, int mode=0)
Configure the data transmission format.

And here's how to do the same thing using a transaction:

#include "mbed.h"
SPI device(SPI_MOSI, SPI_MISO, SPI_SCLK, SPI_CS, use_gpio_ssel)
int main() {
device.format(8, 0);
uint8_t command[2] = {0x0A, 0x0B};
uint8_t response[2];
int result = device.write(command, sizeof(command), response, sizeof(response));
}

Hardware vs Software Chip Selects

Most ARM chips have specific pins marked as "hardware chip selects". This means that they are connected to the SPI peripheral and are automatically brought low by hardware when data is sent. However, chips often only have only one pin for each SPI bus that can be used as a hardware chip select. If you wish to use multiple peripheral devices with one SPI bus, or just don't have access to the HW CS pin, you must use the SPI object in "GPIO chip select" mode.

To use GPIO CS mode, simply pass the CS pin as the 4th constructor parameter, then pass the special constant use_gpio_ssel as the 5th parameter. This puts the object in GPIO mode, where it will operate the CS line as a regular GPIO pin before and after doing an SPI transfer. This mode is marginally slower than HW CS mode, but otherwise should offer the same functionality. In fact, since the pin mapping is more flexible, it may be advisable to use GPIO CS mode by default.

Warning
It is not recommended to control the CS line using a separate DigitalOut instance! This was needed in very old Mbed versions but should now be considered a legacy practice. Not only does it make it difficult to use the asynchronous API, but it can also lead to corner cases which corrupt data. The CS line, whether done through GPIO or HW, should always be managed through the SPI class.

Sharing a Bus

Multiple SPI devices may share the same physical bus, so long as each has its own dedicated chip select (CS) pin. To implement this sharing, each chip's driver should create its own instance of the SPI class, passing the same MOSI, MISO, and SCLK pins but a different CS pin. Mbed OS will internally share the SPI hardware between these objects. Note that this is completely different from how the I2C class handles sharing.

Frame/Word Size

Mbed OS supports configuration of the SPI frame size, also known as the word size, which controls how many bits are sent in one transfer operation (one call to SPI::write()). This parameter should match the "register size" of the SPI peripheral that you are talking to. For example, if you're working with a chip which uses 16-bit registers, you should set the frame size to 16 using SPI::format(). Some Mbed devices also support 32-bit frames – use the DEVICE_SPI_32BIT_WORDS feature macro to test if yours does.

The frame size controls the effective width of data written and read from the chip. For example, if you set frame size to 8, SPI::write(int) will take one byte and return one byte, but if you set it to 16, SPI::write(int) will take a 16 bit value and return a 16 bit value. You can also do transactions with frame sizes other than 8. Just be sure to pass the length in bytes, not words!

It should be noted that changing the frame size can perform an apparent "endian swap" on data being transmitted. For example, suppose you have the 32-bit integer 0x01020408. On a little-endian processor, this will be encoded with the LSByte first in memory: 08 04 02 01. If you send that integer using one-byte word size, it will appear as such. Consider the following example:

spi.format(8, 0);
uint32_t myInteger = 0x01020408;
spi.write(reinterpret_cast<const char *>(&myInteger), sizeof(myInteger), nullptr, 0);

If you ran this code, then used a logic analyzer to view the bytes on the MOSI line, it would show bytes matching the layout of the integer in memory:

MOSI -> 08 04 02 01

But what about if you used a 32-bit frame size?

spi.format(32, 0);
uint32_t myInteger = 0x01020408;
spi.write<uint32_t>(&myInteger, sizeof(myInteger), nullptr, 0);

In this case, the complete 32-bit integer is sent as a single unit, from its MSBit to its LSBit, without breaking it into bytes. This will send the data in an order different from the order in memory. Viewed as bytes, it would look like:

MOSI -> 01 02 04 08

But viewed by a peripheral chip which uses 32-bit SPI words, it would look like:

MOSI -> 0x01020408

When viewed as bytes, it's almost as if you did an endian swap on the data before sending it, but in fact it's standard SPI behavior when using frame sizes greater than one byte. This same rule applies to receiving data, so be sure to check examples in the datasheet to determine what frame size to use and whether byte swapping is needed when working with an external chip.

Note: Some Mbed targets support frame sizes that are not standard integer sizes, e.g. 4 bits, 7 bits, or 24 bits. However, the behavior of these frame sizes is not well defined and may differ across targets or across API calls (e.g. single-byte vs transaction vs async). More work is needed to make these consistent.

Asynchronous API

On many processors, Mbed OS also supports asynchronous SPI. This feature allows you to run SPI transfers completely in the background, while other threads execute in the foreground. This can be extremely useful if you need to send large amounts of data over the SPI bus but don't want to block your main thread for ages. To see if your processor supports async SPI, look for the DEVICE_SPI_ASYNCH macro.

The asynchronous API has two different modes: nonblocking (where your thread can keep running, and the transfer calls a callback when finished) and blocking (where your thread blocks for the duration of the transfer but others can execute). Here's a sample of how to send the same data as above using the blocking async API:

Note that when using the asynchronous API, you must use the CacheAlignedBuffer class when declaring the receive buffer. This is because some processors' async SPI implementations require the received buffer to be at an address which is aligned to the processor cache line size. CacheAlignedBuffer takes care of this for you and provides functions (data(), begin(), end()) to access the underlying data in the buffer.

#include "mbed.h"
SPI device(SPI_MOSI, SPI_MISO, SPI_SCLK, SPI_CS, use_gpio_ssel)
int main() {
device.format(8, 0);
uint8_t command[2] = {0x0A, 0x0B};
int result = device.transfer_and_wait(command, sizeof(command), response, sizeof(command));
}
CacheAlignedBuffer is used by Mbed in locations where we need a cache-aligned buffer.

This code will cause the data in command to be sent to the device and the response to be received into response . During the transfer, the current thread is paused, but other threads can execute. The non-blocking API does not pause the current thread, but is a bit more complicated to use. See the SPI::transfer_and_wait() implementation in SPI.cpp for an example.

Async: DMA vs Interrupts

Some processors only provide asynchronous SPI via interrupts, some only support DMA, and some offer both. Using interrupts is simpler and generally doesn't require additional resources from the chip, however, some CPU time will be spent servicing the interrupt while the transfer is running. This can become very performance-intensive – on some chips, running async SPI at frequencies of just a few MHz can be enough to make the interrupt use 100% of CPU time. In contrast, DMA can require additional resources to be allocated, (e.g. chips with few DMA channels might only support DMA on one or two SPI busses at a time), but can run the bus at full speed without any CPU overhead. Generally, DMA should be preferred if available, especially if medium to fast bus speeds are needed.

Consult your chip documentation and the Mbed port docs for your target to find out what is needed to enable DMA support. For example, for STMicro targets, see here.To select DMA or interrupts, use the SPI::set_dma_usage() function. By default, interrupt SPI will be used unless you change the setting.

Async: Queueing

The async SPI system supports an optional transaction queueing mechanism. When this is enabled, Mbed will allow multiple transactions to be queued up on a single bus, and will execute each one and deliver the appropriate callback in series. This is mainly useful for the non-blocking async api (SPI::transfer()), though you can also use it with the blocking API by having multiple threads call it at once.

The transaction queue size defaults to 2 on most devices, but you can change that using the drivers.spi_transaction_queue_len option, e.g.

{
"target_overrides": {
"*": {
"drivers.spi_transaction_queue_len": 3
}
}
}

To save a little bit of memory, you can also set the queue length to 0 to disable the queueing mechanism.

Warning
Currently, the value set by SPI::set_default_write_value() is not respected by the asynchronous SPI code. This needs to be fixed.

Definition at line 263 of file SPI.h.

Constructor & Destructor Documentation

◆ SPI() [1/4]

SPI ( PinName  mosi,
PinName  miso,
PinName  sclk,
PinName  ssel = NC 
)

Create a SPI master connected to the specified pins.

Note
This constructor passes the SSEL pin selection to the target HAL. Not all targets support SSEL, so this cannot be relied on in portable code. Portable code should use the alternative constructor that uses GPIO for SSEL.
You can specify mosi or miso as NC if not used.
Parameters
mosiSPI Master Out, Slave In pin.
misoSPI Master In, Slave Out pin.
sclkSPI Clock pin.
sselSPI Chip Select pin.

◆ SPI() [2/4]

SPI ( PinName  mosi,
PinName  miso,
PinName  sclk,
PinName  ssel,
use_gpio_ssel_t   
)

Create a SPI master connected to the specified pins.

Note
This constructor manipulates the SSEL pin as a GPIO output using a DigitalOut object. This should work on any target, and permits the use of select() and deselect() methods to keep the pin asserted between transfers.
You can specify mosi or miso as NC if not used.
Parameters
mosiSPI Master Out, Slave In pin.
misoSPI Master In, Slave Out pin.
sclkSPI Clock pin.
sselSPI Chip Select pin.

◆ SPI() [3/4]

SPI ( const spi_pinmap_t static_pinmap)

Create a SPI master connected to the specified pins.

Note
This constructor passes the SSEL pin selection to the target HAL. Not all targets support SSEL, so this cannot be relied on in portable code. Portable code should use the alternative constructor that uses GPIO for SSEL.
You can specify mosi or miso as NC if not used.
Parameters
static_pinmapreference to structure which holds static pinmap.

◆ SPI() [4/4]

SPI ( const spi_pinmap_t static_pinmap,
PinName  ssel 
)

Create a SPI master connected to the specified pins.

Note
This constructor manipulates the SSEL pin as a GPIO output using a DigitalOut object. This should work on any target, and permits the use of select() and deselect() methods to keep the pin asserted between transfers.
You can specify mosi or miso as NC if not used.
Parameters
static_pinmapreference to structure which holds static pinmap.
sselSPI Chip Select pin.

Member Function Documentation

◆ format()

void format ( int  bits,
int  mode = 0 
)

Configure the data transmission format.

Parameters
bitsNumber of bits per SPI frame (4 - 32, target dependent).
modeClock polarity and phase mode (0 - 3).
mode | POL PHA
-----+--------
0 | 0 0
1 | 0 1
2 | 1 0
3 | 1 1

◆ frequency()

void frequency ( int  hz = 1000000)

Set the SPI bus clock frequency.

Parameters
hzClock frequency in Hz (default = 1MHz).

◆ write() [1/2]

virtual int write ( int  value)
virtual

Write to the SPI Slave and return the response.

Parameters
valueData to be sent to the SPI slave. The number of significant bits in this value depend on the bits parameter to format().
Returns
Response from the SPI slave. The number of significant bits in this value depend on the bits parameter to format().

◆ write() [2/2]

std::enable_if< std::is_integral< WordT >::value, int >::type write ( const WordT *  tx_buffer,
int  tx_length,
WordT *  rx_buffer,
int  rx_length 
)

Write to the SPI Slave and obtain the response.

The total number of bytes sent and received will be the maximum of tx_length and rx_length. The bytes written will be padded with the value 0xff.

Note: Even if the word size / bits per frame is not 8, rx_length and tx_length still count bytes of input data, not numbers of words.

Parameters
tx_bufferPointer to the byte-array of data to write to the device.
tx_lengthNumber of bytes to write, may be zero.
rx_bufferPointer to the byte-array of data to read from the device.
rx_lengthNumber of bytes to read, may be zero.
Returns
The number of bytes written and read from the device (as an int). This is maximum of tx_length and rx_length.

Definition at line 385 of file SPI.h.

◆ lock()

virtual void lock ( void  )
virtual

Acquire exclusive access to this SPI bus.

This function blocks until the chosen SPI peripheral is not being used by any other SPI objects. Careful – if other code leaves the bus locked, this could block forever!

◆ unlock()

virtual void unlock ( void  )
virtual

Release exclusive access to this SPI bus.

This allows other code to do operations using the SPI peripheral.

◆ select()

void select ( void  )

Assert the Slave Select line and acquire exclusive access to this SPI bus.

The slave select line will remain selected (low) for all following operations until you call deselect() on this instance. This allows you to string together multiple SPI transactions as if they were a single operation (from the perspective of peripheral chips).

If use_gpio_ssel was not passed to the constructor, manual control of the SSEL line is not possible, and this function behaves identically to lock().

Like lock(), this function will block until exclusive access can be acquired.

Warning
Do not call this function while an asynchronous transfer is in progress, as undefined behavior can occur.

◆ deselect()

void deselect ( void  )

Deassert the Slave Select line, releasing exclusive access to this SPI bus.

If use_gpio_ssel was not passed to the constructor, manual control of the SSEL line is not possible, and this function behaves identically to unlock().

Warning
Do not call this function while an asynchronous transfer is in progress, as undefined behavior can occur.

◆ set_default_write_value()

void set_default_write_value ( char  data)

Set default write data.

SPI requires the master to send some data during a read operation. Different devices may require different default byte values. For example: A SD Card requires default bytes to be 0xFF.

Parameters
dataDefault character to be transmitted during a read operation.

◆ transfer()

std::enable_if< std::is_integral< WordT >::value, int >::type transfer ( const WordT *  tx_buffer,
int  tx_length,
CacheAlignedBuffer< WordT > &  rx_buffer,
int  rx_length,
const event_callback_t callback,
int  event = SPI_EVENT_COMPLETE 
)

Start non-blocking SPI transfer.

This function locks the deep sleep until any event has occurred.

Parameters
tx_bufferThe TX buffer with data to be transferred. If NULL is passed, the default SPI value is sent.
tx_lengthThe length of TX buffer in bytes.
rx_bufferThe RX buffer which is used for received data. Rather than a C array, a CacheAlignedBuffer structure must be passed so that cache alignment can be handled for data received from DMA. May be nullptr if rx_length is 0.
rx_lengthThe length of RX buffer in bytes.
callbackThe event callback function.
eventThe logical OR of events to subscribe to. May be SPI_EVENT_ALL, or some combination of the flags SPI_EVENT_ERROR, SPI_EVENT_COMPLETE, or SPI_EVENT_RX_OVERFLOW
Returns
Operation result (integer)
Return values
0If the transfer has started.
-1if the transfer could not be enqueued (increase drivers.spi_transaction_queue_len option)

Definition at line 481 of file SPI.h.

◆ transfer_and_wait()

std::enable_if< std::is_integral< WordT >::value, int >::type transfer_and_wait ( const WordT *  tx_buffer,
int  tx_length,
CacheAlignedBuffer< WordT > &  rx_buffer,
int  rx_length,
rtos::Kernel::Clock::duration_u32  timeout = rtos::Kernel::wait_for_u32_forever 
)

Start SPI transfer and wait until it is complete.

Like the transactional API this blocks the current thread, however all work is done in the background and other threads may execute.

As long as there is space, this function will enqueue the transfer request onto the peripheral, and block until it is done.

Internally, the chip vendor may implement this function using either DMA or interrupts.

Parameters
tx_bufferThe TX buffer with data to be transferred. May be nullptr if tx_length is 0.
tx_lengthThe length of TX buffer in bytes. If 0, the default SPI data value is sent when receiving data.
rx_bufferThe RX buffer which is used for received data. Rather than a C array, a CacheAlignedBuffer structure must be passed so that cache alignment can be handled for data received from DMA. May be nullptr if rx_length is 0.
rx_lengthThe length of RX buffer in bytes If 0, no reception is done.
timeouttimeout value. Use rtos::Kernel::wait_for_u32_forever to wait forever (the default).
Returns
Operation result (integer)
Return values
-1if the transfer could not be enqueued (increase drivers.spi_transaction_queue_len option)
1on timeout
2on other error
0on success

Definition at line 529 of file SPI.h.

◆ abort_transfer()

void abort_transfer ( )

Abort the on-going SPI transfer, if any, and continue with transfers in the queue, if any.

◆ clear_transfer_buffer()

void clear_transfer_buffer ( )

Clear the queue of transfers.

If a transfer is currently active, it will continue until complete.

◆ abort_all_transfers()

void abort_all_transfers ( )

Clear the queue of transfers and abort any on-going transfer.

◆ set_dma_usage()

int set_dma_usage ( DMAUsage  usage)

Configure DMA usage suggestion for non-blocking transfers.

Parameters
usageThe usage DMA hint for peripheral.
Returns
Result of the operation.
Return values
0The usage was set.
-1Usage cannot be set as there is an ongoing transaction.