Skip to content

Peripherals ​

The peripherals crate currently provides a GPIO controller contract and a deterministic mock. It is a reusable hardware abstraction, not a runtime-managed peripheral service or a complete SPI/I²C/UART implementation.

API and feature boundaries ​

The gpio feature exposes GpioController, LineId, LineCapabilities, InputConfig, OutputConfig, Pull, DriveMode, and PinState. The mock feature exposes GpioMock<N>, MockInput, MockOutput, and MockError.

GpioController declares a controller-specific Error implementing embedded_hal::digital::Error, and lifetime-bound associated types Input<'a> and Output<'a>. Input handles implement embedded_hal::digital::InputPin; output handles implement both OutputPin and StatefulOutputPin. The three controller methods are:

  • capabilities(LineId) -> Option<LineCapabilities>: query an existing line's supported operations.
  • request_input(LineId, InputConfig): reserve an input line and return a borrowed handle.
  • request_output(LineId, OutputConfig): reserve an output line and return a borrowed handle.

The returned handles borrow the controller, so callers cannot treat them as detached, indefinitely owned device objects.

Configurations and validation ​

LineId::new(offset) wraps a u16 line offset. LineCapabilities reports whether the line supports input, output, pull-up, pull-down and open-drain. InputConfig chooses Pull::Disabled, Up, or Down (disabled by default). OutputConfig adds DriveMode::PushPull or OpenDrain and an initial physical PinState::Low or High.

Controllers may reject an invalid line, a line already reserved by another handle, or a requested capability that hardware does not support. Reserving a line is exclusive until the handle is dropped. The output initial state is applied when the output is requested.

Heap-free mock for tests ​

GpioMock<N> receives an array of N line capabilities and stores state in fixed-capacity memory. Its line handles implement the same embedded-hal digital traits as real adapters. Tests can call set_input_level() to inject an input level, read it through InputPin, and verify output levels through StatefulOutputPin.

rust
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use peripherals::gpio::{
    DriveMode, GpioController, GpioMock, InputConfig, LineCapabilities,
    LineId, OutputConfig, PinState, Pull,
};

let capabilities = LineCapabilities {
    input: true, output: true, pull_up: true,
    pull_down: true, open_drain: true,
};
let gpio = GpioMock::new([capabilities; 2]);
let line = LineId::new(0);
gpio.set_input_level(line, PinState::High)?;
let mut input = gpio.request_input(line, InputConfig::default())?;
assert!(input.is_high()?);
drop(input);

let mut output = gpio.request_output(
    line,
    OutputConfig::new(Pull::Disabled, DriveMode::PushPull, PinState::Low),
)?;
output.set_high()?;
assert!(output.is_set_high()?);

This example requires the gpio and mock features. The mock tests cover invalid IDs, exclusive reservations, release on drop, initial output level, injected input state, and unsupported pull/drive modes.

Migration and limitations ​

The earlier PinData byte-buffer interface has been removed. Consumers should migrate to typed controller requests and borrowed line handles; serialized pin-state operations are no longer the GPIO API.

Board-specific adapters remain separate work. A passing mock test does not establish that a particular MCU pin, interrupt, pull resistor or electrical mode is supported. The Espressif integration remains constrained by the target/toolchain work tracked in Core issue #328.

Integration boundaries and physical pin discovery ​

This is a digital GPIO controller trait, not a mounted device or a protocol bus implementation. A GpioController adapter decides which numeric LineId values exist and what LineCapabilities each reports. A client should query capabilities(id) before requesting a borrowed Input<'a> or Output<'a> handle. The handle retains exclusive line ownership until dropped.

rust
use peripherals::gpio::{GpioController, LineId, InputConfig};
let id = LineId::new(0);
if let Some(caps) = controller.capabilities(id) {
    if caps.input {
        let _input = controller.request_input(id, InputConfig::default())?;
        // The line is reserved until _input is dropped.
    }
}

The example assumes a concrete controller implementing GpioController and the gpio Cargo feature. The controller's Error is backend-specific; a caller may need to map it into its own error domain.

Features, memory and unimplemented capabilities ​

peripherals has no default features. Enable gpio for the embedded-hal digital API; mock implies gpio and provides a deterministic fixed-size GpioMock<N>. The mock supports capability checks, injected physical input levels and simulated exclusive reservation, without heap allocation in its line-state storage.

Analog/ADC modes, PWM, interrupt subscriptions, hardware pin multiplexing, and UART/I²C/SPI discovery are not part of this GPIO trait. Likewise a future VFS-backed /devices/gpio provider must adapt these controller contracts; it is not automatically exposed by constructing GpioMock. Do not advertise host/browser pin mocks as electrical or timing-equivalent hardware.

Core implementation references ​

Source and API references ​