1.. _module-pw_digital_io: 2 3.. cpp:namespace-push:: pw::digital_io 4 5============= 6pw_digital_io 7============= 8``pw_digital_io`` provides a set of interfaces for using General Purpose Input 9and Output (GPIO) lines for simple Digital I/O. This module can either be used 10directly by the application code or wrapped in a device driver for more complex 11peripherals. 12 13-------- 14Overview 15-------- 16The interfaces provide an abstract concept of a **Digital IO line**. The 17interfaces abstract away details about the hardware and platform-specific 18drivers. A platform-specific backend is responsible for configuring lines and 19providing an implementation of the interface that matches the capabilities and 20intended usage of the line. 21 22Example API usage: 23 24.. code-block:: cpp 25 26 using namespace pw::digital_io; 27 28 Status UpdateLedFromSwitch(const DigitalIn& switch, DigitalOut& led) { 29 PW_TRY_ASSIGN(const DigitalIo::State state, switch.GetState()); 30 return led.SetState(state); 31 } 32 33 Status ListenForButtonPress(DigitalInterrupt& button) { 34 PW_TRY(button.SetInterruptHandler(Trigger::kActivatingEdge, 35 [](State sampled_state) { 36 // Handle the button press. 37 // NOTE: this may run in an interrupt context! 38 })); 39 return button.EnableInterruptHandler(); 40 } 41 42------------------------- 43pw::digital_io Interfaces 44------------------------- 45There are 3 basic capabilities of a Digital IO line: 46 47* Input - Get the state of the line. 48* Output - Set the state of the line. 49* Interrupt - Register a handler that is called when a trigger happens. 50 51.. note:: **Capabilities** refer to how the line is intended to be used in a 52 particular device given its actual physical wiring, rather than the 53 theoretical capabilities of the hardware. 54 55Additionally, all lines can be *enabled* and *disabled*: 56 57* Enable - tell the hardware to apply power to an output line, connect any 58 pull-up/down resistors, etc. For output lines, the line is set to an initial 59 output state that is backend-specific. 60* Disable - tell the hardware to stop applying power and return the line to its 61 default state. This may save power or allow some other component to drive a 62 shared line. 63 64.. note:: The initial state of a line is implementation-defined and may not 65 match either the "enabled" or "disabled" state. Users of the API who need 66 to ensure the line is disabled (ex. output is not driving the line) should 67 explicitly call ``Disable()``. 68 69Functionality overview 70====================== 71The following table summarizes the interfaces and their required functionality: 72 73.. list-table:: 74 :header-rows: 1 75 :stub-columns: 1 76 77 * - 78 - Interrupts Not Required 79 - Interrupts Required 80 * - Input/Output Not Required 81 - 82 - :cpp:class:`DigitalInterrupt` 83 * - Input Required 84 - :cpp:class:`DigitalIn` 85 - :cpp:class:`DigitalInInterrupt` 86 * - Output Required 87 - :cpp:class:`DigitalOut` 88 - :cpp:class:`DigitalOutInterrupt` 89 * - Input/Output Required 90 - :cpp:class:`DigitalInOut` 91 - :cpp:class:`DigitalInOutInterrupt` 92 93Synchronization requirements 94============================ 95* An instance of a line has exclusive ownership of that line and may be used 96 independently of other line objects without additional synchronization. 97* Access to a single line instance must be synchronized at the application 98 level. For example, by wrapping the line instance in ``pw::Borrowable``. 99* Unless otherwise stated, the line interface must not be used from within an 100 interrupt context. 101 102------------ 103Design Notes 104------------ 105The interfaces are intended to support many but not all use cases, and they do 106not cover every possible type of functionality supported by the hardware. There 107will be edge cases that require the backend to expose some additional (custom) 108interfaces, or require the use of a lower-level API. 109 110Examples of intended use cases: 111 112* Do input and output on lines that have two logical states - active and 113 inactive - regardless of the underlying hardware configuration. 114 115 * Example: Read the state of a switch. 116 * Example: Control a simple LED with on/off. 117 * Example: Activate/deactivate power for a peripheral. 118 * Example: Trigger reset of an I2C bus. 119 120* Run code based on an external interrupt. 121 122 * Example: Trigger when a hardware switch is flipped. 123 * Example: Trigger when device is connected to external power. 124 * Example: Handle data ready signals from peripherals connected to 125 I2C/SPI/etc. 126 127* Enable and disable lines as part of a high-level policy: 128 129 * Example: For power management - disable lines to use less power. 130 * Example: To support shared lines used for multiple purposes (ex. GPIO or 131 I2C). 132 133Examples of use cases we want to allow but don't explicitly support in the API: 134 135* Software-controlled pull up/down resistors, high drive, polarity controls, 136 etc. 137 138 * It's up to the backend implementation to expose configuration for these 139 settings. 140 * Enabling a line should set it into the state that is configured in the 141 backend. 142 143* Level-triggered interrupts on RTOS platforms. 144 145 * We explicitly support disabling the interrupt handler while in the context 146 of the handler. 147 * Otherwise, it's up to the backend to provide any additional level-trigger 148 support. 149 150Examples of uses cases we explicitly don't plan to support: 151 152* Using Digital IO to simulate serial interfaces like I2C (bit banging), or any 153 use cases requiring exact timing and access to line voltage, clock controls, 154 etc. 155* Mode selection - controlling hardware multiplexing or logically switching from 156 GPIO to I2C mode. 157 158API decisions that have been deferred: 159 160* Supporting operations on multiple lines in parallel - for example to simulate 161 a memory register or other parallel interface. 162* Helpers to support different patterns for interrupt handlers - running in the 163 interrupt context, dispatching to a dedicated thread, using a pw_sync 164 primitive, etc. 165 166The following sub-sections discuss specific design decisions in detail. 167 168States vs. voltage levels 169========================= 170Digital IO line values are represented as **active** and **inactive** states. 171These states abstract away the actual electrical level and other physical 172properties of the line. This allows applications to interact with Digital IO 173lines across targets that may have different physical configurations. It is up 174to the backend to provide a consistent definition of state. 175 176There is a helper ``pw::digital_io::Polarity`` enum provided to enable mapping 177from logical to physical states for backends. 178 179Interrupt handling 180================== 181Interrupt handling is part of this API. The alternative was to have a separate 182API for interrupts. We wanted to have a single object that refers to each line 183and represents all the functionality that is available on the line. 184 185Interrupt triggers are configured through the ``SetInterruptHandler`` method. 186The type of trigger is tightly coupled to what the handler wants to do with that 187trigger. 188 189The handler is passed the latest known sampled state of the line. Otherwise 190handlers running in an interrupt context cannot query the state of the line. 191 192Class Hierarchy 193=============== 194``pw_digital_io`` contains a 2-level hierarchy of classes. 195 196* ``DigitalIoOptional`` acts as the base class and represents a line that does 197 not guarantee any particular functionality is available. 198 199 * This should be rarely used in APIs. Prefer to use one of the derived 200 classes. 201 * This class is never extended outside this module. Extend one of the derived 202 classes. 203 204* Derived classes represent a line with a particular combination of 205 functionality. 206 207 * Use a specific class in APIs to represent the requirements. 208 * Extend the specific class that has the actual capabilities of the line. 209 210In the future, we may add new classes that describe lines with **optional** 211functionality. For example, ``DigitalInOptionalInterrupt`` could describe a line 212that supports input and optionally supports interrupts. 213 214When using any classes with optional functionality, including 215``DigitalIoOptional``, you must check that a functionality is available using 216the ``provides_*`` runtime flags. Calling a method that is not supported will 217trigger ``PW_CRASH``. 218 219We define the public API through non-virtual methods declared in 220``DigitalIoOptional``. These methods delegate to private pure virtual methods. 221 222Type Conversions 223================ 224Conversions are provided between classes with compatible requirements. For 225example: 226 227.. code-block:: cpp 228 229 DigitalInInterrupt& in_interrupt_line; 230 DigitalIn& in_line = in_interrupt_line; 231 232 DigitalInInterrupt* in_interrupt_line_ptr; 233 DigitalIn* in_line_ptr = &in_interrupt_line_ptr->as<DigitalIn>(); 234 235Asynchronous APIs 236================= 237At present, ``pw_digital_io`` is synchronous. All the API calls are expected to 238block until the operation is complete. This is desirable for simple GPIO chips 239that are controlled through direct register access. However, this may be 240undesirable for GPIO extenders controlled through I2C or another shared bus. 241 242The API may be extended in the future to add asynchronous capabilities, or a 243separate asynchronous API may be created. 244 245Backend Implemention Notes 246========================== 247* Derived classes explicitly list the non-virtual methods as public or private 248 depending on the supported set of functionality. For example, ``DigitalIn`` 249 declare ``GetState`` public and ``SetState`` private. 250* Derived classes that exclude a particular functionality provide a private, 251 final implementation of the unsupported virtual method that crashes if it is 252 called. For example, ``DigitalIn`` implements ``DoSetState`` to trigger 253 ``PW_CRASH``. 254* Backend implementations provide real implementation for the remaining pure 255 virtual functions of the class they extend. 256* Classes that support optional functionality make the non-virtual optional 257 methods public, but they do not provide an implementation for the pure virtual 258 functions. These classes are never extended. 259* Backend implementations **must** check preconditions for each operations. For 260 example, check that the line is actually enabled before trying to get/set the 261 state of the line. Otherwise return ``pw::Status::FailedPrecondition()``. 262* Backends *may* leave the line in an uninitialized state after construction, 263 but implementors are strongly encouraged to initialize the line to a known 264 state. 265 266 * If backends initialize the line, it must be initialized to the disabled 267 state. i.e. the same state it would be in after calling ``Enable()`` 268 followed by ``Disable()``. 269 * Calling ``Disable()`` on an uninitialized line must put it into the disabled 270 state. In general, ``Disable()`` can be called in any state. 271 272* Calling ``Enable()`` on a line that is already enabled should be a no-op. In 273 particular, the state of an already-enabled output line should not change. 274 275----------- 276RPC Service 277----------- 278The ``DigitalIoService`` pw_rpc service is provided to support bringup/debug 279efforts. It allows manual control of individual DigitalIo lines for both input 280and output. 281 282.. code-block:: cpp 283 284 std::array<std::reference_wrapper<DigitalIoOptional>> lines = { 285 ...DigitalIn(), 286 ...DigitalOut(), 287 }; 288 DigitalIoService service(lines); 289 rpc_server.RegisterService(service); 290 291Set the state of the output line via RPC. This snippet demonstrates how you 292might do that using a Pigweed console device object. 293 294.. code-block:: python 295 296 from pw_digital_io import digital_io_pb2 297 298 device.rpcs.pw.digital_io.DigitalIo.SetState( 299 line_index=0, state=digital_io_pb2.DigitalIoState.ACTIVE) 300 301 302------------- 303API reference 304------------- 305.. note:: 306 This API reference is incomplete. 307 308.. doxygenclass:: pw::digital_io::DigitalIoOptional 309 :members: 310 :private-members: 311 312.. doxygenclass:: pw::digital_io::DigitalInOutMock 313 :members: 314 :private-members: 315 316------------ 317Dependencies 318------------ 319* :ref:`module-pw_assert` 320* :ref:`module-pw_function` 321* :ref:`module-pw_result` 322* :ref:`module-pw_status` 323 324.. cpp:namespace-pop:: 325 326Zephyr 327====== 328To enable ``pw_digital_io`` for Zephyr add ``CONFIG_PIGWEED_DIGITAL_IO=y`` to 329the project's configuration. 330 331 332.. toctree:: 333 :hidden: 334 :maxdepth: 1 335 336 Backends <backends> 337