Serial Peripheral Interface (SPI)#

Hardware Interface#

Defined in namespace hal

#include <libhal/spi.hpp>

class spi#

Serial peripheral interface (SPI) communication protocol hardware abstraction interface.

This interface supports the most common SPI features:

  1. Word length locked to 8-bits

  2. Byte transfer is always MSB first

  3. Chip select is not controlled by this driver

Design Philosophy#

By restricting the SPI interface requirements, we ensure compatibility with nearly all devices that communicate over SPI. This approach simplifies both the requirements and the code for SPI drivers and implementations.

8-bit Transfers#

The 8-bit word length is the most common SPI word length, compatible with almost any SPI peripheral driver, SPI converter driver, and most devices. Devices that support 16-bit word transfers can split the 16-bit word into two bytes. Devices using word formats that aren’t multiples of 8 bits are very rare.

MSB First Transfers#

The most common bit order for SPI is MSB first. Devices that use LSB first are rare. Drivers using this SPI interface must handle bit reversal to comply with the MSB first requirement. This decision helps to eliminate rare and potentially unsupportable configurations, optimizing the interface for the majority of use cases.

Manual Chip Select Control#

Many SPI peripherals have a dedicated chip select pin that can be controlled manually. Automatic chip select control asserts the chip select for the duration of the transfer and de-asserts it at the end, which can be problematic for drivers needing to perform multiple transfers while keeping the chip select asserted. For instance, if payload parts are in ROM, copying them to a buffer for a full transfer requires more stack space than calling the transfer multiple times with the ROM data stream. Some devices, like SD cards, require the chip select to be held with a sequence of clock cycles until they respond with actual data. This sequence can vary, so manual chip select control allows more precise and memory-efficient operations.

Subclassed by hal::bit_bang_spi, hal::lpc40::dma_spi, hal::lpc40::spi, hal::mock::write_only_spi, hal::soft::bit_bang_spi, hal::soft::inert_spi, hal::stm32f1::spi, hal::stm32f411::spi

Public Functions

inline void configure(settings const &p_settings)#

Configure spi to match the settings supplied.

Parameters:

p_settings – - settings to apply to spi

Throws:

hal::operation_not_supported – - if the settings could not be achieved.

inline void transfer(std::span<hal::byte const> p_data_out, std::span<hal::byte> p_data_in, hal::byte p_filler = default_filler)#

Send and receive data between a selected device on the spi bus. This function will block until the entire transfer is finished.

Parameters:
  • p_data_out – - buffer to write data to the bus. If this is set to null/empty then writing is ignored and the p_filler will be written to the bus. If the length is less than p_data_in, then p_filler will be written to the bus after this buffer has been sent.

  • p_data_in – - buffer to read the data off of the bus. If this is null/empty, then the transfer will be write only and the incoming data will be ignored. If the length of this buffer is less than p_data_out, once this buffer has been filled, the rest of the received bytes on the bus will be dropped.

  • p_filler – - filler data placed on the bus in place of actual write data when p_data_out has been exhausted.

Public Static Attributes

static constexpr hal::byte default_filler = hal::byte{0xFF}#

Default filler data placed on the bus in place of actual write data when the write buffer has been exhausted.

struct settings#

Generic settings for a standard SPI device.

Public Members

hertz clock_rate = 100.0_kHz#

Serial clock frequency in hertz.

union hal::spi::settings::[anonymous] [anonymous]#

The polarity of the pins when the signal is idle.

CPOL == 0 == false CPOL == 1 == true

union hal::spi::settings::[anonymous] [anonymous]#

The phase of the clock signal when communicating.

CPHA == 0 == false CPHA == 1 == true

Utilities#

Defined in namespace hal

#include <libhal-util/spi.hpp>

group SPI

Functions

constexpr auto operator==(spi::settings const &p_lhs, spi::settings const &p_rhs)#

Compares two SPI objects via their settings.

Parameters:
  • p_lhs – A SPI object

  • p_rhs – A SPI object

Returns:

A boolean if they are the same or not.

inline void write(spi &p_spi, std::span<hal::byte const> p_data_out)#

Write data to the spi bus, ignore data on the peripherals receive line.

This command is useful for spi operations where data, such as a command, must be sent to a device and the device does not respond with anything or the response is not necessary.

Parameters:
  • p_spi – - spi driver

  • p_data_out – - data to be written to the spi bus

inline void read(spi &p_spi, std::span<hal::byte> p_data_in, hal::byte p_filler = spi::default_filler)#

Read data from the SPI bus.

Filler bytes will be placed on the write/transmit line.

Parameters:
  • p_spi – - spi driver

  • p_data_in – - buffer to receive bytes back from the spi bus

  • p_filler – - filler data placed on the bus in place of actual data.

template<size_t bytes_to_read>
std::array<hal::byte, bytes_to_read> read(spi &p_spi, hal::byte p_filler = spi::default_filler)#

Read data from the SPI bus and return a std::array of bytes.

Filler bytes will be placed on the write line.

Template Parameters:

bytes_to_read – - Number of bytes to read

Parameters:
  • p_spi – - spi driver

  • p_filler – - filler data placed on the bus in place of actual write data.

Returns:

std::array<hal::byte, bytes_to_read> - array containing bytes read from the spi bus

inline void write_then_read(spi &p_spi, std::span<hal::byte const> p_data_out, std::span<hal::byte> p_data_in, hal::byte p_filler = spi::default_filler)#

Write data to the SPI bus and ignore data sent from peripherals on the bus then read data from the SPI and fill the write line with filler bytes.

This utility function that fits the use case of many spi devices where a spi transfer is not full duplex. In many spi devices, full duplex means that as data is being written to the spi bus, the peripheral device is sending data back on the receive line. In most cases, the device’s communication protocol is simply:

  1. Write data to the bus, ignore the receive line

  2. Read from spi bus, filling the write line with filler

Parameters:
  • p_spi – - spi driver

  • p_data_out – - bytes to write to the bus

  • p_data_in – - buffer to receive bytes back from the spi bus

  • p_filler – - filler data placed on the bus when the read operation begins.

template<size_t bytes_to_read>
std::array<hal::byte, bytes_to_read> write_then_read(spi &p_spi, std::span<hal::byte const> p_data_out, hal::byte p_filler = spi::default_filler)#

Write data to the SPI bus and ignore data sent from peripherals on the bus then read data from the SPI, fill the write line with filler bytes and return an array of bytes.

See write_then_read() for details about this function.

Template Parameters:

bytes_to_read – - Number of bytes to read from the bus

Parameters:
  • p_spi – - spi driver

  • p_data_out – - bytes to write to the bus

  • p_filler – - filler data placed on the bus when the read operation begins.

Returns:

std::array<hal::byte, bytes_to_read> - array containing the bytes read from the spi bus.