micro-ROS tutorial: put a ROS 2 node on an ESP32

How micro-ROS works, installing the agent, a minimal ESP32 publisher, transport choices and the limits you hit on a microcontroller.

Reference
RE-867
Published
Read
5 min

About the author · Sources, testing and AI policy

ESP32 board connected to a laptop running a ROS 2 terminal

micro-ROS puts a ROS 2 node on a microcontroller, so an ESP32 can publish sensor data straight into your ROS graph. No bridge script on a laptop, no custom serial protocol.

This tutorial covers the architecture, the agent, a minimal ESP32 publisher, the transport choices and the limits you hit when a 240 MHz chip tries to be a ROS 2 device.

How micro-ROS is put together

PartRuns onJob
micro-ROS client libraryMicrocontrollerNode, publisher, subscriber, executor
Middleware (rmw_microxrcedds)MicrocontrollerCompact DDS/XRS transport
micro-ROS agentPC or SBCBridges the microcontroller into the ROS 2 graph
ROS 2 hostPC, Jetson, PiEverything else: Nav2, RViz, SLAM

The agent is the important idea. The microcontroller does not run a full DDS stack. It speaks a small protocol over serial or UDP to the agent, and the agent presents those entities to the rest of ROS 2 as normal nodes, topics and services.

Install the agent

On Ubuntu with ROS 2 installed, the agent is a single package. Install it, then run it against the serial device your board is on.

sudo apt install ros-$ROS_DISTRO-micro-ros-agent
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyUSB0 -b 115200

For Wi-Fi transport, use the UDP form instead and point it at the port the board is sending to.

ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888

If the agent starts and prints a client connected line, the transport is working. If it sits silent, the baud rate or the wiring is wrong, not the ROS side.

A minimal ESP32 publisher

With the Arduino IDE and the micro_ros_arduino library, the structure is short: allocate, create a node, create a publisher, then spin. The exact calls depend on library version, but the shape is stable.

#include <micro_ros_arduino.h>

rcl_allocator_t allocator;
rclc_support_t support;
rcl_node_t node;
rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;

void setup() {
  Serial.begin(115200);
  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");
}

void loop() {
  msg.data = millis() / 1000;
  rcl_publish(&publisher, &msg, NULL);
  delay(500);
}

On the host, confirm it with a normal ROS 2 command. If the topic appears and the values climb, the whole chain works.

ros2 topic list
ros2 topic echo /esp32_counter

Choosing a transport

TransportBest forLimitation
Serial (UART/USB)Bench work, wired robotsOne cable, limited distance
UDP over Wi-FiMobile robots, ESP32Latency spikes, Wi-Fi dropouts
UDP over EthernetFixed cells, STM32 boardsNeeds a PHY and switch port
CANAutomotive and motor drivesExtra transceiver hardware

For a drivetrain, serial or Ethernet beats Wi-Fi. A dropped Wi-Fi packet during a velocity command is a safety event, not a latency statistic.

Where micro-ROS fits in a robot

  • Publishing wheel encoder ticks and IMU data to the ROS 2 graph.
  • Subscribing to velocity commands and driving motors locally.
  • Reading battery voltage, current and temperature.
  • Exposing GPIO and relay services to higher-level nodes.
  • Acting as a sensor node on a distributed robot with several boards.

The pattern that works well is one microcontroller per physical subsystem: one for the base, one for the arm, one for the sensor head. Each publishes a small, well-defined set of topics.

Limits you will hit

LimitWhat it means in practice
Message typesOnly generated types are available; add them at build time
MemoryESP32 has a few hundred KB of usable RAM for the stack
No DDS discoveryThe agent owns discovery; the MCU sees only its entities
No rosbag or RVizThose live on the host, always
QoS supportSubset only; do not assume transient local works
TimingMicrosecond-level real-time is not the goal

Debugging order that saves hours

  • Check the board enumerates as a serial device on the host.
  • Check baud rate matches on both sides.
  • Start the agent before the board, or reset the board after the agent is up.
  • Confirm the node appears with ros2 node list.
  • Confirm the topic with ros2 topic list, then echo it.
  • Only then debug the data itself.

Most failures are the agent not running, the wrong serial port, or a firmware build with the wrong transport selected. The ROS 2 side is usually innocent.

Mistakes to avoid

  • Running Wi-Fi transport for a safety-relevant command path.
  • Publishing at 1 kHz from a microcontroller over serial at 115200 baud.
  • Building a custom message type and forgetting to regenerate the firmware.
  • Using the agent on the same board that is already saturated.
  • Treating micro-ROS as a replacement for a real-time motor controller.

Related: ros2_control tutorial, ROS 2 topics, services and actions, ESP32 pinout for robotics, ROS 2 for beginners, Raspberry Pi 5 robot guide.

What is micro-ROS used for?

It runs a ROS 2 node on a microcontroller such as an ESP32 or STM32, so sensors and motor drivers can publish and subscribe directly instead of going through a custom serial bridge.

Do I need a micro-ROS agent?

Yes. The agent runs on a PC, Raspberry Pi or Jetson and bridges the microcontroller into the ROS 2 graph over serial, UDP or CAN.

Can an ESP32 run ROS 2?

It runs micro-ROS, a cut-down client library, not full ROS 2. Discovery, rosbag, RViz and heavy message types stay on the host.

Which transport should I use for micro-ROS?

Serial or Ethernet for anything safety-relevant. Wi-Fi UDP is convenient for prototypes but drops packets, so avoid it for velocity commands on a moving robot.

Why does my micro-ROS node not appear in ros2 node list?

Usual causes are the agent not running, the wrong serial device, a baud mismatch, or firmware built with a different transport than the one the agent expects.

Microcontrollers have small buffers, so QoS depth and reliability settings matter more than on a desktop. A reliable profile with a deep queue will drop data silently.

For a full ROS 2 arm rather than a microcontroller node, see the hardware interface tutorial.

On a microcontroller you run a small filter, and the minimal Kalman implementation fits in a few dozen lines.

Whether you use micro-ROS or plain FreeRTOS, pinning the control task still matters, as shown in the task design walkthrough.

micro-ROS uses the same message definitions, so build the custom interfaces on the host and share the headers with the microcontroller.

An STM32 is the most common micro-ROS target, and the STM32 robotics guide covers the toolchain you need first.

If you want to develop against ROS 2 topics without dual-booting, the ROS 2 on Windows with WSL2 walkthrough covers the install and the USB forwarding step.

Bring-up order matters on microcontrollers too, and the lifecycle nodes tutorial describes the state model to mirror.

Sources and further reading

Pass it onWhatsAppLinkedIn

Keep reading

Continue learning

The Build Note

One short email when something is worth your bench time.

A new build sheet, a part price that moved, or a mistake worth avoiding. No daily drip, no recycled news.

One short email when a build or a price is worth your time. Unsubscribe in one click.