Guillaume Beuzeboc

Interested in robotics, I am working on software mainly with Linux

Run Micro-ROS on almost any stm32

19 Sep 2021 » c, cmake, stm32, micro-ros, clion

Micro-ROS brings the ability to integrate microcontrollers into the ROS ecosystem. Having microcontrollers publishing topic directly into your ROS2 host could facilitate a lot of the integration. So far, Micro-ROS is only officially supporting a small amount of boards. Unfortunately, I didn’t have any stm32 board officially supported by Micro-ROS, so I decided to have a look at how to run Micro-ROS on the stm32 board I had at home. So I have a Nucleo-L476RG which is an ultra low power board. It has a lot of pins and configuration possibility, a Flash size of 1 MB and two RAMs (96 KB and 32 KB). In my case, I am going to use the UART to transmit my data (using the debugger UART to avoid additional wiring). Basically what we are going to do is that we are going to create a project with CUBEMX, configure it and adapt the CMakeLists in order to link with the static Micro-ROS library. In my case I will use Clion as an IDE but most of the CMake-compatible IDE should work (Clion has a nice stm32 plugins that facilitates a lot of things like uploading, debugging, etc.). Micro-ROS is not already expecting you to use CMake, it’s rather using plain Makefiles. But with just a few changes we will be able to use Cmake.


Install all you need

In this post, I am using Ubuntu 20.04 with ROS2 foxy already installed.

Micro ROS setup

Clone this repo https://github.com/micro-ROS/micro_ros_setup/tree/foxy and follow the instruction in order to build the Micro-ROS agent (interface between the UART and ROS2).

Micro_ros_stm32cubemx_utils

In order to generate the static library, we are going to need another repo micro_ros_stm32cubemx_utils https://github.com/micro-ROS/micro_ros_stm32cubemx_utils/tree/foxy. For this example, this repo as well as the CubeMX project are going to be located in one directory.

.
├── l476rg_test   # The CubeMX project explained right after
└── micro_ros_stm32cubemx_utils # the repo

Generate your project with CubeMX

UART

Within CubeMX I generated a project for my board: Nucleo-476RG. I activate the USART2 (the one connected to the debugger). Configure it for asynchronous and disable hardware control.

UART2 setup

I left the default basic setup of it (115200 Bits/s, 8 Bits, None, 1). Check the “Global interrupt” checkbox under the NVIC Settings.

NVIC settings

Make sure to enable to DMA for the corresponding UART. Both priorities must be set to very hight and RX direction must be set to Peripheral To Memory and TX one to Memory to Peripheral. Please also make sure that RX DMA Request settings Mode is set to Circular (Keep the Normal on for TX).

DMA setup

FreeRTOS

For our example, we will need FreeRTOS. We will simply need to configure a task (the default) for Micro-ROS. Activate FreeRTOS in CubeMX (I used CMSIS v2). Micro-ros will need at least 12 kB for its task, hence we need to increase the default TOTAL_HEAP_SIZE of FreeRTOS. I picked 20000 bytes for my test (but you can of course use 12000).

FreeRTOS setup

You must also create a task for your Micro-ROS. We are simply going to use the default one. And set a size of 3000 words (3000 words of 4 bytes is 12 kB).

FreeRTOS task setup

Generate the project

Here I want to use Clion with Cmake hence I am going to generate my project for the SW4STM32 toolchain : Project Manage -> Project -> Toolchain / IDE; and select SW4STM32. Generate code in order to have the source files as well as the CMakeLists.txt.


Generate the Micro-ROS static library

Here we are going to use the micro_ros_stm32cubemx_utils repo that we downloaded before. This repository is using the Makefiles to extract the compilation flags in order to build the Micro-ROS static library. Unfortunately, so far it’s only extracting from files with .mk extension and in our case the file containing the flags (generated by Cmake) is called flags.make. Thus, we will have to make a little change to the repo. We will change the file microros_static_library_ide/library_generation/library_generation.sh by adding [a] and [e] line 13:

@@ -10,7 +10,7 @@ if [ -f "$BASE_PATH/libmicroros/libmicroros.a" ]; then
     exit 0
 fi
 ######## Trying to retrieve CFLAGS ########
-export RET_CFLAGS=$(find /project -type f -name *.mk -exec cat {} \; | python3 $BASE_PATH/library_generation/extract_flags.py)
+export RET_CFLAGS=$(find /project -type f -name *.m[a]k[e] -exec cat {} \; | python3 $BASE_PATH/library_generation/extract_flags.py)
 RET_CODE=$?
 
 if [ $RET_CODE = "0" ]; then
 

Once it’s done, from the directory containing the CubeMX project and the micro_ros_stm32cubemx_utils repo, launch

 docker pull microros/micro_ros_static_library_builder:foxy &&\
  docker run --rm -v ${PWD}:/project --env MICROROS_LIBRARY_FOLDER=micro_ros_stm32cubemx_utils/microros_static_library_ide microros/micro_ros_static_library_builder:foxy`
 

You should see the list of CFLAGS detected. In my case:

 Found CFLAGS:
-------------
-ffunction-sections -fdata-sections -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -Og
-------------

Make sure the options are the same that are on for your target, otherwise you could face some undefined var/functions or linking issues. This will take some time and after in the subfolder “libmicroros” containing an “include” folder and the library. In my case, I copied the libmicroros folder in my stm32 project root. I also included in it the extra_sources available in the “micro_ros_stm32cubemx_utils” repo.


Use the library

CMakeLists.txt

In order to use the library, we must add it and the headers in our CMakeLists.txt. We must first add the includes from Micro-ROS:

include_directories(Core/Inc
                    Drivers/STM32L4xx_HAL_Driver/Inc
                    Drivers/STM32L4xx_HAL_Driver/Inc/Legacy
                    Middlewares/Third_Party/FreeRTOS/Source/include
                    Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2
                    Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F
                    Drivers/CMSIS/Device/ST/STM32L4xx/Include Drivers/CMSIS/Include
                    libmicroros/include)

We also need to add the Micro-ROS extra_sources to the source file glob:

file(GLOB_RECURSE SOURCES "startup/*.*" "Middlewares/*.*" "Drivers/*.*" "Core/*.*" "libmicroros/extra_sources/*.*")

And finally we need to link the Micro-ROS static library to our target

target_link_libraries(${PROJECT_NAME}.elf ${CMAKE_SOURCE_DIR}/libmicroros/libmicroros.a)

Updating the main.c

Now that the CMakeLists.txt is ready, we can modify the main in order to publish our topic. We are basically going to use what is defined in https://github.com/micro-ROS/micro_ros_stm32cubemx_utils/blob/foxy/sample_main.c which is a full example of topic publishing. We will only have to modify the main.c (core/src) in our case.

First, we must include the necessary Micro-ROS layer includes, and our message type.

/* USER CODE BEGIN Includes */
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <uxr/client/transport.h>
#include <rmw_microros/rmw_microros.h>
#include <rcutils/stdatomic_helper/gcc/stdatomic.h>

#include <std_msgs/msg/int32.h>
/* USER CODE END Includes */

We need to declare a bunch of methods that are actually defined in the extra_sources files (in our case in dma_transport.c and in microros_allocators.c)

/* USER CODE BEGIN 4 */
bool cubemx_transport_open(struct uxrCustomTransport * transport);
bool cubemx_transport_close(struct uxrCustomTransport * transport);
size_t cubemx_transport_write(struct uxrCustomTransport* transport, const uint8_t * buf, size_t len, uint8_t * err);
size_t cubemx_transport_read(struct uxrCustomTransport* transport, uint8_t* buf, size_t len, int timeout, uint8_t* err);

void * microros_allocate(size_t size, void * state);
void microros_deallocate(void * pointer, void * state);
void * microros_reallocate(void * pointer, size_t size, void * state);
void * microros_zero_allocate(size_t number_of_elements, size_t size_of_element, void * state);
/* USER CODE END 4 */

Then we are going to change the code directly in our StartDefaultTask.

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN 5 */
  /* Here we give our huart2 interface, and the 4 
   * transport functions
   */
  rmw_uros_set_custom_transport(
          true,
          (void *) &huart2,
          cubemx_transport_open,
          cubemx_transport_close,
          cubemx_transport_write,
          cubemx_transport_read);

  /* Here you also give to the allocator the functions
   * that are going to be used in order to allocate memory etc.
   */
  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;

  if (!rcutils_set_default_allocator(&freeRTOS_allocator)) {
      printf("Error on default allocators (line %d)\n", __LINE__);
  }

  // Micro-ROS app


  rcl_publisher_t publisher;
  std_msgs__msg__Int32 msg;
  /* The support is the structure that is going to carry all
   * the information about your Micro-ROS instance 
   * (context, allocator, clock , options)
   */
  rclc_support_t support;
  // The allocator that we defined earlier
  rcl_allocator_t allocator;
  rcl_node_t node;

  allocator = rcl_get_default_allocator();
  //create init_options
  rclc_support_init(&support, 0, NULL, &allocator);

 // create node
  rclc_node_init_default(&node, "cubemx_node", "", &support);

 // create publisher
  rclc_publisher_init_default(
          &publisher,
          &node,
          ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
          "cubemx_publisher");

  msg.data = 0;

  for(;;)
  {
      rcl_ret_t ret = rcl_publish(&publisher, &msg, NULL);
      if (ret != RCL_RET_OK)
      {
          printf("Error publishing (line %d)\n", __LINE__);
          NVIC_SystemReset(); // If we cannot publish we restart
      }


      msg.data++;
      osDelay(10);
  }

  /* USER CODE END 5 */
}

From this point, your code is ready to compile and ready to upload on the MCU. On my MCU compiled with debug mode, the publisher takes one to two second to start publishing. Once your MCU is running and connected to your computer, you will need the Micro-ROS agent. Make sure to source your Micro-ROS ws source microros_ws/install/local_setup.zsh and run the agent ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0 (To identify your serial port, you can simply list your /dev/ and check which one appears when you connect your MCU). Your agent should display something like:

[1632668621.125343] info     | TermiosAgentLinux.cpp | init                     | running...             | fd: 3
[1632668621.125450] info     | Root.cpp           | set_verbose_level        | logger setup           | verbose_level: 4
[1632668621.126870] info     | Root.cpp           | create_client            | create                 | client_key: 0x5851F42D, session_id: 0x81
[1632668621.126911] info     | SessionManager.hpp | establish_session        | session established    | client_key: 0x5851F42D, address: 0
[1632668621.142403] info     | ProxyClient.cpp    | create_participant       | participant created    | client_key: 0x5851F42D, participant_id: 0x000(1)
[1632668621.157779] info     | ProxyClient.cpp    | create_topic             | topic created          | client_key: 0x5851F42D, topic_id: 0x000(2), participant_id: 0x000(1)
[1632668621.166655] info     | ProxyClient.cpp    | create_publisher         | publisher created      | client_key: 0x5851F42D, publisher_id: 0x000(3), participant_id: 0x000(1)
[1632668621.176929] info     | ProxyClient.cpp    | create_datawriter        | datawriter created     | client_key: 0x5851F42D, datawriter_id: 0x000(5), publisher_id: 0x000(3)

Here we see that a node connects and that it create a publisher. Now your MCU topic is available in your ROS2 and you can simply echo it.

:: ~ » ros2 topic echo /cubemx_publisher
data: 123
---
data: 124
---
.
.
.

You can find the complete example here: https://github.com/Guillaumebeuzeboc/Micro-ROS-stm32-basic-pub

If you have any suggestion/question/remark please don’t hesitate to leave a comment.