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