xref: /aosp_15_r20/external/pigweed/pw_digital_io/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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