Porting Micro-ROS on Any Microcontroller with Custom Interfaces

Table of Contents

Porting Micro-ROS on Any Microcontroller with Custom Interfaces

An Introduction to Micro-ROS

Micro-ROS is a streamlined version of the widely used Robot Operating System (ROS), specifically designed for embedded systems and microcontrollers. It adds many key ROS 2 features to microcontrollers (MCUs) like nodes, publish/subscribe, client/service, node graph, and lifecycle management.

ROSThe micro-ROS agent enables seamless communication between the micro-ROS nodes on MCUs and standard ROS 2 systems, allowing the micro-ROS nodes to be accessed using the same tools and APIs as regular ROS nodes.

For more information go through Micro-ROS.

Micro-ROS supports various microcontrollers, allowing embedded systems to use ROS2 and its features.

Porting Micro-ROS on any Controller

This blog post focuses on enabling the development of robotics applications on constrained hardware platforms, such as STM32, ESP32, Arduino, and Raspberry Pi boards. These platforms are widely used due to their excellent performance and low power consumption. Micro-ROS can run on this hardware, providing the core functionality of ROS 2, including publisher-subscriber communication, custom messages, and services.

In this guide, we walk you through the process of porting Micro-ROS onto a controller, and demonstrate how to create a publisher, subscriber with custom messages and services for your application.

We specifically implemented Micro-ROS on the STM32H743VIT6 model, though this setup can be adapted for any microcontroller board.

We have outlined all the necessary steps to develop custom Micro-ROS firmware for the STM32, including setting up Micro-ROS, creating a custom agent, and defining custom messages and services.

Additionally, the setup information we used, along with the example provided, is outlined below:

  • ROS Version: Humble
  • Host OS: Ubuntu 22.04
  • Microcontroller Board: STM32H743VIT6 (Cortex-M7)
  • Build Environment: CMake
  • Application: FreeRTOS-based interface

The Required Setup

The following will be needed to get started with Micro-ROS on STM32:

  • STM32 Development Board (e.g., STM32F4, STM32F7, or STM32H7 series)
  • Micro-ROS-Setup package on host pc
  • ROS 2 setup on a host PC (Ubuntu preferred 22.04, ROS version: humble)
  • Serial communication (USB, UART, CAN, etc.) to evaluate the communication between STM32 and the host PC
  • CMake set up for code build

Steps for porting Micro-ROS on the STM32 board

1. Create generic micro-ROS firmware to build libraries

  • It will create firmware/ directory in the current path.

    cd uros_ws/mkdir src/

    clone git repo: https://github.com/micro-ROS/micro_ros_setup.git

    cd uros_ws/

    colcon build && source install/setup.bash

    ros2 run micro_ros_setup create_firmware_ws.sh generate_lib

2. Custom message and service setup

The advantages of using a custom message and service are:

  • When our application requires specific data that is not available in the standard messages, we can use custom messages and services. This approach allows us to define exactly the data needed for the application, excluding unnecessary fields that standard ROS messages might include but are not relevant to our use case.
  • Using custom messages and services also helps minimize the amount of data transferred over the ROS2 network, optimizing network usage. This is particularly important in resource-constrained environments like micro-ROS.
  • For more details on how to create custom messages and services, please refer to this link. Additionally, an example is provided below, which we have used in our application.

If the application does not require any specific data outside of the standard ROS2 messages and services, you can skip the step of creating custom messages.

Steps for custom setup:

  • Create a ROS2 CMake package inside uros_ws/firmware/mcu_ws/uros/.
  • The Micro-ROS setup will build every package inside firmware/mcu_ws/.
  • To create a ROS2 package for the Micro-ROS application, you can refer to this link.
  • Once the package is created, you can add your custom message and service files inside custom_pkg/msg and custom_pkg/srv.
  • Then, edit CMakeLists.txt and package.xml accordingly.

3. Once all the packages are downloaded, we need to create a few files to cross-compile a custom static library and a set of header files.

  • Two essential files that we need are the Toolchain file and the Colcon meta file.
    touch stm32h7xx_toolchain.cmake
    touch stm32h7_colcon.meta
  • For an example of the colcon.meta file, refer to this link.
  • The Toolchain file is used to configure the cross-compilation environment.
  • The toolchain file needs to be customized for our interface when we are compiling static libraries for our application.
  • Here is an example of how to create a toolchain file.

Toolchain File Example

  • We have created toolchain file common for Micro-ROS build and controller project build.
  • Here are our toolchain file contents for stm32 as well as Micro-ROS build.
  • You can include flags according to your application requirements.
  • To define your board specifics, you can set the following compilation flags.

    set(CMAKE_SYSTEM_PROCESSOR “arm” CACHE STRING “”) // target platform architectureset(CMAKE_CROSSCOMPILING 1) // to use cross compilation

    set(CMAKE_SYSTEM_NAM “Generic” CACHE STRING “”) // type of system

    // Cmake compiler flags

    set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

    set(TOOLCHAIN_PREFIX “arm-none-eabi-“)

    set(CMAKE_C_COMPILER “${TOOLCHAIN_PREFIX}gcc”)

    set(CMAKE_ASM_COMPILER “${TOOLCHAIN_PREFIX}gcc”)

    set(CMAKE_CXX_COMPILER “${TOOLCHAIN_PREFIX}g++”)

    set(CMAKE_AR “${TOOLCHAIN_PREFIX}ar”)

    set(CMAKE_LINKER “${TOOLCHAIN_PREFIX}ld”)

    set(CMAKE_OBJCOPY “${TOOLCHAIN_PREFIX}objcopy”)

    set(CMAKE_RANLIB “${TOOLCHAIN_PREFIX}ranlib”)

    set(CMAKE_SIZE “${TOOLCHAIN_PREFIX}size”)

    set(CMAKE_STRIP “${TOOLCHAIN_PREFIX}ld”)

  • MCU Flags are set to specify the CPU architecture, runtime libraries, and define executable formats.

    set(MCPU “-mcpu=Cortex-M7”) // specify cpu architectureset(MFPU “-mfpu=fpv5-d16”)set(MFLOAT_ABI “-mfloat-abi=hard”) // application binary interface

    set(RUNTIME_LIBRARY “–specs=nano.specs”)

    set(RUNTIME_LIBRARY_SYSCALLS “–specs=nosys.specs”)

    set(CMAKE_EXECUTABLE_SUFFIX “.elf”)

    set(CMAKE_EXECUTABLE_SUFFIX_C “.elf”)

    set(CMAKE_C_COMPILER_WORKS 1 CACHE INTERNAL “”)

    set(CMAKE_CXX_COMPILER_WORKS 1 CACHE INTERNAL “”)

  • -DCLOCK_MONOTONIC=0: This flag needs to be defined for Micro-ROS build process.
  • When compiling for the controller board, clear the MICROROSFLAGS since the controller board supports CLOCK_MONOTONIC and using the -DCLOCK_MONOTONIC=0 flag would result in issues or incorrect behaviour.
  • set(MICROROSFLAGS “-DCLOCK_MONOTONIC=0 -D’__attribute__(x)='” CACHE STRING “” FORCE) //when compiling Micro-ROS# set(MICROROSFLAGS “”) //when compiling controller board

  • MICROROSFLAGS
  • We encountered an issue when the MICROROSFLAGS flag was not defined during the Micro-ROS build process. Therefore, ensure that it is properly defined in your CMakeLists.txt. 
  • Some flags need to be initialized for your specific controller and build platform.
  • In this case, we are using the ARM Cortex-M7 architecture and the CMake build platform. Therefore, the following flags are set, which are necessary for the C and C++ libraries. These flags ensure that the correct compilation settings are applied for the target system.

    set(CMAKE_CXX_FLAGS_INIT “${MCPU} -std=c++14 -ffunction-sections -fdata-sections \ ${RUNTIME_LIBRARY} ${MFPU} ${MFLOAT_ABI} ${MICROROSFLAGS} -Wall -g -fno-rtti” CACHE \ STRING “” FORCE)set(CMAKE_C_FLAGS_INIT “${MCPU} -std=gnu11 -ffunction-sections -fdata-sections \ ${RUNTIME_LIBRARY} ${MFPU} ${MFLOAT_ABI} ${MICROROSFLAGS} -mthumb -Wall -g \ -Wno-implicit-function-declaration” CACHE STRING “” FORCE)

    set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

    set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

    set(CMAKE_BUILD_TYPE Debug)

    set(__BIG_ENDIAN__ 0)

4. Once you have both files ready, just run the build step in the micro-ROS build system:

ros2 run micro_ros_setup build_firmware.sh
$(pwd)/my_custom_toolchain.cmake $(pwd)/my_custom_colcon.meta
  • We are using the absolute path of the current working directory, i.e., $(pwd), so that the build script can be executed from any location.

5.libmicrooros

It creates firmware/ directory and builds the libmicrooros.a library inside the build folder

6. Copy the build folder into the include directory of your board project repo.

Include the header files path and the library path in building the environment (e.g., CMakeLists.txt); add the library path you need for the application, you can add it as in the example given below:

#microROSset(UROS_INCLUDE_PATH “firmware/build/include”)

set(UROS_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rcutils/

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rcl

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rcl_action

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rosidl_runtime_c

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rcl_interfaces

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rmw_microxrcedds

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/rosidl_typesupport_interface

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/uxr

${CMAKE_CURRENT_LIST_DIR}/${UROS_INCLUDE_PATH}/std_msgs

${CMAKE_CURRENT_LIST_DIR}/${UOS_INCLUDE_PATH}/eic_demo_interface)

set(UROS_INCLUDES “${UROS_INCLUDES}” PARENT_SCOPE)

include_directories(${UROS_INCLUDES})

  • Add the library path as shown below in CMakeLists.txt

    target_link_libraries(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/ \third-party/firmware/build/libmicroros.a)

7. Once all the packages are included, we can add the custom interfaces for the controller application. You can find ready-to-use code related to this concept for simple publisher and subscriber in the following directories:

These repos contain example implementations for publishing and subscribing topics in a Micro-ROS environment.

Application Example Code

1. Include headers 

#include <eic_demo_interface/msg/cmd_vel.h>#include <eic_demo_interface/srv/get_time.h>

#include <rcl/error_handling.h>

#include <rcl/rcl.h>

#include <rclc/executor.h>

#include <rclc/rclc.h>

#include <rmw_microros/rmw_microros.h>

#include <rmw_microxrcedds_c/config.h>

#include <uxr/client/transport.h>

2. Create the publisher and subscriber

rcl_publisher_t publisher;rcl_subscription_t subscriber;

eic_demo_interface__msg__CmdVel velocity_data;

eic_demo_interface__srv__GetTime_Request req_msg;

eic_demo_interface__srv__GetTime_Response res_msg;

rcl_client_t client;

int64_t num_of_req = 1;

3. Add Allocators

Advantages of using custom allocators

  • In this configuration, we are running a Micro-ROS application within a FreeRTOS task. We assign memory to the FreeRTOS allocator to manage memory. This allocator provides essential functions for allocating, deallocating, reallocating, and zero-initializing memory, which are crucial for efficient memory management in the embedded systems running FreeRTOS.
  • By using custom memory management functions, like microros_allocate, microros_deallocate, the system can optimize memory allocation according to the specific needs of the Micro-ROS application. This approach ensures that memory management is adapted to the unique constraints and requirements of the embedded environment.
  • If FreeRTOS is not being used, the default allocator can be used instead. For other custom allocators, options like the standard C library allocator or static memory allocators can be implemented. For more information on using custom allocators, refer to this link.

Custom setup

  • The allocator object wraps the dynamic memory allocation and deallocating methods used in Micro-ROS.
  • We can modify the default allocator with our own FreeRTOS allocator:

    rcl_allocator_t freeRTOS_allocator = rcutils_get_zero_initialized_allocator();freeRTOS_allocator.allocate = microros_allocate;

    freeRTOS_allocator.deallocate = microros_deallocate;

    freeRTOS_allocator.reallocate = microros_reallocate;

    freeRTOS_allocator.zero_allocate = microros_zero_allocate;

  • One must define these four functions for memory allocation.
  • Here, rcl_allocator_t configures a custom memory allocator (freeRTOS_allocator) for Micro-ROS running on FreeRTOS.

4. Add external transport in the client application

Advantages of using custom transports

  • The micro-ROS middleware, eProsima Micro XRCE-DDS, provides a user API that allows interfacing with the lowest level transport layer at runtime, which enables users to implement their own transports in both the micro-ROS Client and micro-ROS Agent libraries.
  • Standard ROS2 transports like DDS (Data Distribution Service) are designed for larger, more powerful systems and may be too heavy for embedded systems. Custom transports allow us to choose a communication protocol that best fits the capabilities of our device.
  • Set a custom transport according to your application, You can refer to this GitHub repo for creating custom transport functions.

For this setup, we have selected the USB as the communication transport between the controller and the host. In general, four functions must be implemented, and the behaviour of these functions may vary depending on the chosen transport mode.

Here we are using the USB as a transport, as given below:

rmw_uros_set_custom_transport(true,(void*)&hUsbDeviceHS,
usb_transport_open,
\ usb_transport_close, usb_transport_write, usb_transport_read);
  • One must define these four functions for sending and receiving data from the transport
  • For more information you can refer to this link

5. Init nodes

// create init_optionsrclc_support_init(&support, 0, NULL, &freeRTOS_allocator);

// Create executor

rclc_executor_t executor;

executor = rclc_executor_get_zero_initialized_executor();

// executor init

rclc_executor_init(&executor, &support.context, 2, &freeRTOS_allocator));

// create node

rclc_node_init_default(&node, “test_node”, “”, &support);

Advantages of using an executor:

  • Executors are used to manage and control the execution of tasks (nodes and their callbacks).
  • They are a core part of the communication and processing model in ROS 2, helping to manage multiple tasks concurrently in a real-time or multi-threaded manner.

Create the publisher and subscriber with a custom message type

// 1. Initialize Publisherrclc_publisher_init_default(&publisher, &node, \

ROSIDL_GET_MSG_TYPE_SUPPORT(eic_demo_interface, msg, CmdVel), “pub_topic”);

// 2. Initialize Subscriber

rclc_subscription_init_default( &subscriber, &node, \

ROSIDL_GET_MSG_TYPE_SUPPORT(eic_demo_interface, msg, CmdVel), “sub_topic”);

// 3. Add Subscriber to Executor

rclc_executor_add_subscription(&executor, &subscriber, &sub_msg, &subscription_callback, \ ON_NEW_DATA));

Subscriber callback:

void subscription_callback(const void* msgin) {// Cast received message to used type

const eic_demo_interface__msg__CmdVel* msg = (eic_demo_interface__msg__CmdVel*)msgin;

// Process message

printf(“Received: %ld”, msg->data);

6. Create client on controller side to get data from service:

rclc_client_init_default( &client, &node, \ROSIDL_GET_SRV_TYPE_SUPPORT(eic_demo_interface, srv, GetTime), “get_time”);

// 7. Add Client to Executor

rclc_executor_add_client(&executor, &client, &res_msg, client_callback);

// 8. Initialize Request Message for Client

eic_demo_interface__srv__GetTime_Request__init(&req_msg);

7. Callback function to be called when the service is available:

void client_callback(const void* response_msg) {eic_demo_interface__srv__GetTime_Response* msgin = \ (eic_demo_interface__srv__GetTime_Response*)response_msg;

printf(“RESPONSE: TIME = %ld”, msgin->time_ms_res);

// check service response data received or not

if (msgin->time_ms_res == 0) {

printf(“Invalid data received “);

}

return;

}

8. While loop or For loop for publishing data and calling service

for (;;) {rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));

rcl_publish(&publisher, &velocity_data, NULL);

rcl_send_request(&client, &req_msg, &num_of_req);

velocity_data.left_rpm++;

}

9. You can check the publisher and subscriber by creating a simple publisher and subscriber, here we have checked with the ROS2 python package.

ROS2 python package

ROS2 python package_1

10. Create simple service file to check response

  • source ros2 custom message/service package and run service as shown below:

Micro-ROS

This is how you can create a custom interface for Micro-ROS on a controller board according to your application needs.

Conclusion

In conclusion, porting Micro-ROS to a microcontroller with custom interfaces allows you to leverage the power of ROS2 in embedded systems, enabling efficient communication and control even in resource-constrained environments.

By selecting appropriate transport protocols, defining custom messages and services, and integrating them with the hardware, you can create a tailored solution that meets the specific needs of your application. This flexibility helps optimize performance and ensures that the microcontroller-based system can efficiently interact with the broader ROS ecosystem.

References

https://micro.ros.org/docs/overview/features/ 
https://micro.ros.org/docs/tutorials/advanced/create_new_type/ 
https://micro.ros.org/docs/tutorials/advanced/create_custom_static_library/ 
https://github.com/micro-ROS/micro_ros_setup/tree/humble 
https://micro.ros.org/docs/tutorials/advanced/create_custom_transports/ 
https://github.com/micro-ROS/micro_ros_stm32cubemx_utils/tree/humble/extra_sources/microros_transports 

 

Picture of Ruchi Patel

Ruchi Patel

Ruchi Patel is working as an Embedded Engineer at eInfochips (an Arrow company), where she specializes in developing firmware solutions for embedded systems. She holds a B. Tech degree in Electronics Engineering and has over three years of experience working with microcontrollers, including STM32, Maxim Boards, and ESP32. Currently, she is focused on firmware application development for robotic control systems in the robotics domain.

Explore More

Talk to an Expert

Subscribe
to our Newsletter
Stay in the loop! Sign up for our newsletter & stay updated with the latest trends in technology and innovation.

Download Report

Download Sample Report

Download Brochure

Start a conversation today

Schedule a 30-minute consultation with our Automotive Solution Experts

Start a conversation today

Schedule a 30-minute consultation with our Battery Management Solutions Expert

Start a conversation today

Schedule a 30-minute consultation with our Industrial & Energy Solutions Experts

Start a conversation today

Schedule a 30-minute consultation with our Automotive Industry Experts

Start a conversation today

Schedule a 30-minute consultation with our experts

Please Fill Below Details and Get Sample Report

Reference Designs

Our Work

Innovate

Transform.

Scale

Partnerships

Quality Partnerships

Company

Products & IPs

Privacy Policy

Our website places cookies on your device to improve your experience and to improve our site. Read more about the cookies we use and how to disable them. Cookies and tracking technologies may be used for marketing purposes.

By clicking “Accept”, you are consenting to placement of cookies on your device and to our use of tracking technologies. Click “Read More” below for more information and instructions on how to disable cookies and tracking technologies. While acceptance of cookies and tracking technologies is voluntary, disabling them may result in the website not working properly, and certain advertisements may be less relevant to you.
We respect your privacy. Read our privacy policy.

Strictly Necessary Cookies

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.