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