• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_spi:
2
3======
4pw_spi
5======
6Pigweed's SPI module provides a set of interfaces for communicating with SPI
7responders attached to a target. It also provides an interface for implementing
8SPI responders.
9
10--------
11Overview
12--------
13The ``pw_spi`` module provides a series of interfaces that facilitate the
14development of SPI responder drivers that are abstracted from the target's
15SPI hardware implementation.  The interface consists of these main classes:
16
17- ``pw::spi::Initiator`` - Interface for configuring a SPI bus, and using it
18  to transmit and receive data.
19- ``pw::spi::ChipSelector`` - Interface for enabling/disabling a SPI
20  responder attached to the bus.
21- ``pw::spi::Device`` - primary HAL interface used to interact with a SPI
22  responder.
23- ``pw::spi::Responder`` - Interface for implementing a SPI responder.
24
25``pw_spi`` relies on a target-specific implementations of
26``pw::spi::Initiator`` and ``pw::spi::ChipSelector`` to be defined, and
27injected into ``pw::spi::Device`` objects which are used to communicate with a
28given responder attached to a target's SPI bus.
29
30Example - Constructing a SPI Device:
31
32.. code-block:: cpp
33
34   constexpr pw::spi::Config kConfig = {
35       .polarity = pw::spi::ClockPolarity::kActiveHigh,
36       .phase = pw::spi::ClockPhase::kRisingEdge,
37       .bits_per_word = pw::spi::BitsPerWord(8),
38       .bit_order = pw::spi::BitOrder::kLsbFirst,
39   };
40
41   auto initiator = pw::spi::MyInitator();
42   auto mutex = pw::sync::VirtualMutex();
43   auto selector = pw::spi::MyChipSelector();
44
45   auto device = pw::spi::Device(
46      pw::sync::Borrowable<Initiator>(initiator, mutex), kConfig, selector);
47
48This example demonstrates the construction of a ``pw::spi::Device`` from its
49object dependencies and configuration data; where ``MyDevice`` and
50`MyChipSelector`` are concrete implementations of the ``pw::spi::Initiator``
51and ``pw::spi::ChipSelector`` interfaces, respectively.
52
53The use of ``pw::sync::Borrowable`` in the interface provides a
54mutual-exclusion wrapper for the the injected ``pw::spi::Initiator``, ensuring
55that transactions cannot be interrupted or corrupted by other concurrent
56workloads making use of the same SPI bus.
57
58Once constructed, the ``device`` object can then be passed to functions used to
59perform SPI transfers with a target responder.
60
61Example - Performing a Transfer:
62
63.. code-block:: cpp
64
65   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
66     std::array<std::byte, 16> raw_sensor_data;
67     constexpr std::array<std::byte, 2> kAccelReportCommand = {
68         std::byte{0x13}, std::byte{0x37}};
69
70     // This device supports full-duplex transfers
71     PW_TRY(device.WriteRead(kAccelReportCommand, raw_sensor_data));
72     return UnpackSensorData(raw_sensor_data);
73   }
74
75The ``ReadSensorData()`` function implements a driver function for a contrived
76SPI accelerometer.  The function performs a full-duplex transfer with the
77device to read its current data.
78
79As this function relies on the ``device`` object that abstracts the details
80of bus-access and chip-selection, the function is portable to any target
81that implements its underlying interfaces.
82
83Example - Performing a Multi-part Transaction:
84
85.. code-block:: cpp
86
87   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
88     std::array<std::byte, 16> raw_sensor_data;
89     constexpr std::array<std::byte, 2> kAccelReportCommand = {
90         std::byte{0x13}, std::byte{0x37}};
91
92     // Creation of the RAII `transaction` acquires exclusive access to the bus
93     pw::spi::Device::Transaction transaction =
94       device.StartTransaction(pw::spi::ChipSelectBehavior::kPerTransaction);
95
96     // This device only supports half-duplex transfers
97     PW_TRY(transaction.Write(kAccelReportCommand));
98     PW_TRY(transaction.Read(raw_sensor_data))
99
100     return UnpackSensorData(raw_sensor_data);
101
102     // Destruction of RAII `transaction` object releases lock on the bus
103   }
104
105The code above is similar to the previous example, but makes use of the
106``Transaction`` API in ``pw::spi::Device`` to perform separate, half-duplex
107``Write()`` and ``Read()`` transfers, as is required by the sensor in this
108examplre.
109
110The use of the RAII ``transaction`` object in this example guarantees that
111no other thread can perform transfers on the same SPI bus
112(``pw::spi::Initiator``) until it goes out-of-scope.
113
114------------------
115pw::spi Interfaces
116------------------
117The SPI API consists of the following components:
118
119- The ``pw::spi::Initiator`` interface, and its associated configuration data
120  structs.
121- The ``pw::spi::ChipSelector`` interface.
122- The ``pw::spi::Device`` class.
123- The ``pw::spi::Responder`` interface.
124
125pw::spi::Initiator
126------------------
127.. inclusive-language: disable
128
129The common interface for configuring a SPI bus, and initiating transfers using
130it.
131
132A concrete implementation of this interface class *must* be defined in order
133to use ``pw_spi`` with a specific target.
134
135The ``spi::Initiator`` object configures the SPI bus to communicate with a
136defined set of common bus parameters that include:
137
138- clock polarity/phase
139- bits-per-word (between 3-32 bits)
140- bit ordering (LSB or MSB first)
141
142These bus configuration parameters are aggregated in the ``pw::spi::Config``
143structure, and passed to the ``pw::spi::Initiator`` via its ``Configure()``
144method.
145
146.. Note:
147
148   Throughout ``pw_spi``, the terms "initiator" and "responder" are used to
149   describe the two roles SPI devices can implement.  These terms correspond
150   to the  "master" and "slave" roles described in legacy documentation
151   related to the SPI protocol.
152
153.. inclusive-language: enable
154
155.. cpp:class:: pw::spi::Initiator
156
157   .. cpp:function:: Status Configure(const Config& config)
158
159      Configure the SPI bus to communicate using a specific set of properties,
160      including the clock polarity, clock phase, bit-order, and bits-per-word.
161
162      Returns OkStatus() on success, and implementation-specific values on
163      failure conditions
164
165   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) = 0;
166
167      Perform a synchronous read/write operation on the SPI bus.  Data from the
168      `write_buffer` object is written to the bus, while the `read_buffer` is
169      populated with incoming data on the bus.  The operation will ensure that
170      all requested data is written-to and read-from the bus. In the event the
171      read buffer is smaller than the write buffer (or zero-size), any
172      additional input bytes are discarded. In the event the write buffer is
173      smaller than the read buffer (or zero size), the output is padded with
174      0-bits for the remainder of the transfer.
175
176      Returns OkStatus() on success, and implementation-specific values on
177      failure.
178
179pw::spi::ChipSelector
180---------------------
181.. doxygenclass:: pw::spi::ChipSelector
182   :members:
183
184pw::spi::DigitalOutChipSelector
185-------------------------------
186.. doxygenclass:: pw::spi::DigitalOutChipSelector
187   :members:
188
189pw::spi::Device
190---------------
191This is primary object used by a client to interact with a target SPI device.
192It provides a wrapper for an injected ``pw::spi::Initiator`` object, using
193its methods to configure the bus and perform individual SPI transfers.  The
194injected ``pw::spi::ChipSelector`` object is used internally to activate and
195de-actviate the device on-demand from within the data transfer methods.
196
197The ``Read()``/``Write()``/``WriteRead()`` methods provide support for
198performing individual transfers:  ``Read()`` and ``Write()`` perform
199half-duplex operations, where ``WriteRead()`` provides support for
200full-duplex transfers.
201
202The ``StartTransaction()`` method provides support for performing multi-part
203transfers consisting of a series of ``Read()``/``Write()``/``WriteRead()``
204calls, during which the caller is guaranteed exclusive access to the
205underlying bus.  The ``Transaction`` objects returned from this method
206implements the RAII layer providing exclusive access to the bus; exclusive
207access locking is released when the ``Transaction`` object is destroyed/goes
208out of scope.
209
210Mutual-exclusion to the ``pw::spi::Initiator`` object is provided by the use of
211the ``pw::sync::Borrowable`` object, where the ``pw::spi::Initiator`` object is
212"borrowed" for the duration of a transaction.
213
214.. cpp:class:: pw::spi::Device
215
216   .. cpp:function:: Status Read(Bytespan read_buffer)
217
218      Synchronously read data from the SPI responder until the provided
219      `read_buffer` is full.
220      This call will configure the bus and activate/deactivate chip select
221      for the transfer
222
223      Note: This call will block in the event that other clients are currently
224      performing transactions using the same SPI Initiator.
225
226      Returns OkStatus() on success, and implementation-specific values on
227      failure.
228
229   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
230
231      Synchronously write the contents of `write_buffer` to the SPI responder.
232      This call will configure the bus and activate/deactivate chip select
233      for the transfer
234
235      Note: This call will block in the event that other clients are currently
236      performing transactions using the same SPI Initiator.
237
238      Returns OkStatus() on success, and implementation-specific values on
239      failure.
240
241   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
242
243      Perform a synchronous read/write transfer with the SPI responder. Data
244      from the `write_buffer` object is written to the bus, while the
245      `read_buffer` is populated with incoming data on the bus.  In the event
246      the read buffer is smaller than the write buffer (or zero-size), any
247      additional input bytes are discarded. In the event the write buffer is
248      smaller than the read buffer (or zero size), the output is padded with
249      0-bits for the remainder of the transfer.
250      This call will configure the bus and activate/deactivate chip select
251      for the transfer
252
253      Note: This call will block in the event that other clients are currently
254      performing transactions using the same SPI Initiator.
255
256      Returns OkStatus() on success, and implementation-specific values on
257      failure.
258
259   .. cpp:function:: Transaction StartTransaction(ChipSelectBehavior behavior)
260
261      Begin a transaction with the SPI device.  This creates an RAII
262      `Transaction` object that ensures that only one entity can access the
263      underlying SPI bus (Initiator) for the object's duration. The `behavior`
264      parameter provides a means for a client to select how the chip-select
265      signal will be applied on Read/Write/WriteRead calls taking place with
266      the Transaction object. A value of `kPerWriteRead` will activate/deactivate
267      chip-select on each operation, while `kPerTransaction` will hold the
268      chip-select active for the duration of the Transaction object.
269
270.. cpp:class:: pw::spi::Device::Transaction
271
272   .. cpp:function:: Status Read(Bytespan read_buffer)
273
274      Synchronously read data from the SPI responder until the provided
275      `read_buffer` is full.
276
277      Returns OkStatus() on success, and implementation-specific values on
278      failure.
279
280   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
281
282      Synchronously write the contents of `write_buffer` to the SPI responder
283
284      Returns OkStatus() on success, and implementation-specific values on
285      failure.
286
287   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
288
289      Perform a synchronous read/write transfer on the SPI bus.  Data from the
290      `write_buffer` object is written to the bus, while the `read_buffer` is
291      populated with incoming data on the bus.  The operation will ensure that
292      all requested data is written-to and read-from the bus. In the event the
293      read buffer is smaller than the write buffer (or zero-size), any
294      additional input bytes are discarded. In the event the write buffer is
295      smaller than the read buffer (or zero size), the output is padded with
296      0-bits for the remainder of the transfer.
297
298      Returns OkStatus() on success, and implementation-specific values on
299      failure.
300
301pw::spi::MockInitiator
302----------------------
303A generic mocked backend for for pw::spi::Initiator. This is specifically
304intended for use when developing drivers for spi devices. This is structured
305around a set of 'transactions' where each transaction contains a write, read and
306a status. A transaction list can then be passed to the MockInitiator, where
307each consecutive call to read/write will iterate to the next transaction in the
308list. An example of this is shown below:
309
310.. code-block:: cpp
311
312   using pw::spi::MakeExpectedTransactionlist;
313   using pw::spi::MockInitiator;
314   using pw::spi::MockWriteTransaction;
315
316   constexpr auto kExpectWrite1 = pw::bytes::Array<1, 2, 3, 4, 5>();
317   constexpr auto kExpectWrite2 = pw::bytes::Array<3, 4, 5>();
318   auto expected_transactions = MakeExpectedTransactionArray(
319       {MockWriteTransaction(pw::OkStatus(), kExpectWrite1),
320        MockWriteTransaction(pw::OkStatus(), kExpectWrite2)});
321   MockInitiator spi_mock(expected_transactions);
322
323   // Begin driver code
324   ConstByteSpan write1 = kExpectWrite1;
325   // write1 is ok as spi_mock expects {1, 2, 3, 4, 5} == {1, 2, 3, 4, 5}
326   Status status = spi_mock.WriteRead(write1, ConstByteSpan());
327
328   // Takes the first two bytes from the expected array to build a mismatching
329   // span to write.
330   ConstByteSpan write2 = pw::span(kExpectWrite2).first(2);
331   // write2 fails as spi_mock expects {3, 4, 5} != {3, 4}
332   status = spi_mock.WriteRead(write2, ConstByteSpan());
333   // End driver code
334
335   // Optionally check if the mocked transaction list has been exhausted.
336   // Alternatively this is also called from MockInitiator::~MockInitiator().
337   EXPECT_EQ(spi_mock.Finalize(), OkStatus());
338
339pw::spi::Responder
340------------------
341The common interface for implementing a SPI responder. It provides a way to
342respond to SPI transactions coming from a SPI initiator in a non-target specific
343way. A concrete implementation of the ``Responder`` class should be provided for
344the target hardware. Applications can then use it to implement their specific
345protocols.
346
347.. code-block:: cpp
348
349   MyResponder responder;
350   responder.SetCompletionHandler([](ByteSpan rx_data, Status status) {
351     // Handle incoming data from initiator.
352     // ...
353     // Prepare data to send back to initiator during next SPI transaction.
354     responder.WriteReadAsync(tx_data, rx_data);
355   });
356
357   // Prepare data to send back to initiator during next SPI transaction.
358   responder.WriteReadAsync(tx_data, rx_data)
359
360.. toctree::
361   :hidden:
362   :maxdepth: 1
363
364   Backends <backends>
365