Skip to content

Commit

Permalink
Add int32_publisher_custom_transport_usbcdc example (#255) (#262)
Browse files Browse the repository at this point in the history
* Add int32_publisher_custom_transport_usbcdc example

This example demonstrates how to enable ROS2 communication via USB CDC and log output on the ESP32-S2.

Reviewed-by: Leandro Mendes do Santos <[email protected]>

Signed-off-by: Samuel Henrique Guimarães Alencar <[email protected]>

* Add build for int32_publisher_custom_transport_usbcdc example to the CI workflow

Signed-off-by: Samuel Henrique Guimarães Alencar <[email protected]>

* Fix CI condition for int32_publisher_custom_transport_usbcdc example

- Updated conditional logic to ensure builds are triggered only when idf_target is either 'esp32s2' or 'esp32s3' and idf_version is 'espressif/idf:release-v5.2'.

Signed-off-by: Samuel Henrique Guimarães Alencar <[email protected]>

---------

Signed-off-by: Samuel Henrique Guimarães Alencar <[email protected]>
(cherry picked from commit c2d581a)

Co-authored-by: Samuel Henrique <[email protected]>
  • Loading branch information
mergify[bot] and SamuelLost authored Oct 2, 2024
1 parent 0e2660b commit 2f5c210
Show file tree
Hide file tree
Showing 19 changed files with 563 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ jobs:
idf.py set-target ${{ matrix.idf_target }}
idf.py build
- name: Build sample - int32_publisher_custom_transport_usbcdc
shell: bash
if: (matrix.idf_target == 'esp32s2' || matrix.idf_target == 'esp32s3') && matrix.idf_version == 'espressif/idf:release-v5.2'
run: |
. $IDF_PATH/export.sh
cd micro_ros_espidf_component/examples/int32_publisher_custom_transport_usbcdc
idf.py set-target ${{ matrix.idf_target }}
idf.py build
- name: Build sample - multithread_publisher
shell: bash
run: |
Expand Down
5 changes: 5 additions & 0 deletions examples/int32_publisher_custom_transport_usbcdc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build
sdkconfig
sdkconfig.old
managed_components
.vscode
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.5)

set (EXTRA_COMPONENT_DIRS "./../../.")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(int32_publisher)

138 changes: 138 additions & 0 deletions examples/int32_publisher_custom_transport_usbcdc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

# USB-CDC Custom Transport Example

| Supported Targets | ESP32-S2 | ESP32-S3 |
|-------------------|----------|----------|

This example demonstrates how to set up the ESP32-S2/S3 to function as a USB Serial Device (CDC-ACM) and communicate with micro-ROS agent using USB-CDC custom transport.

The [TinyUSB component](https://components.espressif.com/components/espressif/esp_tinyusb) is used as the USB stack.

This example is based on the [int32_publisher_custom_transport](https://github.com/micro-ROS/micro_ros_espidf_component/tree/jazzy/examples/int32_publisher_custom_transport), the [TinyUSB Serial Device Example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device/tusb_serial_device), and the [TinyUSB Console Example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device/tusb_console) for log output.

## How to use example

This example is configured to use the two interfaces of USB-CDC. One interface is used for the micro-ROS communication, and the other interface is used for the log output.

### Hardware Required

This example can be run on any development board that has a USB-CDC interface.

### Configure the project

Set the target device in the project configuration:

```bash
idf.py set-target esp32s2 # or esp32s3
```

If you want to use only the micro-ROS communication interface, you need to turn off log output in menuconfig. Run `idf.py menuconfig` and navigate to `Component config → Log output → Default log verbosity` and set it to `No output`. You should also set `Component config → TinyUSB Stack → Communication Device Class (CDC) → CDC Channel Count` to 1.

### Build and Flash

> [!NOTE]
> The ESP32-S2/S3 chip needs to be in bootloader mode before it can be detected as a DFU device and flash. This can be achieved by pulling GPIO0 down (e.g., pressing the BOOT button), pulling RESET down for a moment, and releasing GPIO0.
#### Build the project

Build DFU image:

```bash
idf.py dfu
```

#### Flash the project

Put the ESP32-S2/S3 into bootloader mode and run the following command:

```bash
idf.py dfu-flash
```

### Run micro-ROS Agent

```bash
export ROS_DOMAIN_ID=100 # Set the ROS2 domain ID
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0
```

Output expected:

```bash
[1724443525.673894] info | TermiosAgentLinux.cpp | init | running... | fd: 3
[1724443525.674071] info | Root.cpp | set_verbose_level | logger setup | verbose_level: 4
[1724443529.936542] info | TermiosAgentLinux.cpp | init | running... | fd: 3
[1724443531.062646] info | Root.cpp | create_client | create | client_key: 0x3E801A05, session_id: 0x81
[1724443531.062805] info | SessionManager.hpp | establish_session | session established | client_key: 0x3E801A05, address: 0
[1724443531.107532] info | ProxyClient.cpp | create_participant | participant created | client_key: 0x3E801A05, participant_id: 0x000(1)
[1724443531.137064] info | ProxyClient.cpp | create_topic | topic created | client_key: 0x3E801A05, topic_id: 0x000(2), participant_id: 0x000(1)
[1724443531.167351] info | ProxyClient.cpp | create_publisher | publisher created | client_key: 0x3E801A05, publisher_id: 0x000(3), participant_id: 0x000(1)
[1724443531.237811] info | ProxyClient.cpp | create_datawriter | datawriter created | client_key: 0x3E801A05, datawriter_id: 0x000(5), publisher_id: 0x000(3)
```

After connecting the ESP32-S2/S3 and the micro-ROS agent, you can list the topics:

```bash
export ROS_DOMAIN_ID=100 # Set the ROS2 domain ID
ros2 topic list
```

Output expected:

```bash
/esp32s2/int32_publisher_usbcdc
/parameter_events
/rosout
```

And see if the `esp32s2/int32_publisher_usbcdc` topic is available. You can echo the topic to see the messages:

```bash
export ROS_DOMAIN_ID=100 # Set the ROS2 domain ID
ros2 topic echo esp32s2/int32_publisher_usbcdc
```

Output expected:

```bash
data: 1
---
data: 2
---
data: 3
---
data: 4
---
data: 5
.
.
.
```

To see the log output, you can use the following command:

```bash
minicom -D /dev/ttyACM1 -b 115200
```

Output expected:

```bash
Welcome to minicom 2.8

OPTIONS: I18n
Port /dev/ttyACM1
Press CTRL-A Z for help on special keys

I (2688) MAIN: micro-ROS task created
I (2688) main_task: Returned from app_main()
I (3708) TIMER_CALLBACK: Message published: 0
I (4708) TIMER_CALLBACK: Message published: 1
I (5708) TIMER_CALLBACK: Message published: 2
I (6708) TIMER_CALLBACK: Message published: 3
I (7708) TIMER_CALLBACK: Message published: 4
I (8708) TIMER_CALLBACK: Message published: 5
.
.
.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"names": {
"rmw_microxrcedds": {
"cmake-args": [
"-DRMW_UXRCE_TRANSPORT=custom"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
idf_component_register(SRCS "esp32s2_usbcdc_logging.c" INCLUDE_DIRS ".")
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "esp32s2_usbcdc_logging.h"

// Initialize USB-CDC logging
esp_err_t esp32s2_usbcdc_logging_init(void)
{
const tinyusb_config_t tinyusb_config = {
.descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
.configuration_descriptor = NULL,
};

esp_err_t ret = tinyusb_driver_install(&tinyusb_config);

if (ret == ESP_ERR_INVALID_ARG || ret == ESP_FAIL) {
return ret;
}

tinyusb_config_cdcacm_t acm_config = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_1,
.rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL,
};

ret = tusb_cdc_acm_init(&acm_config);

if (ret != ESP_OK) {
return ret;
}

ret = esp_tusb_init_console(TINYUSB_CDC_ACM_1);

return ret;
}

// Deinitialize USB-CDC logging
esp_err_t esp32s2_usbcdc_logging_deinit(void)
{
esp_err_t ret = esp_tusb_deinit_console(TINYUSB_CDC_ACM_1);

return ret;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef ESP32S2_USBCDC_LOGGING_H
#define ESP32S2_USBCDC_LOGGING_H

#include "esp_err.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"
#include "sdkconfig.h"

#if (CONFIG_TINYUSB_CDC_COUNT < 2)
#warning "Define CONFIG_TINYUSB_CDC_COUNT to 2 in menuconfig if you want log over USBCDC. Otherwise, disable log output in menuconfig."
#endif

#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)

#ifdef __cplusplus
extern "C"
{
#endif

esp_err_t esp32s2_usbcdc_logging_init(void);
esp_err_t esp32s2_usbcdc_logging_deinit(void);

#ifdef __cplusplus
}
#endif

#else
#error "Logging over USB-CDC is only supported on ESP32-S2 or ESP32-S3 targets"
#endif

#endif // ESP32S2_USBCDC_LOGGING_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "^1.4.4"
## Required IDF version
idf:
version: ">=5.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(SRCS "esp32s2_usbcdc_transport.c"
INCLUDE_DIRS "."
REQUIRES micro_ros_espidf_component # include <uxr/client/transport.h>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "esp32s2_usbcdc_transport.h"

// Open USB-CDC
bool esp32s2_usbcdc_open(struct uxrCustomTransport* transport) {
const tinyusb_config_t tinyusb_config = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
.configuration_descriptor = NULL,
};

esp_err_t ret = tinyusb_driver_install(&tinyusb_config);

if (ret == ESP_ERR_INVALID_ARG || ret == ESP_FAIL) {
return ret;
}

tinyusb_cdcacm_itf_t* cdc_port = (tinyusb_cdcacm_itf_t*)transport->args;

tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = *cdc_port,
.rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};

if (tusb_cdc_acm_init(&acm_cfg) != ESP_OK) {
return false;
}

return true;
}

// Close USB-CDC
bool esp32s2_usbcdc_close(struct uxrCustomTransport* transport) {
tinyusb_cdcacm_itf_t* cdc_port = (tinyusb_cdcacm_itf_t*)transport->args;
return (tusb_cdc_acm_deinit(*cdc_port) == ESP_OK) ? true : false;
}

// Write to USB-CDC
size_t esp32s2_usbcdc_write(struct uxrCustomTransport* transport, const uint8_t* buf, size_t len, uint8_t* err) {
tinyusb_cdcacm_itf_t* cdc_port = (tinyusb_cdcacm_itf_t*)transport->args;
size_t tx_size = tinyusb_cdcacm_write_queue(*cdc_port, buf, len);
tinyusb_cdcacm_write_flush(*cdc_port, 0);
return tx_size;
}

// Read from USB-CDC
size_t esp32s2_usbcdc_read(struct uxrCustomTransport* transport, uint8_t* buf, size_t len, int timeout, uint8_t* err) {
tinyusb_cdcacm_itf_t* cdc_port = (tinyusb_cdcacm_itf_t*)transport->args;
size_t rx_size = 0;
esp_err_t ret = tinyusb_cdcacm_read(*cdc_port, buf, len, &rx_size);
return (ret == ESP_OK) ? rx_size : 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef ESP32S2_USBCDC_TRANSPORT_H
#define ESP32S2_USBCDC_TRANSPORT_H

#include <uxr/client/transport.h>

#include "tinyusb.h"
#include "tusb_cdc_acm.h"

#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)

#ifdef __cplusplus
extern "C"
{
#endif

bool esp32s2_usbcdc_open(struct uxrCustomTransport* transport);
bool esp32s2_usbcdc_close(struct uxrCustomTransport* transport);
size_t esp32s2_usbcdc_write(struct uxrCustomTransport* transport, const uint8_t* buf, size_t len, uint8_t* err);
size_t esp32s2_usbcdc_read(struct uxrCustomTransport* transport, uint8_t* buf, size_t len, int timeout, uint8_t* err);

#ifdef __cplusplus
}
#endif

#else
#error "This transport is only supported on ESP32-S2 or ESP32-S3 targets"
#endif // CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3

#endif // ESP32S2_USBCDC_TRANSPORT_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "^1.4.4"
## Required IDF version
idf:
version: ">=5.0"
21 changes: 21 additions & 0 deletions examples/int32_publisher_custom_transport_usbcdc/dependencies.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
dependencies:
espressif/esp_tinyusb:
component_hash: f151d680d6847bfcfd5d8eb6d1c3ff926c208e6b963b2e83643a141bc70baa15
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.4.4
espressif/tinyusb:
component_hash: 214989d502fc168241a4a4f83b097d8ac44a93cd6f1787b4ac10069a8b3bebd3
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.15.0~10
idf:
component_hash: null
source:
type: idf
version: 5.1.2
manifest_hash: 7f60fd8da9b1e2b73aafbc324107a75ce3928aa50666473c15511b764484240c
target: esp32s2
version: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
idf_component_register(SRCS main.c INCLUDE_DIRS ".")
Loading

0 comments on commit 2f5c210

Please sign in to comment.