Skip to content

Commit 06a583a

Browse files
authored
Feature/hidden zephyr section (micro-ROS#163)
* Base * Initial content * Zephyr content
1 parent d08a5dc commit 06a583a

9 files changed

Lines changed: 379 additions & 2 deletions

File tree

_data/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
- tutorials/core/first_application_rtos
4646
- tutorials/core/programming_rcl_rclc
4747
- tutorials/core/microxrcedds_rmw_configuration
48+
- tutorials/core/hidden_section_first_application_zephyr
4849

4950
- title: Advanced Tutorials
5051
docs:

_docs/tutorials/core/first_application_rtos/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ title: First micro-ROS Application on Zephyr, FreeRTOS, or NuttX
33
permalink: /docs/tutorials/core/first_application_rtos/
44
redirect_from:
55
- /docs/tutorials/advanced/freertos/freertos_getting_started/
6-
- /docs/tutorials/advanced/zephyr/zephyr_getting_started/
76
---
87

98
| RTOS | Board Compatible | ROS2 Version |
600 KB
Loading
3.66 MB
Loading
106 KB
Loading
75.3 KB
Loading
671 KB
Loading
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
---
2+
title: First micro-ROS Application on Zephyr
3+
permalink: /docs/tutorials/core/hidden_section_first_application_zephyr/
4+
redirect_from:
5+
- /docs/tutorials/advanced/zephyr/zephyr_getting_started/
6+
---
7+
8+
This tutorial aims to create a new micro-ROS application on **[Olimex STM32-E407](https://www.olimex.com/Products/ARM/ST/STM32-E407/open-source-hardware)** evaluation board with **[Zephyr RTOS](https://www.zephyrproject.org/)**
9+
10+
<div>
11+
<img width="400" style="padding-right: 25px;" src="imgs/3.jpg">
12+
13+
<img width="300" style="padding-right: 25px;" src="imgs/4.jpg">
14+
</div>
15+
16+
## Required hardware
17+
18+
This tutorial uses the following hardware:
19+
20+
| Item |
21+
|---------------|
22+
| [Olimex STM32-E407](https://www.olimex.com/Products/ARM/ST/STM32-E407/open-source-hardware) |
23+
| [Olimex ARM-USB-TINY-H](https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H/) |
24+
| [USB-Serial Cable Female](https://www.olimex.com/Products/Components/Cables/USB-Serial-Cable/USB-Serial-Cable-F/) |
25+
26+
27+
## Adding a new micro-ROS app
28+
29+
First of all, make sure that you have a **ROS 2** installation.
30+
31+
***TIP:** if you are familiar with Docker containers, this image may be useful: [ros:dashing](https://hub.docker.com/layers/ros/library/ros/dashing/images/sha256-b796c14ea663537129897769aa6c715a851ca08dffd4875ef2ecaa31a4dbd431?context=explore)*
32+
33+
On the **ROS 2** installation open a command line and follow these steps:
34+
35+
```bash
36+
# Source the ROS 2 installation
37+
source /opt/ros/$ROS_DISTRO/setup.bash
38+
39+
# Create a workspace and download the micro-ROS tools
40+
mkdir microros_ws
41+
cd microros_ws
42+
git clone -b $ROS_DISTRO https://github.com/micro-ROS/micro-ros-build.git src/micro-ros-build
43+
44+
# Update dependencies using rosdep
45+
sudo apt update && rosdep update
46+
rosdep install --from-path src --ignore-src -y
47+
48+
# Build micro-ROS tools and source them
49+
colcon build
50+
source install/local_setup.bash
51+
```
52+
53+
54+
Now, let's create a firmware workspace that targets all the required code and tools for Olimex development board and Zephyr:
55+
56+
```bash
57+
# Create step
58+
ros2 run micro_ros_setup create_firmware_ws.sh zephyr olimex-stm32-e407
59+
```
60+
61+
Now you have all the required tools to crosscompile micro-ROS and Zephyr for Olimex STM32-E407 development board. At this point, you must know that the micro-ROS build system is a four-step workflow:
62+
63+
<!-- TODO (pablogs9): Remove and link to build-system tutorial when done -->
64+
1. **Create**: retrieves all the required packages for a specific RTOS and hardware platform.
65+
2. **Configure**: configures the downloaded packages with options such as the micro-ROS application, the selected transport layer or the micro-ROS agent IP address (in network transports).
66+
3. **Build**: generates a binary file ready for being loaded in the hardware.
67+
4. **Flash**: load the micro-ROS software in the hardware.
68+
69+
micro-ROS apps for Olimex + Zephyr are located at `firmware/zephyr_apps/apps`. In order to create a new application, create a new folder containing two files: the app code (inside a `src` folder) and the RMW configuration.
70+
71+
```bash
72+
# Creating a new app
73+
pushd firmware/zephyr_apps/apps
74+
mkdir my_brand_new_app
75+
cd my_brand_new_app
76+
mkdir src
77+
touch src/app.c app-colcon.meta
78+
popd
79+
```
80+
81+
You will also need some other Zephyr related files: a `CMakeLists.txt` in order to define the building process and a `prj.conf` where Zephyr is configured. You have these two files [here](https://github.com/micro-ROS/zephyr_apps/tree/dashing/apps/ping_pong), for now it is ok to copy them.
82+
83+
For this example we are going to create a ping pong app where a node sends a ping package with a unique identifier using a publisher and the same package is received by a pong subscriber. The node will also answer to pings received from other nodes with a pong message:
84+
85+
![pingpong](http://www.plantuml.com/plantuml/png/ZOwnIWGn48RxFCNFzSkoUG2vqce5jHEHi1dtWZkPa6GByNntavZY10yknMJu-ORlFwPiOjvvK-d3-M2YOR1uMKvHc93ZJafvoMML07d7h1NAE-DPWblg_na8vnwEx9OeZmzFOt1-BK7AzetJciPxCfRYVw1S0SbRLBEg1IpXPIvpUWLCmZpXIm6BS3addt7uQpu0ZQlxT1MK2r0g-7sfqbsbRrVfMrMwgbev3CDTlsqJGtJhATUmSMrMg5TKwaZUxfcttuMt7m00)
86+
87+
To start creating this app, let's configure the RMW with the required static memory. You can read more about RMW and Micro XRCE-DDS Configuration [here](/docs/tutorials/core/microxrcedds_rmw_configuration/). The `app-colcon.meta` should look like:
88+
89+
```
90+
{
91+
"names": {
92+
"rmw_microxrcedds": {
93+
"cmake-args": [
94+
"-DRMW_UXRCE_MAX_NODES=1",
95+
"-DRMW_UXRCE_MAX_PUBLISHERS=2",
96+
"-DRMW_UXRCE_MAX_SUBSCRIPTIONS=2",
97+
"-DRMW_UXRCE_MAX_SERVICES=0",
98+
"-DRMW_UXRCE_MAX_CLIENTS=0",
99+
"-DRMW_UXRCE_MAX_HISTORY=4",
100+
]
101+
}
102+
}
103+
}
104+
```
105+
106+
Meanwhile `src/app.c` should look like the following code:
107+
108+
```c
109+
#include <rcl/rcl.h>
110+
#include <rcl_action/rcl_action.h>
111+
#include <rcl/error_handling.h>
112+
#include "rosidl_generator_c/string_functions.h"
113+
#include <std_msgs/msg/header.h>
114+
115+
#include <rmw_uros/options.h>
116+
117+
#include <stdio.h>
118+
#include <unistd.h>
119+
120+
#include <zephyr.h>
121+
122+
#define STRING_BUFFER_LEN 100
123+
124+
// App main function
125+
void main(void)
126+
{
127+
//Init RCL options
128+
rcl_init_options_t options = rcl_get_zero_initialized_init_options();
129+
rcl_init_options_init(&options, rcl_get_default_allocator());
130+
131+
// Init RCL context
132+
rcl_context_t context = rcl_get_zero_initialized_context();
133+
rcl_init(0, NULL, &options, &context);
134+
135+
// Create a node
136+
rcl_node_options_t node_ops = rcl_node_get_default_options();
137+
rcl_node_t node = rcl_get_zero_initialized_node();
138+
rcl_node_init(&node, "pingpong_node", "", &context, &node_ops);
139+
140+
// Create a reliable ping publisher
141+
rcl_publisher_options_t ping_publisher_ops = rcl_publisher_get_default_options();
142+
rcl_publisher_t ping_publisher = rcl_get_zero_initialized_publisher();
143+
rcl_publisher_init(&ping_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_publisher_ops);
144+
145+
// Create a best effort pong publisher
146+
rcl_publisher_options_t pong_publisher_ops = rcl_publisher_get_default_options();
147+
pong_publisher_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
148+
rcl_publisher_t pong_publisher = rcl_get_zero_initialized_publisher();
149+
rcl_publisher_init(&pong_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_publisher_ops);
150+
151+
// Create a best effort pong subscriber
152+
rcl_subscription_options_t pong_subscription_ops = rcl_subscription_get_default_options();
153+
pong_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
154+
rcl_subscription_t pong_subscription = rcl_get_zero_initialized_subscription();
155+
rcl_subscription_init(&pong_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_subscription_ops);
156+
157+
// Create a best effort ping subscriber
158+
rcl_subscription_options_t ping_subscription_ops = rcl_subscription_get_default_options();
159+
ping_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
160+
rcl_subscription_t ping_subscription = rcl_get_zero_initialized_subscription();
161+
rcl_subscription_init(&ping_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_subscription_ops);
162+
163+
// Create a wait set
164+
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
165+
rcl_wait_set_init(&wait_set, 2, 0, 0, 0, 0, 0, &context, rcl_get_default_allocator());
166+
167+
// Create and allocate the pingpong publication message
168+
std_msgs__msg__Header msg;
169+
char msg_buffer[STRING_BUFFER_LEN];
170+
msg.frame_id.data = msg_buffer;
171+
msg.frame_id.capacity = STRING_BUFFER_LEN;
172+
173+
// Create and allocate the pingpong subscription message
174+
std_msgs__msg__Header rcv_msg;
175+
char rcv_buffer[STRING_BUFFER_LEN];
176+
rcv_msg.frame_id.data = rcv_buffer;
177+
rcv_msg.frame_id.capacity = STRING_BUFFER_LEN;
178+
179+
// Set device id and sequence number;
180+
int device_id = rand();
181+
int seq_no;
182+
183+
int pong_count = 0;
184+
struct timespec ts;
185+
rcl_ret_t rc;
186+
187+
uint32_t iterations = 0;
188+
189+
do {
190+
// Clear and set the waitset
191+
rcl_wait_set_clear(&wait_set);
192+
193+
size_t index_pong_subscription;
194+
rcl_wait_set_add_subscription(&wait_set, &pong_subscription, &index_pong_subscription);
195+
196+
size_t index_ping_subscription;
197+
rcl_wait_set_add_subscription(&wait_set, &ping_subscription, &index_ping_subscription);
198+
199+
// Run session for 100 ms
200+
rcl_wait(&wait_set, RCL_MS_TO_NS(100));
201+
202+
// Check if it is time to send a ping
203+
if (iterations++ % 50 == 0) {
204+
// Generate a new random sequence number
205+
seq_no = rand();
206+
sprintf(msg.frame_id.data, "%d_%d", seq_no, device_id);
207+
msg.frame_id.size = strlen(msg.frame_id.data);
208+
209+
// Fill the message timestamp
210+
clock_gettime(CLOCK_REALTIME, &ts);
211+
msg.stamp.sec = ts.tv_sec;
212+
msg.stamp.nanosec = ts.tv_nsec;
213+
214+
// Reset the pong count and publish the ping message
215+
pong_count = 0;
216+
rcl_publish(&ping_publisher, (const void*)&msg, NULL);
217+
printf("Ping send seq %s\n", msg.frame_id.data);
218+
}
219+
220+
// Check if some pong message is received
221+
if (wait_set.subscriptions[index_pong_subscription]) {
222+
rc = rcl_take(wait_set.subscriptions[index_pong_subscription], &rcv_msg, NULL, NULL);
223+
224+
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) == 0) {
225+
pong_count++;
226+
printf("Pong for seq %s (%d)\n", rcv_msg.frame_id.data, pong_count);
227+
}
228+
}
229+
230+
// Check if some ping message is received and pong it
231+
if (wait_set.subscriptions[index_ping_subscription]) {
232+
rc = rcl_take(wait_set.subscriptions[index_ping_subscription], &rcv_msg, NULL, NULL);
233+
234+
// Dont pong my own pings
235+
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) != 0){
236+
printf("Ping received with seq %s. Answering.\n", rcv_msg.frame_id.data);
237+
rcl_publish(&pong_publisher, (const void*)&rcv_msg, NULL);
238+
}
239+
}
240+
241+
usleep(10000);
242+
} while (true);
243+
}
244+
```
245+
246+
Once the new folder is created, let's configure our new app with a serial transport on the USB:
247+
248+
```bash
249+
# Configure step
250+
ros2 run micro_ros_setup configure_firmware.sh my_brand_new_app --transport serial-usb
251+
```
252+
253+
When the configuring step ends, just build the firmware:
254+
255+
```bash
256+
# Build step
257+
ros2 run micro_ros_setup build_firmware.sh
258+
```
259+
260+
Once the build has successfully ended, let's power and connect the board. First, connect Olimex ARM-USB-TINY-H JTAG programmer to the board's JTAG port:
261+
262+
<img width="400" style="padding-right: 25px;" src="imgs/2.jpg">
263+
264+
Make sure that the board power supply jumper (PWR_SEL) is in the 3-4 position in order to power the board from the JTAG connector:
265+
266+
<img width="400" style="padding-right: 25px;" src="imgs/1.jpg">
267+
268+
You should see the red LED lighting. It is time to flash the board:
269+
270+
```bash
271+
# Flash step
272+
ros2 run micro_ros_setup flash_firmware.sh
273+
```
274+
## Running the micro-ROS app
275+
276+
The micro-ROS app is ready to connect to a micro-ROS-Agent and start talking with the rest of the ROS 2 world.
277+
278+
First of all, create and build a micro-ROS agent:
279+
280+
```bash
281+
# Download micro-ROS-Agent packages
282+
ros2 run micro_ros_setup create_agent_ws.sh
283+
284+
# Build micro-ROS-Agent packages, this may take a while.
285+
colcon build
286+
source install/local_setup.bash
287+
```
288+
289+
Then connect the Olimex development board to the computer using the USB OTG 2 connector (the miniUSB connector that is furthest from the Ethernet port).
290+
291+
***TIP:** Color codes are applicable to [this cable](https://www.olimex.com/Products/Components/Cables/USB-Serial-Cable/USB-Serial-Cable-F/). Make sure to match Olimex Rx with Cable Tx and vice-versa. Remember GND!*
292+
293+
Then run the agent:
294+
295+
```bash
296+
# Run a micro-ROS agent
297+
ros2 run micro_ros_agent micro_ros_agent serial --dev [device]
298+
```
299+
300+
***TIP:** you can use this command to find your serial device name: `ls /dev/serial/by-id/*`. Probably it will be something like `/dev/serial/by-id/usb-ZEPHYR_Zephyr_microROS_3536510100290035-if00`*
301+
302+
And finally, let's check that everything is working in another command line. We are going to listen to ping topic to check whether the Ping Pong node is publishing its own pings
303+
304+
```bash
305+
source /opt/ros/$ROS_DISTRO/setup.bash
306+
307+
# Subscribe to micro-ROS ping topic
308+
ros2 topic echo /microROS/ping
309+
```
310+
311+
You should see the topic messages published by the Ping Pong node every 5 seconds:
312+
313+
```
314+
user@user:~$ ros2 topic echo /microROS/ping
315+
stamp:
316+
sec: 20
317+
nanosec: 867000000
318+
frame_id: '1344887256_1085377743'
319+
---
320+
stamp:
321+
sec: 25
322+
nanosec: 942000000
323+
frame_id: '730417256_1085377743'
324+
---
325+
```
326+
327+
On another command line, let's subscribe to the pong topic
328+
329+
```bash
330+
source /opt/ros/$ROS_DISTRO/setup.bash
331+
332+
# Subscribe to micro-ROS pong topic
333+
ros2 topic echo /microROS/pong
334+
```
335+
336+
At this point, we know that our app is publishing pings. Let's check if it also answers to someone else pings in a new command line:
337+
338+
```bash
339+
source /opt/ros/$ROS_DISTRO/setup.bash
340+
341+
# Send a fake ping
342+
ros2 topic pub --once /microROS/ping std_msgs/msg/Header '{frame_id: "fake_ping"}'
343+
```
344+
345+
Now, we should see on the ping subscriber our fake ping along with the board pings:
346+
347+
```
348+
user@user:~$ ros2 topic echo /microROS/ping
349+
stamp:
350+
sec: 0
351+
nanosec: 0
352+
frame_id: fake_ping
353+
---
354+
stamp:
355+
sec: 305
356+
nanosec: 973000000
357+
frame_id: '451230256_1085377743'
358+
---
359+
stamp:
360+
sec: 310
361+
nanosec: 957000000
362+
frame_id: '2084670932_1085377743'
363+
---
364+
```
365+
366+
And in the pong subscriber, we should see the board's answer to our fake ping:
367+
368+
```
369+
user@user:~$ ros2 topic echo /microROS/pong
370+
stamp:
371+
sec: 0
372+
nanosec: 0
373+
frame_id: fake_ping
374+
---
375+
```

0 commit comments

Comments
 (0)