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