aboutsummaryrefslogtreecommitdiff
path: root/pw_digital_io/docs.rst
blob: 46f3ba077c626e354236aac6d089bd4dfe969ef6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
.. _module-pw_digital_io:

.. cpp:namespace-push:: pw::digital_io

=============
pw_digital_io
=============
.. warning::
   This module is under construction and may not be ready for use.

``pw_digital_io`` provides a set of interfaces for using General Purpose Input
and Output (GPIO) lines for simple Digital I/O. This module can either be used
directly by the application code or wrapped in a device driver for more complex
peripherals.

--------
Overview
--------
The interfaces provide an abstract concept of a **Digital IO line**. The
interfaces abstract away details about the hardware and platform-specific
drivers. A platform-specific backend is responsible for configuring lines and
providing an implementation of the interface that matches the capabilities and
intended usage of the line.

Example API usage:

.. code-block:: cpp

   using namespace pw::digital_io;

   Status UpdateLedFromSwitch(const DigitalIn& switch, DigitalOut& led) {
     PW_TRY_ASSIGN(const DigitalIo::State state, switch.GetState());
     return led.SetState(state);
   }

   Status ListenForButtonPress(DigitalInterrupt& button) {
     PW_TRY(button.SetInterruptHandler(Trigger::kActivatingEdge,
       [](State sampled_state) {
         // Handle the button press.
         // NOTE: this may run in an interrupt context!
       }));
     return button.EnableInterruptHandler();
   }

-------------------------
pw::digital_io Interfaces
-------------------------
There are 3 basic capabilities of a Digital IO line:

* Input - Get the state of the line.
* Output - Set the state of the line.
* Interrupt - Register a handler that is called when a trigger happens.

.. note:: **Capabilities** refer to how the line is intended to be used in a
   particular device given its actual physical wiring, rather than the
   theoretical capabilities of the hardware.

Additionally, all lines can be *enabled* and *disabled*:

* Enable - tell the hardware to apply power to an output line, connect any
  pull-up/down resistors, etc.
* Disable - tell the hardware to stop applying power and return the line to its
  default state. This may save power or allow some other component to drive a
  shared line.

.. note:: The initial state of a line is implementation-defined and may not
   match either the "enabled" or "disabled" state.  Users of the API who need
   to ensure the line is disabled (ex. output is not driving the line) should
   explicitly call ``Disable()``.

Functionality overview
======================
The following table summarizes the interfaces and their required functionality:

.. list-table::
   :header-rows: 1
   :stub-columns: 1

   * -
     - Interrupts Not Required
     - Interrupts Required
   * - Input/Output Not Required
     -
     - :cpp:class:`DigitalInterrupt`
   * - Input Required
     - :cpp:class:`DigitalIn`
     - :cpp:class:`DigitalInInterrupt`
   * - Output Required
     - :cpp:class:`DigitalOut`
     - :cpp:class:`DigitalOutInterrupt`
   * - Input/Output Required
     - :cpp:class:`DigitalInOut`
     - :cpp:class:`DigitalInOutInterrupt`

Synchronization requirements
============================
* An instance of a line has exclusive ownership of that line and may be used
  independently of other line objects without additional synchronization.
* Access to a single line instance must be synchronized at the application
  level. For example, by wrapping the line instance in ``pw::Borrowable``.
* Unless otherwise stated, the line interface must not be used from within an
  interrupt context.

------------
Design Notes
------------
The interfaces are intended to support many but not all use cases, and they do
not cover every possible type of functionality supported by the hardware. There
will be edge cases that require the backend to expose some additional (custom)
interfaces, or require the use of a lower-level API.

Examples of intended use cases:

* Do input and output on lines that have two logical states - active and
  inactive - regardless of the underlying hardware configuration.

  * Example: Read the state of a switch.
  * Example: Control a simple LED with on/off.
  * Example: Activate/deactivate power for a peripheral.
  * Example: Trigger reset of an I2C bus.

* Run code based on an external interrupt.

  * Example: Trigger when a hardware switch is flipped.
  * Example: Trigger when device is connected to external power.
  * Example: Handle data ready signals from peripherals connected to
    I2C/SPI/etc.

* Enable and disable lines as part of a high-level policy:

  * Example: For power management - disable lines to use less power.
  * Example: To support shared lines used for multiple purposes (ex. GPIO or
    I2C).

Examples of use cases we want to allow but don't explicitly support in the API:

* Software-controlled pull up/down resistors, high drive, polarity controls,
  etc.

  * It's up to the backend implementation to expose configuration for these
    settings.
  * Enabling a line should set it into the state that is configured in the
    backend.

* Level-triggered interrupts on RTOS platforms.

  * We explicitly support disabling the interrupt handler while in the context
    of the handler.
  * Otherwise, it's up to the backend to provide any additional level-trigger
    support.

Examples of uses cases we explicitly don't plan to support:

* Using Digital IO to simulate serial interfaces like I2C (bit banging), or any
  use cases requiring exact timing and access to line voltage, clock controls,
  etc.
* Mode selection - controlling hardware multiplexing or logically switching from
  GPIO to I2C mode.

API decisions that have been deferred:

* Supporting operations on multiple lines in parallel - for example to simulate
  a memory register or other parallel interface.
* Helpers to support different patterns for interrupt handlers - running in the
  interrupt context, dispatching to a dedicated thread, using a pw_sync
  primitive, etc.

The following sub-sections discuss specific design decisions in detail.

States vs. voltage levels
=========================
Digital IO line values are represented as **active** and **inactive** states.
These states abstract away the actual electrical level and other physical
properties of the line. This allows applications to interact with Digital IO
lines across targets that may have different physical configurations. It is up
to the backend to provide a consistent definition of state.

Interrupt handling
==================
Interrupt handling is part of this API. The alternative was to have a separate
API for interrupts. We wanted to have a single object that refers to each line
and represents all the functionality that is available on the line.

Interrupt triggers are configured through the ``SetInterruptHandler`` method.
The type of trigger is tightly coupled to what the handler wants to do with that
trigger.

The handler is passed the latest known sampled state of the line. Otherwise
handlers running in an interrupt context cannot query the state of the line.

Class Hierarchy
===============
``pw_digital_io`` contains a 2-level hierarchy of classes.

* ``DigitalIoOptional`` acts as the base class and represents a line that does
  not guarantee any particular functionality is available.

  * This should be rarely used in APIs. Prefer to use one of the derived
    classes.
  * This class is never extended outside this module. Extend one of the derived
    classes.

* Derived classes represent a line with a particular combination of
  functionality.

  * Use a specific class in APIs to represent the requirements.
  * Extend the specific class that has the actual capabilities of the line.

In the future, we may add new classes that describe lines with **optional**
functionality. For example, ``DigitalInOptionalInterrupt`` could describe a line
that supports input and optionally supports interrupts.

When using any classes with optional functionality, including
``DigitalIoOptional``, you must check that a functionality is available using
the ``provides_*`` runtime flags. Calling a method that is not supported will
trigger ``PW_CRASH``.

We define the public API through non-virtual methods declared in
``DigitalIoOptional``. These methods delegate to private pure virtual methods.

Type Conversions
================
Conversions are provided between classes with compatible requirements. For
example:

.. code-block:: cpp

   DigitalInInterrupt& in_interrupt_line;
   DigitalIn& in_line = in_interrupt_line;

   DigitalInInterrupt* in_interrupt_line_ptr;
   DigitalIn* in_line_ptr = &in_interrupt_line_ptr->as<DigitalIn>();

Asynchronous APIs
=================
At present, ``pw_digital_io`` is synchronous. All the API calls are expected to
block until the operation is complete. This is desirable for simple GPIO chips
that are controlled through direct register access. However, this may be
undesirable for GPIO extenders controlled through I2C or another shared bus.

The API may be extended in the future to add asynchronous capabilities, or a
separate asynchronous API may be created.

Backend Implemention Notes
==========================
* Derived classes explicitly list the non-virtual methods as public or private
  depending on the supported set of functionality. For example, ``DigitalIn``
  declare ``GetState`` public and ``SetState`` private.
* Derived classes that exclude a particular functionality provide a private,
  final implementation of the unsupported virtual method that crashes if it is
  called. For example, ``DigitalIn`` implements ``DoSetState`` to trigger
  ``PW_CRASH``.
* Backend implementations provide real implementation for the remaining pure
  virtual functions of the class they extend.
* Classes that support optional functionality make the non-virtual optional
  methods public, but they do not provide an implementation for the pure virtual
  functions. These classes are never extended.
* Backend implementations **must** check preconditions for each operations. For
  example, check that the line is actually enabled before trying to get/set the
  state of the line. Otherwise return ``pw::Status::FailedPrecondition()``.
* Backends *may* leave the line in an uninitialized state after construction,
  but implementors are strongly encouraged to initialize the line to a known
  state.

  * If backends initialize the line, it must be initialized to the disabled
    state. i.e. the same state it would be in after calling ``Enable()``
    followed by ``Disable()``.
  * Calling ``Disable()`` on an uninitialized line must put it into the disabled
    state.

------------
Dependencies
------------
* :ref:`module-pw_assert`
* :ref:`module-pw_function`
* :ref:`module-pw_result`
* :ref:`module-pw_status`

.. cpp:namespace-pop::

Zephyr
======
To enable ``pw_digital_io`` for Zephyr add ``CONFIG_PIGWEED_DIGITAL_IO=y`` to
the project's configuration.