#swd #arm #stm32 #airfrog

airfrog-rpc

RPC support for co-processing using debug protocols

2 releases

Uses new Rust 2024

0.1.1 Sep 6, 2025
0.1.0 Sep 6, 2025

#2627 in Embedded development

Download history 227/week @ 2025-09-03 37/week @ 2025-09-10 4/week @ 2025-09-17 30/week @ 2025-09-24 19/week @ 2025-10-01 51/week @ 2025-10-08 115/week @ 2025-10-15 67/week @ 2025-10-22

261 downloads per month
Used in 2 crates (via sdrr-fw-parser)

MIT license

55KB
877 lines

airfrog-rpc

A crate to perform remote procedure calls and reading/writing between controllers and targets over SWD and other debug protocols.

Overview

Airfrog-rpc provides a robust, bidirectional communication protocol between an SWD controller and target microcontroller. Unlike debugging tools, this enables the controller and target to function as a co-processing pair of MCUs, each handling commands and returning results reliably over a channel usually used for debug purposes.

Features

  • Host and Target implementations
  • Asynchronous and synchronous APIs
  • Command/response semantics with sequence numbers
  • Variable-length payloads
  • Atomic operations using aligned word writes
  • no-std target implementation
  • Error handling and timeout support

Getting Started

See the crate documentation.


lib.rs:

Remote Procedure Call crate for co-processing with ARM targets over SWD and other debug protocols.

This crate enables reliable, bidirectional communication between a debug host and target microcontroller using memory-mapped channels. Unlike traditional debugging tools, this allows the host and target act as co-processors alongisde each other, with one being controlled by the other, handling commands and returning results.

no_std. Requires alloc for async traits, typically used on the Host side..

Includes sample host and target implementations.

Architecture

Assumes a Host (debug controller) and Target (microcontroller) architecture.

Communication typically takes place using two unidirectional channels in the target's SRAM:

  • Command channel: Host writes commands, target reads
  • Response channel: Target writes responses, host reads

Channels can be used in either direction, and for different purposes as required. All that is required is a dedicated memory region in the target's SRAM for each channel. CCM RAM may also be used on STM32F4 MCUs if available.

Each channel contains a control block (sequence numbers, flags, data size) followed by a data area, with the data area used to transmit information on the channel. The amount of memory provided for each channel is used for both the control block and data area. The control block is small (tens of byes), so the majority of the reserved memory is available for data.

Only a single producer and single consumer are supported per channel, and only one data chunk can be written and "transferred" by the producer to the consumer at a time.

The channel protocol ensures reliable delivery using producer/consumer sequence numbers and atomic word operations using the target's memory ordering guarantees.

Data can be writte in any format, using bytes or 32-bit little endian words.

Currently this crate only supports little-endian Hosts and Targets.

Modules

  • channel - Channel objects for unidirectional communication in either direction
  • client - RPC client for sending commands and receiving responses, typically used on the host
  • [io] - Async I/O traits for debug interface access, typically used for host access to target RAM/flash peripherals

Supported Targets

Works on ARM Cortex-M microcontrollers with strong memory ordering, with SWD accessing the SRAM via the AHB bus matrix, and no data caches:

  • Cortex-M0/M0+/M3/M4/M23/M33 (STM32, RP2040/2350, nRF52, etc.)

The target implementation, channel::RamChannel can be used as is. The host implementation needs io::Reader and io::Writer implementations.

airfrog::airfrog_bin::firmare contains an SWD implementation of io::Reader and io::Writer for ESP32-C3, and uses channel::ReaderWriterChannel, allowing it to communicate using these channels with an SWD target.

Getting Started

The target must reserved dedicated SRAM regions for each channel.

The host (debug controller) must know the addresses of these regions and reads/writes them over the debug interface.

If you wish the host to dynamically learn the addresses/sizes of these regions you must implement this separately - for example, include pointers at well know SRAM or flash locations on the target pointeing to these regions. Alternatively, you could arrange for the locations and sizes to be fixed by the target's linker script.

Each communication channel consists of a control block, and data area. The control block is used by the protocol to ensure reliable message passing. The data area is where the actual command or response payload is stored.

Target setup:

  1. Reserve SRAM region(s) e.g. 1KB each for each channel
  2. Create a channel::RamChannelIo instance for each required channel
  3. Create a channel::RamChannel instance for each channel
  4. Poll [channel::RamChannel::data_available()] for your consumer channel, in your main loop or dedicated task
  5. When data arrives, process it, and optionally send responses on alternate channel
  6. Data format is application-specific and currently either bytes or u32s

Host setup:

  1. Configure channel locations, or dynamically read from the target using well-known locations for pointers to the channel locations/sizes
  2. Create a channel::ReaderWriterChannelIo instance with your debug interface reader/writer implementation (see airfrog::airfrog_bin::firmware for an SWD implementation)
  3. Create a channel::ReaderWriterChannel instance for your channel.
  4. Send data with [channel::ReaderWriterChannel::publish_bytes()].
  5. Before using a different channel you will likely need to ensure the previous channel is dropped, to free up the Io instance to be mutably borrowed by your new channel.

As implied a Channel is intended to be short-lived - create, use, drop. This allows temporary ownership of the Reader/Writer, which may be a shared hardware resource on the host. If this becomes tedious, create a wrapper to abstract away this creation/ destruction or use client::AsyncRpcClient which abstracts this lifecycle away, and provides a higher-level request() API, using a pair of channels (one command, the other response).

The RPC layer handles reliable delivery, but your application defines the actual command/response protocol and data formats.

While the above documentation describes the Host controlling the Target, it is possible to use the channel(s) in the reverse direction.

See individual module and struct documentation for usage examples.

Features

Default features:

  • async - Enable async channel implementations and traits (requires alloc), which is generally required by the Host, but not by the Target.

Compile with --no-default-features to disable unnecessary async support for a Target.

Dependencies

~195KB