blob: 40cbb54e24536c792c8ae37a3cc4a8138aa00635 [file]
.. _module-pw_spi:
======
pw_spi
======
.. pigweed-module::
:name: pw_spi
``pw_spi`` provides a set of interfaces for communicating with Serial
Peripheral Interface (SPI) responders attached to a target. It also provides an
interface for implementing SPI responders.
--------
Overview
--------
The ``pw_spi`` module provides a series of interfaces that facilitate the
development of SPI responder drivers that are abstracted from the target's
SPI hardware implementation. The interface consists of these main classes:
- :cc:`Initiator <pw::spi::Initiator>` - Interface for configuring a SPI
bus, and using it to transmit and receive data.
- :cc:`ChipSelector <pw::spi::ChipSelector>` - Interface for
enabling/disabling a SPI responder attached to the bus.
- :cc:`Device <pw::spi::Device>` - primary HAL interface used to interact
with a SPI responder.
- :cc:`Responder <pw::spi::Responder>` - Interface for implementing a SPI
responder.
``pw_spi`` relies on a target-specific implementations of :cc:`Initiator
<pw::spi::Initiator>` and :cc:`ChipSelector <pw::spi::ChipSelector>` to
be defined, and injected into :cc:`Device <pw::spi::Device>` objects
which are used to communicate with a given responder attached to a target's SPI
bus.
--------
Examples
--------
Constructing a SPI device
=========================
.. code-block:: cpp
constexpr pw::spi::Config kConfig = {
.polarity = pw::spi::ClockPolarity::kActiveHigh,
.phase = pw::spi::ClockPhase::kRisingEdge,
.bits_per_word = pw::spi::BitsPerWord(8),
.bit_order = pw::spi::BitOrder::kLsbFirst,
};
auto initiator = pw::spi::MyInitator();
auto mutex = pw::sync::VirtualMutex();
auto selector = pw::spi::MyChipSelector();
auto device = pw::spi::Device(
pw::sync::Borrowable<Initiator>(initiator, mutex), kConfig, selector);
This example demonstrates the construction of a :cc:`Device
<pw::spi::Device>` from its object dependencies and configuration data; where
``MyDevice`` and ``MyChipSelector`` are concrete implementations of the
:cc:`Initiator <pw::spi::Initiator>` and :cc:`ChipSelector
<pw::spi::ChipSelector>` interfaces, respectively.
The use of :cc:`Borrowable <pw::sync::Borrowable>` in the interface
provides a mutual-exclusion wrapper for the injected :cc:`Initiator
<pw::spi::Initiator>`, ensuring that transactions cannot be interrupted or
corrupted by other concurrent workloads making use of the same SPI bus.
Once constructed, the ``device`` object can then be passed to functions used to
perform SPI transfers with a target responder.
Performing a transfer
=====================
.. code-block:: cpp
pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
std::array<std::byte, 16> raw_sensor_data;
constexpr std::array<std::byte, 2> kAccelReportCommand = {std::byte{0x13},
std::byte{0x37}};
// This device supports full-duplex transfers
PW_TRY(device.WriteRead(kAccelReportCommand, raw_sensor_data));
return UnpackSensorData(raw_sensor_data);
}
The ``ReadSensorData()`` function implements a driver function for a contrived
SPI accelerometer. The function performs a full-duplex transfer with the
device to read its current data.
As this function relies on the ``device`` object that abstracts the details
of bus-access and chip-selection, the function is portable to any target
that implements its underlying interfaces.
Performing a multi-part transaction
===================================
.. code-block:: cpp
pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
std::array<std::byte, 16> raw_sensor_data;
constexpr std::array<std::byte, 2> kAccelReportCommand = {std::byte{0x13},
std::byte{0x37}};
// Creation of the RAII `transaction` acquires exclusive access to the bus
pw::spi::Device::Transaction transaction =
device.StartTransaction(pw::spi::ChipSelectBehavior::kPerTransaction);
// This device only supports half-duplex transfers
PW_TRY(transaction.Write(kAccelReportCommand));
PW_TRY(transaction.Read(raw_sensor_data))
return UnpackSensorData(raw_sensor_data);
// Destruction of RAII `transaction` object releases lock on the bus
}
The code above is similar to the previous example, but makes use of the
``Transaction`` API in :cc:`Device <pw::spi::Device>` to perform separate,
half-duplex ``Write()`` and ``Read()`` transfers, as is required by the sensor
in this example.
The use of the RAII ``transaction`` object in this example guarantees that
no other thread can perform transfers on the same SPI bus
(:cc:`Initiator <pw::spi::Initiator>`) until it goes out-of-scope.
Responding to an initiator
==========================
.. code-block:: cpp
MyResponder responder;
responder.SetCompletionHandler([](ByteSpan rx_data, Status status) {
// Handle incoming data from initiator.
// ...
// Prepare data to send back to initiator during next SPI transaction.
responder.WriteReadAsync(tx_data, rx_data);
});
// Prepare data to send back to initiator during next SPI transaction.
responder.WriteReadAsync(tx_data, rx_data)
Mocking transactions
====================
:cc:`MockInitiator <pw::spi::MockInitiator>` is a generic mocked backend
for ``Initiator`` that is specifically intended for use when developing drivers
for SPI devices. It's structured around a set of "transactions" where each
transaction contains a write, a read, and a status. A transaction list can then
be passed to the ``MockInitiator``, where each consecutive call to
read/write will iterate to the next transaction in the list. Example:
.. code-block:: cpp
using pw::spi::MakeExpectedTransactionlist;
using pw::spi::MockInitiator;
using pw::spi::MockWriteTransaction;
constexpr auto kExpectWrite1 = pw::bytes::Array<1, 2, 3, 4, 5>();
constexpr auto kExpectWrite2 = pw::bytes::Array<3, 4, 5>();
auto expected_transactions = MakeExpectedTransactionArray(
{MockWriteTransaction(pw::OkStatus(), kExpectWrite1),
MockWriteTransaction(pw::OkStatus(), kExpectWrite2)});
MockInitiator spi_mock(expected_transactions);
// Begin driver code
ConstByteSpan write1 = kExpectWrite1;
// write1 is ok as spi_mock expects {1, 2, 3, 4, 5} == {1, 2, 3, 4, 5}
Status status = spi_mock.WriteRead(write1, ConstByteSpan());
// Takes the first two bytes from the expected array to build a mismatching
// span to write.
ConstByteSpan write2 = pw::span(kExpectWrite2).first(2);
// write2 fails as spi_mock expects {3, 4, 5} != {3, 4}
status = spi_mock.WriteRead(write2, ConstByteSpan());
// End driver code
// Optionally check if the mocked transaction list has been exhausted.
// Alternatively this is also called from MockInitiator::~MockInitiator().
EXPECT_EQ(spi_mock.Finalize(), OkStatus());
-------------
API reference
-------------
Moved: :cc:`pw_spi`
.. toctree::
:hidden:
:maxdepth: 1
backends