Skip to content

Commit cf007c9

Browse files
committed
feat!: add usb_cdc example
1 parent a9841c3 commit cf007c9

12 files changed

+410
-10
lines changed

.build-test-rules.yml

+4
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ examples/loopback:
66
examples/uart:
77
enable:
88
- if: IDF_TARGET in ["esp32s3", "esp32c6"]
9+
10+
examples/usb_cdc:
11+
enable:
12+
- if: IDF_TARGET in ["esp32s3", "esp32p4"]

API.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
| Type | Name |
1616
| ---: | :--- |
17-
| enum | [**rfc2217\_control\_t**](#enum-rfc2217_control_t) <br>_RFC2217 control signal definitions._ |
17+
| enum | [**rfc2217\_control\_t**](#enum-rfc2217_control_t) <br>_RFC2217 control signal definitions FIXME: split this into separate enums and callbacks._ |
1818
| typedef unsigned(\* | [**rfc2217\_on\_baudrate\_t**](#typedef-rfc2217_on_baudrate_t) <br>_baudrate change request callback_ |
1919
| typedef void(\* | [**rfc2217\_on\_client\_connected\_t**](#typedef-rfc2217_on_client_connected_t) <br>_callback on client connection_ |
2020
| typedef void(\* | [**rfc2217\_on\_client\_disconnected\_t**](#typedef-rfc2217_on_client_disconnected_t) <br>_callback on client disconnection_ |
@@ -40,13 +40,18 @@
4040

4141
### enum `rfc2217_control_t`
4242

43-
_RFC2217 control signal definitions._
43+
_RFC2217 control signal definitions FIXME: split this into separate enums and callbacks._
4444
```c
4545
enum rfc2217_control_t {
46-
RFC2217_CONTROL_NONE = 0,
47-
RFC2217_CONTROL_RTS = 1,
48-
RFC2217_CONTROL_DTR = 2,
49-
RFC2217_CONTROL_RTS_DTR = 3
46+
RFC2217_CONTROL_SET_NO_FLOW_CONTROL = 1,
47+
RFC2217_CONTROL_SET_XON_XOFF_FLOW_CONTROL = 2,
48+
RFC2217_CONTROL_SET_HARDWARE_FLOW_CONTROL = 3,
49+
RFC2217_CONTROL_SET_BREAK = 5,
50+
RFC2217_CONTROL_CLEAR_BREAK = 6,
51+
RFC2217_CONTROL_SET_DTR = 8,
52+
RFC2217_CONTROL_CLEAR_DTR = 9,
53+
RFC2217_CONTROL_SET_RTS = 11,
54+
RFC2217_CONTROL_CLEAR_RTS = 12
5055
};
5156
```
5257

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ With this library, you can build a "remote serial port" type of device using an
1010

1111
- `loopback` example sets up an RFC2217 server and echoes back any data received.
1212
- `uart` is an example of an RFC2217-to-UART bridge.
13+
- `usb_cdc` is an example of an RFC2217-to-USB-CDC bridge.
1314

1415
## Using the component
1516

examples/usb_cdc/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
set(COMPONENTS main)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
7+
project(rfc2217-server-usb-cdc)
8+

examples/usb_cdc/README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# RFC2217 USB CDC Example
2+
3+
This example demonstrates a bridge between an RFC2217 server and a USB CDC device. ESP chip acts as a USB Host and an RFC2217 server at the same time. Data received from the network using RFC2217 protocol is sent to the USB CDC device, and data received from the USB CDC device is sent to the network using RFC2217 protocol. With this example, you can get remote access to a USB CDC device over the network.
4+
5+
The example can be used with any ESP chip with USB_OTG peripheral (currently ESP32-S2, ESP32-S3, ESP32-P4). The development board needs to have a USB host port to which a USB CDC device (e.g. a USB-to-UART adapter) can be connected.
6+
7+
Network connection is achieved using `protocols_examples_common` component, which provides a simple API for connecting to Wi-Fi or Ethernet. You can configure the connection method and the credentials in menuconfig.
8+
9+
## How to Use the Example
10+
11+
Build and flash the example as usual. For example, when using an ESP32-P4 chip:
12+
13+
```shell
14+
idf.py set-target esp32p4
15+
idf.py flash monitor
16+
```
17+
18+
Note the IP address printed on the console when the example runs. You will need it to connect to the device. For example:
19+
```
20+
I (4547) example_common: Connected to example_netif_sta
21+
I (4557) example_common: - IPv4 address: 192.168.0.196,
22+
```
23+
24+
After flashing, the device will start an RFC2217 server. You can connect to it using an RFC2217 client. Since you have IDF already installed, the easiest way is to use `miniterm` from pySerial (replace the IP address with the one you noted earlier):
25+
26+
```shell
27+
python -m serial.tools.miniterm rfc2217://192.168.0.180:3333 115200
28+
```
29+
30+
Plug the USB-to-serial adapter into the USB host port of the ESP board. On ESP32-P4-Function-EVB, the USB A port is the USB host port. On other boards, you may need to wire up the port yourself. Check ESP-IDF USB Host examples for more information.
31+
32+
Open another terminal and connect to the USB-to-serial adapter using `miniterm` or any other terminal emulator. For example, if the USB-to-serial adapter is connected to `/dev/ttyUSB1`:
33+
```shell
34+
python -m serial.tools.miniterm /dev/ttyUSB1 115200
35+
```
36+
37+
Characters typed into the first miniterm window will be sent to the server, forwarded to USB CDC, and will appear in the second miniterm window. Same goes for the opposite direction.
38+
39+
The example doesn't echo the typed characters to the console of the ESP chip (UART0 or USB_SERIAL_JTAG), but you can modify the code to do that if needed.
40+
41+
To exit miniterm, press `Ctrl+]`.
42+
43+
## Example output
44+
45+
```
46+
I (3156) example_common: Connected to example_netif_eth
47+
I (3156) example_common: - IPv4 address: 192.168.0.125,
48+
I (3156) example_common: - IPv6 address: fe80:0000:0000:0000:6255:f9ff:fef9:045d, type: ESP_IP6_ADDR_IS_LINK_LOCAL
49+
I (3156) VCP example: Installing USB Host
50+
I (3186) VCP example: Installing CDC-ACM driver
51+
I (3186) app_main: Starting RFC2217 server on port 3333
52+
I (3186) VCP example: Opening VCP device...
53+
I (3836) VCP example: Setting up line coding
54+
I (3836) app_main: USB Device connected
55+
I (6596) rfc2217_server: TCP receive thread started, socket: 55
56+
I (6596) app_main: RFC2217 client connected
57+
I (6646) VCP example: Setting baud rate to 115200
58+
I (12996) rfc2217_server: Connection closed
59+
I (12996) app_main: RFC2217 client disconnected
60+
I (12996) rfc2217_server: TCP receive thread done
61+
```

examples/usb_cdc/main/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
idf_component_register(
2+
SRCS "usb_cdc_example_main.c" "cdc_wrapper.cpp"
3+
PRIV_INCLUDE_DIRS "."
4+
PRIV_REQUIRES esp_system lwip nvs_flash esp_netif esp_event)

examples/usb_cdc/main/cdc_wrapper.cpp

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
4+
#include "esp_err.h"
5+
#include "esp_log.h"
6+
#include "esp_check.h"
7+
#include "freertos/FreeRTOS.h"
8+
#include "freertos/task.h"
9+
#include "freertos/semphr.h"
10+
11+
#include "usb/cdc_acm_host.h"
12+
#include "usb/vcp_ch34x.hpp"
13+
#include "usb/vcp_cp210x.hpp"
14+
#include "usb/vcp_ftdi.hpp"
15+
#include "usb/vcp.hpp"
16+
#include "usb/usb_host.h"
17+
#include "cdc_wrapper.h"
18+
19+
using namespace esp_usb;
20+
21+
static const char *TAG = "VCP example";
22+
static usb_cdc_wrapper_on_data_t s_on_data;
23+
static volatile bool s_device_connected;
24+
static SemaphoreHandle_t s_device_disconnected_sem;
25+
static std::unique_ptr<CdcAcmDevice> s_vcp;
26+
27+
static bool handle_rx(const uint8_t *data, size_t data_len, void *arg)
28+
{
29+
if (s_on_data) {
30+
s_on_data(data, data_len);
31+
}
32+
return true;
33+
}
34+
35+
static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
36+
{
37+
switch (event->type) {
38+
case CDC_ACM_HOST_ERROR:
39+
ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %d", event->data.error);
40+
break;
41+
case CDC_ACM_HOST_DEVICE_DISCONNECTED:
42+
ESP_LOGI(TAG, "Device suddenly disconnected");
43+
xSemaphoreGive(s_device_disconnected_sem);
44+
s_device_connected = false;
45+
break;
46+
case CDC_ACM_HOST_SERIAL_STATE:
47+
ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val);
48+
break;
49+
case CDC_ACM_HOST_NETWORK_CONNECTION:
50+
default: break;
51+
}
52+
}
53+
54+
static void usb_lib_task(void *arg)
55+
{
56+
while (1) {
57+
uint32_t event_flags;
58+
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
59+
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
60+
ESP_ERROR_CHECK(usb_host_device_free_all());
61+
}
62+
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
63+
ESP_LOGI(TAG, "USB: All devices freed");
64+
}
65+
}
66+
}
67+
68+
extern "C" esp_err_t usb_cdc_wrapper_init(usb_cdc_wrapper_on_data_t on_data)
69+
{
70+
s_on_data = on_data;
71+
s_device_connected = false;
72+
s_device_disconnected_sem = xSemaphoreCreateBinary();
73+
ESP_LOGI(TAG, "Installing USB Host");
74+
usb_host_config_t host_config = {};
75+
host_config.skip_phy_setup = false;
76+
host_config.intr_flags = ESP_INTR_FLAG_LEVEL1;
77+
ESP_RETURN_ON_ERROR(usb_host_install(&host_config), TAG, "usb_host_install failed");
78+
79+
BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, NULL);
80+
ESP_RETURN_ON_FALSE(task_created, ESP_ERR_NO_MEM, TAG, "xTaskCreate failed");
81+
82+
ESP_LOGI(TAG, "Installing CDC-ACM driver");
83+
ESP_RETURN_ON_ERROR(cdc_acm_host_install(NULL), TAG, "cdc_acm_host_install failed");
84+
85+
VCP::register_driver<FT23x>();
86+
VCP::register_driver<CP210x>();
87+
VCP::register_driver<CH34x>();
88+
89+
return ESP_OK;
90+
}
91+
92+
extern "C" esp_err_t usb_cdc_wrapper_wait_for_device_connected(void)
93+
{
94+
s_vcp.reset();
95+
const cdc_acm_host_device_config_t dev_config = {
96+
.connection_timeout_ms = 5000,
97+
.out_buffer_size = 512,
98+
.in_buffer_size = 512,
99+
.event_cb = handle_event,
100+
.data_cb = handle_rx,
101+
.user_arg = NULL,
102+
};
103+
ESP_LOGI(TAG, "Opening VCP device...");
104+
s_vcp = std::unique_ptr<CdcAcmDevice>(VCP::open(&dev_config));
105+
106+
if (s_vcp == nullptr) {
107+
ESP_LOGI(TAG, "Failed to open VCP device");
108+
return ESP_FAIL;
109+
}
110+
vTaskDelay(10);
111+
112+
ESP_LOGI(TAG, "Setting up line coding");
113+
cdc_acm_line_coding_t line_coding = {
114+
.dwDTERate = 115200,
115+
.bCharFormat = 0, // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits
116+
.bParityType = 0, // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
117+
.bDataBits = 8,
118+
};
119+
ESP_RETURN_ON_ERROR(s_vcp->line_coding_set(&line_coding), TAG, "line_coding_set failed");
120+
s_device_connected = true;
121+
return ESP_OK;
122+
}
123+
124+
esp_err_t usb_cdc_wrapper_wait_for_device_disconnected(void)
125+
{
126+
xSemaphoreTake(s_device_disconnected_sem, portMAX_DELAY);
127+
return ESP_OK;
128+
}
129+
130+
extern "C" esp_err_t usb_cdc_wrapper_set_baudrate(unsigned baudrate)
131+
{
132+
if (!s_device_connected) {
133+
return ESP_ERR_INVALID_STATE;
134+
}
135+
ESP_LOGI(TAG, "Setting baud rate to %u", baudrate);
136+
cdc_acm_line_coding_t line_coding = {
137+
.dwDTERate = baudrate,
138+
.bCharFormat = 0, // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits
139+
.bParityType = 0, // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
140+
.bDataBits = 8,
141+
};
142+
ESP_RETURN_ON_ERROR(s_vcp->line_coding_set(&line_coding), TAG, "line_coding_set failed");
143+
return ESP_OK;
144+
}
145+
146+
extern "C" esp_err_t usb_cdc_wrapper_send_data(const uint8_t *data, size_t len)
147+
{
148+
if (!s_device_connected) {
149+
return ESP_ERR_INVALID_STATE;
150+
}
151+
ESP_RETURN_ON_ERROR(s_vcp->tx_blocking((uint8_t *) data, len), TAG, "tx_blocking failed");
152+
return ESP_OK;
153+
}
154+
155+
extern "C" esp_err_t usb_cdc_wrapper_set_line_control(bool dtr, bool rts)
156+
{
157+
if (!s_device_connected) {
158+
return ESP_ERR_INVALID_STATE;
159+
}
160+
ESP_RETURN_ON_ERROR(s_vcp->set_control_line_state(dtr, rts), TAG, "set_control_line_state failed");
161+
return ESP_OK;
162+
}

examples/usb_cdc/main/cdc_wrapper.h

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include <stdbool.h>
4+
#include "esp_err.h"
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
typedef void (*usb_cdc_wrapper_on_data_t)(const uint8_t *data, size_t len);
11+
12+
esp_err_t usb_cdc_wrapper_init(usb_cdc_wrapper_on_data_t on_data);
13+
esp_err_t usb_cdc_wrapper_wait_for_device_connected(void);
14+
esp_err_t usb_cdc_wrapper_wait_for_device_disconnected(void);
15+
esp_err_t usb_cdc_wrapper_set_baudrate(unsigned baudrate);
16+
esp_err_t usb_cdc_wrapper_set_line_control(bool dtr, bool rts);
17+
esp_err_t usb_cdc_wrapper_send_data(const uint8_t *data, size_t len);
18+
19+
20+
#ifdef __cplusplus
21+
}
22+
#endif
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
dependencies:
2+
igrr/rfc2217-server:
3+
version: "*"
4+
override_path: ../../../
5+
protocol_examples_common:
6+
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
7+
usb_host_ch34x_vcp: "^2"
8+
usb_host_cp210x_vcp: "^2"
9+
usb_host_ftdi_vcp: "^2"
10+
usb_host_vcp: "^1"

0 commit comments

Comments
 (0)