Mbed OS Reference
|
An SPI Master, used for communicating with SPI slave devices. More...
#include <SPI.h>
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... | |
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.
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:
And here's how to do the same thing using a transaction:
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.
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.
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:
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?
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.
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.
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.
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.
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.
To save a little bit of memory, you can also set the queue length to 0 to disable the queueing mechanism.
SPI | ( | PinName | mosi, |
PinName | miso, | ||
PinName | sclk, | ||
PinName | ssel = NC |
||
) |
Create a SPI master connected to the specified pins.
SPI | ( | PinName | mosi, |
PinName | miso, | ||
PinName | sclk, | ||
PinName | ssel, | ||
use_gpio_ssel_t | |||
) |
Create a SPI master connected to the specified pins.
SPI | ( | const spi_pinmap_t & | static_pinmap | ) |
Create a SPI master connected to the specified pins.
static_pinmap | reference to structure which holds static pinmap. |
SPI | ( | const spi_pinmap_t & | static_pinmap, |
PinName | ssel | ||
) |
Create a SPI master connected to the specified pins.
static_pinmap | reference to structure which holds static pinmap. |
ssel | SPI Chip Select pin. |
void format | ( | int | bits, |
int | mode = 0 |
||
) |
Configure the data transmission format.
bits | Number of bits per SPI frame (4 - 32, target dependent). |
mode | Clock polarity and phase mode (0 - 3). |
void frequency | ( | int | hz = 1000000 | ) |
Set the SPI bus clock frequency.
hz | Clock frequency in Hz (default = 1MHz). |
|
virtual |
Write to the SPI Slave and return the response.
value | Data to be sent to the SPI slave. The number of significant bits in this value depend on the bits parameter to format(). |
bits
parameter to format(). 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.
tx_buffer | Pointer to the byte-array of data to write to the device. |
tx_length | Number of bytes to write, may be zero. |
rx_buffer | Pointer to the byte-array of data to read from the device. |
rx_length | Number of bytes to read, may be zero. |
|
virtual |
|
virtual |
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.
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().
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.
data | Default character to be transmitted during a read operation. |
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.
tx_buffer | The TX buffer with data to be transferred. If NULL is passed, the default SPI value is sent. |
tx_length | The length of TX buffer in bytes. |
rx_buffer | The 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_length | The length of RX buffer in bytes. |
callback | The event callback function. |
event | The 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 |
0 | If the transfer has started. |
-1 | if the transfer could not be enqueued (increase drivers.spi_transaction_queue_len option) |
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.
tx_buffer | The TX buffer with data to be transferred. May be nullptr if tx_length is 0. |
tx_length | The length of TX buffer in bytes. If 0, the default SPI data value is sent when receiving data. |
rx_buffer | The 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_length | The length of RX buffer in bytes If 0, no reception is done. |
timeout | timeout value. Use rtos::Kernel::wait_for_u32_forever to wait forever (the default). |
-1 | if the transfer could not be enqueued (increase drivers.spi_transaction_queue_len option) |
1 | on timeout |
2 | on other error |
0 | on success |
void abort_transfer | ( | ) |
Abort the on-going SPI transfer, if any, and continue with transfers in the queue, if any.
void clear_transfer_buffer | ( | ) |
Clear the queue of transfers.
If a transfer is currently active, it will continue until complete.
void abort_all_transfers | ( | ) |
Clear the queue of transfers and abort any on-going transfer.
int set_dma_usage | ( | DMAUsage | usage | ) |
Configure DMA usage suggestion for non-blocking transfers.
usage | The usage DMA hint for peripheral. |
0 | The usage was set. |
-1 | Usage cannot be set as there is an ongoing transaction. |