diff --git a/components/esp_modem/examples/modem_console/CMakeLists.txt b/components/esp_modem/examples/modem_console/CMakeLists.txt index 8df0278b8..73765204f 100644 --- a/components/esp_modem/examples/modem_console/CMakeLists.txt +++ b/components/esp_modem/examples/modem_console/CMakeLists.txt @@ -4,5 +4,13 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS "../..") +include($ENV{IDF_PATH}/tools/cmake/version.cmake) + +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "4.4") +if(("${IDF_TARGET}" STREQUAL "esp32s2") OR ("${IDF_TARGET}" STREQUAL "esp32s3")) + list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common") +endif() +endif() + include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(modem-console) diff --git a/components/esp_modem/examples/modem_console/README.md b/components/esp_modem/examples/modem_console/README.md index 66ade1809..890ebe049 100644 --- a/components/esp_modem/examples/modem_console/README.md +++ b/components/esp_modem/examples/modem_console/README.md @@ -1,7 +1,5 @@ # Modem console example -(See the README.md file in the upper level 'examples' directory for more information about examples.) - ## Overview This example is mainly targets experimenting with a modem device, sending custom commands and switching to PPP mode using esp-console, command line API. @@ -16,6 +14,16 @@ that sets up the DCE based on a custom module implemented in the `my_module_dce. `get_module_name()` method supplying a user defined name, but keeps all other commands the same as defined in the `GenericModule` class. +### USB DTE support + +For USB enabled targets (ESP32-S2 and ESP32-S3), it is possible to connect to the modem device via USB. +1. In menuconfig, navigate to `Example Configuration->Type of serial connection to the modem` and choose `USB`. +2. Connect the modem USB signals to pin 19 (DATA-) and 20 (DATA+) on your ESP chip. + +USB example uses Quactel BG96 modem device. + ### Supported IDF versions -This example is only supported from `v4.2`, due to support of the console repl mode. \ No newline at end of file +This example is only supported from `v4.2`, due to support of the console repl mode. + +USB example is supported from `v4.4`. \ No newline at end of file diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/CMakeLists.txt b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/CMakeLists.txt new file mode 100644 index 000000000..dbec05b31 --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/CMakeLists.txt @@ -0,0 +1,19 @@ +include($ENV{IDF_PATH}/tools/cmake/version.cmake) +idf_build_get_property(target IDF_TARGET) + +# USB is supported on S2 and S3 targets and IDF version >= 4.4 +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "4.4") +if(("${target}" STREQUAL "esp32s2") OR ("${target}" STREQUAL "esp32s3")) + +idf_component_register(SRCS "esp_modem_usb.cpp" "esp_modem_usb_api_target.cpp" + REQUIRES cdc_acm_host esp_modem + REQUIRED_IDF_TARGETS esp32s2 esp32s3 + PRIV_INCLUDE_DIRS "private_include" + INCLUDE_DIRS "include") + +set_target_properties(${COMPONENT_LIB} PROPERTIES + CXX_STANDARD 17 +) + +endif() +endif() \ No newline at end of file diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb.cpp b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb.cpp new file mode 100644 index 000000000..887aa5d98 --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb.cpp @@ -0,0 +1,191 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_modem_config.h" +#include "esp_modem_usb_config.h" +#include "cxx_include/esp_modem_dte.hpp" +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include "sdkconfig.h" +#include "usb_terminal.hpp" + +static const char *TAG = "usb_terminal"; + +/** + * @brief USB Host task + * + * This task is created only if install_usb_host is set to true in DTE configuration. + * In case the user doesn't want in install USB Host driver here, he must install it before creating UsbTerminal object. + * + * @param arg Unused + */ +void usb_host_task(void *arg) +{ + while (1) { + // Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + ESP_LOGD(TAG, "No more clients: clean up\n"); + usb_host_device_free_all(); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGD(TAG, "All free: uninstall USB lib\n"); + break; + } + } + + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + usb_host_lib_handle_events(0, NULL); // Make sure there are now pending events + usb_host_uninstall(); + vTaskDelete(NULL); +} + +namespace esp_modem { +class UsbTerminal : public Terminal, private CdcAcmDevice { +public: + explicit UsbTerminal(const esp_modem_dte_config *config) + { + const struct esp_modem_usb_term_config* usb_config = (struct esp_modem_usb_term_config*)(config->extension_config); + + // Install USB Host driver + if (usb_config->install_usb_host) { + const usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + throw_if_esp_fail(usb_host_install(&host_config), "USB Host install failed"); + ESP_LOGD(TAG, "USB Host installed"); + throw_if_false(pdTRUE == xTaskCreatePinnedToCore(usb_host_task, "usb_host", 4096, NULL, config->task_priority + 1, NULL, usb_config->xCoreID), "USB host task failed"); + } + + // Install CDC-ACM driver + const cdc_acm_host_driver_config_t esp_modem_cdc_acm_driver_config = { + .driver_task_stack_size = config->task_stack_size, + .driver_task_priority = config->task_priority, + .xCoreID = (BaseType_t)usb_config->xCoreID + }; + + // Silently continue of error: CDC-ACM driver might be already installed + cdc_acm_host_install(&esp_modem_cdc_acm_driver_config); + + // Open CDC-ACM device + const cdc_acm_host_device_config_t esp_modem_cdc_acm_device_config = { + .connection_timeout_ms = usb_config->timeout_ms, + .out_buffer_size = config->dte_buffer_size, + .event_cb = handle_notif, + .data_cb = handle_rx, + .user_arg = this + }; + + if (usb_config->cdc_compliant) { + throw_if_esp_fail(this->CdcAcmDevice::open(usb_config->vid, usb_config->pid, + usb_config->interface_idx, &esp_modem_cdc_acm_device_config), + "USB Device open failed"); + } else { + throw_if_esp_fail(this->CdcAcmDevice::open_vendor_specific(usb_config->vid, usb_config->pid, + usb_config->interface_idx, &esp_modem_cdc_acm_device_config), + "USB Device open failed"); + } + }; + + ~UsbTerminal() + { + this->CdcAcmDevice::close(); + }; + + void start() override + { + return; + } + + void stop() override + { + return; + } + + int write(uint8_t *data, size_t len) override + { + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG); + if (this->CdcAcmDevice::tx_blocking(data, len) != ESP_OK) { + return -1; + } + return len; + } + + int read(uint8_t *data, size_t len) override + { + // This function should never be called. UsbTerminal provides data through Terminal::on_read callback + ESP_LOGW(TAG, "Unexpected call to UsbTerminal::read function"); + return -1; + } + +private: + UsbTerminal() = delete; + UsbTerminal(const UsbTerminal ©) = delete; + UsbTerminal &operator=(const UsbTerminal ©) = delete; + bool operator== (const UsbTerminal ¶m) const = delete; + bool operator!= (const UsbTerminal ¶m) const = delete; + + static void handle_rx(uint8_t *data, size_t data_len, void *user_arg) + { + ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_DEBUG); + UsbTerminal *this_terminal = static_cast(user_arg); + if (data_len > 0 && this_terminal->on_read) { + this_terminal->on_read(data, data_len); + } else { + ESP_LOGD(TAG, "Unhandled RX data"); + } + } + + static void handle_notif(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx) + { + UsbTerminal *this_terminal = static_cast(user_ctx); + + switch (event->type) { + // Notifications like Ring, Rx Carrier indication or Network connection indication are not relevant for USB terminal + case CDC_ACM_HOST_NETWORK_CONNECTION: + case CDC_ACM_HOST_SERIAL_STATE: + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + ESP_LOGW(TAG, "USB terminal disconnected"); + cdc_acm_host_close(cdc_hdl); + if (this_terminal->on_error) { + this_terminal->on_error(terminal_error::UNEXPECTED_CONTROL_FLOW); + } + break; + case CDC_ACM_HOST_ERROR: + ESP_LOGE(TAG, "Unexpected CDC-ACM error: %d.", event->data.error); + if (this_terminal->on_error) { + this_terminal->on_error(terminal_error::UNEXPECTED_CONTROL_FLOW); + } + break; + default: + abort(); + } + }; +}; + +std::unique_ptr create_usb_terminal(const esp_modem_dte_config *config) +{ + TRY_CATCH_RET_NULL( + return std::make_unique(config); + ) +} +} // namespace esp_modem diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb_api_target.cpp b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb_api_target.cpp new file mode 100644 index 000000000..024269db7 --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/esp_modem_usb_api_target.cpp @@ -0,0 +1,32 @@ +// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include "usb_terminal.hpp" +#include "cxx_include/esp_modem_api.hpp" +#include "cxx_include/esp_modem_netif.hpp" + +#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS +static const char *TAG = "modem_usb_api_target"; +#endif + +namespace esp_modem { +std::shared_ptr create_usb_dte(const dte_config *config) +{ + TRY_CATCH_RET_NULL( + auto term = create_usb_terminal(config); + return std::make_shared(config, std::move(term)); + ) +} +} diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/cxx_include/esp_modem_usb_api.hpp b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/cxx_include/esp_modem_usb_api.hpp new file mode 100644 index 000000000..d7e24114e --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/cxx_include/esp_modem_usb_api.hpp @@ -0,0 +1,29 @@ +// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include "cxx_include/esp_modem_dce.hpp" +#include "cxx_include/esp_modem_dce_module.hpp" + +namespace esp_modem { +/** + * @brief Create USB DTE + * + * @param config DTE configuration + * @return shared ptr to DTE on success + * nullptr on failure (either due to insufficient memory or wrong dte configuration) + * if exceptions are disabled the API abort()'s on error + */ +std::shared_ptr create_usb_dte(const dte_config *config); +} diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/esp_modem_usb_config.h b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/esp_modem_usb_config.h new file mode 100644 index 000000000..b28e07ef3 --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/include/esp_modem_usb_config.h @@ -0,0 +1,63 @@ +// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include + +/** + * @brief USB configuration structure + * @see USB host CDC-ACM driver documentation for details about interfaces settings + */ +struct esp_modem_usb_term_config { + uint16_t vid; /*!< Vendor ID of the USB device */ + uint16_t pid; /*!< Product ID of the USB device */ + int interface_idx; /*!< USB Interface index that will be used */ + uint32_t timeout_ms; /*!< Time for a USB modem to connect to USB host. 0 means wait forever. */ + int xCoreID; /*!< Core affinity of created tasks: CDC-ACM driver task and optional USB Host task */ + bool cdc_compliant; /*!< Treat the USB device as CDC-compliant. Read CDC-ACM driver documentation for more details */ + bool install_usb_host; /*!< Flag whether USB Host driver should be installed */ +}; + +/** + * @brief ESP Mode USB DTE Default Configuration + * + * @param[in] _usb_config esp_modem_usb_term_config configuration structure. Can be obtained by ESP_MODEM_DEFAULT_USB_CONFIG + * + */ +#define ESP_MODEM_DTE_DEFAULT_USB_CONFIG(_usb_config) \ + { \ + .dte_buffer_size = 512, \ + .task_stack_size = 4096, \ + .task_priority = 5, \ + .extension_config = &_usb_config \ + } + +/** + * @brief ESP Modem USB Default Configuration + * + * @param[in] _vid USB Vendor ID + * @param[in] _pid USB Product ID + * @see USB host CDC-ACM driver documentation for details about interfaces settings + */ +#define ESP_MODEM_DEFAULT_USB_CONFIG(_vid, _pid) \ + { \ + .vid = _vid, \ + .pid = _pid, \ + .interface_idx = 0, \ + .timeout_ms = 0, \ + .xCoreID = 0, \ + .cdc_compliant = false, \ + .install_usb_host = true \ + } diff --git a/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/private_include/usb_terminal.hpp b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/private_include/usb_terminal.hpp new file mode 100644 index 000000000..68c70ff9d --- /dev/null +++ b/components/esp_modem/examples/modem_console/components/esp_modem_usb_dte/private_include/usb_terminal.hpp @@ -0,0 +1,51 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include "cxx_include/esp_modem_dte.hpp" +#include "sdkconfig.h" +#include "esp_log.h" + +struct esp_modem_dte_config; + +// Copy-pasted from esp_modem/private_include/exception_stub.hpp +#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS +#define TRY_CATCH_OR_DO(block, action) \ + try { block \ + } catch (std::bad_alloc& e) { \ + ESP_LOGE(TAG, "Out of memory"); \ + action; \ + } catch (::esp_modem::esp_err_exception& e) { \ + esp_err_t err = e.get_err_t(); \ + ESP_LOGE(TAG, "%s: Exception caught with ESP err_code=%d", __func__, err); \ + ESP_LOGE(TAG, "%s", e.what()); \ + action; \ + } + +#define TRY_CATCH_RET_NULL(block) TRY_CATCH_OR_DO(block, return nullptr) +#else + +#define TRY_CATCH_OR_DO(block, action) \ + block + +#define TRY_CATCH_RET_NULL(block) \ + block + +#endif + +namespace esp_modem { + +std::unique_ptr create_usb_terminal(const esp_modem_dte_config *config); + +} // namespace esp_modem diff --git a/components/esp_modem/examples/modem_console/main/CMakeLists.txt b/components/esp_modem/examples/modem_console/main/CMakeLists.txt index ae7fd98d4..9379ec748 100644 --- a/components/esp_modem/examples/modem_console/main/CMakeLists.txt +++ b/components/esp_modem/examples/modem_console/main/CMakeLists.txt @@ -2,4 +2,5 @@ idf_component_register(SRCS "modem_console_main.cpp" "console_helper.cpp" "httpget_handle.c" "ping_handle.c" + REQUIRES console esp_http_client nvs_flash esp_modem esp_modem_usb_dte INCLUDE_DIRS ".") diff --git a/components/esp_modem/examples/modem_console/main/Kconfig.projbuild b/components/esp_modem/examples/modem_console/main/Kconfig.projbuild new file mode 100644 index 000000000..14a070a86 --- /dev/null +++ b/components/esp_modem/examples/modem_console/main/Kconfig.projbuild @@ -0,0 +1,17 @@ + +menu "Example Configuration" + + choice EXAMPLE_SERIAL_CONFIG + prompt "Type of serial connection to the modem" + default EXAMPLE_SERIAL_CONFIG_UART + config EXAMPLE_SERIAL_CONFIG_UART + bool "UART" + help + Connect to modem via UART. + config EXAMPLE_SERIAL_CONFIG_USB + bool "USB" + depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + help + Connect to modem via USB (CDC-ACM class). For IDF version >= 4.4. + endchoice +endmenu diff --git a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp index eb9486f6f..fd68dc5b7 100644 --- a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp +++ b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp @@ -18,6 +18,10 @@ #include "cxx_include/esp_modem_dte.hpp" #include "esp_modem_config.h" #include "cxx_include/esp_modem_api.hpp" +#if defined(CONFIG_USB_OTG_SUPPORTED) +#include "esp_modem_usb_config.h" +#include "cxx_include/esp_modem_usb_api.hpp" +#endif #include "esp_log.h" #include "console_helper.hpp" #include "my_module_dce.hpp" @@ -53,13 +57,29 @@ extern "C" void app_main(void) ESP_ERROR_CHECK(esp_event_loop_create_default()); // init the netif, DTE and DCE respectively - esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); + esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(DEFAULT_APN); esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP(); esp_netif_t *esp_netif = esp_netif_new(&ppp_netif_config); assert(esp_netif); + +#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_UART) + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); auto uart_dte = create_uart_dte(&dte_config); - esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(DEFAULT_APN); auto dce = create_shiny_dce(&dce_config, uart_dte, esp_netif); + +#elif defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB) + struct esp_modem_usb_term_config usb_config = ESP_MODEM_DEFAULT_USB_CONFIG(0x2C7C, 0x0296); // VID and PID of BG96 modem + // BG96 modem implements Vendor Specific class, that is CDC-ACM like. Interface for AT commands has index no. 2. + usb_config.interface_idx = 2; + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_USB_CONFIG(usb_config); + ESP_LOGI(TAG, "Waiting for USB device connection..."); + auto dte = create_usb_dte(&dte_config); + std::unique_ptr dce = create_BG96_dce(&dce_config, dte, esp_netif); + +#else +#error Invalid serial connection to modem. +#endif + assert(dce != nullptr); // init console REPL environment diff --git a/components/esp_modem/idf_component.yml b/components/esp_modem/idf_component.yml index ea2271424..ae252eb5f 100644 --- a/components/esp_modem/idf_component.yml +++ b/components/esp_modem/idf_component.yml @@ -1,4 +1,4 @@ -version: "0.1.15" +version: "0.1.16" description: esp modem url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem dependencies: diff --git a/components/esp_modem/include/esp_modem_config.h b/components/esp_modem/include/esp_modem_config.h index a33733324..5da3966ed 100644 --- a/components/esp_modem/include/esp_modem_config.h +++ b/components/esp_modem/include/esp_modem_config.h @@ -83,10 +83,11 @@ struct esp_modem_vfs_term_config { struct esp_modem_dte_config { size_t dte_buffer_size; /*!< DTE buffer size */ uint32_t task_stack_size; /*!< Terminal task stack size */ - int task_priority; /*!< Terminal task priority */ + unsigned task_priority; /*!< Terminal task priority */ union { struct esp_modem_uart_term_config uart_config; /*!< Configuration for UART Terminal */ struct esp_modem_vfs_term_config vfs_config; /*!< Configuration for VFS Terminal */ + void *extension_config; /*!< Configuration for app specific Terminal */ }; };