The Evolution of Arduino and ROS Connectivity
For over a decade, the robotics maker community relied heavily on the rosserial package to bridge the gap between low-level microcontrollers and high-level robot navigation stacks. However, as of 2026, ROS 1 (Noetic) has reached complete end-of-life. The industry has definitively shifted to ROS 2, leveraging the Data Distribution Service (DDS) middleware for real-time, decentralized communication. Consequently, integrating Arduino and ROS now requires a modern approach: micro-ROS.
micro-ROS brings the ROS 2 client library (rcl) directly to resource-constrained microcontrollers via the eXtremely Resource Constrained Environments (XRCE-DDS) protocol. This tutorial provides a comprehensive, step-by-step guide to setting up a micro-ROS node on an Arduino-compatible board, configuring the micro-ROS Agent, and publishing telemetry data to a ROS 2 Jazzy Jalisco network.
Hardware Prerequisites and Board Selection
Not all microcontrollers possess the RAM and clock speed required to handle DDS serialization and UDP/Serial framing. While an ATmega328P (Arduino Uno) lacks the memory for modern ROS 2 stacks, 32-bit ARM Cortex-M boards handle it effortlessly. Below is a comparison of optimal boards for micro-ROS development in 2026.
| Microcontroller Board | Approx. Price (2026) | Architecture | micro-ROS Transport | Best Use Case |
|---|---|---|---|---|
| Arduino Nano RP2040 Connect | $24.00 | Dual-core ARM Cortex-M0+ | Serial (UART), WiFi (UDP) | Entry-level mobile robots, sensor nodes |
| Teensy 4.1 | $32.50 | ARM Cortex-M7 @ 600MHz | Serial (UART), Native Ethernet | High-frequency IMU publishing, complex kinematics |
| Arduino Portenta H7 | $105.00 | Dual-core Cortex-M7/M4 | Serial, WiFi, Ethernet | Industrial robotics, vision-guided manipulation |
Note: For this tutorial, we will use the Arduino Nano RP2040 Connect communicating over Serial (USB/UART) due to its accessibility and native support within the Arduino IDE.
Step 1: Deploying the micro-ROS Agent
Microcontrollers cannot run a full DDS implementation natively due to memory constraints. Instead, they run an XRCE-DDS client that communicates with a micro-ROS Agent running on your host PC (e.g., Ubuntu 24.04 with ROS 2 Jazzy). The Agent translates XRCE-DDS traffic into standard ROS 2 DDS topics.
Using Docker (Recommended)
The most reliable way to run the Agent without compiling from source is via the official Docker image. Ensure Docker is installed on your host machine, then execute the following command to start the Agent in serial mode:
docker run -it --rm --privileged --net=host microros/micro-ros-agent:jazzy serial --dev /dev/ttyACM0 -b 115200
--privileged: Grants the container access to your host's USB serial devices.--net=host: Maps the container's network directly to the host, allowing DDS discovery on your local LAN.--dev /dev/ttyACM0: The specific serial port of your Arduino. Verify this usingls /dev/tty*before connecting.
Expert Tip: If you are running headless or in a production environment, avoid Docker and install the Agent natively via Snap:
snap install micro-ros-agent --edge. This reduces overhead and simplifies systemd service creation for autonomous boot sequences.
Step 2: Configuring the Arduino IDE for micro-ROS
To write ROS 2 nodes in C++, you need the official micro_ros_arduino library. However, simply installing it via the Library Manager is only half the battle for non-AVR boards.
- Install the Board Core: Open the Boards Manager and install the
Arduino Mbed OS Nano Boardscore (version 4.0.x or newer). - Install the Library: Go to Sketch > Include Library > Manage Libraries. Search for
micro_ros_arduinoand install the latest release (v2.0.6 or newer). - Apply the Platform Patch (Critical): The RP2040 and ESP32 cores do not natively link the pre-compiled
libmicrorosstatic library. You must run the patch script included in the library. Navigate to your Arduino libraries folder and execute:
This script modifies thecd ~/Arduino/libraries/micro_ros_arduino sudo docker run -v $PWD:/arduino microros/micro_ros_arduino_builder:jazzyplatform.txtfile of your Mbed core to include the necessary linker flags. Without this step, compilation will fail withundefined reference to rcl_initerrors.
Step 3: Writing the Publisher Sketch
Below is a minimal, robust publisher sketch that broadcasts an Int32 message (simulating a sonar distance reading) to the /sonar_front topic at 10 Hz.
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.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;
#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}
void error_loop(){
while(1){ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); delay(100); }
}
void timer_callback(rcl_timer_t * timer, int64_t last_call_time){
RCLC_UNUSED(last_call_time);
if (timer != NULL) {
msg.data = analogRead(A0); // Read simulated sensor
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
}
}
rcl_timer_t timer;
void setup() {
set_microros_serial_transports(Serial);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
allocator = rcl_get_default_allocator();
RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
RCCHECK(rclc_node_init_default(&node, "arduino_sonar_node", "sensors", &support));
RCCHECK(rclc_publisher_init_default(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
"sonar_front"));
const unsigned int timer_timeout = 100;
RCCHECK(rclc_timer_init_default(
&timer,
&support,
RCL_MS_TO_NS(timer_timeout),
timer_callback));
RCCHECK(rclc_executor_init(&executor, &support.context, 1, &allocator));
RCCHECK(rclc_executor_add_timer(&executor, &timer));
msg.data = 0;
}
void loop() {
delay(10);
RCSOFTCHECK(rclc_executor_spin_some(&executor, RCL_MS_TO_NS(10)));
}
Understanding QoS and Memory Allocation
Notice the use of RCCHECK and RCSOFTCHECK macros. In micro-ROS, memory allocation failures are common if the heap is exhausted. The default QoS (Quality of Service) profile for publishers is rmw_qos_profile_default, which requires reliable delivery and history depth. For high-frequency sensor data (like IMUs or LiDAR), you should explicitly configure the publisher to use rmw_qos_profile_sensor_data to switch to Best-Effort reliability, drastically reducing RAM overhead and preventing network congestion.
Step 4: Verification and DDS Discovery
With the Agent running in your terminal and the sketch uploaded to the Arduino Nano RP2040, the onboard LED should remain solidly lit (indicating successful Agent handshake). Open a new terminal on your host PC, source your ROS 2 environment, and verify the data flow:
source /opt/ros/jazzy/setup.bash
ros2 topic list
# Expected output: /sonar_front /parameter_events /rosout
ros2 topic echo /sonar_front
# Expected output:
# data: 412
# ---
# data: 415
# ---
If the topic appears but no data is echoed, your DDS implementation (e.g., FastDDS or CycloneDDS) might be blocking multicast discovery. You can force unicast discovery by exporting the ROS_LOCALHOST_ONLY=1 environment variable before launching the Agent and the CLI tools.
Troubleshooting Common Edge Cases
Integrating low-level embedded C with distributed middleware introduces unique failure modes. Refer to this diagnostic matrix when encountering issues.
| Symptom / Error Code | Root Cause | Actionable Solution |
|---|---|---|
RCL_RET_TIMEOUT during rclc_support_init |
The micro-ROS Agent is not running, or the serial port is locked by the Arduino IDE Serial Monitor. | Close the Arduino IDE Serial Monitor. The Agent and the Serial Monitor cannot share the same /dev/ttyACM0 port simultaneously. |
| Agent crashes immediately upon board connection | Missing Linux udev rules, causing permission denied errors on the USB device node. |
Add your user to the dialout group: sudo usermod -a -G dialout $USER, then reboot the host PC. |
Compilation fails: undefined reference to rcl_init |
The micro-ROS static library was not linked correctly for the RP2040/ESP32 architecture. | Re-run the Docker patch script mentioned in Step 2. Ensure you selected the correct Mbed core version in the Board Manager. |
| High latency / dropped messages at 100Hz+ | Default Reliable QoS causing network queue blocking on serial transport. | Modify the publisher initialization to use rclc_publisher_init_best_effort instead of the default initialization. |
Expanding Your Robotics Stack
Once your Arduino and ROS 2 pipeline is established via micro-ROS, the microcontroller transitions from a simple peripheral into a first-class citizen in your robotics architecture. You can seamlessly subscribe to cmd_vel (Twist) topics to drive motor controllers, publish sensor_msgs/JointState for robotic arms, or synchronize timestamps using the micro-ROS time sync service.
For further reading and advanced configurations, consult the micro-ROS official project documentation and the ROS 2 Jazzy documentation. The source code and latest patch scripts are maintained in the micro_ros_arduino GitHub repository.






