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