ROS 2 - micro-ROS - Publisher, subscriber, node, topic with STM32 and Python

While receiving data from your microcontroller is great, what about sending data in the opposite direction?

First, we need to understand the difference between a publisher and a subscriber.

In order to achieve this goal, we'll need to use ROS 2 topics.

Each node has to send and receive data to and from a topic, that is publishing and subscribing to a topic.

 

Shopping list

  • Github for the tutorial:
  • STM32F3 Discovery board STM32F303VC
    • microcontroller and Cortex M4 processor 
  • A Docker container for the micro-ROS Agent (Humble version)
    • microros/micro-ros-agent:humble
  • A Docker container for ROS 2 (Humble version)
    • ros:humble-ros-base
  • VSCode
    • ROS 2 will be installed inside a VSCode Dev Container.
    • The PIO extension will be used and needs to know where is the project directory:
      • From VSCode, select: File > Add Folder to Workspace... > cpp_stm32_firmware

 

The Node

In the ROS 2 world, a node is a primary entry point (a program on a device) from which we can create other entities (in our tutorial it will be publishers, subscribers and topics).

It’s in general a device that communicates within the ROS 2 network, sending data to other devices or receiving data from other devices.

 

The publisher

In our tutorial, we have 2 publishers:

  • The STM32F3 microcontroller
  • The PC (via our Python script on Ubuntu 22 on Dev Container)

The publisher can publish on a topic without knowing if anyone is listening.

It’s like a radio broadcasting.

For example our microcontroller needs to update a value every 100 ms.

It can then publish the data directly on a topic.

In our tutorial, the topic is called badprog_topic_counter.

Each time a message is published, the program triggers a special function called a callback.

So every subscriber can listen to this topic by subscribing to the badprog_topic_counter topic.

 

The subscriber

We have also 2 subscribers in our tutorial (yes they are the same as the publishers):

  • The STM32F3 microcontroller
  • The PC (via our Python script on Ubuntu 22 on Dev Container)

The role of a subscriber is to read what happens on a topic in order to listen to data published on this topic.

And each time a data is broadcasted on this topic (by the publisher), all subscribers receive this data.

 

The topic

The topic has to have a strict type.

For example, you can’t send an Int32 on a topic set as a Bool.

It won’t work (or it may not the result expected).

Your message has to be the exact same type as the topic.

 

The communication protocol

In order to communicate, devices need to understand each other.

In ROS 2, the main protocol used is DDS (Data Distribution Service).

This protocol requires a significant amount of RAM, which our STM32 board lacks.

So we need to find a solution to allow our microcontroller to communicate with other nodes on the ROS 2 network.

Here comes the XRCE (eXtremely Resource Constrained Environments), a ROS 2 extension made expressly for the microcontroller world.

Instead of using the complex ROS 2 network we delegate this task to a proxy, the micro-ROS Agent.

The STM32 can then send short messages with the XRCE protocol to the network.

Then the Agent transforms those XRCE messages into the DDS protocol in order to be fully understood by others nodes on the ROS 2 network.

 

Let’s resume it

To resume simply :

  • The publisher node sends data on a topic
  • The subscriber node receives data from a topic
  • The topic is strictly typed
  • ROS 2 uses the DDS protocol
  • The STM32F3 can only communicate with XRCE protocol
  • The micro-ROS Agent translates data from XRCE to DDS protocol (and vice versa)
  • So every node on the ROS 2 network can understand each other with the Agent

 

Let’s code a bit

The micro-ROS Agent

In order to launch your micro-ROS Agent, use the following Docker command to install your container on your host:

 docker run -it --rm --privileged --net=host -v /dev:/dev microros/micro-ros-agent:humble serial --dev /dev/ttyACM0 -v6

 

The C++ code

In this tutorial we use the HAL (Hardware Abstraction Layer) in order to manipulate our LEDs on the STM32F3Discovery board.

The handles are blocking.

It means that if a callback called by the executor lasts 50 ms, the next callback to be called won’t be called during these 50 ms.

There is no software interruption during the process of a callback.

The callbacks are indeed executed sequentially.

Only the hardware can interrupt the program.

So, each time a callback is executed, the loop() function is blocked.

A new loop cycle is started only at the end of each callback.

The Cortex M4, from the STM32F3, is single-tasked.

It means that it can handle only one task at a time.

So the code inside each callback must be as short as possible.

 

The Python code

For the Python code we have a class inheriting from Node, a class of the RCL (ROS 2 Client Library).

Inside this constructor class we set the publisher and the subscriber in order to send and receive data to and from the STM32 program.

Every 500 ms, a timer triggers a function to send a new boolean value to the STM32.

Independently, whenever a message is received from the STM32 on the badprog_topic_counter topic, the executor triggers the listener_callback() to process that data.

This callback is triggered by the rclpy.spin() function which detects every message on topics and dispatch events as needed.

 

The output

Let’s have a look at what happens for every kind of message.

First, we have to launch the micro-ROS Agent with a Docker image directly from our host.

So we should see something like this with of course a lot of other logs displayed by the debugger:

  • running...
  • logger setup
  • create
  • session established
  • participant created 
  • topic created   
  • publisher created  
  • datawriter created
  • topic created
  • subscriber created  
  • subscriber created  

Then a lot of logs like:

  • [** <<SER>> **]
  • [==>> SER <<==]

The first element is displayed when the micro-ROS Agent sends data and the second when it receives data.

So it’s the opposite for the STM32 microcontroller.

The SER stands for Serial and it’s the XRCE protocol (discussed above in the tutorial) between the micro-ROS Agent and the microcontroller.

Then we have the following elements:

  • [** <<DDS>> **]
  • [==>> DDS <<==]

The DDS is when the micro-ROS Agent writes or reads information directly on or from the ROS 2 topics.

From your Dev Container (the container from ROS 2 humble image) you can see the counter increments by typing this command:

ros2 topic echo /badprog_topic_counter

You’ll see:

data: 0
---
data: 1
---
data: 2
---
...

But you can also execute the Python script (from this Dev Container as well):

python3 python_node_pc/scripts/node_pc.py

And see:

[INFO [xxx] [badprog_node_pc]: The LED is turned: ON

[INFO] [xxx] [badprog_node_pc]: The LED is turned: OFF

[INFO] [xxx] [badprog_node_pc]: ---> Received from STM32: Counter = 0

[INFO] [xxx] [badprog_node_pc]: The LED is turned: ON

[INFO] [xxx] [badprog_node_pc]: The LED is turned: OFF

[INFO] [xxx] [badprog_node_pc]: ---> Received from STM32: Counter = 1

[INFO] [xxx] [badprog_node_pc]: The LED is turned: ON

[INFO] [xxx] [badprog_node_pc]: The LED is turned: OFF

[INFO] [xxx] [badprog_node_pc]: ---> Received from STM32: Counter = 2

...

 

And finally, on your STM32F3 Discovery, you should see the green LED (East) and the orange LED (North East) blink at different rates.

If this is happening, then good job, you did it! cool

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.