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