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