Merge pull request #89 from tore-espressif/feature/esp_modem_usb

Use USB CDC driver from component registry (IDFGH-7816)
This commit is contained in:
david-cermak
2022-10-05 11:58:43 +02:00
committed by GitHub
22 changed files with 203 additions and 438 deletions

View File

@ -35,6 +35,28 @@ jobs:
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/examples/${{ matrix.example }} cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/examples/${{ matrix.example }}
idf.py build idf.py build
build_esp_modem_usb:
strategy:
matrix:
idf_ver: ["latest", "release-v4.4", "release-v5.0"]
example: ["modem_console", "pppos_client"]
idf_target: ["esp32s2", "esp32s3"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
working-directory: components/esp_modem/examples/${{ matrix.example }}
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
cat sdkconfig.ci.usb >> sdkconfig.defaults
idf.py build
build_mdns: build_mdns:
strategy: strategy:
matrix: matrix:

View File

@ -91,3 +91,6 @@ dependencies.lock
# ignore generated docs # ignore generated docs
docs/html docs/html
# esp-idf managed components
**/managed_components/**

View File

@ -2,15 +2,5 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modem-console) project(modem-console)

View File

@ -20,7 +20,11 @@ For USB enabled targets (ESP32-S2 and ESP32-S3), it is possible to connect to th
1. In menuconfig, navigate to `Example Configuration->Type of serial connection to the modem` and choose `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. 2. Connect the modem USB signals to pin 19 (DATA-) and 20 (DATA+) on your ESP chip.
USB example uses Quactel BG96 modem device. USB example uses Quactel BG96 modem device. BG96 needs a positive pulse on its PWK pin to boot-up.
This example supports USB modem hot-plugging and reconnection. There is one limitation coming from esp_console component:
When esp_console REPL is being destroyed (after USB mode disconnection or after `exit` command), it will block on UART read.
You must send a character to it (via idf.py monitor), so it unblocks and properly exits.
### Supported IDF versions ### Supported IDF versions

View File

@ -1,19 +0,0 @@
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()

View File

@ -1,191 +0,0 @@
// 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,
};
ESP_MODEM_THROW_IF_ERROR(usb_host_install(&host_config), "USB Host install failed");
ESP_LOGD(TAG, "USB Host installed");
ESP_MODEM_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) {
ESP_MODEM_THROW_IF_ERROR(this->CdcAcmDevice::open(usb_config->vid, usb_config->pid,
usb_config->interface_idx, &esp_modem_cdc_acm_device_config),
"USB Device open failed");
} else {
ESP_MODEM_THROW_IF_ERROR(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 &copy) = delete;
UsbTerminal &operator=(const UsbTerminal &copy) = delete;
bool operator== (const UsbTerminal &param) const = delete;
bool operator!= (const UsbTerminal &param) 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<UsbTerminal *>(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<UsbTerminal *>(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<Terminal> create_usb_terminal(const esp_modem_dte_config *config)
{
TRY_CATCH_RET_NULL(
return std::make_unique<UsbTerminal>(config);
)
}
} // namespace esp_modem

View File

@ -1,32 +0,0 @@
// 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<DTE> create_usb_dte(const dte_config *config)
{
TRY_CATCH_RET_NULL(
auto term = create_usb_terminal(config);
return std::make_shared<DTE>(config, std::move(term));
)
}
}

View File

@ -1,29 +0,0 @@
// 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<DTE> create_usb_dte(const dte_config *config);
}

View File

@ -1,63 +0,0 @@
// 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 <stdint.h>
#include <stdbool.h>
/**
* @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 \
}

View File

@ -1,51 +0,0 @@
// 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<Terminal> create_usb_terminal(const esp_modem_dte_config *config);
} // namespace esp_modem

View File

@ -2,6 +2,6 @@ idf_component_register(SRCS "modem_console_main.cpp"
"console_helper.cpp" "console_helper.cpp"
"httpget_handle.c" "httpget_handle.c"
"ping_handle.c" "ping_handle.c"
REQUIRES console esp_http_client nvs_flash esp_modem esp_modem_usb_dte REQUIRES console esp_http_client nvs_flash
INCLUDE_DIRS ".") INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -16,6 +16,7 @@ menu "Example Configuration"
endchoice endchoice
choice EXAMPLE_MODEM_DEVICE choice EXAMPLE_MODEM_DEVICE
depends on EXAMPLE_SERIAL_CONFIG_UART
prompt "Choose supported modem device (DCE)" prompt "Choose supported modem device (DCE)"
default EXAMPLE_MODEM_DEVICE_BG96 default EXAMPLE_MODEM_DEVICE_BG96
help help
@ -74,6 +75,9 @@ menu "Example Configuration"
help help
Set to true for the PPP client to skip authentication Set to true for the PPP client to skip authentication
menu "UART Configuration"
depends on EXAMPLE_SERIAL_CONFIG_UART
choice EXAMPLE_FLOW_CONTROL choice EXAMPLE_FLOW_CONTROL
bool "Set preferred modem control flow" bool "Set preferred modem control flow"
default EXAMPLE_FLOW_CONTROL_NONE default EXAMPLE_FLOW_CONTROL_NONE
@ -88,7 +92,6 @@ menu "Example Configuration"
bool "HW control flow" bool "HW control flow"
endchoice endchoice
menu "UART Configuration"
config EXAMPLE_MODEM_UART_TX_PIN config EXAMPLE_MODEM_UART_TX_PIN
int "TXD Pin Number" int "TXD Pin Number"
default 25 default 25

View File

@ -18,6 +18,16 @@ ConsoleCommand::ConsoleCommand(const char *command, const char *help, const std:
RegisterCommand(command, help, args); RegisterCommand(command, help, args);
} }
ConsoleCommand::~ConsoleCommand()
{
// Find this command in static list of commands and remove it
auto cmd = std::find(console_commands.begin(), console_commands.end(), this);
if (cmd != console_commands.end()) {
console_commands.erase(cmd);
last_command--;
}
}
void ConsoleCommand::RegisterCommand(const char *command, const char *help, const std::vector<CommandArgs> &args) void ConsoleCommand::RegisterCommand(const char *command, const char *help, const std::vector<CommandArgs> &args)
{ {
assert(last_command <= MAX_REPEAT_NR); assert(last_command <= MAX_REPEAT_NR);

View File

@ -92,6 +92,11 @@ public:
*/ */
explicit ConsoleCommand(const char *command, const char *help, const std::vector<CommandArgs> &args, std::function<bool(ConsoleCommand *)> f); explicit ConsoleCommand(const char *command, const char *help, const std::vector<CommandArgs> &args, std::function<bool(ConsoleCommand *)> f);
/**
* @brief Destructor of ConsoleCommand
*/
~ConsoleCommand();
/** /**
* @brief Utility getters of various params from the argument list * @brief Utility getters of various params from the argument list
*/ */

View File

@ -0,0 +1,12 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf: ">=4.1.0"
espressif/esp_modem:
version: "^0.1.20"
override_path: "../../../"
espressif/esp_modem_usb_dte:
version: "^1.0.0"
rules:
- if: "idf_version >=4.4"
- if: "target in [esp32s2, esp32s3]"

View File

@ -18,7 +18,7 @@
#include "cxx_include/esp_modem_dte.hpp" #include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h" #include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp" #include "cxx_include/esp_modem_api.hpp"
#if defined(CONFIG_USB_OTG_SUPPORTED) #if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
#include "esp_modem_usb_config.h" #include "esp_modem_usb_config.h"
#include "cxx_include/esp_modem_usb_api.hpp" #include "cxx_include/esp_modem_usb_api.hpp"
#endif #endif
@ -56,6 +56,7 @@ static const char *TAG = "modem_console";
static esp_console_repl_t *s_repl = nullptr; static esp_console_repl_t *s_repl = nullptr;
using namespace esp_modem; using namespace esp_modem;
static SignalGroup exit_signal;
extern "C" void app_main(void) extern "C" void app_main(void)
@ -111,12 +112,18 @@ extern "C" void app_main(void)
#elif defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB) #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 while (1) {
// BG96 modem implements Vendor Specific class, that is CDC-ACM like. Interface for AT commands has index no. 2. exit_signal.clear(1);
usb_config.interface_idx = 2; struct esp_modem_usb_term_config usb_config = ESP_MODEM_DEFAULT_USB_CONFIG(0x2C7C, 0x0296, 2); // VID, PID and interface num of BG96 modem
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_USB_CONFIG(usb_config); const esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_USB_CONFIG(usb_config);
ESP_LOGI(TAG, "Waiting for USB device connection..."); ESP_LOGI(TAG, "Waiting for USB device connection...");
auto dte = create_usb_dte(&dte_config); auto dte = create_usb_dte(&dte_config);
dte->set_error_cb([&](terminal_error err) {
ESP_LOGI(TAG, "error handler %d", err);
if (err == terminal_error::DEVICE_GONE) {
exit_signal.set(1);
}
});
std::unique_ptr<DCE> dce = create_BG96_dce(&dce_config, dte, esp_netif); std::unique_ptr<DCE> dce = create_BG96_dce(&dce_config, dte, esp_netif);
#else #else
@ -277,16 +284,19 @@ extern "C" void app_main(void)
return 0; return 0;
}); });
SignalGroup exit_signal;
const ConsoleCommand ExitConsole("exit", "exit the console application", no_args, [&](ConsoleCommand * c) { const ConsoleCommand ExitConsole("exit", "exit the console application", no_args, [&](ConsoleCommand * c) {
ESP_LOGI(TAG, "Exiting..."); ESP_LOGI(TAG, "Exiting...");
exit_signal.set(1); exit_signal.set(1);
s_repl->del(s_repl);
return 0; return 0;
}); });
// start console REPL // start console REPL
ESP_ERROR_CHECK(esp_console_start_repl(s_repl)); ESP_ERROR_CHECK(esp_console_start_repl(s_repl));
// wait for exit // wait for exit
exit_signal.wait_any(1, UINT32_MAX); exit_signal.wait_any(1, UINT32_MAX);
s_repl->del(s_repl);
ESP_LOGI(TAG, "Exiting...%d", esp_get_free_heap_size()); ESP_LOGI(TAG, "Exiting...%d", esp_get_free_heap_size());
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
// USB example runs in a loop to demonstrate hot-plugging and sudden disconnection features.
} // while (1)
#endif
} }

View File

@ -0,0 +1 @@
CONFIG_EXAMPLE_SERIAL_CONFIG_USB=y

View File

@ -9,6 +9,18 @@ This example shows how to act as a MQTT client after the PPPoS channel created b
See the README.md file in the upper level `pppos` directory for more information about the PPPoS examples. See the README.md file in the upper level `pppos` directory for more information about the PPPoS examples.
### 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. BG96 needs a positive pulse on its PWK pin to boot-up.
This example supports USB modem hot-plugging and reconnection.
### Supported IDF versions ### Supported IDF versions
This example is only supported from `v4.1`, as this is the default dependency of `esp-modem` component. This example is only supported from `v4.1`, as this is the default dependency of `esp-modem` component.
USB example is supported from `v4.4`.

View File

@ -1,6 +1,21 @@
menu "Example Configuration" 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
choice EXAMPLE_MODEM_DEVICE choice EXAMPLE_MODEM_DEVICE
depends on EXAMPLE_SERIAL_CONFIG_UART
prompt "Choose supported modem device (DCE)" prompt "Choose supported modem device (DCE)"
default EXAMPLE_MODEM_DEVICE_BG96 default EXAMPLE_MODEM_DEVICE_BG96
help help
@ -74,6 +89,7 @@ menu "Example Configuration"
Pin to unlock the SIM Pin to unlock the SIM
menu "UART Configuration" menu "UART Configuration"
depends on EXAMPLE_SERIAL_CONFIG_UART
config EXAMPLE_MODEM_UART_TX_PIN config EXAMPLE_MODEM_UART_TX_PIN
int "TXD Pin Number" int "TXD Pin Number"
default 25 default 25

View File

@ -0,0 +1,12 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf: ">=4.1.0"
espressif/esp_modem:
version: "^0.1.23"
override_path: "../../../"
espressif/esp_modem_usb_dte:
version: "^1.0.0"
rules:
- if: "idf_version >=4.4"
- if: "target in [esp32s2, esp32s3]"

View File

@ -14,6 +14,7 @@
#include "mqtt_client.h" #include "mqtt_client.h"
#include "esp_modem_api.h" #include "esp_modem_api.h"
#include "esp_log.h" #include "esp_log.h"
#include "sdkconfig.h"
#define BROKER_URL "mqtt://mqtt.eclipseprojects.io" #define BROKER_URL "mqtt://mqtt.eclipseprojects.io"
@ -21,6 +22,28 @@ static const char *TAG = "pppos_example";
static EventGroupHandle_t event_group = NULL; static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0; static const int CONNECT_BIT = BIT0;
static const int GOT_DATA_BIT = BIT2; static const int GOT_DATA_BIT = BIT2;
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
#include "esp_modem_usb_c_api.h"
#include "esp_modem_usb_config.h"
#include "freertos/task.h"
static void usb_terminal_error_handler(esp_modem_terminal_error_t err)
{
if (err == ESP_MODEM_TERMINAL_DEVICE_GONE) {
ESP_LOGI(TAG, "USB modem disconnected");
assert(event_group);
xEventGroupSetBits(event_group, USB_DISCONNECTED_BIT);
}
}
#define CHECK_USB_DISCONNECTION(event_group) \
if ((xEventGroupGetBits(event_group) & USB_DISCONNECTED_BIT) == USB_DISCONNECTED_BIT) { \
esp_modem_destroy(dce); \
continue; \
}
#else
#define CHECK_USB_DISCONNECTION(event_group)
#endif
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{ {
@ -111,16 +134,22 @@ static void on_ip_event(void *arg, esp_event_base_t event_base,
void app_main(void) void app_main(void)
{ {
/* Init and register system/core components */ /* Init and register system/core components */
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL));
/* Configure the PPP netif */
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
assert(esp_netif);
event_group = xEventGroupCreate(); event_group = xEventGroupCreate();
/* Configure the DTE */ /* Configure the DTE */
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_UART)
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
/* setup UART specific configuration based on kconfig options */ /* setup UART specific configuration based on kconfig options */
dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN; dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN;
@ -134,17 +163,6 @@ void app_main(void)
dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY; dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY;
dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2; dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2;
/* Configure the DCE */
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
/* Configure the PPP netif */
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
/* Run the modem demo app */
// Init netif object
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
assert(esp_netif);
#if CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1 #if CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1
ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module..."); ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module...");
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_BG96, &dte_config, &dce_config, esp_netif); esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_BG96, &dte_config, &dce_config, esp_netif);
@ -158,8 +176,24 @@ void app_main(void)
ESP_LOGI(TAG, "Initializing esp_modem for a generic module..."); ESP_LOGI(TAG, "Initializing esp_modem for a generic module...");
esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif); esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif);
#endif #endif
assert(dce);
#elif defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
while (1) {
ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module...");
struct esp_modem_usb_term_config usb_config = ESP_MODEM_DEFAULT_USB_CONFIG(0x2C7C, 0x0296, 2); // VID, PID and interface num of BG96 modem
const esp_modem_dte_config_t dte_usb_config = ESP_MODEM_DTE_DEFAULT_USB_CONFIG(usb_config);
ESP_LOGI(TAG, "Waiting for USB device connection...");
esp_modem_dce_t *dce = esp_modem_new_dev_usb(ESP_MODEM_DCE_BG96, &dte_usb_config, &dce_config, esp_netif);
esp_modem_set_error_cb(dce, usb_terminal_error_handler);
vTaskDelay(pdMS_TO_TICKS(1000)); // Although the DTE should be ready after USB enumeration, sometimes it fails to respond without this delay
#else
#error Invalid serial connection to modem.
#endif
assert(dce);
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT);
/* Run the modem demo app */
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1 #if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
// check if PIN needed // check if PIN needed
bool pin_ok = false; bool pin_ok = false;
@ -199,7 +233,10 @@ void app_main(void)
return; return;
} }
/* Wait for IP address */ /* Wait for IP address */
xEventGroupWaitBits(event_group, CONNECT_BIT, pdTRUE, pdTRUE, portMAX_DELAY); ESP_LOGI(TAG, "Waiting for IP address");
xEventGroupWaitBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
CHECK_USB_DISCONNECTION(event_group);
/* Config MQTT */ /* Config MQTT */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_mqtt_client_config_t mqtt_config = { esp_mqtt_client_config_t mqtt_config = {
@ -213,7 +250,10 @@ void app_main(void)
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config); esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt_client); esp_mqtt_client_start(mqtt_client);
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdTRUE, portMAX_DELAY); ESP_LOGI(TAG, "Waiting for MQTT data");
xEventGroupWaitBits(event_group, GOT_DATA_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
CHECK_USB_DISCONNECTION(event_group);
esp_mqtt_client_destroy(mqtt_client); esp_mqtt_client_destroy(mqtt_client);
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND); err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
if (err != ESP_OK) { if (err != ESP_OK) {
@ -228,6 +268,15 @@ void app_main(void)
} }
ESP_LOGI(TAG, "IMSI=%s", imsi); ESP_LOGI(TAG, "IMSI=%s", imsi);
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
// USB example runs in a loop to demonstrate hot-plugging and sudden disconnection features.
ESP_LOGI(TAG, "USB demo finished. Disconnect and connect the modem to run it again");
xEventGroupWaitBits(event_group, USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
CHECK_USB_DISCONNECTION(event_group); // dce will be destroyed here
} // while (1)
#else
// UART DTE clean-up
esp_modem_destroy(dce); esp_modem_destroy(dce);
esp_netif_destroy(esp_netif); esp_netif_destroy(esp_netif);
#endif
} }

View File

@ -0,0 +1 @@
CONFIG_EXAMPLE_SERIAL_CONFIG_USB=y