> For the complete documentation index, see [llms.txt](https://panav.gitbook.io/robotics-handbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://panav.gitbook.io/robotics-handbook/ros-2/micro-ros.md).

# micro-ROS

micro-ROS is how you get a real ROS 2 node - same QoS, same DDS semantics, same `rclc` API - running on a microcontroller. It is the modern answer to ROS 1's `rosserial`, and it is a complete rebuild rather than a port. If you have ever tried to wedge `rosserial_arduino` into a project with serious sensor traffic, micro-ROS is a relief.

The project lives at [micro.ros.org](https://micro.ros.org). Source: [github.com/micro-ROS](https://github.com/micro-ROS).

I've used micro-ROS in production for ESP32-based motor controllers and sensor interfaces in mobile platforms - wheel encoders, ESC interfaces, IMU shims. It is also what I would reach for if I were building [Polka](/robotics-handbook/authors-projects/polka.md) again today, instead of the custom serial protocol I started with.

## Why micro-ROS exists

Microcontrollers can't run full DDS. The implementations (Cyclone, Fast DDS) assume a host OS, multi-threading, dynamic memory, network stacks with megabytes of buffer space. An STM32F4 has 128 KB of RAM total.

The OMG already had an answer in spec form: **DDS-XRCE** (eXtremely Resource-Constrained Environments). It defines a tiny binary protocol with a single TCP/UDP connection to an *agent* running on a host, which then bridges into the full DDS network.

micro-ROS implements DDS-XRCE on the MCU side, exposes it through `rclc` (a C API that mirrors `rclcpp`'s shape), and ships a Linux-side `micro-ROS Agent` (built on Fast DDS) that translates between XRCE and full DDS.

## Architecture

```
        ┌───────────────────────────┐
        │       MCU (Client)        │
        │  ┌─────────────────────┐  │
        │  │  Your application   │  │
        │  └──────────┬──────────┘  │
        │             │ rclc API    │
        │  ┌──────────▼──────────┐  │
        │  │  micro-ROS client   │  │
        │  │  (rclc / rmw_uxrce) │  │
        │  └──────────┬──────────┘  │
        └─────────────┼─────────────┘
                      │ DDS-XRCE over UART / UDP / CAN
        ┌─────────────▼─────────────┐
        │   micro-ROS Agent (Linux) │
        │   (eProsima Micro XRCE-DDS Agent) │
        └─────────────┬─────────────┘
                      │ full DDS (RTPS)
        ┌─────────────▼─────────────┐
        │   Regular ROS 2 nodes     │
        │   (rclcpp, rclpy on host) │
        └───────────────────────────┘
```

From the host's perspective, the MCU appears as a normal ROS 2 node - same topics, services, parameters. From the MCU's perspective, it talks to an agent over a serial or network link.

### The agent

The agent is a regular ROS 2 node you run on your robot's main computer:

```bash
# Install
sudo apt install ros-jazzy-micro-ros-agent

# Run, listening on UART
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyUSB0 -b 115200

# Or UDP
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888
```

A single agent can host multiple MCU clients. In practice I run one agent per MCU; cheaper to debug.

## Supported MCUs

micro-ROS targets a long list of hardware. The ones I actually see in the field:

| MCU family                         | RAM range     | Notes                                                              |
| ---------------------------------- | ------------- | ------------------------------------------------------------------ |
| **STM32 F4 / F7 / H7**             | 192 KB – 1 MB | The industrial workhorse. STM32CubeIDE integration.                |
| **ESP32** (Xtensa)                 | 320–520 KB    | Wi-Fi + Bluetooth on board. My go-to for prototypes.               |
| **ESP32-S3 / C3 / C6**             | varies        | Newer ESP variants, all supported.                                 |
| **RP2040** (Raspberry Pi Pico)     | 264 KB        | Cheap, dual-core, decent toolchain.                                |
| **Teensy 4.0 / 4.1**               | 1 MB          | Arduino-compatible, very fast (Cortex-M7 @ 600 MHz).               |
| **Arduino UNO R4 (Minima / WiFi)** | 32 KB         | Lower bound. Renesas RA4M1, ESP32-S3 co-processor on WiFi variant. |
| Nuttx, Zephyr, FreeRTOS, ThreadX   | -             | RTOSes supported alongside bare-metal.                             |

The official list is at [micro.vulcanexus.org/docs/overview/hardware/](https://micro.vulcanexus.org/docs/overview/hardware/). The official Arduino integration is `micro_ros_arduino` ([github.com/micro-ROS/micro\_ros\_arduino](https://github.com/micro-ROS/micro_ros_arduino)).

### Practical RAM minimum

You can technically run micro-ROS on the Arduino UNO R4 (32 KB), but in practice anything below \~64 KB of usable RAM is painful - you'll spend more time tuning memory pool sizes than writing application code. ESP32 and Teensy 4.x are the sweet spot.

## Transport options

| Transport                  | Pros                                    | Cons                                                                                |
| -------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |
| **UART / Serial**          | Simple, deterministic, no network stack | Single client per port, 115 200 baud default is slow. Bump to 921 600 or 4 000 000. |
| **USB CDC**                | Looks like serial to the host           | Same throughput limits.                                                             |
| **UDP (Wi-Fi / Ethernet)** | High throughput, multiple clients       | Wi-Fi flakiness on busy networks.                                                   |
| **CAN bus**                | Native to automotive / industrial       | Tiny payload per frame, custom integration.                                         |
| **BLE**                    | Wireless without IP stack               | Bandwidth limited, mostly for telemetry.                                            |

For a typical "wheel encoder + IMU + ESC control" node I run on ESP32: UART to the main computer. Reliable, predictable, no Wi-Fi politics.

## Build flow

There are two main toolchains. Pick based on what your team already uses.

### Option A: PlatformIO + micro\_ros\_platformio

If you're already using PlatformIO ([platformio.org](https://platformio.org)) for embedded work, this is the cleanest path. The `micro_ros_platformio` library handles all the build wiring.

`platformio.ini` for an ESP32:

```ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    https://github.com/micro-ROS/micro_ros_platformio.git
board_microros_distro = jazzy
board_microros_transport = serial
```

`src/main.cpp`:

```cpp
#include <Arduino.h>
#include <micro_ros_platformio.h>
#include <rcl/rcl.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>

rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
rcl_timer_t timer;

void timer_callback(rcl_timer_t* timer, int64_t /*last_call_time*/) {
    (void) timer;
    msg.data++;
    rcl_publish(&publisher, &msg, NULL);
}

void setup() {
    set_microros_serial_transports(Serial);
    delay(2000);

    allocator = rcl_get_default_allocator();
    rclc_support_init(&support, 0, NULL, &allocator);
    rclc_node_init_default(&node, "esp32_node", "", &support);
    rclc_publisher_init_default(
        &publisher, &node,
        ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
        "esp32/counter");
    rclc_timer_init_default(&timer, &support, RCL_MS_TO_NS(100), timer_callback);
    rclc_executor_init(&executor, &support.context, 1, &allocator);
    rclc_executor_add_timer(&executor, &timer);

    msg.data = 0;
}

void loop() {
    rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));
}
```

The PlatformIO library builds the micro-ROS static library for you the first time you compile. After that it's just a regular embedded build.

### Option B: ESP-IDF or STM32CubeIDE

For when you're not on Arduino and want full control. The micro-ROS source tree provides component modules for ESP-IDF, makefile snippets for STM32 HAL, and CMake integration for Zephyr.

The flow is roughly:

1. Clone the micro-ROS firmware repo for your platform (e.g., `micro_ros_espidf_component`).
2. Drop it into your IDF project as a component.
3. Add `colcon meta` files describing which packages you want (every message type in your firmware has to be pre-compiled).
4. Build the static library once.
5. Link your firmware against it.

This is fiddly the first time and routine afterward.

## API patterns

micro-ROS' `rclc` is intentionally similar in shape to `rclcpp`, but without exceptions or RAII. Everything is C, everything checks return codes.

### Publishing

```cpp
rcl_publisher_t publisher;
rclc_publisher_init_default(
    &publisher, &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Float32),
    "imu/temperature");

std_msgs__msg__Float32 msg;
msg.data = 23.5;
rcl_publish(&publisher, &msg, NULL);
```

### Subscribing

```cpp
rcl_subscription_t subscriber;
geometry_msgs__msg__Twist cmd_vel_msg;

void cmd_vel_callback(const void* msgin) {
    const auto* msg = (const geometry_msgs__msg__Twist*) msgin;
    set_motor_velocity(msg->linear.x, msg->angular.z);
}

rclc_subscription_init_default(
    &subscriber, &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(geometry_msgs, msg, Twist),
    "cmd_vel");

rclc_executor_add_subscription(
    &executor, &subscriber, &cmd_vel_msg,
    cmd_vel_callback, ON_NEW_DATA);
```

### Services and parameters

Supported, but heavier in code and memory than publishers. Use sparingly. A 32-bit parameter takes more code-space than a `Float32` publisher.

## Memory tuning

Every message type has a pre-allocated buffer. The defaults in `colcon.meta` are conservative - fine for small messages, wasteful for sensors.

The knobs that matter, set in `colcon.meta` and rebuilt:

```yaml
names:
  rmw_microxrcedds:
    cmake-args:
      - -DRMW_UXRCE_MAX_NODES=1
      - -DRMW_UXRCE_MAX_PUBLISHERS=10
      - -DRMW_UXRCE_MAX_SUBSCRIPTIONS=5
      - -DRMW_UXRCE_MAX_SERVICES=2
      - -DRMW_UXRCE_MAX_CLIENTS=1
      - -DRMW_UXRCE_MAX_HISTORY=4
      - -DRMW_UXRCE_STREAM_HISTORY=4
      - -DRMW_UXRCE_MAX_INPUT_BEST_EFFORT_STREAMS=1
      - -DRMW_UXRCE_MAX_OUTPUT_BEST_EFFORT_STREAMS=1
      - -DRMW_UXRCE_MAX_OUTPUT_RELIABLE_STREAMS=1
      - -DRMW_UXRCE_MAX_INPUT_RELIABLE_STREAMS=1
```

If you're running out of memory: turn `MAX_NODES`, `MAX_PUBLISHERS`, `MAX_HISTORY` down. If sessions are dropping under load: turn `STREAM_HISTORY` up.

Each `ROSIDL_GET_MSG_TYPE_SUPPORT` you reference also costs flash (the message struct's metadata). For unused message types, don't include them - `colcon` will skip building them if they're not in your dependencies.

## micro-ROS vs rosserial - short version

| Feature                    | rosserial (ROS 1)         | micro-ROS (ROS 2)                       |
| -------------------------- | ------------------------- | --------------------------------------- |
| Protocol                   | Custom binary             | DDS-XRCE (OMG standard)                 |
| QoS                        | None                      | Full DDS QoS                            |
| Multiple clients per agent | No                        | Yes                                     |
| Transport                  | UART only                 | UART / UDP / CAN / custom               |
| Message size limit         | \~512 bytes               | Up to RAM budget (\~few KB practical)   |
| Status                     | EOL with ROS 1 (May 2025) | Active, primary embedded path for ROS 2 |

rosserial does not exist on ROS 2. If someone tells you to use it for a new ROS 2 project, they are about five years out of date. The migration story is "rewrite the MCU firmware in micro-ROS."

## Practical limits

A few things that don't fit on an MCU at all, no matter how much you tune:

* **Large messages** - A 1 MP image is bigger than most MCU RAM. Strip down to the data you actually need. Send raw uint8 arrays for short telemetry, point-to-point IK calls, joint positions. Cameras and LiDARs are not MCU territory.
* **High-rate transient\_local topics** - Latching needs buffer memory. Use sparingly; one or two transient\_local topics is fine, ten will exhaust your RAM.
* **Cross-network discovery** - The MCU does not participate in DDS discovery directly. It connects to one agent; the agent fans out. Multi-machine setups need careful agent placement.
* **Hot reconfiguration** - micro-ROS clients don't gracefully resync if the agent restarts. Wrap your `rclc_support_init` in a reconnection loop.

## Reconnection pattern

The default examples assume the connection never drops. In a real robot the agent process, the USB cable, or the Wi-Fi will hiccup. Always wrap initialization:

```cpp
enum states { WAITING_AGENT, AGENT_AVAILABLE, AGENT_CONNECTED, AGENT_DISCONNECTED };
states state = WAITING_AGENT;

void loop() {
    switch (state) {
        case WAITING_AGENT:
            EXECUTE_EVERY_N_MS(500, state = (RMW_RET_OK == rmw_uros_ping_agent(100, 1))
                                            ? AGENT_AVAILABLE : WAITING_AGENT;);
            break;
        case AGENT_AVAILABLE:
            state = create_entities() ? AGENT_CONNECTED : WAITING_AGENT;
            if (state == WAITING_AGENT) destroy_entities();
            break;
        case AGENT_CONNECTED:
            EXECUTE_EVERY_N_MS(200, state = (RMW_RET_OK == rmw_uros_ping_agent(100, 1))
                                            ? AGENT_CONNECTED : AGENT_DISCONNECTED;);
            if (state == AGENT_CONNECTED) {
                rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));
            }
            break;
        case AGENT_DISCONNECTED:
            destroy_entities();
            state = WAITING_AGENT;
            break;
    }
}
```

This is verbose but bulletproof. The examples folder in `micro_ros_arduino` has cleaner versions; copy from there.

## Common pitfalls

* **Agent and client distro mismatch** - Humble agent can't talk to Iron client and vice versa. Match versions.
* **Forgetting to set the transport** - `set_microros_serial_transports(Serial)` must run *before* `rclc_support_init`. Otherwise the client tries to use a null transport.
* **Calling `rclc_executor_spin_some` from inside an ISR** - don't. micro-ROS is not interrupt-safe. Spin from the main loop, set flags from ISRs.
* **Stack overflow on FreeRTOS** - micro-ROS tasks need \~10 KB of stack. The default FreeRTOS task stack is 1–2 KB. Bump it.
* **Power-cycling the MCU without restarting the agent** - sometimes the agent doesn't notice the client is gone and refuses to accept a new session. Restart the agent.
* **Custom messages without rebuilding** - if you add a new `.msg` file you must rebuild the micro-ROS static library against the new IDL. There's no runtime registration.
* **Trying to run RVIZ visualization of a sensor on the MCU** - the message size budget will kill you. Push raw values to the agent and let a host node compose visualization markers.

## Where to go next

* [micro.ros.org](https://micro.ros.org) - official docs, tutorials, supported boards.
* [github.com/micro-ROS/micro\_ros\_arduino](https://github.com/micro-ROS/micro_ros_arduino) - pre-built Arduino library, easiest path for ESP32 / Teensy / Portenta.
* [DDS and QoS](/robotics-handbook/ros-2/dds-qos.md) - the QoS settings work the same on micro-ROS, with caveats about reliable streams.
* [Setup](/robotics-handbook/ros-2/setup.md) - installing the agent on your robot's main computer.
* [Polka](/robotics-handbook/authors-projects/polka.md) - my mobile platform, candidate for a micro-ROS firmware rewrite.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://panav.gitbook.io/robotics-handbook/ros-2/micro-ros.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
