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