Merge branch 'feature/usb/update_cdc_examples' into 'master'

usb: Update host CDC examples

See merge request espressif/esp-idf!22825
This commit is contained in:
Tomas Rezucha 2023-04-04 21:24:44 +08:00
commit 9f2012156c
15 changed files with 212 additions and 550 deletions

View File

@ -363,8 +363,8 @@ CDC-ACM
* A host class driver for the Communication Device Class (Abstract Control Model) is deployed to `IDF component registry <https://components.espressif.com/component/espressif/usb_host_cdc_acm>`__.
* The :example:`peripherals/usb/host/cdc/cdc_acm_host` example uses the CDC-ACM host driver component to communicate with CDC-ACM devices
* The :example:`peripherals/usb/host/cdc/cdc_acm_bg96` example uses the CDC-ACM host driver component to communicate with non-compliant CDC-ACM devices (i.e., vendor-specific classes that support a subset of CDC-ACM features) such as the Quectel BG96 modem.
* The :example:`peripherals/usb/host/cdc/cdc_acm_vcp` example shows how can you extend the CDC-ACM host driver to interface Virtual COM Port devices.
* The CDC-ACM driver is also used in `esp_modem examples <https://github.com/espressif/esp-protocols/tree/master/components/esp_modem/examples>`__, where it is used for communication with cellular modems.
MSC
"""
@ -405,4 +405,4 @@ Maintainers Notes
:hidden:
:maxdepth: 0
usb_host/usb_host_notes_index
usb_host/usb_host_notes_index

View File

@ -1,8 +0,0 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(cdc_acm_host_bg96)

View File

@ -1,99 +0,0 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB CDC-ACM Host Driver BG96 Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to set up ESP chip to interface with CDC-like device by using the CDC-ACM Host Driver. CDC-like devices implement a Vendor-specific class, and support a subset of the functions of a fully compliant CDC-ACM device.
## How to use example
### Hardware Required
Any ESP board with USB-OTG supported and a Quectel BG96 LTE/GPS modem.
Connect USB_D+, USB_D-, GND and +5V signals of ESP board to BG96.
_Note:_ Quectel BG96 modem must be started after power-up by applying low pulse on PWRKEY (pin 15).
#### Pin Assignment
See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments).
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
After the flashing you should see the output at idf monitor:
```
I (276) BG96: USB Host installed
I (24446) AT: ATE0
I (24446) AT:
OK
I (24526) AT:
+QIND: SMS DONE
I (24646) AT:
APP RDY
I (25446) BG96: Sending AT
I (25446) AT:
OK
I (26446) BG96: Enabling GNSS
I (26446) AT:
OK
GPVTG Sentence:
Track [deg]: 0.00
Speed [kmph]: 0.00
Speed [knots]: 0.00
GPGSA Sentence:
Mode: A
Fix: 1
PDOP: 0.00
HDOP: 0.00
VDOP: 0.00
GPGGA sentence
Number of satellites: 0
Altitude: 0.000000
GPRMC sentence
Longitude:
Degrees: 0
Minutes: 0.000000
Cardinal:
Latitude:
Degrees: 0
Minutes: 0.000000
Cardinal:
Date & Time: 00 Jan 00:00:00 1900
Speed, in Knots: 0.000000
Track, in degrees: 0.000000
Magnetic Variation:
Degrees: 0.000000
Cardinal:
Invalid Magnetic Variation Direction!
Adjusted Track (heading): 0.000000
I (27446) BG96: Sending AT+GSN
I (27446) AT:
860517045660414
OK
...
```

View File

@ -1,2 +0,0 @@
idf_component_register(SRCS "cdc_acm_host_bg96.cpp"
INCLUDE_DIRS ".")

View File

@ -1,84 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include "usb/cdc_acm_host.h"
#include "esp_log.h"
#define BG96_VID (0x2C7C)
#define BG96_PID (0x0296)
#define BG96_AT_INTERFACE (2)
#define BG96_NMEA_INTERFACE (1)
class Bg96Usb {
public:
explicit Bg96Usb() : at_opened(false) {
};
esp_err_t at_start(cdc_acm_data_callback_t data_cb, void *user_arg)
{
// This driver doesn't support CDC notifications. This can lead to silent failures
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 10000,
.out_buffer_size = 64,
.event_cb = NULL,
.data_cb = data_cb,
.user_arg = user_arg,
};
ESP_ERROR_CHECK(this->at_port.open_vendor_specific(BG96_VID, BG96_PID, BG96_AT_INTERFACE, &dev_config));
this->at_opened = true;
// Some FW versions have Echo enabled by default. Disable it with ATE0 command
ESP_LOGD("BG96_USB", "Turning off echo with ATE0");
ESP_ERROR_CHECK(this->at_port.tx_blocking((uint8_t *)"ATE0\r", 5, 1000));
vTaskDelay(100);
return ESP_OK;
}
void at_stop()
{
this->at_port.close();
this->at_opened = false;
}
esp_err_t at_write(uint8_t *data, size_t len)
{
ESP_LOG_BUFFER_HEXDUMP("BG96_USB", data, len, ESP_LOG_DEBUG);
return this->at_port.tx_blocking(data, len, 1000);
}
esp_err_t gnss_start(cdc_acm_data_callback_t data_cb)
{
if (!this->at_opened) {
return ESP_ERR_INVALID_STATE;
}
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 1000,
.out_buffer_size = 0, // Read-only
.event_cb = NULL,
.data_cb = data_cb,
.user_arg = this,
};
ESP_ERROR_CHECK(this->nmea_port.open_vendor_specific(BG96_VID, BG96_PID, BG96_NMEA_INTERFACE, &dev_config));
return this->at_port.tx_blocking((uint8_t*)"AT+QGPS=1\r", 10, 1000);
}
esp_err_t gnss_stop()
{
esp_err_t ret = this->at_port.tx_blocking((uint8_t*)"AT+QGPSEND\r", 11, 1000);
this->nmea_port.close();
return ret;
}
protected:
CdcAcmDevice at_port; // Main control port for AT commands
CdcAcmDevice nmea_port; // Read only port for NMEA messages
private:
bool at_opened;
};

View File

@ -1,208 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "usb/usb_host.h"
#include "bg96_usb.hpp"
#include "nmea.h"
#include "gpgll.h"
#include "gpgga.h"
#include "gprmc.h"
#include "gpgsa.h"
#include "gpvtg.h"
#include "gptxt.h"
#include "gpgsv.h"
#define EXAMPLE_USB_HOST_PRIORITY 20
static const char* TAG = "BG96";
static char fmt_buf[32];
/* ------------------------------- Callbacks -------------------------------- */
static void handle_rx(uint8_t *data, size_t data_len, void *user_arg)
{
data[data_len] = '\0';
ESP_LOGI("AT", "%s", data);
}
static void handle_gps(uint8_t* data, size_t data_len, void *user_arg)
{
// handle nmea_data
nmea_s *nmea_data = nmea_parse((char *)data, data_len, 0);
if (nmea_data == NULL) {
printf("Failed to parse the sentence!\n");
printf(" Type: %.5s (%d)\n", data + 1, nmea_get_type((const char *)data));
} else {
if (nmea_data->errors != 0) {
printf("WARN: The sentence struct contains parse errors!\n");
}
if (NMEA_GPGGA == nmea_data->type) {
printf("GPGGA sentence\n");
nmea_gpgga_s *gpgga = (nmea_gpgga_s *)nmea_data;
printf("Number of satellites: %d\n", gpgga->n_satellites);
printf("Altitude: %f %c\n", gpgga->altitude, gpgga->altitude_unit);
}
if (NMEA_GPGLL == nmea_data->type) {
printf("GPGLL sentence\n");
nmea_gpgll_s *pos = (nmea_gpgll_s *)nmea_data;
printf("Longitude:\n");
printf(" Degrees: %d\n", pos->longitude.degrees);
printf(" Minutes: %f\n", pos->longitude.minutes);
printf(" Cardinal: %c\n", (char)pos->longitude.cardinal);
printf("Latitude:\n");
printf(" Degrees: %d\n", pos->latitude.degrees);
printf(" Minutes: %f\n", pos->latitude.minutes);
printf(" Cardinal: %c\n", (char)pos->latitude.cardinal);
strftime(fmt_buf, sizeof(fmt_buf), "%H:%M:%S", &pos->time);
printf("Time: %s\n", fmt_buf);
}
if (NMEA_GPRMC == nmea_data->type) {
printf("GPRMC sentence\n");
nmea_gprmc_s *pos = (nmea_gprmc_s *)nmea_data;
printf("Longitude:\n");
printf(" Degrees: %d\n", pos->longitude.degrees);
printf(" Minutes: %f\n", pos->longitude.minutes);
printf(" Cardinal: %c\n", (char)pos->longitude.cardinal);
printf("Latitude:\n");
printf(" Degrees: %d\n", pos->latitude.degrees);
printf(" Minutes: %f\n", pos->latitude.minutes);
printf(" Cardinal: %c\n", (char)pos->latitude.cardinal);
strftime(fmt_buf, sizeof(fmt_buf), "%d %b %T %Y", &pos->date_time);
printf("Date & Time: %s\n", fmt_buf);
printf("Speed, in Knots: %f\n", pos->gndspd_knots);
printf("Track, in degrees: %f\n", pos->track_deg);
printf("Magnetic Variation:\n");
printf(" Degrees: %f\n", pos->magvar_deg);
printf(" Cardinal: %c\n", (char)pos->magvar_cardinal);
double adjusted_course = pos->track_deg;
if (NMEA_CARDINAL_DIR_EAST == pos->magvar_cardinal) {
adjusted_course -= pos->magvar_deg;
} else if (NMEA_CARDINAL_DIR_WEST == pos->magvar_cardinal) {
adjusted_course += pos->magvar_deg;
} else {
printf("Invalid Magnetic Variation Direction!\n");
}
printf("Adjusted Track (heading): %f\n", adjusted_course);
}
if (NMEA_GPGSA == nmea_data->type) {
nmea_gpgsa_s *gpgsa = (nmea_gpgsa_s *)nmea_data;
printf("GPGSA Sentence:\n");
printf(" Mode: %c\n", gpgsa->mode);
printf(" Fix: %d\n", gpgsa->fixtype);
printf(" PDOP: %.2lf\n", gpgsa->pdop);
printf(" HDOP: %.2lf\n", gpgsa->hdop);
printf(" VDOP: %.2lf\n", gpgsa->vdop);
}
if (NMEA_GPGSV == nmea_data->type) {
nmea_gpgsv_s *gpgsv = (nmea_gpgsv_s *)nmea_data;
printf("GPGSV Sentence:\n");
printf(" Num: %d\n", gpgsv->sentences);
printf(" ID: %d\n", gpgsv->sentence_number);
printf(" SV: %d\n", gpgsv->satellites);
printf(" #1: %d %d %d %d\n", gpgsv->sat[0].prn, gpgsv->sat[0].elevation, gpgsv->sat[0].azimuth,
gpgsv->sat[0].snr);
printf(" #2: %d %d %d %d\n", gpgsv->sat[1].prn, gpgsv->sat[1].elevation, gpgsv->sat[1].azimuth,
gpgsv->sat[1].snr);
printf(" #3: %d %d %d %d\n", gpgsv->sat[2].prn, gpgsv->sat[2].elevation, gpgsv->sat[2].azimuth,
gpgsv->sat[2].snr);
printf(" #4: %d %d %d %d\n", gpgsv->sat[3].prn, gpgsv->sat[3].elevation, gpgsv->sat[3].azimuth,
gpgsv->sat[3].snr);
}
if (NMEA_GPTXT == nmea_data->type) {
nmea_gptxt_s *gptxt = (nmea_gptxt_s *)nmea_data;
printf("GPTXT Sentence:\n");
printf(" ID: %d %d %d\n", gptxt->id_00, gptxt->id_01, gptxt->id_02);
printf(" %s\n", gptxt->text);
}
if (NMEA_GPVTG == nmea_data->type) {
nmea_gpvtg_s *gpvtg = (nmea_gpvtg_s *)nmea_data;
printf("GPVTG Sentence:\n");
printf(" Track [deg]: %.2lf\n", gpvtg->track_deg);
printf(" Speed [kmph]: %.2lf\n", gpvtg->gndspd_kmph);
printf(" Speed [knots]: %.2lf\n", gpvtg->gndspd_knots);
}
nmea_free(nmea_data);
}
}
void usb_lib_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) {
printf("No more clients\n");
ESP_ERROR_CHECK(usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
break;
}
}
//Short delay to allow task to be cleaned up
vTaskDelay(10);
//Clean up USB Host
ESP_ERROR_CHECK(usb_host_uninstall());
vTaskDelete(NULL);
}
/* ---------------------------------- Main ---------------------------------- */
extern "C" void app_main(void)
{
//Install USB Host driver. Should only be called once in entire application
ESP_LOGI(TAG, "Installing USB Host");
usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
// Create a task that will handle USB library events
xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, EXAMPLE_USB_HOST_PRIORITY, NULL);
ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
Bg96Usb *bg96 = new Bg96Usb();
bg96->at_start(handle_rx, NULL);
static char text1[] = "AT\r";
static char text2[] = "AT+GSN\r";
ESP_LOGI(TAG, "Sending AT");
bg96->at_write((uint8_t *)text1, strlen(text1));
vTaskDelay(100);
ESP_LOGI(TAG, "Enabling GNSS");
bg96->gnss_start(handle_gps);
vTaskDelay(100);
ESP_LOGI(TAG, "Sending AT+GSN");
bg96->at_write((uint8_t *)text2, strlen(text2));
}

View File

@ -1,5 +0,0 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4"
igrr/libnmea: "^0.1.1"
usb_host_cdc_acm: "1.*"

View File

@ -33,15 +33,6 @@ idf.py -p PORT flash monitor
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Running with dual USB CDC device
USB CDC device example [tusb_serial_device example](../../../device/tusb_serial_device)
can be configured to act as dual CDC device.
In the device example project, enter command `idf.py menuconfig` and set Component config->TinyUSB Stack->Communication Device Class (CDC)->CDC channel Count to `2`.
This settings also changes device's PID, so `EXAMPLE_USB_DEVICE_PID` in [usb-cdc.c](./main/usb-cdc.c) must be changed to `0x4002`.
## Example Output
After the flashing you should see the output at idf monitor:

View File

@ -1,3 +1,2 @@
idf_component_register(SRCS "usb-cdc.c"
idf_component_register(SRCS "usb_cdc_example_main.c"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -1,4 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
usb_host_cdc_acm: "1.*"
usb_host_cdc_acm: "2.*"
idf: ">=4.4"

View File

@ -1,113 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "usb/usb_host.h"
#include "usb/cdc_acm_host.h"
#define EXAMPLE_USB_HOST_PRIORITY 20
#define EXAMPLE_USB_DEVICE_VID 0x303A // 0x303A:0x4001 (TinyUSB CDC device)
#define EXAMPLE_USB_DEVICE_PID 0x4001 // Change this to 0x4002 for dual CDC device
static const char *TAG = "USB-CDC";
/* ------------------------------- Callbacks -------------------------------- */
static void handle_rx(uint8_t *data, size_t data_len, void *arg)
{
ESP_LOGI(TAG, "Data received");
ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_INFO);
}
void usb_lib_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_LOGI(TAG, "All clients deregistered");
ESP_ERROR_CHECK(usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
break;
}
}
//Clean up USB Host
ESP_ERROR_CHECK(usb_host_uninstall());
vTaskDelete(NULL);
}
/* ---------------------------------- Main ---------------------------------- */
void app_main(void)
{
//Install USB Host driver. Should only be called once in entire application
ESP_LOGI(TAG, "Installing USB Host");
usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
// Create a task that will handle USB library events
xTaskCreate(usb_lib_task, "usb_lib", 4096, xTaskGetCurrentTaskHandle(), EXAMPLE_USB_HOST_PRIORITY, NULL);
ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
ESP_LOGI(TAG, "Opening CDC ACM device 0x%04X:0x%04X", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID);
cdc_acm_dev_hdl_t cdc_dev;
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 5000,
.out_buffer_size = 64,
.user_arg = NULL,
.event_cb = NULL,
.data_cb = handle_rx
};
ESP_ERROR_CHECK(cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID, 0, &dev_config, &cdc_dev));
assert(cdc_dev);
cdc_acm_host_desc_print(cdc_dev);
vTaskDelay(100);
// Test sending and receiving: Send AT commands, responses are handled in handle_rx callback
static char text1[] = "AT\r";
ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (uint8_t *)text1, strlen(text1), 1000));
vTaskDelay(100);
static char text2[] = "AT+GSN\r";
ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (uint8_t *)text2, strlen(text2), 1000));
vTaskDelay(100);
// Test Line Coding commands: Get current line coding, change it 9600 7N1 and read again
ESP_LOGI(TAG, "Setting up line coding");
cdc_acm_line_coding_t line_coding;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
line_coding.dwDTERate = 9600;
line_coding.bDataBits = 7;
line_coding.bParityType = 1;
line_coding.bCharFormat = 1;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_set(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Set: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_set_control_line_state(cdc_dev, true, false));
ESP_LOGI(TAG, "Example finished successfully!");
}

View File

@ -0,0 +1,179 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/usb_host.h"
#include "usb/cdc_acm_host.h"
#define EXAMPLE_USB_HOST_PRIORITY (20)
#define EXAMPLE_USB_DEVICE_VID (0x303A)
#define EXAMPLE_USB_DEVICE_PID (0x4001) // 0x303A:0x4001 (TinyUSB CDC device)
#define EXAMPLE_USB_DEVICE_DUAL_PID (0x4002) // 0x303A:0x4002 (TinyUSB Dual CDC device)
#define EXAMPLE_TX_STRING ("CDC test string!")
#define EXAMPLE_TX_TIMEOUT_MS (1000)
static const char *TAG = "USB-CDC";
static SemaphoreHandle_t device_disconnected_sem;
/**
* @brief Data received callback
*
* @param[in] data Pointer to received data
* @param[in] data_len Length of received data in bytes
* @param[in] arg Argument we passed to the device open function
* @return
* true: We have processed the received data
* false: We expect more data
*/
static bool handle_rx(const uint8_t *data, size_t data_len, void *arg)
{
ESP_LOGI(TAG, "Data received");
ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_INFO);
return true;
}
/**
* @brief Device event callback
*
* Apart from handling device disconnection it doesn't do anything useful
*
* @param[in] event Device event type and data
* @param[in] user_ctx Argument we passed to the device open function
*/
static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
{
switch (event->type) {
case CDC_ACM_HOST_ERROR:
ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %i", event->data.error);
break;
case CDC_ACM_HOST_DEVICE_DISCONNECTED:
ESP_LOGI(TAG, "Device suddenly disconnected");
ESP_ERROR_CHECK(cdc_acm_host_close(event->data.cdc_hdl));
xSemaphoreGive(device_disconnected_sem);
break;
case CDC_ACM_HOST_SERIAL_STATE:
ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val);
break;
case CDC_ACM_HOST_NETWORK_CONNECTION:
default:
ESP_LOGW(TAG, "Unsupported CDC event: %i", event->type);
break;
}
}
/**
* @brief USB Host library handling task
*
* @param arg Unused
*/
static void usb_lib_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_ERROR_CHECK(usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
ESP_LOGI(TAG, "USB: All devices freed");
// Continue handling USB events to allow device reconnection
}
}
}
/**
* @brief Main application
*
* Here we open a USB CDC device and send some data to it
*/
void app_main(void)
{
device_disconnected_sem = xSemaphoreCreateBinary();
assert(device_disconnected_sem);
// Install USB Host driver. Should only be called once in entire application
ESP_LOGI(TAG, "Installing USB Host");
const usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
// Create a task that will handle USB library events
BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, xTaskGetCurrentTaskHandle(), EXAMPLE_USB_HOST_PRIORITY, NULL);
assert(task_created == pdTRUE);
ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 1000,
.out_buffer_size = 512,
.in_buffer_size = 512,
.user_arg = NULL,
.event_cb = handle_event,
.data_cb = handle_rx
};
while (true) {
cdc_acm_dev_hdl_t cdc_dev = NULL;
// Open USB device from tusb_serial_device example example. Either single or dual port configuration.
ESP_LOGI(TAG, "Opening CDC ACM device 0x%04X:0x%04X...", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID);
esp_err_t err = cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID, 0, &dev_config, &cdc_dev);
if (ESP_OK != err) {
ESP_LOGI(TAG, "Opening CDC ACM device 0x%04X:0x%04X...", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_DUAL_PID);
err = cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_DUAL_PID, 0, &dev_config, &cdc_dev);
if (ESP_OK != err) {
ESP_LOGI(TAG, "Failed to open device");
continue;
}
}
cdc_acm_host_desc_print(cdc_dev);
vTaskDelay(pdMS_TO_TICKS(100));
// Test sending and receiving: responses are handled in handle_rx callback
ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (const uint8_t *)EXAMPLE_TX_STRING, strlen(EXAMPLE_TX_STRING), EXAMPLE_TX_TIMEOUT_MS));
vTaskDelay(pdMS_TO_TICKS(100));
// Test Line Coding commands: Get current line coding, change it 9600 7N1 and read again
ESP_LOGI(TAG, "Setting up line coding");
cdc_acm_line_coding_t line_coding;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
line_coding.dwDTERate = 9600;
line_coding.bDataBits = 7;
line_coding.bParityType = 1;
line_coding.bCharFormat = 1;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_set(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Set: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_set_control_line_state(cdc_dev, true, false));
// We are done. Wait for device disconnection and start over
ESP_LOGI(TAG, "Example finished successfully! You can reconnect the device to run again.");
xSemaphoreTake(device_disconnected_sem, portMAX_DELAY);
}
}

View File

@ -1,18 +1,18 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB CDC-ACM Virtual Com Port example
# USB CDC-ACM Virtual COM Port example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to extend CDC-ACM driver for Virtual Communication Port (VCP) devices,
such as CP210x, FTDI FT23x or CP34x devices.
such as CP210x, FTDI FT23x or CH34x devices.
The drivers are fetched from [IDF Component Registry](https://components.espressif.com/) together with VCP service that automatically loads correct driver for plugged-in device.
## How to use example
1. Connect your USB<->UART converter to ESP32-S2/S3, the device will be automatically enumerated and correct driver will be picked
1. Connect your USB<->UART converter to ESP32-S2/S3, the device will be automatically enumerated and correct driver will be loaded
2. Change baudrate and other line coding parameters in [cdc_acm_vcp_example_main.cpp](main/cdc_acm_vcp_example_main.cpp) to match your needs
3. Now you can use the usual CDC-ACM API to control the device and send data. Data are received in `handle_rx` callback
4. Try disconnecting and then reconnecting of the USB device to experiment with USB hotplugging

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
@ -28,25 +28,36 @@ using namespace esp_usb;
#define EXAMPLE_DATA_BITS (8)
namespace {
const char *TAG = "VCP example";
SemaphoreHandle_t device_disconnected_sem;
static const char *TAG = "VCP example";
static SemaphoreHandle_t device_disconnected_sem;
/**
* @brief Data received callback
*
* Just pass received data to stdout
*
* @param[in] data Pointer to received data
* @param[in] data_len Length of received data in bytes
* @param[in] arg Argument we passed to the device open function
* @return
* true: We have processed the received data
* false: We expect more data
*/
void handle_rx(uint8_t *data, size_t data_len, void *arg)
static bool handle_rx(const uint8_t *data, size_t data_len, void *arg)
{
printf("%.*s", data_len, data);
return true;
}
/**
* @brief Device event callback
*
* Apart from handling device disconnection it doesn't do anything useful
*
* @param[in] event Device event type and data
* @param[in] user_ctx Argument we passed to the device open function
*/
void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
{
switch (event->type) {
case CDC_ACM_HOST_ERROR:
@ -69,7 +80,7 @@ void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
*
* @param arg Unused
*/
void usb_lib_task(void *arg)
static void usb_lib_task(void *arg)
{
while (1) {
// Start handling system events
@ -89,14 +100,14 @@ void usb_lib_task(void *arg)
/**
* @brief Main application
*
* This function shows how you can use VCP drivers
* This function shows how you can use Virtual COM Port drivers
*/
extern "C" void app_main(void)
{
device_disconnected_sem = xSemaphoreCreateBinary();
assert(device_disconnected_sem);
//Install USB Host driver. Should only be called once in entire application
// Install USB Host driver. Should only be called once in entire application
ESP_LOGI(TAG, "Installing USB Host");
const usb_host_config_t host_config = {
.skip_phy_setup = false,
@ -111,7 +122,7 @@ extern "C" void app_main(void)
ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
// Register VCP drivers to VCP service.
// Register VCP drivers to VCP service
VCP::register_driver<FT23x>();
VCP::register_driver<CP210x>();
VCP::register_driver<CH34x>();
@ -120,13 +131,14 @@ extern "C" void app_main(void)
while (true) {
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 5000, // 5 seconds, enough time to plug the device in or experiment with timeout
.out_buffer_size = 64,
.out_buffer_size = 512,
.in_buffer_size = 512,
.event_cb = handle_event,
.data_cb = handle_rx,
.user_arg = NULL,
};
// You don't need to know the device's VID and PID. Just plug in any device and the VCP service will pick correct (already registered) driver for the device
// You don't need to know the device's VID and PID. Just plug in any device and the VCP service will load correct (already registered) driver for the device
ESP_LOGI(TAG, "Opening any VCP device...");
auto vcp = std::unique_ptr<CdcAcmDevice>(VCP::open(&dev_config));

View File

@ -1,7 +1,7 @@
## IDF Component Manager Manifest File
dependencies:
usb_host_ch34x_vcp: "^1"
usb_host_cp210x_vcp: "^1"
usb_host_ftdi_vcp: "^1"
usb_host_ch34x_vcp: "^2"
usb_host_cp210x_vcp: "^2"
usb_host_ftdi_vcp: "^2"
usb_host_vcp: "^1"
idf: ">=5.1.0"