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#
Pigweed AI summary: This section provides an overview of the Digital IO line interfaces, which abstract away hardware and platform-specific drivers. A platform-specific backend configures the lines and provides an implementation of the interface. The section also includes an example API usage for updating an LED from a switch and listening for a button press.
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:
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#
Pigweed AI summary: This section discusses the three basic capabilities of a Digital IO line: input, output, and interrupt. It also explains how all lines can be enabled and disabled, and provides a table summarizing the required functionality for different interfaces. The section also outlines synchronization requirements, including exclusive ownership of a line instance and the need for application-level synchronization. It warns against using the line interface within an interrupt context.
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. For output lines, the line is set to an initial output state that is backend-specific.
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#
Pigweed AI summary: This paragraph summarizes a table that lists different interfaces and their required functionality. The table includes information on whether interrupts are required, as well as whether input/output is required for each interface. The interfaces listed include DigitalInterrupt, DigitalIn, DigitalInInterrupt, DigitalOut, DigitalOutInterrupt, DigitalInOut, and DigitalInOutInterrupt.
The following table summarizes the interfaces and their required functionality:
Interrupts Not Required |
Interrupts Required |
|
---|---|---|
Input/Output Not Required |
|
|
Input Required |
|
|
Output Required |
|
|
Input/Output Required |
|
|
Synchronization requirements#
Pigweed AI summary: This paragraph discusses the synchronization requirements for using a line object. It states that each instance of a line has exclusive ownership and can be used independently without additional synchronization. However, access to a single line instance must be synchronized at the application level, such as by using pw::Borrowable. Additionally, the line interface should not be used from within an interrupt context unless specified otherwise.
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#
Pigweed AI summary: The Digital IO API interfaces are designed 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. The API supports intended use cases such as input and output on lines that have two logical states, running code based on an external interrupt, and enabling and disabling lines as part of a high-level policy. The
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#
Pigweed AI summary: This section discusses how digital IO line values are represented as active and inactive states, which abstract away the actual electrical level and physical properties of the line. This allows for consistency across different targets with varying physical configurations. The backend is responsible for providing a consistent definition of state.
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#
Pigweed AI summary: The API includes interrupt handling, which is integrated into a single object that represents all available functionality on a line. Interrupt triggers are configured using the SetInterruptHandler method, and the type of trigger is dependent on the desired handler action. The handler is given the latest known state of the line, as querying the state in an interrupt context is not possible.
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#
Pigweed AI summary: The "pw_digital_io" module contains a 2-level hierarchy of classes, with "DigitalIoOptional" as the base class and several derived classes representing lines with specific functionality. It is recommended to use the derived classes instead of the base class in APIs. The module may add new classes in the future to describe lines with optional functionality. When using classes with optional functionality, it is important to check for availability using the "provides_*" runtime flags to avoid triggering a crash. The public
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#
Pigweed AI summary: This paragraph discusses type conversions in programming, specifically between classes with compatible requirements. It provides examples of how this can be done using DigitalInInterrupt and DigitalIn classes.
Conversions are provided between classes with compatible requirements. For example:
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#
Pigweed AI summary: The current API for pw_digital_io is synchronous, meaning that all API calls are expected to block until the operation is complete. This is suitable for simple GPIO chips controlled through direct register access, but not ideal for GPIO extenders controlled through I2C or another shared bus. There may be plans to extend the API in the future to include asynchronous capabilities or create a separate asynchronous API.
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#
Pigweed AI summary: This section provides implementation notes for the backend of a class that supports digital input and output functionality. It explains how derived classes should list non-virtual methods as public or private depending on the supported set of functionality, and how to handle unsupported virtual methods. The section also emphasizes the importance of checking preconditions for each operation and initializing the line to a known state. Finally, it specifies that calling Enable() on an already-enabled line should be a no-op, and that Disable() can be called in any
Derived classes explicitly list the non-virtual methods as public or private depending on the supported set of functionality. For example,
DigitalIn
declareGetState
public andSetState
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
implementsDoSetState
to triggerPW_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 byDisable()
.Calling
Disable()
on an uninitialized line must put it into the disabled state. In general,Disable()
can be called in any state.
Calling
Enable()
on a line that is already enabled should be a no-op. In particular, the state of an already-enabled output line should not change.
RPC Service#
Pigweed AI summary: The RPC Service is a pw_rpc service that allows manual control of individual DigitalIo lines for both input and output. It provides support for bringup/debug efforts. The state of the output line can be set via RPC using a Pigweed console device object.
The DigitalIoService
pw_rpc service is provided to support bringup/debug
efforts. It allows manual control of individual DigitalIo lines for both input
and output.
std::array<std::reference_wrapper<DigitalIoOptional>> lines = {
...DigitalIn(),
...DigitalOut(),
};
DigitalIoService service(lines);
rpc_server.RegisterService(service);
Set the state of the output line via RPC. This snippet demonstrates how you might do that using a Pigweed console device object.
from pw_digital_io import digital_io_pb2
device.rpcs.pw.digital_io.DigitalIo.SetState(
line_index=0, state=digital_io_pb2.DigitalIoState.ACTIVE)
API reference#
Note
This API reference is incomplete.
-
class DigitalIoOptional#
A digital I/O line that may support input, output, and interrupts, but makes no guarantees about whether any operations are supported. You must check the various
provides_*
flags before calling optional methods. Unsupported methods invokePW_CRASH
.All methods are potentially blocking. Unless otherwise specified, access from multiple threads to a single line must be externally synchronized - for example using
pw::Borrowable
. Unless otherwise specified, none of the methods are safe to call from an interrupt handler. Therefore, this abstraction may not be suitable for bitbanging and other low-level uses of GPIO.Note that the initial state of a line is not guaranteed to be consistent with either the “enabled” or “disabled” state. Users of the API who need to ensure the line is disabled (ex. output not driving the line) should call
Disable()
.This class should almost never be used in APIs directly. Instead, use one of the derived classes that explicitly supports the functionality that your API needs.
This class cannot be extended directly. Instead, extend one of the derived classes that explicitly support the functionality that you want to implement.
Subclassed by pw::digital_io::DigitalIn, pw::digital_io::DigitalInInterrupt, pw::digital_io::DigitalInOut, pw::digital_io::DigitalInOutInterrupt, pw::digital_io::DigitalInterrupt, pw::digital_io::DigitalOut, pw::digital_io::DigitalOutInterrupt
Public Functions
-
inline constexpr bool provides_input() const#
- Returns:
true
if input (getting state) is supported.
-
inline constexpr bool provides_output() const#
- Returns:
true
if output (setting state) is supported.
-
inline constexpr bool provides_interrupt() const#
- Returns:
true
if interrupt handlers can be registered.
-
inline Result<State> GetState()#
Gets the state of the line.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- An active or inactive state.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
-
inline Status SetState(State state)#
Sets the state of the line.
Callers are responsible to wait for the voltage level to settle after this call returns.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The state has been set.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
-
inline Result<bool> IsStateActive()#
Checks if the line is in the active state.
The line is in the active state when
GetState()
returnsState::kActive
.Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
-true
if the line is in the active state, otherwisefalse
.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
-
inline Status SetStateActive()#
Sets the line to the active state. Equivalent to
SetState(State::kActive)
.Callers are responsible to wait for the voltage level to settle after this call returns.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The state has been set.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
-
inline Status SetStateInactive()#
Sets the line to the inactive state. Equivalent to
SetState(State::kInactive)
.Callers are responsible to wait for the voltage level to settle after this call returns.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The state has been set.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
-
inline Status SetInterruptHandler(InterruptTrigger trigger, InterruptHandler &&handler)#
Sets an interrupt handler to execute when an interrupt is triggered, and configures the condition for triggering the interrupt.
The handler is executed in a backend-specific context—this may be a system interrupt handler or a shared notification thread. Do not do any blocking or expensive work in the handler. The only universally safe operations are the IRQ-safe functions on
pw_sync
primitives.In particular, it is NOT safe to get the state of a
DigitalIo
line—either from this line or any otherDigitalIoOptional
instance—inside the handler.- return:
OK
- The interrupt handler was configured.INVALID_ARGUMENT
- The handler is empty.Other status codes as defined by the backend.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Pre:
No handler is currently set.
-
inline Status ClearInterruptHandler()#
Clears the interrupt handler and disables any existing interrupts that are enabled.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The interrupt handler was cleared.Other status codes as defined by the backend.
-
inline Status EnableInterruptHandler()#
Enables interrupts which will trigger the interrupt handler.
- return:
OK
- The interrupt handler was configured.FAILED_PRECONDITION
- The line has not been enabled.Other status codes as defined by the backend.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Pre:
A handler has been set using
SetInterruptHandler()
.
-
inline Status DisableInterruptHandler()#
Disables the interrupt handler. This is a no-op if interrupts are disabled.
This method can be called inside the interrupt handler for this line without any external synchronization. However, the exact behavior is backend-specific. There may be queued events that will trigger the handler again after this call returns.
- Returns:
OK
- The interrupt handler was disabled.Other status codes as defined by the backend.
-
inline Status Enable()#
Enables the line to initialize it into the default state as determined by the backend.
This may enable pull-up/down resistors, drive the line high/low, etc. The line must be enabled before getting/setting the state or enabling interrupts. Callers are responsible for waiting for the voltage level to settle after this call returns.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The line is enabled and ready for use.Other status codes as defined by the backend.
-
inline Status Disable()#
Disables the line to power down any pull-up/down resistors and disconnect from any voltage sources.
This is usually done to save power. Interrupt handlers are automatically disabled.
Warning
This method is not thread-safe and cannot be used in interrupt handlers.
- Returns:
OK
- The line is disabled.Other status codes as defined by the backend.
-
inline constexpr bool provides_input() const#
Dependencies#
Pigweed AI summary: This paragraph discusses the dependencies and configuration required for using pw_digital_io in Zephyr. The dependencies include pw_assert, pw_function, pw_result, and pw_status. To enable pw_digital_io in Zephyr, the project's configuration must have CONFIG_PIGWEED_DIGITAL_IO set to "y".
Zephyr#
Pigweed AI summary: To use pw_digital_io for Zephyr, simply add CONFIG_PIGWEED_DIGITAL_IO=y to the project's configuration.
To enable pw_digital_io
for Zephyr add CONFIG_PIGWEED_DIGITAL_IO=y
to
the project’s configuration.