Skip to content

Commit 3329fa8

Browse files
authored
feat(cli): Add support for USB CDC to cli component, for use on ESP32-S2 for example (#564)
1 parent 1da640a commit 3329fa8

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

components/cli/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
idf_component_register(
22
INCLUDE_DIRS "include" "detail/cli/include"
33
SRC_DIRS "src"
4-
REQUIRES driver esp_driver_uart esp_driver_usb_serial_jtag vfs logger)
4+
REQUIRES driver esp_driver_uart esp_driver_usb_serial_jtag vfs esp_vfs_console logger)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# on the ESP32S2, which has native USB, we need to set the console so that the
2+
# CLI can be configured correctly:
3+
CONFIG_ESP_CONSOLE_USB_CDC=y

components/cli/include/cli.hpp

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@
1111
#include "driver/usb_serial_jtag_vfs.h"
1212
#include "esp_err.h"
1313
#include "esp_system.h"
14+
#include <fcntl.h>
1415

16+
#include "esp_vfs_cdcacm.h"
1517
#include "esp_vfs_dev.h"
1618
#include "esp_vfs_usb_serial_jtag.h"
1719

1820
#include "line_input.hpp"
1921

20-
#ifdef CONFIG_ESP_CONSOLE_USB_CDC
21-
#error The cli component is currently incompatible with CONFIG ESP_CONSOLE_USB_CDC console.
22-
#endif // CONFIG_ESP_CONSOLE_USB_CDC
23-
2422
#ifndef STRINGIFY
2523
#define STRINGIFY(s) STRINGIFY2(s)
2624
#define STRINGIFY2(s) #s
@@ -38,9 +36,9 @@ namespace espp {
3836
*
3937
* @note You should call configure_stdin_stdout() before creating a Cli object
4038
* to ensure that std::cin works as needed. If you do not want to use the
41-
* Cli over the ESP CONSOLE (e.g. the ESP's UART, USB Serial/JTAG) and
42-
* instead want to run it over a different UART port, VFS, or some other
43-
* configuration, then you should call one of
39+
* Cli over the ESP CONSOLE (e.g. the ESP's UART, USB Serial/JTAG, USB
40+
* CDC) and instead want to run it over a different UART port, VFS, or
41+
* some other configuration, then you should call one of
4442
* - configure_stdin_stdout_uart()
4543
* - configure_stdin_stdout_vfs()
4644
* - configure_stdin_stdout_custom()
@@ -58,6 +56,7 @@ class Cli : private cli::CliSession {
5856
* compiled to use. This will only work if the ESP_CONSOLE was
5957
* configured to use one of the following:
6058
* - CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
59+
* - CONFIG_ESP_CONSOLE_USB_CDC
6160
* - CONFIG_ESP_CONSOLE_UART
6261
*
6362
* If you want to use a different console, you should use one of the
@@ -77,6 +76,8 @@ class Cli : private cli::CliSession {
7776

7877
#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
7978
configure_stdin_stdout_usb_serial_jtag();
79+
#elif CONFIG_ESP_CONSOLE_USB_CDC
80+
configure_stdin_stdout_usb_cdc();
8081
#elif CONFIG_ESP_CONSOLE_UART
8182
configure_stdin_stdout_uart((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
8283
CONFIG_ESP_CONSOLE_UART_BAUDRATE);
@@ -182,6 +183,47 @@ class Cli : private cli::CliSession {
182183
configured_ = true;
183184
}
184185

186+
/**
187+
* @brief Configure the USB CDC driver to support blocking input read, so
188+
* that std::cin (which assumes a blocking read) will function. This
189+
* should be primarily used when you want to use the std::cin/std::getline
190+
* and other std input functions or you want to use the cli library.
191+
*/
192+
static void configure_stdin_stdout_usb_cdc(void) {
193+
if (configured_) {
194+
return;
195+
}
196+
197+
// drain stdout before reconfiguring it
198+
fflush(stdout);
199+
fsync(fileno(stdout));
200+
201+
const std::string_view dev_name = "/dev/cdcacm";
202+
203+
// redirect stdin, stdout, stderr to the USB CDC interface
204+
console_.in = freopen(dev_name.data(), "r", stdin);
205+
console_.out = freopen(dev_name.data(), "w", stdout);
206+
console_.err = freopen(dev_name.data(), "w", stderr);
207+
208+
esp_vfs_dev_cdcacm_register();
209+
210+
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
211+
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
212+
213+
// Enable blocking mode on stdin and stdout
214+
fcntl(fileno(stdout), F_SETFL, 0);
215+
fcntl(fileno(stdin), F_SETFL, 0);
216+
217+
// Initialize VFS & UART so we can use std::cout/cin
218+
// _IOFBF = full buffering
219+
// _IOLBF = line buffering
220+
// _IONBF = no buffering
221+
// disable buffering on stdin
222+
setvbuf(stdin, nullptr, _IONBF, 0);
223+
224+
configured_ = true;
225+
}
226+
185227
/**
186228
* @brief Configure stdin/stdout to use a custom VFS driver. This should be
187229
* used when you have a custom VFS driver that you want to use for
@@ -218,8 +260,6 @@ class Cli : private cli::CliSession {
218260
// Register the USB CDC interface
219261
[[maybe_unused]] auto err = esp_vfs_register(dev_name.data(), &vfs, NULL);
220262

221-
// TODO: this function is mostly untested, so we should probably add some
222-
// error handling here and store the resultant pointers for later use
223263
// redirect stdin, stdout, stderr to the USB CDC interface
224264
console_.in = freopen(dev_name.data(), "r", stdin);
225265
console_.out = freopen(dev_name.data(), "w", stdout);

components/cli/src/cli.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#include "cli.hpp"
22

33
bool espp::Cli::configured_ = false;
4+
espp::Cli::console_handle_t espp::Cli::console_ = {nullptr, nullptr, nullptr};

0 commit comments

Comments
 (0)