usb: Add USB host CDC-ACM class driver

This commit is contained in:
Tomas Rezucha 2021-07-16 10:01:15 +02:00
parent 6e7716bcf7
commit dd1b698075
23 changed files with 2747 additions and 1 deletions

View File

@ -413,6 +413,7 @@ esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets,
*
* - Free a transfer object previously allocated using usb_host_transfer_alloc()
* - The transfer must not be in-flight when attempting to free it
* - If a NULL pointer is passed, this function will simply return ESP_OK
*
* @param[in] transfer Transfer object
* @return esp_err_t

View File

@ -571,6 +571,7 @@ static void _handle_pending_ep(client_t *client_obj)
esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret)
{
HOST_CHECK(p_host_lib_obj, ESP_ERR_INVALID_STATE);
HOST_CHECK(client_config != NULL && client_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
HOST_CHECK(client_config->max_num_event_msg > 0, ESP_ERR_INVALID_ARG);
if (!client_config->is_synchronous) {
@ -1229,7 +1230,9 @@ esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets,
esp_err_t usb_host_transfer_free(usb_transfer_t *transfer)
{
HOST_CHECK(transfer != NULL, ESP_ERR_INVALID_ARG);
if (transfer == NULL) {
return ESP_OK;
}
urb_t *urb = __containerof(transfer, urb_t, transfer);
urb_free(urb);
return ESP_OK;

View File

@ -0,0 +1,9 @@
# 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.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(cdc_acm_host_bg96)

View File

@ -0,0 +1,99 @@
| 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

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

View File

@ -0,0 +1,84 @@
/*
* 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

@ -0,0 +1,207 @@
/*
* SPDX-FileCopyrightText: 2015-2021 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 = {
.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

@ -0,0 +1,3 @@
dependencies:
idf: ">=4.4"
igrr/libnmea: ">=0.1.1"

View File

@ -0,0 +1,9 @@
# 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.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(cdc_acm_host)

View File

@ -0,0 +1,64 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB CDC-ACM Host Driver Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to use the CDC-ACM Host Driver to allow an ESP chip to communicate with a USB CDC-ACM device.
## How to use example
### Hardware Required
Two ESP boards that have USB-OTG supported. One will act as USB host and the other as USB device.
Connect USB_D+, USB_D-, GND and +5V signals of USB host to USB device.
#### Pin Assignment
See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments).
### Build and Flash
1. Build and flash [tusb_serial_device example](../../../tusb_serial_device) to USB device board.
2. Build this project and flash it to the USB host 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 (256) USB-CDC: USB Host installed
I (256) USB-CDC: Opening CDC ACM device 0x303A:0x4001
CDC Header Descriptor:
bcdCDC: 1.20
CDC Call Descriptor:
bmCapabilities: 0x00
bDataInterface: 1
CDC ACM Descriptor:
bmCapabilities: 0x02
CDC Union Descriptor:
bControlInterface: 0
bSubordinateInterface[0]: 1
I (1666) USB-CDC: Data received
I (1666) USB-CDC: 0x3ffc4c20 41 54 0d |AT.|
I (2666) USB-CDC: Data received
I (2666) USB-CDC: 0x3ffc4c20 41 54 2b 47 53 4e 0d |AT+GSN.|
I (3666) USB-CDC: Setting up line coding
I (3666) USB-CDC: Line Get: Rate: 115200, Stop bits: 0, Parity: 0, Databits: 8
I (3666) USB-CDC: Line Set: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
I (3666) USB-CDC: Line Get: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
I (3676) Example finished successfully!
...
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "usb-cdc.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2015-2021 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
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 = {
.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,3 @@
idf_component_register(SRCS "cdc_acm_host.c"
INCLUDE_DIRS "include"
REQUIRES usb)

View File

@ -0,0 +1,46 @@
# USB Host CDC-ACM Class Driver
This directory contains an implementation of a USB CDC-ACM Host Class Driver that is implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
## Supported Devices
The CDC-ACM Host driver supports the following types of CDC devices:
1. CDC-ACM devices
2. CDC-like vendor specific devices (usually found on USB to UART bridge devices)
### CDC-ACM Devices
The CDC-ACM Class driver supports CDC-ACM devices that meet the following requirements:
- The device class code must be set to the CDC class `0x02` or implement Interface Association Descriptor (IAD)
- The CDC-ACM must contain the following interfaces:
- A Communication Class Interface containing a management element (EP0) and may also contain a notification element (an interrupt endpoint). The driver will check this interface for CDC Functional Descriptors.
- A Data Class Interface with two BULK endpoints (IN and OUT). Other transfer types are not supported by the driver
### CDC-Like Vendor Specific Devices
The CDC-ACM Class driver supports CDC-like devices that meet the following requirements:
- The device class code must be set to the vendor specific class code `0xFF`
- The device needs to provide and interface containing the following endpoints:
- (Mandatory) Two Bulk endpoints (IN and OUT) for data
- (Optional) An interrupt endpoint (IN) for the notification element
For CDC-like devices, users are responsible for ensuring that they only call APIs (e.g., `cdc_acm_host_send_break()`) that are supported by the target device.
## Usage
The following steps outline the typical API call pattern of the CDC-ACM Class Driver
1. Install the USB Host Library via `usb_host_install()`
2. Install the CDC-ACM driver via `cdc_acm_host_install()`
3. Call `cdc_acm_host_open()`/`cdc_acm_host_open_vendor_specific()` to open a target CDC-ACM/CDC-like device. These functions will block until the target device is connected
4. To transmit data, call `cdc_acm_host_data_tx_blocking()`
5. When data is received, the driver will automatically run the receive data callback
6. An opened device can be closed via `cdc_acm_host_close()`
7. The CDC-ACM driver can be uninstalled via `cdc_acm_host_uninstall()`
## Examples
- For an example with a CDC-ACM device, refer to [cdc_acm_host](../../cdc_acm_host)
- For an example with a CDC-like device, refer to [cdc_acm_host_bg96](../../cdc_acm_bg96)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,305 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "usb_types_cdc.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct cdc_dev_s *cdc_acm_dev_hdl_t;
/**
* @brief Line Coding structure
* @see Table 17, USB CDC-PSTN specification rev. 1.2
*/
typedef struct {
uint32_t dwDTERate; // in bits per second
uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits
uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
uint8_t bDataBits; // 5, 6, 7, 8 or 16
} __attribute__((packed)) cdc_acm_line_coding_t;
/**
* @brief UART State Bitmap
* @see Table 31, USB CDC-PSTN specification rev. 1.2
*/
typedef union {
struct {
uint16_t bRxCarrier : 1; // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD.
uint16_t bTxCarrier : 1; // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR.
uint16_t bBreak : 1; // State of break detection mechanism of the device.
uint16_t bRingSignal : 1; // State of ring signal detection of the device.
uint16_t bFraming : 1; // A framing error has occurred.
uint16_t bParity : 1; // A parity error has occurred.
uint16_t bOverRun : 1; // Received data has been discarded due to overrun in the device.
uint16_t reserved : 9;
};
uint16_t val;
} cdc_acm_uart_state_t;
/**
* @brief CDC-ACM Device Event types to upper layer
*
*/
typedef enum {
CDC_ACM_HOST_ERROR,
CDC_ACM_HOST_SERIAL_STATE,
CDC_ACM_HOST_NETWORK_CONNECTION,
CDC_ACM_HOST_DEVICE_DISCONNECTED
} cdc_acm_host_dev_event_t;
/**
* @brief CDC-ACM Device Event data structure
*
*/
typedef struct {
cdc_acm_host_dev_event_t type;
union {
int error; // Error code from USB Host
cdc_acm_uart_state_t serial_state; // Serial (UART) state
bool network_connected; // Network connection event
} data;
} cdc_acm_host_dev_event_data_t;
/**
* @brief Data receive callback type
*/
typedef void (*cdc_acm_data_callback_t)(uint8_t* data, size_t data_len, void *user_arg);
/**
* @brief Device event callback type
* @see cdc_acm_host_dev_event_t
*/
typedef void (*cdc_acm_host_dev_callback_t)(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx);
/**
* @brief Configuration structure of USB Host CDC-ACM driver
*
*/
typedef struct {
size_t driver_task_stack_size; /**< Stack size of the driver's task */
unsigned driver_task_priority; /**< Priority of the driver's task */
int xCoreID; /**< Core affinity of the driver's task */
} cdc_acm_host_driver_config_t;
/**
* @brief Configuration structure of CDC-ACM device
*
*/
typedef struct {
uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */
size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */
cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */
cdc_acm_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */
void *user_arg; /**< User's argument that will be passed to the callbacks */
} cdc_acm_host_device_config_t;
/**
* @brief Install CDC-ACM driver
*
* - USB Host Library must already be installed before calling this function (via usb_host_install())
* - This function should be called before calling any other CDC driver functions
*
* @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used.
* @return esp_err_t
*/
esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config);
/**
* @brief Uninstall CDC-ACM driver
*
* - Users must ensure that all CDC devices must be closed via cdc_acm_host_close() before calling this function
*
* @return esp_err_t
*/
esp_err_t cdc_acm_host_uninstall(void);
/**
* @brief Open CDC-ACM compliant device
*
* CDC-ACM compliant device must contain either an Interface Association Descriptor or CDC-Union descriptor,
* which are used for the driver's configuration.
*
* @param[in] vid Device's Vendor ID
* @param[in] pid Device's Product ID
* @param[in] interface_idx Index of device's interface used for CDC-ACM communication
* @param[in] dev_config Configuration structure of the device
* @param[out] cdc_hdl_ret CDC device handle
* @return esp_err_t
*/
esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret);
/**
* @brief Open CDC-ACM non-compliant device
*
* CDC-ACM non-compliant device acts as CDC-ACM device but doesn't support all its features.
* User must provide the interface index that will be used (zero for non-composite devices).
*
* @param[in] vid Device's Vendor ID
* @param[in] pid Device's Product ID
* @param[in] interface_idx Index of device's interface used for CDC-ACM like communication
* @param[in] dev_config Configuration structure of the device
* @param[out] cdc_hdl_ret CDC device handle
* @return esp_err_t
*/
esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret);
/**
* @brief Close CDC device and release its resources
*
* @note All in-flight transfers will be prematurely canceled.
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @return esp_err_t
*/
esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl);
/**
* @brief Transmit data - blocking mode
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[in] data Data to be sent
* @param[in] data_len Data length
* @param[in] timeout_ms Timeout in [ms]
* @return esp_err_t
*/
esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms);
/**
* @brief SetLineCoding function
*
* @see Chapter 6.3.10, USB CDC-PSTN specification rev. 1.2
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[in] line_coding Line Coding structure
* @return esp_err_t
*/
esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding);
/**
* @brief GetLineCoding function
*
* @see Chapter 6.3.11, USB CDC-PSTN specification rev. 1.2
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[out] line_coding Line Coding structure to be filled
* @return esp_err_t
*/
esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding);
/**
* @brief SetControlLineState function
*
* @see Chapter 6.3.12, USB CDC-PSTN specification rev. 1.2
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready.
* @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send.
* @return esp_err_t
*/
esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts);
/**
* @brief SendBreak function
*
* This function will block until the duration_ms has passed.
*
* @see Chapter 6.3.13, USB CDC-PSTN specification rev. 1.2
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[in] duration_ms Duration of the Break signal in [ms]
* @return esp_err_t
*/
esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms);
/**
* @brief Print CDC-ACM specific descriptors
*
* Descriptors are printed in human readable format to stdout.
* Intended for debugging and for CDC-ACM compliant devices only.
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
*/
void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl);
/**
* @brief Get protocols defined in USB-CDC interface descriptors
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[out] comm Communication protocol
* @param[out] data Data protocol
* @return esp_err_t
*/
esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data);
#ifdef __cplusplus
}
class CdcAcmDevice
{
public:
// Operators
CdcAcmDevice() : cdc_hdl(NULL){};
~CdcAcmDevice()
{
// Close CDC-ACM device, if it wasn't explicitly closed
if (this->cdc_hdl != NULL) {
this->close();
}
}
inline esp_err_t tx_blocking(uint8_t *data, size_t len, uint32_t timeout_ms = 100)
{
return cdc_acm_host_data_tx_blocking(this->cdc_hdl, data, len, timeout_ms);
}
inline esp_err_t open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t* dev_config)
{
return cdc_acm_host_open(vid, pid, interface_idx, dev_config, &this->cdc_hdl);
}
inline esp_err_t open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t* dev_config)
{
return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl);
}
inline void close()
{
cdc_acm_host_close(this->cdc_hdl);
this->cdc_hdl = NULL;
}
inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding)
{
return cdc_acm_host_line_coding_get(this->cdc_hdl, line_coding);
}
inline esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding)
{
return cdc_acm_host_line_coding_set(this->cdc_hdl, line_coding);
}
inline esp_err_t set_control_line_state(bool dtr, bool rts)
{
return cdc_acm_host_set_control_line_state(this->cdc_hdl, dtr, rts);
}
inline esp_err_t send_break(uint16_t duration_ms)
{
return cdc_acm_host_send_break(this->cdc_hdl, duration_ms);
}
private:
CdcAcmDevice(const CdcAcmDevice &Copy);
CdcAcmDevice &operator= (const CdcAcmDevice &Copy);
bool operator== (const CdcAcmDevice &param) const;
bool operator!= (const CdcAcmDevice &param) const;
cdc_acm_dev_hdl_t cdc_hdl;
};
#endif

View File

@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <inttypes.h>
/**
* @brief USB CDC Descriptor Subtypes
*
* @see Table 13, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_DESC_SUBTYPE_HEADER = 0x00, // Header Functional Descriptor
CDC_DESC_SUBTYPE_CALL = 0x01, // Call Management Functional Descriptor
CDC_DESC_SUBTYPE_ACM = 0x02, // Abstract Control Management Functional Descriptor
CDC_DESC_SUBTYPE_DLM = 0x03, // Direct Line Management Functional Descriptor
CDC_DESC_SUBTYPE_TEL_RINGER = 0x04, // Telephone Ringer Functional Descriptor
CDC_DESC_SUBTYPE_TEL_CLSR = 0x05, // Telephone Call and Line State Reporting Capabilities Functional Descriptor
CDC_DESC_SUBTYPE_UNION = 0x06, // Union Functional Descriptor
CDC_DESC_SUBTYPE_COUNTRY = 0x07, // Country Selection Functional Descriptor
CDC_DESC_SUBTYPE_TEL_MODE = 0x08, // Telephone Operational Modes Functional Descriptor
CDC_DESC_SUBTYPE_TERMINAL = 0x09, // USB Terminal
CDC_DESC_SUBTYPE_NCHT = 0x0A, // Network Channel Terminal
CDC_DESC_SUBTYPE_PROTOCOL = 0x08, // Protocol Unit
CDC_DESC_SUBTYPE_EXTENSION = 0x0C, // Extension Unit
CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D, // Multi-Channel Management Functional Descriptor
CDC_DESC_SUBTYPE_CAPI = 0x0E, // CAPI Control
CDC_DESC_SUBTYPE_ETH = 0x0F, // Ethernet Networking
CDC_DESC_SUBTYPE_ATM = 0x10, // ATM Networking
CDC_DESC_SUBTYPE_WHANDSET = 0x11, // Wireless Handset Control Model Functional Descriptor
CDC_DESC_SUBTYPE_MDLM = 0x12, // Mobile Direct Line Model
CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13, // MDLM Detail
CDC_DESC_SUBTYPE_DMM = 0x14, // Device Management Model
CDC_DESC_SUBTYPE_OBEX = 0x15, // OBEX Functional
CDC_DESC_SUBTYPE_COMMAND_SET = 0x16, // Command Set
CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor
CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor
CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor
CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor
} __attribute__((packed)) cdc_desc_subtype_t;
/**
* @brief USB CDC Subclass codes
*
* @see Table 4, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_SUBCLASS_DLCM = 0x01, // Direct Line Control Model
CDC_SUBCLASS_ACM = 0x02, // Abstract Control Model
CDC_SUBCLASS_TCM = 0x03, // Telephone Control Model
CDC_SUBCLASS_MCHCM = 0x04, // Multi-Channel Control Model
CDC_SUBCLASS_CAPI = 0x05, // CAPI Control Model
CDC_SUBCLASS_ECM = 0x06, // Ethernet Networking Control Model
CDC_SUBCLASS_ATM = 0x07, // ATM Networking Model
CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model
CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management
CDC_SUBCLASS_MOBILE = 0x0A, // Mobile Direct Line Model
CDC_SUBCLASS_OBEX = 0x0B, // OBEX
CDC_SUBCLASS_EEM = 0x0C, // Ethernet Emulation Model
CDC_SUBCLASS_NCM = 0x0D // Network Control Model
} __attribute__((packed)) cdc_subclass_t;
/**
* @brief USB CDC Communications Protocol Codes
*
* @see Table 5, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_COMM_PROTOCOL_NONE = 0x00, // No class specific protocol required
CDC_COMM_PROTOCOL_V250 = 0x01, // AT Commands: V.250 etc
CDC_COMM_PROTOCOL_PCAA = 0x02, // AT Commands defined by PCCA-101
CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O
CDC_COMM_PROTOCOL_GSM = 0x04, // AT Commands defined by GSM 07.07
CDC_COMM_PROTOCOL_3GPP = 0x05, // AT Commands defined by 3GPP 27.007
CDC_COMM_PROTOCOL_TIA = 0x06, // AT Commands defined by TIA for CDMA
CDC_COMM_PROTOCOL_EEM = 0x07, // Ethernet Emulation Model
CDC_COMM_PROTOCOL_EXT = 0xFE, // External Protocol: Commands defined by Command Set Functional Descriptor
CDC_COMM_PROTOCOL_VENDOR = 0xFF // Vendor-specific
} __attribute__((packed)) cdc_comm_protocol_t;
/**
* @brief USB CDC Data Protocol Codes
*
* @see Table 7, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_DATA_PROTOCOL_NONE = 0x00, // No class specific protocol required
CDC_DATA_PROTOCOL_NCM = 0x01, // Network Transfer Block
CDC_DATA_PROTOCOL_I430 = 0x30, // Physical interface protocol for ISDN BRI
CDC_DATA_PROTOCOL_HDLC = 0x31, // HDLC
CDC_DATA_PROTOCOL_Q921M = 0x50, // Management protocol for Q.921 data link protocol
CDC_DATA_PROTOCOL_Q921 = 0x51, // Data link protocol for Q.931
CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol
CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures
CDC_DATA_PROTOCOL_Q931 = 0x91, // Euro-ISDN protocol control
CDC_DATA_PROTOCOL_V120 = 0x92, // V.24 rate adaptation to ISDN
CDC_DATA_PROTOCOL_CAPI = 0x93, // CAPI Commands
CDC_DATA_PROTOCOL_VENDOR = 0xFF // Vendor-specific
} __attribute__((packed)) cdc_data_protocol_t;
/**
* @brief USB CDC Request Codes
*
* @see Table 19, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00,
CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01,
CDC_REQ_SET_COMM_FEATURE = 0x02,
CDC_REQ_GET_COMM_FEATURE = 0x03,
CDC_REQ_CLEAR_COMM_FEATURE = 0x04,
CDC_REQ_SET_AUX_LINE_STATE = 0x10,
CDC_REQ_SET_HOOK_STATE = 0x11,
CDC_REQ_PULSE_SETUP = 0x12,
CDC_REQ_SEND_PULSE = 0x13,
CDC_REQ_SET_PULSE_TIME = 0x14,
CDC_REQ_RING_AUX_JACK = 0x15,
CDC_REQ_SET_LINE_CODING = 0x20,
CDC_REQ_GET_LINE_CODING = 0x21,
CDC_REQ_SET_CONTROL_LINE_STATE = 0x22,
CDC_REQ_SEND_BREAK = 0x23,
CDC_REQ_SET_RINGER_PARMS = 0x30,
CDC_REQ_GET_RINGER_PARMS = 0x31,
CDC_REQ_SET_OPERATION_PARMS = 0x32,
CDC_REQ_GET_OPERATION_PARMS = 0x33,
CDC_REQ_SET_LINE_PARMS = 0x34,
CDC_REQ_GET_LINE_PARMS = 0x35,
CDC_REQ_DIAL_DIGITS = 0x36,
CDC_REQ_SET_UNIT_PARAMETER = 0x37,
CDC_REQ_GET_UNIT_PARAMETER = 0x38,
CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39,
CDC_REQ_GET_PROFILE = 0x3A,
CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40,
CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41,
CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42,
CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43,
CDC_REQ_GET_ETHERNET_STATISTIC = 0x44,
CDC_REQ_SET_ATM_DATA_FORMAT = 0x50,
CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51,
CDC_REQ_SET_ATM_DEFAULT_VC = 0x52,
CDC_REQ_GET_ATM_VC_STATISTICS = 0x53,
CDC_REQ_GET_NTB_PARAMETERS = 0x80,
CDC_REQ_GET_NET_ADDRESS = 0x81,
CDC_REQ_SET_NET_ADDRESS = 0x82,
CDC_REQ_GET_NTB_FORMAT = 0x83,
CDC_REQ_SET_NTB_FORMAT = 0x84,
CDC_REQ_GET_NTB_INPUT_SIZE = 0x85,
CDC_REQ_SET_NTB_INPUT_SIZE = 0x86,
CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87,
CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88,
CDC_REQ_GET_CRC_MODE = 0x89,
CDC_REQ_SET_CRC_MODE = 0x8A
} __attribute__((packed)) cdc_request_code_t;
/**
* @brief USB CDC Notification Codes
*
* @see Table 20, USB CDC specification rev. 1.2
*/
typedef enum {
CDC_NOTIF_NETWORK_CONNECTION = 0x00,
CDC_NOTIF_RESPONSE_AVAILABLE = 0x01,
CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08,
CDC_NOTIF_RING_DETECT = 0x09,
CDC_NOTIF_SERIAL_STATE = 0x20,
CDC_NOTIF_CALL_STATE_CHANGE = 0x28,
CDC_NOTIF_LINE_STATE_CHANGE = 0x29,
CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A
} __attribute__((packed)) cdc_notification_code_t;
typedef struct {
uint8_t bmRequestType;
cdc_notification_code_t bNotificationCode;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
uint8_t Data[];
} __attribute__((packed)) cdc_notification_t;
/**
* @brief USB CDC Header Functional Descriptor
*
* @see Table 15, USB CDC specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05
const cdc_desc_subtype_t bDescriptorSubtype;
uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2
} __attribute__((packed)) cdc_header_desc_t;
/**
* @brief USB CDC Union Functional Descriptor
*
* @see Table 16, USB CDC specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05
const cdc_desc_subtype_t bDescriptorSubtype;
const uint8_t bControlInterface; // Master/controlling interface
uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces
} __attribute__((packed)) cdc_union_desc_t;

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "test_cdc_acm_host.c"
INCLUDE_DIRS "."
REQUIRES cdc_acm_host unity)

View File

@ -0,0 +1,375 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.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"
#include <string.h>
#include "esp_intr_alloc.h"
#include "unity.h"
#include "soc/usb_wrap_struct.h"
static uint8_t tx_buf[] = "HELLO";
static uint8_t tx_buf2[] = "WORLD";
static int nb_of_responses;
static int nb_of_responses2;
void test_usb_force_conn_state(bool connected, TickType_t delay_ticks)
{
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
usb_wrap_dev_t *wrap = &USB_WRAP;
if (connected) {
//Disable test mode to return to previous internal PHY configuration
wrap->test_conf.test_enable = 0;
} else {
/*
Mimic a disconnection by using the internal PHY's test mode.
Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0,
this will look like a disconnection.
*/
wrap->test_conf.val = 0;
wrap->test_conf.test_usb_wrap_oe = 1;
wrap->test_conf.test_enable = 1;
}
}
void usb_lib_task(void *arg)
{
// Install USB Host driver. Should only be called once in entire application
const usb_host_config_t host_config = {
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config));
printf("USB Host installed\n");
xTaskNotifyGive(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: clean up\n");
// The device should not have been freed yet, so we expect an ESP_ERR_NOT_FINISHED
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
printf("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
TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall());
vTaskDelete(NULL);
}
void test_install_cdc_driver(void)
{
// Create a task that will handle USB library events
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(usb_lib_task, "usb_lib", 4*4096, xTaskGetCurrentTaskHandle(), 10, NULL));
ulTaskNotifyTake(false, 1000);
printf("Installing CDC-ACM driver\n");
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_install(NULL));
}
/* ------------------------------- Callbacks -------------------------------- */
static void handle_rx(uint8_t *data, size_t data_len, void *arg)
{
printf("Data received\n");
nb_of_responses++;
TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len);
}
static void handle_rx2(uint8_t *data, size_t data_len, void *arg)
{
printf("Data received 2\n");
nb_of_responses2++;
TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len);
}
static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
{
switch (event->type) {
case CDC_ACM_HOST_ERROR:
printf("Error event %d\n", event->data.error);
break;
case CDC_ACM_HOST_SERIAL_STATE:
break;
case CDC_ACM_HOST_NETWORK_CONNECTION:
break;
case CDC_ACM_HOST_DEVICE_DISCONNECTED:
printf("Disconnection event\n");
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_hdl));
xTaskNotifyGive(user_ctx);
break;
default:
assert(false);
}
}
/* Basic test to check CDC communication:
* open/read/write/close device
* CDC-ACM specific commands: set/get_line_coding, set_control_line_state */
TEST_CASE("USB Host CDC-ACM driver: Basic test", "[cdc_acm][ignore]")
{
nb_of_responses = 0;
cdc_acm_dev_hdl_t cdc_dev = NULL;
test_install_cdc_driver();
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 500,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = handle_rx,
.user_arg = tx_buf,
};
printf("Opening CDC-ACM device\n");
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
TEST_ASSERT_NOT_NULL(cdc_dev);
cdc_acm_host_desc_print(cdc_dev);
vTaskDelay(100);
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
vTaskDelay(100); // Wait until responses are processed
// We sent two messages, should get two responses
TEST_ASSERT_EQUAL(2, nb_of_responses);
cdc_acm_line_coding_t line_coding_get;
const cdc_acm_line_coding_t line_coding_set = {
.dwDTERate = 9600,
.bDataBits = 7,
.bParityType = 1,
.bCharFormat = 1,
};
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_set(cdc_dev, &line_coding_set));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get));
TEST_ASSERT_EQUAL_MEMORY(&line_coding_set, &line_coding_get, sizeof(cdc_acm_line_coding_t));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
vTaskDelay(20); //Short delay to allow task to be cleaned up
}
/* Test communication with multiple CDC-ACM devices from one thread */
TEST_CASE("USB Host CDC-ACM driver: Multiple devices test", "[cdc_acm][ignore]")
{
nb_of_responses = 0;
nb_of_responses2 = 0;
test_install_cdc_driver();
printf("Opening 2 CDC-ACM devices\n");
cdc_acm_dev_hdl_t cdc_dev1, cdc_dev2;
cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 1000,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = handle_rx,
.user_arg = tx_buf,
};
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev1)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
dev_config.data_cb = handle_rx2;
dev_config.user_arg = tx_buf2;
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 2, &dev_config, &cdc_dev2)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
TEST_ASSERT_NOT_NULL(cdc_dev1);
TEST_ASSERT_NOT_NULL(cdc_dev2);
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev1, tx_buf, sizeof(tx_buf), 1000));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev2, tx_buf2, sizeof(tx_buf2), 1000));
vTaskDelay(100); // Wait for RX callbacks
// We sent two messages, should get two responses
TEST_ASSERT_EQUAL(1, nb_of_responses);
TEST_ASSERT_EQUAL(1, nb_of_responses2);
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev1));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev2));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
//Short delay to allow task to be cleaned up
vTaskDelay(20);
}
#define MULTIPLE_THREADS_TRANSFERS_NUM 5
#define MULTIPLE_THREADS_TASKS_NUM 4
void tx_task(void *arg)
{
cdc_acm_dev_hdl_t cdc_dev = (cdc_acm_dev_hdl_t) arg;
// Send multiple transfers to make sure that some of them will run at the same time
for (int i = 0; i < MULTIPLE_THREADS_TRANSFERS_NUM; i++) {
// BULK endpoints
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
// CTRL endpoints
cdc_acm_line_coding_t line_coding_get;
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false));
}
vTaskDelete(NULL);
}
/**
* @brief Multiple threads test
*
* In this test, one CDC device is accessed from multiple threads.
* It has to be opened/closed just once, though.
*/
TEST_CASE("USB Host CDC-ACM driver: Multiple threads test", "[cdc_acm][ignore]")
{
nb_of_responses = 0;
cdc_acm_dev_hdl_t cdc_dev;
test_install_cdc_driver();
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 5000,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = handle_rx,
.user_arg = tx_buf,
};
printf("Opening CDC-ACM device\n");
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
TEST_ASSERT_NOT_NULL(cdc_dev);
// Create two tasks that will try to access cdc_dev
for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) {
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(tx_task, "CDC TX", 4096, cdc_dev, i + 3, NULL));
}
// Wait until all tasks finish
vTaskDelay(pdMS_TO_TICKS(500));
TEST_ASSERT_EQUAL(MULTIPLE_THREADS_TASKS_NUM * MULTIPLE_THREADS_TRANSFERS_NUM, nb_of_responses);
// Clean-up
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
vTaskDelay(20);
}
/* Test CDC driver reaction to USB device sudden disconnection */
TEST_CASE("USB Host CDC-ACM driver: Sudden disconnection test", "[cdc_acm][ignore]")
{
test_install_cdc_driver();
cdc_acm_dev_hdl_t cdc_dev;
cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 1000,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = handle_rx
};
dev_config.user_arg = xTaskGetCurrentTaskHandle();
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
TEST_ASSERT_NOT_NULL(cdc_dev);
test_usb_force_conn_state(false, pdMS_TO_TICKS(10));
// Notify will succeed only if CDC_ACM_HOST_DEVICE_DISCONNECTED notification was generated
TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(false, pdMS_TO_TICKS(100)));
test_usb_force_conn_state(true, 0); // Switch back to real PHY
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
vTaskDelay(20); //Short delay to allow task to be cleaned up
}
/**
* @brief CDC-ACM error handling test
*
* There are multiple erroneous scenarios checked in this test:
*
* -# Install CDC-ACM driver without USB Host
* -# Open device without installed driver
* -# Uninstall driver before installing it
* -# Open non-existent device
* -# Open the same device twice
* -# Uninstall driver with open devices
* -# Send data that is too large
* -# Send unsupported CDC request
* -# Write to read-only device
*/
TEST_CASE("USB Host CDC-ACM driver: Error handling", "[cdc_acm][ignore]")
{
cdc_acm_dev_hdl_t cdc_dev;
cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 500,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = handle_rx
};
// Install CDC-ACM driver without USB Host
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_install(NULL));
// Open device without installed driver
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
// Uninstall driver before installing it
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall());
// Properly install USB and CDC drivers
test_install_cdc_driver();
// Open non-existent device
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, cdc_acm_host_open(0x303A, 0x1234, 0, &dev_config, &cdc_dev)); // 0x303A:0x1234 this device is not connected to USB Host
TEST_ASSERT_NULL(cdc_dev);
// Open regular device
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
TEST_ASSERT_NOT_NULL(cdc_dev);
// Open one CDC-ACM device twice //@todo this test is commented out due to bug in usb_host
//cdc_acm_dev_hdl_t cdc_dev_test;
//TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev_test));
//TEST_ASSERT_NULL(cdc_dev_test);
// Uninstall driver with open devices
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall());
// Send data that is too large and NULL data
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, 1024, 1000));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_data_tx_blocking(cdc_dev, NULL, 10, 1000));
// Change mode to read-only and try to write to it
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
dev_config.out_buffer_size = 0; // Read-only device
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
TEST_ASSERT_NOT_NULL(cdc_dev);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
// Send unsupported CDC request (TinyUSB accepts SendBreak command, eventhough it doesn't support it)
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_break(cdc_dev, 100));
// Clean-up
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
vTaskDelay(20);
}
#endif

View File

@ -0,0 +1,10 @@
# The following 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.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
# Set the components to include the tests for.
set(TEST_COMPONENTS "cdc_acm_host" CACHE STRING "List of components to test")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(usb_test_app)

View File

@ -0,0 +1,14 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB Host CDC-ACM driver test project
Main purpose of this application is to test the USB Host CDC-ACM driver.
It tests basic functionality of the driver like open/close/read/write operations,
advanced features like CDC control request, multi-threaded or multi-device access,
as well as reaction to sudden disconnection and other error states.
## Hardware Required
This test expects that TinyUSB dual CDC device with VID = 0x303A and PID = 0x4002
is connected to the USB host.

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "usb_test_main.c"
INCLUDE_DIRS ""
REQUIRES unity)

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
void app_main(void)
{
UNITY_BEGIN();
unity_run_all_tests();
UNITY_END();
}