• 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
7peripherals attached to a target.
8
9--------
10Overview
11--------
12The ``pw_spi`` module provides a series of interfaces that facilitate the
13development of SPI peripheral drivers that are abstracted from the target's
14SPI hardware implementation.  The interface consists of three main classes:
15
16- ``pw::spi::Initiator`` - Interface for configuring a SPI bus, and using it
17  to transmit and receive data.
18- ``pw::spi::ChipSelector`` - Interface for enabling/disabling a SPI
19  peripheral attached to the bus.
20- ``pw::spi::Device`` - primary HAL interface used to interact with a SPI
21  peripheral.
22
23``pw_spi`` relies on a target-specific implementations of
24``pw::spi::Initiator`` and ``pw::spi::ChipSelector`` to be defined, and
25injected into ``pw::spi::Device`` objects which are used to communicate with a
26given peripheral attached to a target's SPI bus.
27
28Example - Constructing a SPI Device:
29
30.. code-block:: cpp
31
32   constexpr pw::spi::Config kConfig = {
33       .polarity = pw::spi::ClockPolarity::kActiveHigh,
34       .phase = pw::spi::ClockPhase::kRisingEdge,
35       .bits_per_word = pw::spi::BitsPerWord(8),
36       .bit_order = pw::spi::BitOrder::kLsbFirst,
37   };
38
39   auto initiator = pw::spi::MyInitator();
40   auto selector = pw::spi::MyChipSelector();
41   auto borrowable_initiator = pw::sync::Borrowable<Initiator&>(initiator);
42
43   auto device = pw::spi::Device(borrowable_initiator, kConfig, selector);
44
45This example demonstrates the construction of a ``pw::spi::Device`` from its
46object dependencies and configuration data; where ``MyDevice`` and
47`MyChipSelector`` are concrete implementations of the ``pw::spi::Initiator``
48and ``pw::spi::ChipSelector`` interfaces, respectively.
49
50The use of ``pw::sync::Borrowable`` in the interface provides a
51mutual-exclusion wrapper for the the injected ``pw::spi::Initiator``, ensuring
52that transactions cannot be interrupted or corrupted by other concurrent
53workloads making use of the same SPI bus.
54
55Once constructed, the ``device`` object can then be passed to functions used to
56perform SPI transfers with a target peripheral.
57
58Example - Performing a Transfer:
59
60.. code-block:: cpp
61
62   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
63     std::array<std::byte, 16> raw_sensor_data;
64     constexpr std::array<std::byte, 2> kAccelReportCommand = {
65         std::byte{0x13}, std::byte{0x37}};
66
67     // This device supports full-duplex transfers
68     PW_TRY(device.WriteRead(kAccelReportCommand, raw_sensor_data));
69     return UnpackSensorData(raw_sensor_data);
70   }
71
72The ``ReadSensorData()`` function implements a driver function for a contrived
73SPI accelerometer.  The function performs a full-duplex transfer with the
74device to read its current data.
75
76As this function relies on the ``device`` object that abstracts the details
77of bus-access and chip-selection, the function is portable to any target
78that implements its underlying interfaces.
79
80Example - Performing a Multi-part Transaction:
81
82.. code-block:: cpp
83
84   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
85     std::array<std::byte, 16> raw_sensor_data;
86     constexpr std::array<std::byte, 2> kAccelReportCommand = {
87         std::byte{0x13}, std::byte{0x37}};
88
89     // Creation of the RAII `transaction` acquires exclusive access to the bus
90     std::optional<pw::spi::Device::Transaction> transaction =
91       device.StartTransaction(pw::spi::ChipSelectBehavior::kPerTransaction);
92     if (!transaction.has_value()) {
93       return pw::Status::Unknown();
94     )
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
124pw::spi::Initiator
125------------------
126.. inclusive-language: disable
127
128The common interface for configuring a SPI bus, and initiating transfers using
129it.
130
131A concrete implementation of this interface class *must* be defined in order
132to use ``pw_spi`` with a specific target.
133
134The ``spi::Initiator`` object configures the SPI bus to communicate with a
135defined set of common bus parameters that include:
136
137- clock polarity/phase
138- bits-per-word (between 3-32 bits)
139- bit ordering (LSB or MSB first)
140
141These bus configuration parameters are aggregated in the ``pw::spi::Config``
142structure, and passed to the ``pw::spi::Initiator`` via its ``Configure()``
143method.
144
145.. Note:
146
147   Throughtout ``pw_spi``, the terms "controller" and "peripheral" are used to
148   describe the two roles SPI devices can implement.  These terms correspond
149   to the  "master" and "slave" roles described in legacy documentation
150   related to the SPI protocol.
151
152   ``pw_spi`` only supports SPI transfers where the target implements the
153   "controller" role, and does not support the target acting in the
154   "peripheral" role.
155
156.. inclusive-language: enable
157
158.. cpp:class:: pw::spi::Initiator
159
160   .. cpp:function:: Status Configure(const Config& config)
161
162      Configure the SPI bus to coummunicate using a specific set of properties,
163      including the clock polarity, clock phase, bit-order, and bits-per-word.
164
165      Returns OkStatus() on success, and implementation-specific values on
166      failure conditions
167
168   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) = 0;
169
170      Perform a synchronous read/write operation on the SPI bus.  Data from the
171      `write_buffer` object is written to the bus, while the `read_buffer` is
172      populated with incoming data on the bus.  The operation will ensure that
173      all requested data is written-to and read-from the bus. In the event the
174      read buffer is smaller than the write buffer (or zero-size), any
175      additional input bytes are discarded. In the event the write buffer is
176      smaller than the read buffer (or zero size), the output is padded with
177      0-bits for the remainder of the transfer.
178
179      Returns OkStatus() on success, and implementation-specific values on
180      failure.
181
182pw::spi::ChipSelector
183---------------------
184The ChipSelector class provides an abstract interface for controlling the
185chip-select signal associated with a specific SPI peripheral.
186
187This interface provides a ``SetActive()`` method, which activates/deactivates
188the device based on the value of the `active` parameter.  The associated
189``Activate()`` and ``Deactivate()`` methods are utility wrappers for
190``SetActive(true)`` and ``SetActive(false)``, respectively.
191
192A concrete implementation of this interface class must be provided in order to
193use the SPI HAL to communicate with a peripheral.
194
195.. Note::
196
197   `Active` does not imply a specific logic-level; it is left to the
198   implementor to correctly map logic-levels to the device's active/inactive
199   states.
200
201.. cpp:class:: pw::spi::ChipSelector
202
203   .. cpp:function:: Status SetActive(bool active)
204
205      SetActive sets the state of the chip-select signal to the value
206      represented by the `active` parameter.  Passing a value of `true` will
207      activate the chip-select signal, and `false` will deactive the
208      chip-select signal.
209
210      Returns OkStatus() on success, and implementation-specific values on
211      failure.
212
213   .. cpp:function:: Status Activate()
214
215      Helper method to activate the chip-select signal
216
217      Returns OkStatus() on success, and implementation-specific values on
218      failure.
219
220   .. cpp:function:: Status Deactivate()
221
222      Helper method to deactivate the chip-select signal
223
224      Returns OkStatus() on success, and implementation-specific values on
225      failure.
226
227pw::spi::Device
228---------------
229This is primary object used by a client to interact with a target SPI device.
230It provides a wrapper for an injected ``pw::spi::Initator`` object, using
231its methods to configure the bus and perform individual SPI transfers.  The
232injected ``pw::spi::ChipSelector`` object is used internally to activate and
233de-actviate the device on-demand from within the data transfer methods.
234
235The ``Read()``/``Write()``/``WriteRead()`` methods provide support for
236performing inidividual transfers:  ``Read()`` and ``Write()`` perform
237half-duplex operations, where ``WriteRead()`` provides support for
238full-duplex transfers.
239
240The ``StartTransaction()`` method provides support for performing multi-part
241transfers consisting of a series of ``Read()``/``Write()``/``WriteRead()``
242calls, during which the caller is guaranteed exclusive access to the
243underlying bus.  The ``Transaction`` objects returned from this method
244implements the RAII layer providing exclusive access to the bus; exclusive
245access locking is released when the ``Transaction`` object is destroyed/goes
246out of scope.
247
248Mutual-exclusion to the ``pw::spi::Initiator`` object is provided by the use of
249the ``pw::sync::Borrowable`` object, where the ``pw::spi::Initiator`` object is
250"borrowed" for the duration of a transaction.
251
252.. cpp:class:: pw::spi::Device
253
254   .. cpp:function:: Status Read(Bytespan read_buffer)
255
256      Synchronously read data from the SPI peripheral until the provided
257      `read_buffer` is full.
258      This call will configure the bus and activate/deactive chip select
259      for the transfer
260
261      Note: This call will block in the event that other clients are currently
262      performing transactions using the same SPI Initiator.
263
264      Returns OkStatus() on success, and implementation-specific values on
265      failure.
266
267   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
268
269      Synchronously write the contents of `write_buffer` to the SPI peripheral.
270      This call will configure the bus and activate/deactive chip select
271      for the transfer
272
273      Note: This call will block in the event that other clients are currently
274      performing transactions using the same SPI Initiator.
275
276      Returns OkStatus() on success, and implementation-specific values on
277      failure.
278
279   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
280
281      Perform a synchronous read/write transfer with the SPI peripheral. Data
282      from the `write_buffer` object is written to the bus, while the
283      `read_buffer` is populated with incoming data on the bus.  In the event
284      the read buffer is smaller than the write buffer (or zero-size), any
285      additional input bytes are discarded. In the event the write buffer is
286      smaller than the read buffer (or zero size), the output is padded with
287      0-bits for the remainder of the transfer.
288      This call will configure the bus and activate/deactive chip select
289      for the transfer
290
291      Note: This call will block in the event that other clients are currently
292      performing transactions using the same SPI Initiator.
293
294      Returns OkStatus() on success, and implementation-specific values on
295      failure.
296
297   .. cpp:function:: Transaction StartTransaction(ChipSelectBehavior behavior)
298
299      Begin a transaction with the SPI device.  This creates an RAII
300      `Transaction` object that ensures that only one entity can access the
301      underlying SPI bus (Initiator) for the object's duration. The `behavior`
302      parameter provides a means for a client to select how the chip-select
303      signal will be applied on Read/Write/WriteRead calls taking place with
304      the Transaction object. A value of `kPerWriteRead` will activate/deactive
305      chip-select on each operation, while `kPerTransaction` will hold the
306      chip-select active for the duration of the Transaction object.
307
308.. cpp:class:: pw::spi::Device::Transaction
309
310   .. cpp:function:: Status Read(Bytespan read_buffer)
311
312      Synchronously read data from the SPI peripheral until the provided
313      `read_buffer` is full.
314
315      Returns OkStatus() on success, and implementation-specific values on
316      failure.
317
318   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
319
320      Synchronously write the contents of `write_buffer` to the SPI peripheral
321
322      Returns OkStatus() on success, and implementation-specific values on
323      failure.
324
325   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
326
327      Perform a synchronous read/write transfer on the SPI bus.  Data from the
328      `write_buffer` object is written to the bus, while the `read_buffer` is
329      populated with incoming data on the bus.  The operation will ensure that
330      all requested data is written-to and read-from the bus. In the event the
331      read buffer is smaller than the write buffer (or zero-size), any
332      additional input bytes are discarded. In the event the write buffer is
333      smaller than the read buffer (or zero size), the output is padded with
334      0-bits for the remainder of the transfer.
335
336      Returns OkStatus() on success, and implementation-specific values on
337      failure.
338
339