• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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