2022-06-14 16:48:23 +00:00
|
|
|
.. _module-pw_digital_io:
|
2022-05-06 19:24:29 +00:00
|
|
|
|
2022-06-14 16:48:23 +00:00
|
|
|
.. cpp:namespace-push:: pw::digital_io
|
2022-05-06 19:24:29 +00:00
|
|
|
|
2022-06-14 16:48:23 +00:00
|
|
|
=============
|
|
|
|
pw_digital_io
|
|
|
|
=============
|
2022-05-06 19:24:29 +00:00
|
|
|
.. warning::
|
|
|
|
This module is under construction and may not be ready for use.
|
|
|
|
|
2022-06-14 16:48:23 +00:00
|
|
|
``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.
|
2022-05-06 19:24:29 +00:00
|
|
|
|
|
|
|
--------
|
|
|
|
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
|
|
|
|
|
2022-06-14 16:48:23 +00:00
|
|
|
using namespace pw::digital_io;
|
2022-05-06 19:24:29 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-06-14 16:48:23 +00:00
|
|
|
-------------------------
|
|
|
|
pw::digital_io Interfaces
|
|
|
|
-------------------------
|
2022-05-06 19:24:29 +00:00
|
|
|
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
|
|
|
|
===============
|
2022-06-14 16:48:23 +00:00
|
|
|
``pw_digital_io`` contains a 2-level hierarchy of classes.
|
2022-05-06 19:24:29 +00:00
|
|
|
|
|
|
|
* ``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 support additional for 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
|
|
|
|
=================
|
2022-06-14 16:48:23 +00:00
|
|
|
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.
|
2022-05-06 19:24:29 +00:00
|
|
|
|
|
|
|
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
|
2022-06-24 18:17:52 +00:00
|
|
|
called. For example, ``DigitalIn`` implements ``DoSetState`` to trigger
|
2022-05-06 19:24:29 +00:00
|
|
|
``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
|
|
|
|
======
|
2022-06-14 16:48:23 +00:00
|
|
|
To enable ``pw_digital_io`` for Zephyr add ``CONFIG_PIGWEED_DIGITAL_IO=y`` to
|
|
|
|
the project's configuration.
|