Merge branch 'feature/usb/update_cdc_msc_examples_v4.4' into 'release/v4.4'

usb: Update CDC and MSC host examples to use drivers from ESP Registry (backport v4.4)

See merge request espressif/esp-idf!23074
This commit is contained in:
morris 2023-06-25 15:53:57 +08:00
commit 0a3f7e81a3
43 changed files with 528 additions and 4977 deletions

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,3 +0,0 @@
dependencies:
idf: ">=4.4"
igrr/libnmea: ">=0.1.1"

View File

@ -4,6 +4,5 @@
# 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

@ -38,18 +38,12 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
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
...
Device descriptor is printed here
...
I (1666) USB-CDC: Data received
I (1666) USB-CDC: 0x3ffc4c20 41 54 0d |AT.|
I (2666) USB-CDC: Data received

View File

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

View File

@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
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
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

@ -4,6 +4,5 @@
# 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)
project(cdc_acm_vcp)

View File

@ -0,0 +1,43 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# 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 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 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
### Hardware Required
* ESP board with USB-OTG supported
* Silicon Labs CP210x, FTDI FT23x or CP34x USB to UART converter
Connect USB_D+, USB_D-, GND and +5V signals of your ESP chip to matching signals on USB to UART converter.
#### Pin Assignment
See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments).
### Build and Flash
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.

View File

@ -0,0 +1,9 @@
idf_component_register(
SRCS "cdc_acm_vcp_example_main.cpp"
INCLUDE_DIRS "."
)
# We cannot set property CXX_STANDARD to '17'
# because CMake 3.5 (shipped with IDF 4.4) does not know this standard
# -fconcepts flag should be propagated from usb_host_vcp component...
target_compile_options(${COMPONENT_LIB} PRIVATE -fconcepts -std=gnu++17)

View File

@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/cdc_acm_host.h"
#include "usb/vcp_ch34x.hpp"
#include "usb/vcp_cp210x.hpp"
#include "usb/vcp_ftdi.hpp"
#include "usb/vcp.hpp"
#include "usb/usb_host.h"
using namespace esp_usb;
// Change these values to match your needs
#define EXAMPLE_BAUDRATE (115200)
#define EXAMPLE_STOP_BITS (0) // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits
#define EXAMPLE_PARITY (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
#define EXAMPLE_DATA_BITS (8)
namespace {
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
*/
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
*/
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 = %d", event->data.error);
break;
case CDC_ACM_HOST_DEVICE_DISCONNECTED:
ESP_LOGI(TAG, "Device suddenly disconnected");
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: 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
*
* 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
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, NULL, 10, NULL);
assert(task_created == pdTRUE);
ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
// Register VCP drivers to VCP service
VCP::register_driver<FT23x>();
VCP::register_driver<CP210x>();
VCP::register_driver<CH34x>();
// Do everything else in a loop, so we can demonstrate USB device reconnections
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 = 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 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));
if (vcp == nullptr) {
ESP_LOGI(TAG, "Failed to open VCP device");
continue;
}
vTaskDelay(10);
ESP_LOGI(TAG, "Setting up line coding");
cdc_acm_line_coding_t line_coding = {
.dwDTERate = EXAMPLE_BAUDRATE,
.bCharFormat = EXAMPLE_STOP_BITS,
.bParityType = EXAMPLE_PARITY,
.bDataBits = EXAMPLE_DATA_BITS,
};
ESP_ERROR_CHECK(vcp->line_coding_set(&line_coding));
/*
Now the USB-to-UART converter is configured and receiving data.
You can use standard CDC-ACM API to interact with it. E.g.
ESP_ERROR_CHECK(vcp->set_control_line_state(false, true));
ESP_ERROR_CHECK(vcp->tx_blocking((uint8_t *)"Test string", 12));
*/
// Send some dummy data
ESP_LOGI(TAG, "Sending data through CdcAcmDevice");
uint8_t data[] = "test_string";
ESP_ERROR_CHECK(vcp->tx_blocking(data, sizeof(data)));
ESP_ERROR_CHECK(vcp->set_control_line_state(true, true));
// We are done. Wait for device disconnection and start over
ESP_LOGI(TAG, "Done. You can reconnect the VCP device to run again.");
xSemaphoreTake(device_disconnected_sem, portMAX_DELAY);
}
}

View File

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

View File

@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@ -1,15 +0,0 @@
set(srcs)
set(include)
# As CONFIG_USB_OTG_SUPPORTED comes from Kconfig, it is not evaluated yet
# when components are being registered.
set(require usb)
if(CONFIG_USB_OTG_SUPPORTED)
list(APPEND srcs "cdc_acm_host.c")
list(APPEND include "include")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${include}
REQUIRES ${require}
)

View File

@ -1,46 +0,0 @@
# 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)

View File

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

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

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

View File

@ -1,378 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2022 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 "esp_private/usb_phy.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;
static usb_phy_handle_t phy_hdl = NULL;
static void force_conn_state(bool connected, TickType_t delay_ticks)
{
TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl);
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
}
void usb_lib_task(void *arg)
{
//Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
.gpio_conf = NULL,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl));
// Install USB Host driver. Should only be called once in entire application
const usb_host_config_t host_config = {
.skip_phy_setup = true,
.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());
//Tear down USB PHY
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl));
phy_hdl = NULL;
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);
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)));
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

@ -6,7 +6,8 @@
## Overview
This example demonstrates usage of Mass Storage Class to get access to storage on USB memory stick.
Example caries out read and write file operations, as USB storage is mounted to Virtual filesystem.
Upon connection of USB stick, storage is mounted to Virtual filesystem. Example then creates `ESP` subdirectory(if not present already), as well as `text.txt` file. Its content is then repetitively printed to monitor until USB stick is manually ejected. User can decide whether or not to deinitialize the whole
USB stack or not by shorting GPIO10 to ground. When GPIO10 is left unconnected USB stack is not deinitialized, USB stick can be plugged-in again.
### Hardware Required
@ -29,6 +30,8 @@ ESP BOARD USB CONNECTOR (type A)
--
```
Additionally, GPIO10 can be shorted to ground in order to deinitialize USB stack after ejecting USB stick.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:

View File

@ -1,9 +0,0 @@
set(sources src/msc_scsi_bot.c
src/diskio_usb.c
src/msc_host.c
src/msc_host_vfs.c)
idf_component_register( SRCS ${sources}
INCLUDE_DIRS include
PRIV_INCLUDE_DIRS private_include
REQUIRES usb fatfs )

View File

@ -1,32 +0,0 @@
# USB Host MSC (Mass Storage Class) Driver
This directory contains an implementation of a USB Mass Storage Class Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
MSC driver allows access to USB flash drivers using the BOT “Bulk-Only Transport” protocol and the Transparent SCSI command set.
## Usage
- First, usb host library has to be initialized by calling `usb_host_install`
- USB Host Library events have to be handled by invoking `usb_host_lib_handle_events` periodically.
In general, an application should spawn a dedicated task handle USB Host Library events.
However, in order to save RAM, an already existing task can also be used to call `usb_host_lib_handle_events`.
- Mass Storage Class driver is installed by calling `usb_msc_install` function along side with configuration.
- Supplied configuration contains user provided callback function invoked whenever MSC device is connected/disconnected
and optional parameters for creating background task handling MSC related events.
Alternatively, user can call `usb_msc_handle_events` function from already existing task.
- After receiving `MSC_DEVICE_CONNECTED` event, user has to install device with `usb_msc_install_device` function,
obtaining MSC device handle.
- USB descriptors can be printed out with `usb_msc_print_descriptors` and general information about MSC device retrieved
with `from usb_msc_get_device_info` function.
- Obtained device handle is then used in helper function `usb_msc_vfs_register` mounting USB Disk to Virtual filesystem.
- At this point, standard C functions for accessing storage (`fopen`, `fwrite`, `fread`, `mkdir` etc.) can be carried out.
- In order to uninstall the whole USB stack, deinitializing counterparts to functions above has to be called in reverse order.
## Known issues
- Driver only supports USB 2.0 flash drives using the BOT “Bulk-Only Transport” protocol and the Transparent SCSI command set
- Composite USB devices are not supported
## Troubleshooting
After connecting composite USB device, driver prints `COMPOSITE DEVICES UNSUPPORTED`

View File

@ -1,169 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <wchar.h>
#include <stdint.h>
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_ERR_MSC_HOST_BASE 0x1700 /*!< MSC host error code base */
#define ESP_ERR_MSC_MOUNT_FAILED (ESP_ERR_MSC_HOST_BASE + 1) /*!< Failed to mount storage */
#define ESP_ERR_MSC_FORMAT_FAILED (ESP_ERR_MSC_HOST_BASE + 2) /*!< Failed to format storage */
#define ESP_ERR_MSC_INTERNAL (ESP_ERR_MSC_HOST_BASE + 3) /*!< MSC host internal error */
#define MSC_STR_DESC_SIZE 32
typedef struct msc_host_device *msc_host_device_handle_t; /**< Handle to a Mass Storage Device */
/**
* @brief USB Mass Storage event containing event type and associated device handle.
*/
typedef struct {
enum {
MSC_DEVICE_CONNECTED, /**< MSC device has been connected to the system.*/
MSC_DEVICE_DISCONNECTED, /**< MSC device has been disconnected from the system.*/
} event;
union {
uint8_t address; /**< Address of connected MSC device.*/
msc_host_device_handle_t handle; /**< MSC device handle to disconnected device.*/
} device;
} msc_host_event_t;
/**
* @brief USB Mass Storage event callback.
*
* @param[in] event mass storage event
*/
typedef void (*msc_host_event_cb_t)(const msc_host_event_t *event, void *arg);
/**
* @brief MSC configuration structure.
*/
typedef struct {
bool create_backround_task; /**< When set to true, background task handling usb events is created.
Otherwise user has to periodically call msc_host_handle_events function */
size_t task_priority; /**< Task priority of crated background task */
size_t stack_size; /**< Stack size of crated background task */
BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */
msc_host_event_cb_t callback; /**< Callback invoked when MSC event occurs. Must not be NULL. */
void *callback_arg; /**< User provided argument passed to callback */
} msc_host_driver_config_t;
/**
* @brief MSC device info.
*/
typedef struct {
uint32_t sector_count;
uint32_t sector_size;
uint16_t idProduct;
uint16_t idVendor;
wchar_t iManufacturer[MSC_STR_DESC_SIZE];
wchar_t iProduct[MSC_STR_DESC_SIZE];
wchar_t iSerialNumber[MSC_STR_DESC_SIZE];
} msc_host_device_info_t;
/**
* @brief Install USB Host Mass Storage Class driver
*
* @param[in] config configuration structure MSC to create
* @return esp_err_r
*/
esp_err_t msc_host_install(const msc_host_driver_config_t *config);
/**
* @brief Uninstall Mass Storage Class driver
* @return esp_err_t
*/
esp_err_t msc_host_uninstall(void);
/**
* @brief Initialization of MSC device.
*
* @param[in] device_address Device address obtained from MSC callback provided upon connection and enumeration
* @param[out] device Mass storage device handle to be used for subsequent calls.
* @return esp_err_t
*/
esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *device);
/**
* @brief Deinitialization of MSC device.
*
* @param[in] device Device handle obtained from msc_host_install_device function
* @return esp_err_t
*/
esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device);
/**
* @brief Helper function for reading sector from mass storage device.
*
* @warning This call is not thread safe and should not be combined
* with accesses to storage through file system.
*
* @note Provided sector and size cannot exceed
* sector_count and sector_size obtained from msc_host_device_info_t
*
* @param[in] device Device handle
* @param[in] sector Number of sector to be read
* @param[out] data Buffer into which data will be written
* @param[in] size Number of bytes to be read
* @return esp_err_t
*/
esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size);
/**
* @brief Helper function for writing sector to mass storage device.
*
* @warning This call is not thread safe and should not be combined
* with accesses to storare through file system.
*
* @note Provided sector and size cannot exceed
* sector_count and sector_size obtained from msc_host_device_info_t
*
* @param[in] device Device handle
* @param[in] sector Number of sector to be read
* @param[in] data Data to be written to the sector
* @param[in] size Number of bytes to be written
* @return esp_err_t
*/
esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size);
/**
* @brief Handle MSC HOST events.
*
* @param[in] timeout_ms Timeout in miliseconds
* @return esp_err_t
*/
esp_err_t msc_host_handle_events(uint32_t timeout_ms);
/**
* @brief Gets devices information.
*
* @warning This call is not thread safe and should not be combined
* with accesses to storare through file system.
*
* @param[in] device Handle to device
* @param[out] info Structure to be populated with device info
* @return esp_err_t
*/
esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info);
/**
* @brief Print configuration descriptor.
*
* @param[in] device Handle of MSC device
* @return esp_err_t
*/
esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_vfs_fat.h"
#include "msc_host.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct msc_host_vfs *msc_host_vfs_handle_t; /**< VFS handle to attached Mass Storage device */
/**
* @brief Register MSC device to Virtual filesystem.
*
* @param[in] device Device handle obtained from MSC callback provided upon initialization
* @param[in] base_path Base VFS path to be used to access file storage
* @param[in] mount_config Mount configuration.
* @param[out] vfs_handle Handle to MSC device associated with registered VFS
* @return esp_err_t
*/
esp_err_t msc_host_vfs_register(msc_host_device_handle_t device,
const char *base_path,
const esp_vfs_fat_mount_config_t *mount_config,
msc_host_vfs_handle_t *vfs_handle);
/**
* @brief Unregister MSC device from Virtual filesystem.
*
* @param[in] vfs_handle VFS handle obtained from MSC callback provided upon initialization
* @return esp_err_t
*/
esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle);
#ifdef __cplusplus
}
#endif

View File

@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Mass storage disk initialization structure
*/
typedef struct {
uint32_t block_size; /**< Block size */
uint32_t block_count; /**< Block count */
} usb_disk_t;
/**
* @brief Register mass storage disk to fat file system
*
* @param[in] pdrv Number of free drive obtained from ff_diskio_get_drive() function
* @param[in] disk usb_disk_t structure
*/
void ff_diskio_register_msc(uint8_t pdrv, usb_disk_t *disk);
/**
* @brief Obtains number of drive assigned to usb disk upon calling ff_diskio_register_msc()
*
* @param[in] disk usb_disk_t structure
* @return Drive number
*/
uint8_t ff_diskio_get_pdrv_disk(const usb_disk_t *disk);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -1,61 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <sys/queue.h>
#include "esp_err.h"
#include "esp_check.h"
#include "diskio_usb.h"
#include "usb/usb_host.h"
#include "usb/usb_types_stack.h"
#include "freertos/semphr.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef enum {
MSC_EP_OUT,
MSC_EP_IN
} msc_endpoint_t;
typedef struct {
uint16_t bulk_in_mps;
uint8_t bulk_in_ep;
uint8_t bulk_out_ep;
uint8_t iface_num;
} msc_config_t;
typedef struct msc_host_device {
STAILQ_ENTRY(msc_host_device) tailq_entry;
usb_transfer_status_t transfer_status;
SemaphoreHandle_t transfer_done;
usb_device_handle_t handle;
usb_transfer_t *xfer;
msc_config_t config;
usb_disk_t disk;
} msc_device_t;
esp_err_t msc_bulk_transfer(msc_device_t *device_handle, uint8_t *data, size_t size, msc_endpoint_t ep);
esp_err_t msc_control_transfer(msc_device_t *device_handle, usb_transfer_t *xfer, size_t len);
#define MSC_GOTO_ON_ERROR(exp) ESP_GOTO_ON_ERROR(exp, fail, TAG, "")
#define MSC_GOTO_ON_FALSE(exp, err) ESP_GOTO_ON_FALSE( (exp), err, fail, TAG, "" )
#define MSC_RETURN_ON_ERROR(exp) ESP_RETURN_ON_ERROR((exp), TAG, "")
#define MSC_RETURN_ON_FALSE(exp, err) ESP_RETURN_ON_FALSE( (exp), (err), TAG, "")
#define MSC_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "")
#ifdef __cplusplus
}
#endif

View File

@ -1,56 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "msc_common.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct {
uint8_t key;
uint8_t code;
uint8_t code_q;
} scsi_sense_data_t;
esp_err_t scsi_cmd_read10(msc_device_t *device,
uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size);
esp_err_t scsi_cmd_write10(msc_device_t *device,
const uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size);
esp_err_t scsi_cmd_read_capacity(msc_device_t *device,
uint32_t *block_size,
uint32_t *block_count);
esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense);
esp_err_t scsi_cmd_unit_ready(msc_device_t *device);
esp_err_t scsi_cmd_inquiry(msc_device_t *device);
esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent);
esp_err_t scsi_cmd_mode_sense(msc_device_t *device);
esp_err_t msc_mass_reset(msc_device_t *device);
esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun);
#ifdef __cplusplus
}
#endif

View File

@ -1,118 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "diskio_impl.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_log.h"
#include "diskio_usb.h"
#include "msc_scsi_bot.h"
#include "msc_common.h"
#include "usb/usb_types_stack.h"
static usb_disk_t *s_disks[FF_VOLUMES] = { NULL };
static const char *TAG = "diskio_usb";
static DSTATUS usb_disk_initialize (BYTE pdrv)
{
return RES_OK;
}
static DSTATUS usb_disk_status (BYTE pdrv)
{
return RES_OK;
}
static DRESULT usb_disk_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
assert(pdrv < FF_VOLUMES);
assert(s_disks[pdrv]);
esp_err_t err;
usb_disk_t *disk = s_disks[pdrv];
size_t sector_size = disk->block_size;
msc_device_t *dev = __containerof(disk, msc_device_t, disk);
for (int i = 0; i < count; i++) {
err = scsi_cmd_read10(dev, &buff[i * sector_size], sector + i, 1, sector_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "scsi_cmd_read10 failed (%d)", err);
return RES_ERROR;
}
}
return RES_OK;
}
static DRESULT usb_disk_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
assert(pdrv < FF_VOLUMES);
assert(s_disks[pdrv]);
esp_err_t err;
usb_disk_t *disk = s_disks[pdrv];
size_t sector_size = disk->block_size;
msc_device_t *dev = __containerof(disk, msc_device_t, disk);
for (int i = 0; i < count; i++) {
err = scsi_cmd_write10(dev, &buff[i * sector_size], sector + i, 1, sector_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "scsi_cmd_write10 failed (%d)", err);
return RES_ERROR;
}
}
return RES_OK;
}
static DRESULT usb_disk_ioctl (BYTE pdrv, BYTE cmd, void *buff)
{
assert(pdrv < FF_VOLUMES);
assert(s_disks[pdrv]);
usb_disk_t *disk = s_disks[pdrv];
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*((DWORD *) buff) = disk->block_count;
return RES_OK;
case GET_SECTOR_SIZE:
*((WORD *) buff) = disk->block_size;
return RES_OK;
case GET_BLOCK_SIZE:
return RES_ERROR;
}
return RES_ERROR;
}
void ff_diskio_register_msc(BYTE pdrv, usb_disk_t *disk)
{
assert(pdrv < FF_VOLUMES);
static const ff_diskio_impl_t usb_disk_impl = {
.init = &usb_disk_initialize,
.status = &usb_disk_status,
.read = &usb_disk_read,
.write = &usb_disk_write,
.ioctl = &usb_disk_ioctl
};
s_disks[pdrv] = disk;
ff_diskio_register(pdrv, &usb_disk_impl);
}
BYTE ff_diskio_get_pdrv_disk(const usb_disk_t *disk)
{
for (int i = 0; i < FF_VOLUMES; i++) {
if (disk == s_disks[i]) {
return i;
}
}
return 0xff;
}

View File

@ -1,553 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/param.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/usb_host.h"
#include "diskio_usb.h"
#include "msc_common.h"
#include "msc_host.h"
#include "msc_scsi_bot.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_helpers.h"
static portMUX_TYPE msc_lock = portMUX_INITIALIZER_UNLOCKED;
#define MSC_ENTER_CRITICAL() portENTER_CRITICAL(&msc_lock)
#define MSC_EXIT_CRITICAL() portEXIT_CRITICAL(&msc_lock)
#define MSC_GOTO_ON_FALSE_CRITICAL(exp, err) \
do { \
if(!(exp)) { \
MSC_EXIT_CRITICAL(); \
ret = err; \
goto fail; \
} \
} while(0)
#define MSC_RETURN_ON_FALSE_CRITICAL(exp, err) \
do { \
if(!(exp)) { \
MSC_EXIT_CRITICAL(); \
return err; \
} \
} while(0)
#define WAIT_FOR_READY_TIMEOUT_MS 3000
#define TAG "USB_MSC"
#define SCSI_COMMAND_SET 0x06
#define BULK_ONLY_TRANSFER 0x50
#define MSC_NO_SENSE 0x00
#define MSC_NOT_READY 0x02
#define MSC_UNIT_ATTENTION 0x06
typedef struct {
usb_host_client_handle_t client_handle;
msc_host_event_cb_t user_cb;
void *user_arg;
SemaphoreHandle_t all_events_handled;
volatile bool end_client_event_handling;
} msc_driver_t;
static msc_driver_t *s_msc_driver;
STAILQ_HEAD(devices, msc_host_device) devices_tailq;
static const usb_standard_desc_t *next_interface_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset)
{
return usb_parse_next_descriptor_of_type(desc, len, USB_W_VALUE_DT_INTERFACE, (int *)offset);
}
static const usb_standard_desc_t *next_endpoint_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset)
{
return usb_parse_next_descriptor_of_type(desc, len, USB_B_DESCRIPTOR_TYPE_ENDPOINT, (int *)offset);
}
static const usb_intf_desc_t *find_msc_interface(const usb_config_desc_t *config_desc, size_t *offset)
{
size_t total_length = config_desc->wTotalLength;
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc;
next_desc = next_interface_desc(next_desc, total_length, offset);
while ( next_desc ) {
const usb_intf_desc_t *ifc_desc = (const usb_intf_desc_t *)next_desc;
if ( ifc_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE &&
ifc_desc->bInterfaceSubClass == SCSI_COMMAND_SET &&
ifc_desc->bInterfaceProtocol == BULK_ONLY_TRANSFER ) {
return ifc_desc;
}
next_desc = next_interface_desc(next_desc, total_length, offset);
};
return NULL;
}
/**
* @brief Extracts configuration from configuration descriptor.
*
* @note Passes interface and endpoint descriptors to obtain:
* - interface number, IN endpoint, OUT endpoint, max. packet size
*
* @param[in] cfg_desc Configuration descriptor
* @param[out] cfg Obtained configuration
* @return esp_err_t
*/
static esp_err_t extract_config_from_descriptor(const usb_config_desc_t *cfg_desc, msc_config_t *cfg)
{
size_t offset = 0;
size_t total_len = cfg_desc->wTotalLength;
const usb_intf_desc_t *ifc_desc = find_msc_interface(cfg_desc, &offset);
assert(ifc_desc);
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)ifc_desc;
const usb_ep_desc_t *ep_desc = NULL;
cfg->iface_num = ifc_desc->bInterfaceNumber;
next_desc = next_endpoint_desc(next_desc, total_len, &offset);
MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED);
ep_desc = (const usb_ep_desc_t *)next_desc;
if (ep_desc->bEndpointAddress & 0x80) {
cfg->bulk_in_ep = ep_desc->bEndpointAddress;
cfg->bulk_in_mps = ep_desc->wMaxPacketSize;
} else {
cfg->bulk_out_ep = ep_desc->bEndpointAddress;
}
next_desc = next_endpoint_desc(next_desc, total_len, &offset);
MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED);
ep_desc = (const usb_ep_desc_t *)next_desc;
if (ep_desc->bEndpointAddress & 0x80) {
cfg->bulk_in_ep = ep_desc->bEndpointAddress;
cfg->bulk_in_mps = ep_desc->wMaxPacketSize;
} else {
cfg->bulk_out_ep = ep_desc->bEndpointAddress;
}
return ESP_OK;
}
static esp_err_t msc_deinit_device(msc_device_t *dev, bool install_failed)
{
MSC_ENTER_CRITICAL();
MSC_RETURN_ON_FALSE_CRITICAL( dev, ESP_ERR_INVALID_STATE );
STAILQ_REMOVE(&devices_tailq, dev, msc_host_device, tailq_entry);
MSC_EXIT_CRITICAL();
if (dev->transfer_done) {
vSemaphoreDelete(dev->transfer_done);
}
if (install_failed) {
// Error code is unchecked, as it's unknown at what point installation failed.
usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num);
usb_host_device_close(s_msc_driver->client_handle, dev->handle);
usb_host_transfer_free(dev->xfer);
} else {
MSC_RETURN_ON_ERROR( usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num) );
MSC_RETURN_ON_ERROR( usb_host_device_close(s_msc_driver->client_handle, dev->handle) );
MSC_RETURN_ON_ERROR( usb_host_transfer_free(dev->xfer) );
}
free(dev);
return ESP_OK;
}
// Some MSC devices requires to change its internal state from non-ready to ready
static esp_err_t msc_wait_for_ready_state(msc_device_t *dev, size_t timeout_ms)
{
esp_err_t err;
scsi_sense_data_t sense;
uint32_t trials = MAX(1, timeout_ms / 100);
do {
err = scsi_cmd_unit_ready(dev);
if (err != ESP_OK) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(dev, &sense) );
if (sense.key != MSC_NOT_READY &&
sense.key != MSC_UNIT_ATTENTION &&
sense.key != MSC_NO_SENSE) {
return ESP_ERR_MSC_INTERNAL;
}
}
vTaskDelay( pdMS_TO_TICKS(100) );
} while (trials-- && err);
return err;
}
static bool is_mass_storage_device(uint8_t dev_addr)
{
size_t dummy = 0;
bool is_msc_device = false;
usb_device_handle_t device;
const usb_config_desc_t *config_desc;
if ( usb_host_device_open(s_msc_driver->client_handle, dev_addr, &device) == ESP_OK) {
if ( usb_host_get_active_config_descriptor(device, &config_desc) == ESP_OK ) {
if ( find_msc_interface(config_desc, &dummy) ) {
is_msc_device = true;
} else {
ESP_LOGD(TAG, "Connected USB device is not MSC");
}
}
usb_host_device_close(s_msc_driver->client_handle, device);
}
return is_msc_device;
}
static void event_handler_task(void *arg)
{
while (1) {
usb_host_client_handle_events(s_msc_driver->client_handle, pdMS_TO_TICKS(50));
if (s_msc_driver->end_client_event_handling) {
break;
}
}
usb_host_client_unblock(s_msc_driver->client_handle);
ESP_ERROR_CHECK( usb_host_client_deregister(s_msc_driver->client_handle) );
xSemaphoreGive(s_msc_driver->all_events_handled);
vTaskDelete(NULL);
}
static msc_device_t *find_msc_device(usb_device_handle_t device_handle)
{
msc_host_device_handle_t device;
STAILQ_FOREACH(device, &devices_tailq, tailq_entry) {
if (device_handle == device->handle) {
return device;
}
}
return NULL;
}
static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg)
{
if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
if (is_mass_storage_device(event->new_dev.address)) {
const msc_host_event_t msc_event = {
.event = MSC_DEVICE_CONNECTED,
.device.address = event->new_dev.address,
};
s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg);
}
} else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
msc_device_t *msc_device = find_msc_device(event->dev_gone.dev_hdl);
if (msc_device) {
const msc_host_event_t msc_event = {
.event = MSC_DEVICE_DISCONNECTED,
.device.handle = msc_device,
};
s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg);
}
}
}
esp_err_t msc_host_install(const msc_host_driver_config_t *config)
{
esp_err_t ret;
MSC_RETURN_ON_INVALID_ARG(config);
MSC_RETURN_ON_INVALID_ARG(config->callback);
if ( config->create_backround_task ) {
MSC_RETURN_ON_FALSE(config->stack_size != 0, ESP_ERR_INVALID_ARG);
MSC_RETURN_ON_FALSE(config->task_priority != 0, ESP_ERR_INVALID_ARG);
}
MSC_RETURN_ON_FALSE(!s_msc_driver, ESP_ERR_INVALID_STATE);
msc_driver_t *driver = calloc(1, sizeof(msc_driver_t));
MSC_RETURN_ON_FALSE(driver, ESP_ERR_NO_MEM);
driver->user_cb = config->callback;
driver->user_arg = config->callback_arg;
usb_host_client_config_t client_config = {
.async.client_event_callback = client_event_cb,
.async.callback_arg = NULL,
.max_num_event_msg = 10,
};
driver->end_client_event_handling = false;
driver->all_events_handled = xSemaphoreCreateBinary();
MSC_GOTO_ON_FALSE(driver->all_events_handled, ESP_ERR_NO_MEM);
MSC_GOTO_ON_ERROR( usb_host_client_register(&client_config, &driver->client_handle) );
MSC_ENTER_CRITICAL();
MSC_GOTO_ON_FALSE_CRITICAL(!s_msc_driver, ESP_ERR_INVALID_STATE);
s_msc_driver = driver;
STAILQ_INIT(&devices_tailq);
MSC_EXIT_CRITICAL();
if (config->create_backround_task) {
BaseType_t task_created = xTaskCreatePinnedToCore(
event_handler_task, "USB MSC", config->stack_size,
NULL, config->task_priority, NULL, config->core_id);
MSC_GOTO_ON_FALSE(task_created, ESP_ERR_NO_MEM);
}
return ESP_OK;
fail:
s_msc_driver = NULL;
usb_host_client_deregister(driver->client_handle);
if (driver->all_events_handled) {
vSemaphoreDelete(driver->all_events_handled);
}
free(driver);
return ret;
}
esp_err_t msc_host_uninstall(void)
{
// Make sure msc driver is installed,
// not being uninstalled from other task
// and no msc device is registered
MSC_ENTER_CRITICAL();
MSC_RETURN_ON_FALSE_CRITICAL( s_msc_driver != NULL, ESP_ERR_INVALID_STATE );
MSC_RETURN_ON_FALSE_CRITICAL( !s_msc_driver->end_client_event_handling, ESP_ERR_INVALID_STATE );
MSC_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&devices_tailq), ESP_ERR_INVALID_STATE );
s_msc_driver->end_client_event_handling = true;
MSC_EXIT_CRITICAL();
xSemaphoreTake(s_msc_driver->all_events_handled, portMAX_DELAY);
vSemaphoreDelete(s_msc_driver->all_events_handled);
free(s_msc_driver);
s_msc_driver = NULL;
return ESP_OK;
}
esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *msc_device_handle)
{
esp_err_t ret;
uint32_t block_size, block_count;
const usb_config_desc_t *config_desc;
msc_device_t *msc_device;
uint8_t lun;
size_t transfer_size = 512; // Normally the smallest block size
MSC_GOTO_ON_FALSE( msc_device = calloc(1, sizeof(msc_device_t)), ESP_ERR_NO_MEM );
MSC_ENTER_CRITICAL();
MSC_GOTO_ON_FALSE_CRITICAL( s_msc_driver, ESP_ERR_INVALID_STATE );
MSC_GOTO_ON_FALSE_CRITICAL( s_msc_driver->client_handle, ESP_ERR_INVALID_STATE );
STAILQ_INSERT_TAIL(&devices_tailq, msc_device, tailq_entry);
MSC_EXIT_CRITICAL();
MSC_GOTO_ON_FALSE( msc_device->transfer_done = xSemaphoreCreateBinary(), ESP_ERR_NO_MEM);
MSC_GOTO_ON_ERROR( usb_host_device_open(s_msc_driver->client_handle, device_address, &msc_device->handle) );
MSC_GOTO_ON_ERROR( usb_host_get_active_config_descriptor(msc_device->handle, &config_desc) );
MSC_GOTO_ON_ERROR( extract_config_from_descriptor(config_desc, &msc_device->config) );
MSC_GOTO_ON_ERROR( usb_host_transfer_alloc(transfer_size, 0, &msc_device->xfer) );
MSC_GOTO_ON_ERROR( usb_host_interface_claim(s_msc_driver->client_handle,
msc_device->handle,
msc_device->config.iface_num, 0) );
MSC_GOTO_ON_ERROR( msc_get_max_lun(msc_device, &lun) );
MSC_GOTO_ON_ERROR( scsi_cmd_inquiry(msc_device) );
MSC_GOTO_ON_ERROR( msc_wait_for_ready_state(msc_device, WAIT_FOR_READY_TIMEOUT_MS) );
MSC_GOTO_ON_ERROR( scsi_cmd_read_capacity(msc_device, &block_size, &block_count) );
// Configuration descriptor size of simple MSC device is 32 bytes.
if (config_desc->wTotalLength != 32) {
ESP_LOGE(TAG, "COMPOSITE DEVICES UNSUPPORTED");
}
msc_device->disk.block_size = block_size;
msc_device->disk.block_count = block_count;
if (block_size > transfer_size) {
usb_transfer_t *larger_xfer;
MSC_GOTO_ON_ERROR( usb_host_transfer_alloc(block_size, 0, &larger_xfer) );
usb_host_transfer_free(msc_device->xfer);
msc_device->xfer = larger_xfer;
}
*msc_device_handle = msc_device;
return ESP_OK;
fail:
msc_deinit_device(msc_device, true);
return ret;
}
esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device)
{
MSC_RETURN_ON_INVALID_ARG(device);
return msc_deinit_device((msc_device_t *)device, false);
}
esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size)
{
MSC_RETURN_ON_INVALID_ARG(device);
msc_device_t *dev = (msc_device_t *)device;
return scsi_cmd_read10(dev, data, sector, 1, dev->disk.block_size);
}
esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size)
{
MSC_RETURN_ON_INVALID_ARG(device);
msc_device_t *dev = (msc_device_t *)device;
return scsi_cmd_write10(dev, data, sector, 1, dev->disk.block_size);
}
esp_err_t msc_host_handle_events(uint32_t timeout_ms)
{
MSC_RETURN_ON_FALSE(s_msc_driver != NULL, ESP_ERR_INVALID_STATE);
return usb_host_client_handle_events(s_msc_driver->client_handle, timeout_ms);
}
static esp_err_t msc_read_string_desc(msc_device_t *dev, uint8_t index, wchar_t *str)
{
if (index == 0) {
// String descriptor not available
str[0] = 0;
return ESP_OK;
}
usb_transfer_t *xfer = dev->xfer;
USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)xfer->data_buffer, index, 0x409, 64);
MSC_RETURN_ON_ERROR( msc_control_transfer(dev, xfer, USB_SETUP_PACKET_SIZE + 64) );
usb_standard_desc_t *desc = (usb_standard_desc_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE);
wchar_t *data = (wchar_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE + 2);
size_t len = MIN((desc->bLength - USB_STANDARD_DESC_SIZE) / 2, MSC_STR_DESC_SIZE - 1);
wcsncpy(str, data, len);
str[len] = 0;
return ESP_OK;
}
esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info)
{
MSC_RETURN_ON_INVALID_ARG(device);
MSC_RETURN_ON_INVALID_ARG(info);
msc_device_t *dev = (msc_device_t *)device;
const usb_device_desc_t *desc;
MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &desc) );
info->idProduct = desc->idProduct;
info->idVendor = desc->idVendor;
info->sector_size = dev->disk.block_size;
info->sector_count = dev->disk.block_count;
MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iManufacturer, info->iManufacturer) );
MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iProduct, info->iProduct) );
MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iSerialNumber, info->iSerialNumber) );
return ESP_OK;
}
esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device)
{
msc_device_t *dev = (msc_device_t *)device;
const usb_device_desc_t *device_desc;
const usb_config_desc_t *config_desc;
MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &device_desc) );
MSC_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(dev->handle, &config_desc) );
usb_print_device_descriptor(device_desc);
usb_print_config_descriptor(config_desc, NULL);
return ESP_OK;
}
static void transfer_callback(usb_transfer_t *transfer)
{
msc_device_t *device = (msc_device_t *)transfer->context;
if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE("Transfer failed", "Status %d", transfer->status);
}
device->transfer_status = transfer->status;
xSemaphoreGive(device->transfer_done);
}
static esp_err_t wait_for_transfer_done(usb_transfer_t *xfer)
{
msc_device_t *device = (msc_device_t *)xfer->context;
BaseType_t received = xSemaphoreTake(device->transfer_done, pdMS_TO_TICKS(xfer->timeout_ms));
if (received != pdTRUE) {
usb_host_endpoint_halt(xfer->device_handle, xfer->bEndpointAddress);
usb_host_endpoint_flush(xfer->device_handle, xfer->bEndpointAddress);
xSemaphoreTake(device->transfer_done, portMAX_DELAY);
return ESP_ERR_TIMEOUT;
}
return (device->transfer_status == USB_TRANSFER_STATUS_COMPLETED) ? ESP_OK : ESP_FAIL;
}
static inline bool is_in_endpoint(uint8_t endpoint)
{
return endpoint & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK ? true : false;
}
esp_err_t msc_bulk_transfer(msc_device_t *device, uint8_t *data, size_t size, msc_endpoint_t ep)
{
usb_transfer_t *xfer = device->xfer;
MSC_RETURN_ON_FALSE(size <= xfer->data_buffer_size, ESP_ERR_INVALID_SIZE);
uint8_t endpoint = (ep == MSC_EP_IN) ? device->config.bulk_in_ep : device->config.bulk_out_ep;
if (is_in_endpoint(endpoint)) {
xfer->num_bytes = usb_round_up_to_mps(size, device->config.bulk_in_mps);
} else {
memcpy(xfer->data_buffer, data, size);
xfer->num_bytes = size;
}
xfer->device_handle = device->handle;
xfer->bEndpointAddress = endpoint;
xfer->callback = transfer_callback;
xfer->timeout_ms = 1000;
xfer->context = device;
MSC_RETURN_ON_ERROR( usb_host_transfer_submit(xfer) );
MSC_RETURN_ON_ERROR( wait_for_transfer_done(xfer) );
if (is_in_endpoint(endpoint)) {
memcpy(data, xfer->data_buffer, size);
}
return ESP_OK;
}
esp_err_t msc_control_transfer(msc_device_t *device, usb_transfer_t *xfer, size_t len)
{
xfer->device_handle = device->handle;
xfer->bEndpointAddress = 0;
xfer->callback = transfer_callback;
xfer->timeout_ms = 1000;
xfer->num_bytes = len;
xfer->context = device;
MSC_RETURN_ON_ERROR( usb_host_transfer_submit_control(s_msc_driver->client_handle, xfer));
return wait_for_transfer_done(xfer);
}

View File

@ -1,124 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "msc_common.h"
#include "msc_host_vfs.h"
#include "diskio_impl.h"
#include "ffconf.h"
#include "ff.h"
#define DRIVE_STR_LEN 3
typedef struct msc_host_vfs {
char drive[DRIVE_STR_LEN];
char *base_path;
uint8_t pdrv;
} msc_host_vfs_t;
static const char *TAG = "MSC VFS";
static esp_err_t msc_format_storage(size_t block_size, size_t allocation_size, const char *drv)
{
void *workbuf = NULL;
const size_t workbuf_size = 4096;
MSC_RETURN_ON_FALSE( workbuf = ff_memalloc(workbuf_size), ESP_ERR_NO_MEM );
// Valid value of cluster size is between sector_size and 128 * sector_size.
size_t cluster_size = MIN(MAX(allocation_size, block_size), 128 * block_size);
FRESULT err = f_mkfs(drv, FM_ANY | FM_SFD, cluster_size, workbuf, workbuf_size);
if (err) {
ESP_LOGE(TAG, "Formatting failed with error: %d", err);
free(workbuf);
return ESP_ERR_MSC_FORMAT_FAILED;
}
free(workbuf);
return ESP_OK;
}
static void dealloc_msc_vfs(msc_host_vfs_t *vfs)
{
free(vfs->base_path);
free(vfs);
}
esp_err_t msc_host_vfs_register(msc_host_device_handle_t device,
const char *base_path,
const esp_vfs_fat_mount_config_t *mount_config,
msc_host_vfs_handle_t *vfs_handle)
{
MSC_RETURN_ON_INVALID_ARG(device);
MSC_RETURN_ON_INVALID_ARG(base_path);
MSC_RETURN_ON_INVALID_ARG(mount_config);
MSC_RETURN_ON_INVALID_ARG(vfs_handle);
FATFS *fs = NULL;
BYTE pdrv;
bool diskio_registered = false;
esp_err_t ret = ESP_ERR_MSC_MOUNT_FAILED;
msc_device_t *dev = (msc_device_t *)device;
size_t block_size = dev->disk.block_size;
size_t alloc_size = mount_config->allocation_unit_size;
msc_host_vfs_t *vfs = calloc(1, sizeof(msc_host_vfs_t));
MSC_RETURN_ON_FALSE(vfs != NULL, ESP_ERR_NO_MEM);
MSC_GOTO_ON_ERROR( ff_diskio_get_drive(&pdrv) );
ff_diskio_register_msc(pdrv, &dev->disk);
char drive[DRIVE_STR_LEN] = {(char)('0' + pdrv), ':', 0};
diskio_registered = true;
strncpy(vfs->drive, drive, DRIVE_STR_LEN);
MSC_GOTO_ON_FALSE( vfs->base_path = strdup(base_path), ESP_ERR_NO_MEM );
vfs->pdrv = pdrv;
MSC_GOTO_ON_ERROR( esp_vfs_fat_register(base_path, drive, mount_config->max_files, &fs) );
FRESULT fresult = f_mount(fs, drive, 1);
if ( fresult != FR_OK) {
if (mount_config->format_if_mount_failed &&
(fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR)) {
MSC_GOTO_ON_ERROR( msc_format_storage(block_size, alloc_size, drive) );
MSC_GOTO_ON_FALSE( f_mount(fs, drive, 0) == FR_OK, ESP_ERR_MSC_MOUNT_FAILED );
} else {
goto fail;
}
}
*vfs_handle = vfs;
return ESP_OK;
fail:
if (diskio_registered) {
ff_diskio_unregister(pdrv);
}
esp_vfs_fat_unregister_path(base_path);
if(fs) {
f_mount(NULL, drive, 0);
}
dealloc_msc_vfs(vfs);
return ret;
}
esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle)
{
MSC_RETURN_ON_INVALID_ARG(vfs_handle);
msc_host_vfs_t *vfs = (msc_host_vfs_t *)vfs_handle;
f_mount(NULL, vfs->drive, 0);
ff_diskio_unregister(vfs->pdrv);
esp_vfs_fat_unregister_path(vfs->base_path);
dealloc_msc_vfs(vfs);
return ESP_OK;
}

View File

@ -1,434 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "esp_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "esp_check.h"
#include "esp_log.h"
#include "msc_common.h"
#include "msc_scsi_bot.h"
#define TAG "USB_MSC_SCSI"
/* --------------------------- SCSI Definitions ----------------------------- */
#define CMD_SENSE_VALID_BIT (1 << 7)
#define SCSI_FLAG_DPO (1<<4)
#define SCSI_FLAG_FUA (1<<3)
#define SCSI_CMD_FORMAT_UNIT 0x04
#define SCSI_CMD_INQUIRY 0x12
#define SCSI_CMD_MODE_SELECT 0x55
#define SCSI_CMD_MODE_SENSE 0x5A
#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E
#define SCSI_CMD_READ10 0x28
#define SCSI_CMD_READ12 0xA8
#define SCSI_CMD_READ_CAPACITY 0x25
#define SCSI_CMD_READ_FORMAT_CAPACITIES 0x23
#define SCSI_CMD_REQUEST_SENSE 0x03
#define SCSI_CMD_REZERO 0x01
#define SCSI_CMD_SEEK10 0x2B
#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D
#define SCSI_CMD_START_STOP Unit 0x1B
#define SCSI_CMD_TEST_UNIT_READY 0x00
#define SCSI_CMD_VERIFY 0x2F
#define SCSI_CMD_WRITE10 0x2A
#define SCSI_CMD_WRITE12 0xAA
#define SCSI_CMD_WRITE_AND_VERIFY 0x2E
#define IN_DIR CWB_FLAG_DIRECTION_IN
#define OUT_DIR 0
#define INQUIRY_VID_SIZE 8
#define INQUIRY_PID_SIZE 16
#define INQUIRY_REV_SIZE 4
#define CBW_CMD_SIZE(cmd) (sizeof(cmd) - sizeof(msc_cbw_t))
#define CBW_BASE_INIT(dir, cbw_len, data_len) \
.base = { \
.signature = 0x43425355, \
.tag = ++cbw_tag, \
.flags = dir, \
.lun = 0, \
.data_length = data_len, \
.cbw_length = cbw_len, \
}
#define FEATURE_SELECTOR_ENDPOINT 0
#define CSW_SIGNATURE 0x53425355
#define CBW_SIZE 31
#define USB_MASS_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \
(ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | \
USB_BM_REQUEST_TYPE_TYPE_CLASS | \
USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \
(ctrl_req_ptr)->bRequest = 0xFF; \
(ctrl_req_ptr)->wValue = 0; \
(ctrl_req_ptr)->wIndex = (intf_num); \
(ctrl_req_ptr)->wLength = 0; \
})
#define USB_MASS_REQ_INIT_GET_MAX_LUN(ctrl_req_ptr, intf_num) ({ \
(ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | \
USB_BM_REQUEST_TYPE_TYPE_CLASS | \
USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \
(ctrl_req_ptr)->bRequest = 0xFE; \
(ctrl_req_ptr)->wValue = 0; \
(ctrl_req_ptr)->wIndex = (intf_num); \
(ctrl_req_ptr)->wLength = 1; \
})
#define USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP(ctrl_req_ptr, ep_num) ({ \
(ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | \
USB_BM_REQUEST_TYPE_TYPE_STANDARD | \
USB_BM_REQUEST_TYPE_RECIP_ENDPOINT; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_CLEAR_FEATURE; \
(ctrl_req_ptr)->wValue = FEATURE_SELECTOR_ENDPOINT; \
(ctrl_req_ptr)->wIndex = (ep_num); \
(ctrl_req_ptr)->wLength = 0; \
})
#define CWB_FLAG_DIRECTION_IN (1<<7) // device -> host
/**
* @brief Command Block Wrapper structure
*/
typedef struct __attribute__((packed))
{
uint32_t signature;
uint32_t tag;
uint32_t data_length;
uint8_t flags;
uint8_t lun;
uint8_t cbw_length;
} msc_cbw_t;
/**
* @brief Command Status Wrapper structure
*/
typedef struct __attribute__((packed))
{
uint32_t signature;
uint32_t tag;
uint32_t dataResidue;
uint8_t status;
} msc_csw_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint32_t address;
uint8_t reserved1;
uint16_t length;
uint8_t reserved2[3];
} cbw_read10_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint32_t address;
uint8_t reserved1;
uint16_t length;
uint8_t reserved2[1];
} cbw_write10_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint32_t address;
uint8_t reserved[6];
} cbw_read_capacity_t;
typedef struct __attribute__((packed))
{
uint32_t block_count;
uint32_t block_size;
} cbw_read_capacity_response_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint8_t reserved[10];
} cbw_unit_ready_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint8_t reserved_0[2];
uint8_t allocation_length;
uint8_t reserved_1[7];
} cbw_sense_t;
typedef struct __attribute__((packed))
{
uint8_t error_code;
uint8_t reserved_0;
uint8_t sense_key;
uint32_t info;
uint8_t sense_len;
uint32_t reserved_1;
uint8_t sense_code;
uint8_t sense_code_qualifier;
uint32_t reserved_2;
} cbw_sense_response_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint8_t page_code;
uint8_t reserved_0;
uint8_t allocation_length;
uint8_t reserved_1[7];
} cbw_inquiry_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint8_t pc_page_code;
uint8_t reserved_1[4];
uint16_t parameter_list_length;
uint8_t reserved_2[3];
} mode_sense_t;
typedef struct __attribute__((packed))
{
uint8_t data[8];
} mode_sense_response_t;
typedef struct __attribute__((packed))
{
msc_cbw_t base;
uint8_t opcode;
uint8_t flags;
uint8_t reserved_1[2];
uint8_t prevent;
uint8_t reserved_2[7];
} prevent_allow_medium_removal_t;
typedef struct __attribute__((packed))
{
uint8_t data[36];
} cbw_inquiry_response_t;
// Unique number based on which MSC protocol pairs request and response
static uint32_t cbw_tag;
static esp_err_t check_csw(msc_csw_t *csw, uint32_t tag)
{
bool csw_ok = csw->signature == CSW_SIGNATURE && csw->tag == tag &&
csw->dataResidue == 0 && csw->status == 0;
if (!csw_ok) {
ESP_LOGD(TAG, "CSW failed: status %d", csw->status);
}
return csw_ok ? ESP_OK : ESP_FAIL;
}
static esp_err_t clear_feature(msc_device_t *device, uint8_t endpoint)
{
usb_device_handle_t dev = device->handle;
usb_transfer_t *xfer = device->xfer;
MSC_RETURN_ON_ERROR( usb_host_endpoint_clear(dev, endpoint) );
USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP((usb_setup_packet_t *)xfer->data_buffer, endpoint);
MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) );
return ESP_OK;
}
esp_err_t msc_mass_reset(msc_device_t *device)
{
usb_transfer_t *xfer = device->xfer;
USB_MASS_REQ_INIT_RESET((usb_setup_packet_t *)xfer->data_buffer, 0);
MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) );
return ESP_OK;
}
esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun)
{
usb_transfer_t *xfer = device->xfer;
USB_MASS_REQ_INIT_GET_MAX_LUN((usb_setup_packet_t *)xfer->data_buffer, 0);
MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE + 1) );
*lun = xfer->data_buffer[USB_SETUP_PACKET_SIZE];
return ESP_OK;
}
static esp_err_t bot_execute_command(msc_device_t *device, msc_cbw_t *cbw, void *data, size_t size)
{
msc_csw_t csw;
msc_endpoint_t ep = (cbw->flags & CWB_FLAG_DIRECTION_IN) ? MSC_EP_IN : MSC_EP_OUT;
MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)cbw, CBW_SIZE, MSC_EP_OUT) );
if (data) {
MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)data, size, ep) );
}
esp_err_t err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
if (err == ESP_FAIL && device->transfer_status == USB_TRANSFER_STATUS_STALL) {
ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" );
// Try to read csw again after clearing feature
err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
if (err) {
ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" );
ESP_RETURN_ON_ERROR( msc_mass_reset(device), TAG, "Mass reset failed" );
return ESP_FAIL;
}
}
MSC_RETURN_ON_ERROR(err);
return check_csw(&csw, cbw->tag);
}
esp_err_t scsi_cmd_read10(msc_device_t *device,
uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size)
{
cbw_read10_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read10_t), num_sectors * sector_size),
.opcode = SCSI_CMD_READ10,
.flags = 0, // lun
.address = __builtin_bswap32(sector_address),
.length = __builtin_bswap16(num_sectors),
};
return bot_execute_command(device, &cbw.base, data, num_sectors * sector_size);
}
esp_err_t scsi_cmd_write10(msc_device_t *device,
const uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size)
{
cbw_write10_t cbw = {
CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(cbw_write10_t), num_sectors * sector_size),
.opcode = SCSI_CMD_WRITE10,
.address = __builtin_bswap32(sector_address),
.length = __builtin_bswap16(num_sectors),
};
return bot_execute_command(device, &cbw.base, (void *)data, num_sectors * sector_size);
}
esp_err_t scsi_cmd_read_capacity(msc_device_t *device, uint32_t *block_size, uint32_t *block_count)
{
cbw_read_capacity_response_t response;
cbw_read_capacity_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read_capacity_t), sizeof(response)),
.opcode = SCSI_CMD_READ_CAPACITY,
};
MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) );
*block_count = __builtin_bswap32(response.block_count);
*block_size = __builtin_bswap32(response.block_size);
return ESP_OK;
}
esp_err_t scsi_cmd_unit_ready(msc_device_t *device)
{
cbw_unit_ready_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_unit_ready_t), 0),
.opcode = SCSI_CMD_TEST_UNIT_READY,
};
return bot_execute_command(device, &cbw.base, NULL, 0);
}
esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense)
{
cbw_sense_response_t response;
cbw_sense_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_sense_t), sizeof(response)),
.opcode = SCSI_CMD_REQUEST_SENSE,
.allocation_length = sizeof(response),
};
MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) );
if (sense->key) {
ESP_LOGD(TAG, "sense_key: 0x%02X, code: 0x%02X, qualifier: 0x%02X",
response.sense_key, response.sense_code, response.sense_code_qualifier);
}
sense->key = response.sense_key;
sense->code = response.sense_code;
sense->code_q = response.sense_code_qualifier;
return ESP_OK;
}
esp_err_t scsi_cmd_inquiry(msc_device_t *device)
{
cbw_inquiry_response_t response = { 0 };
cbw_inquiry_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_inquiry_t), sizeof(response)),
.opcode = SCSI_CMD_INQUIRY,
.allocation_length = sizeof(response),
};
return bot_execute_command(device, &cbw.base, &response, sizeof(response) );
}
esp_err_t scsi_cmd_mode_sense(msc_device_t *device)
{
mode_sense_response_t response = { 0 };
mode_sense_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(mode_sense_t), sizeof(response)),
.opcode = SCSI_CMD_MODE_SENSE,
.pc_page_code = 0x3F,
.parameter_list_length = sizeof(response),
};
return bot_execute_command(device, &cbw.base, &response, sizeof(response) );
}
esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent)
{
prevent_allow_medium_removal_t cbw = {
CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(prevent_allow_medium_removal_t), 0),
.opcode = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL,
.prevent = 1,
};
return bot_execute_command(device, &cbw.base, NULL, 0);
}

View File

@ -1,3 +0,0 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity usb msc tinyusb)

View File

@ -1,295 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org)
*
* SPDX-License-Identifier: MIT
*
* SPDX-FileContributor: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
*/
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include <stdint.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "test_common.h"
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#define MASS_STORAGE_CLASS 0x08
#define SCSI_COMMAND_SET 0x06
#define BULK_ONLY_TRANSFER 0x50
static const char *TAG = "msc_example";
/**** Kconfig driven Descriptor ****/
tusb_desc_device_t device_descriptor = {
.bLength = sizeof(device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = MASS_STORAGE_CLASS,
.bDeviceSubClass = SCSI_COMMAND_SET,
.bDeviceProtocol = BULK_ONLY_TRANSFER,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_ESPRESSIF_VID,
.idProduct = 0x1234,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
void device_app(void)
{
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t tusb_cfg = {
.descriptor = &device_descriptor
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
while (1) {
vTaskDelay(100);
}
}
// whether host does safe-eject
static bool ejected = false;
// Some MCU doesn't have enough 8KB SRAM to store the whole disk
// We will use Flash as read-only disk with board that has
// CFG_EXAMPLE_MSC_READONLY defined
uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = {
//------------- Block0: Boot Sector -------------//
// byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = DISK_BLOCK_NUM;
// sector_per_cluster = 1; reserved_sectors = 1;
// fat_num = 1; fat12_root_entry_num = 16;
// sector_per_fat = 1; sector_per_track = 1; head_num = 1; hidden_sectors = 0;
// drive_number = 0x80; media_type = 0xf8; extended_boot_signature = 0x29;
// filesystem_type = "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB MSC";
// FAT magic code at offset 510-511
{
0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, 0x02, 0x01, 0x01, 0x00,
0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U',
'S', 'B', ' ', 'M', 'S', 'C', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00,
// Zero up to 2 last bytes of FAT magic code
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA
},
//------------- Block1: FAT12 Table -------------//
{
0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third entry is cluster end of readme file
},
//------------- Block2: Root Directory -------------//
{
// first entry is volume label
'T', 'i', 'n', 'y', 'U', 'S', 'B', ' ', 'M', 'S', 'C', 0x08, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// second entry is readme file
'R', 'E', 'A', 'D', 'M', 'E', ' ', ' ', 'T', 'X', 'T', 0x20, 0x00, 0xC6, 0x52, 0x6D,
0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, 0x02, 0x00,
sizeof(README_CONTENTS) - 1, 0x00, 0x00, 0x00 // readme's files size (4 Bytes)
},
//------------- Block3: Readme Content -------------//
README_CONTENTS
};
// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
(void) lun;
const char vid[] = "TinyUSB";
const char pid[] = "Mass Storage";
const char rev[] = "1.0";
memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, pid, strlen(pid));
memcpy(product_rev, rev, strlen(rev));
}
// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
(void) lun;
// RAM disk is ready until ejected
if (ejected) {
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00);
return false;
}
return true;
}
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
{
(void) lun;
*block_count = DISK_BLOCK_NUM;
*block_size = DISK_BLOCK_SIZE;
}
// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
(void) lun;
(void) power_condition;
if ( load_eject ) {
if (start) {
// load disk storage
} else {
// unload disk storage
ejected = true;
}
}
return true;
}
// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
{
(void) lun;
uint8_t const *addr = msc_disk[lba] + offset;
memcpy(buffer, addr, bufsize);
return bufsize;
}
// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
(void) lun;
#ifndef CFG_EXAMPLE_MSC_READONLY
uint8_t *addr = msc_disk[lba] + offset;
memcpy(addr, buffer, bufsize);
#else
(void) lba; (void) offset; (void) buffer;
#endif
return bufsize;
}
// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
{
// read10 & write10 has their own callback and MUST not be handled here
void const *response = NULL;
uint16_t resplen = 0;
// most scsi handled is input
bool in_xfer = true;
switch (scsi_cmd[0]) {
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
// Host is about to read/write etc ... better not to disconnect disk
resplen = 0;
break;
default:
// Set Sense = Invalid Command Operation
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
// negative means error -> tinyusb could stall and/or response with failed status
resplen = -1;
break;
}
// return resplen must not larger than bufsize
if ( resplen > bufsize ) {
resplen = bufsize;
}
if ( response && (resplen > 0) ) {
if (in_xfer) {
memcpy(buffer, response, resplen);
} else {
// SCSI output
}
}
return resplen;
}
#endif

View File

@ -1,19 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
enum {
// FatFS only allows to format disks with number of blocks greater than 128
DISK_BLOCK_NUM = 128 + 1,
DISK_BLOCK_SIZE = 512
};
#define README_CONTENTS \
"This is tinyusb's MassStorage Class demo.\r\n\r\n\
If you find any bugs or get any questions, feel free to file an\r\n\
issue at github.com/hathach/tinyusb"
void device_app(void);

View File

@ -1,321 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_private/usb_phy.h"
#include "usb/usb_host.h"
#include "msc_host.h"
#include "msc_host_vfs.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_vfs.h"
#include "test_common.h"
#include "soc/usb_wrap_struct.h"
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
static const char *TAG = "APP";
#define ESP_OK_ASSERT(exp) TEST_ASSERT_EQUAL(ESP_OK, exp)
static esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 1024,
};
static QueueHandle_t app_queue;
static SemaphoreHandle_t ready_to_deinit_usb;
static msc_host_device_handle_t device;
static msc_host_vfs_handle_t vfs_handle;
static volatile bool waiting_for_sudden_disconnect;
static usb_phy_handle_t phy_hdl = NULL;
static void force_conn_state(bool connected, TickType_t delay_ticks)
{
TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl);
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
}
static void msc_event_cb(const msc_host_event_t *event, void *arg)
{
if (waiting_for_sudden_disconnect) {
waiting_for_sudden_disconnect = false;
TEST_ASSERT(event->event == MSC_DEVICE_DISCONNECTED);
}
if (event->event == MSC_DEVICE_CONNECTED) {
printf("MSC_DEVICE_CONNECTED\n");
} else {
printf("MSC_DEVICE_DISCONNECTED\n");
}
xQueueSend(app_queue, event, 10);
}
static const char *TEST_STRING = "Hello World!";
static const char *FILE_NAME = "/usb/ESP32.txt";
static void write_read_file(const char *file_path)
{
char line[64];
ESP_LOGI(TAG, "Writing file");
FILE *f = fopen(file_path, "w");
TEST_ASSERT( f != NULL);
fprintf(f, TEST_STRING);
fclose(f);
ESP_LOGI(TAG, "Reading file");
TEST_ASSERT( fopen(file_path, "r") != NULL);
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
TEST_ASSERT_EQUAL_STRING(line, TEST_STRING);
ESP_LOGI(TAG, "Done");
}
static bool file_exists(const char *file_path)
{
return ( access(file_path, F_OK) == 0 );
}
// Handles common USB host library events
static void handle_usb_events(void *args)
{
uint32_t end_flags = 0;
while (1) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
printf("USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS\n");
usb_host_device_free_all();
end_flags |= 1;
}
// Give ready_to_deinit_usb semaphore to indicate that USB Host library
// can be deinitialized, and terminate this task.
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
printf("USB_HOST_LIB_EVENT_FLAGS_ALL_FREE\n");
end_flags |= 2;
}
if (end_flags == 3) {
xSemaphoreGive(ready_to_deinit_usb);
break;
}
}
vTaskDelete(NULL);
}
static void check_file_content(const char *file_path, const char *expected)
{
ESP_LOGI(TAG, "Reading %s:", file_path);
FILE *file = fopen(file_path, "r");
TEST_ASSERT(file != NULL)
char content[200];
fread(content, 1, sizeof(content), file);
TEST_ASSERT_EQUAL_STRING(content, expected);
fclose(file);
}
static void check_sudden_disconnect(void)
{
uint8_t data[512];
const size_t DATA_SIZE = sizeof(data);
ESP_LOGI(TAG, "Creating test.tx");
FILE *file = fopen("/usb/test.txt", "w");
TEST_ASSERT( file != NULL);
ESP_LOGI(TAG, "Write data");
TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
TEST_ASSERT( fflush(file) == 0 );
ESP_LOGI(TAG, "Trigger a disconnect");
//Trigger a disconnect
waiting_for_sudden_disconnect = true;
force_conn_state(false, 0);
// Make sure flag was leared in callback
vTaskDelay( pdMS_TO_TICKS(100) );
TEST_ASSERT( waiting_for_sudden_disconnect == false );
ESP_LOGI(TAG, "Write data after disconnect");
TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) != DATA_SIZE );
fclose(file);
}
static void msc_setup(void)
{
BaseType_t task_created;
ready_to_deinit_usb = xSemaphoreCreateBinary();
TEST_ASSERT( app_queue = xQueueCreate(5, sizeof(msc_host_event_t)) );
//Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
.gpio_conf = NULL,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl));
const usb_host_config_t host_config = {
.skip_phy_setup = true,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_OK_ASSERT( usb_host_install(&host_config) );
task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL);
TEST_ASSERT(task_created);
const msc_host_driver_config_t msc_config = {
.create_backround_task = true,
.callback = msc_event_cb,
.stack_size = 4096,
.task_priority = 5,
};
ESP_OK_ASSERT( msc_host_install(&msc_config) );
ESP_LOGI(TAG, "Waiting for USB stick to be connected");
msc_host_event_t app_event;
xQueueReceive(app_queue, &app_event, portMAX_DELAY);
TEST_ASSERT( app_event.event == MSC_DEVICE_CONNECTED );
uint8_t device_addr = app_event.device.address;
ESP_OK_ASSERT( msc_host_install_device(device_addr, &device) );
ESP_OK_ASSERT( msc_host_vfs_register(device, "/usb", &mount_config, &vfs_handle) );
}
static void msc_teardown(void)
{
// Wait to finish any ongoing USB operations
vTaskDelay(100);
ESP_OK_ASSERT( msc_host_vfs_unregister(vfs_handle) );
ESP_OK_ASSERT( msc_host_uninstall_device(device) );
ESP_OK_ASSERT( msc_host_uninstall() );
xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY);
vSemaphoreDelete(ready_to_deinit_usb);
ESP_OK_ASSERT( usb_host_uninstall() );
//Tear down USB PHY
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl));
phy_hdl = NULL;
vQueueDelete(app_queue);
}
static void write_read_sectors(void)
{
uint8_t write_data[DISK_BLOCK_SIZE];
uint8_t read_data[DISK_BLOCK_SIZE];
memset(write_data, 0x55, DISK_BLOCK_SIZE);
memset(read_data, 0, DISK_BLOCK_SIZE);
msc_host_write_sector(device, 10, write_data, DISK_BLOCK_SIZE);
msc_host_read_sector(device, 10, read_data, DISK_BLOCK_SIZE);
TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, DISK_BLOCK_SIZE);
}
static void erase_storage(void)
{
uint8_t data[DISK_BLOCK_SIZE];
memset(data, 0xFF, DISK_BLOCK_SIZE);
for (int block = 0; block < DISK_BLOCK_NUM; block++) {
msc_host_write_sector(device, block, data, DISK_BLOCK_SIZE);
}
}
static void check_readme_content(void)
{
msc_setup();
check_file_content("/usb/README.TXT", README_CONTENTS);
msc_teardown();
}
TEST_CASE("Write and read file", "[usb_msc][ignore]")
{
msc_setup();
write_read_file(FILE_NAME);
msc_teardown();
}
TEST_CASE("Sudden disconnect", "[usb_msc][ignore]")
{
msc_setup();
check_sudden_disconnect();
msc_teardown();
}
void read_write_sectors(void)
{
msc_setup();
write_read_sectors();
msc_teardown();
}
void check_formatting(void)
{
printf("Create file\n");
msc_setup();
write_read_file(FILE_NAME);
msc_teardown();
printf("File exists after mounting again\n");
msc_setup();
TEST_ASSERT( file_exists(FILE_NAME) );
printf("Erase storage device\n");
erase_storage();
msc_teardown();
printf("Check file does not exist after formatting\n");
mount_config.format_if_mount_failed = true;
msc_setup();
TEST_ASSERT( !file_exists(FILE_NAME) );
msc_teardown();
mount_config.format_if_mount_failed = false;
}
TEST_CASE_MULTIPLE_DEVICES("Sectors can be written and read", "[usb_msc][ignore]", read_write_sectors, device_app);
TEST_CASE_MULTIPLE_DEVICES("Can be Formated", "[usb_msc][ignore]", check_formatting, device_app);
TEST_CASE_MULTIPLE_DEVICES("Check README content", "[usb_msc][ignore]", check_readme_content, device_app);
#endif

View File

@ -1,3 +1,3 @@
idf_component_register(SRCS "msc_example_main.c"
INCLUDE_DIRS ""
REQUIRES usb msc fatfs)
REQUIRES usb vfs)

View File

@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4"
usb_host_msc: "^1.0.0"

View File

@ -1,41 +1,53 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "esp_err.h"
#include "esp_log.h"
#include "usb/usb_host.h"
#include "msc_host.h"
#include "msc_host_vfs.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_vfs.h"
#include "errno.h"
#include "hal/usb_hal.h"
#include "driver/gpio.h"
#include <esp_vfs_fat.h>
#define USB_DISCONNECT_PIN GPIO_NUM_10
#define READY_TO_UNINSTALL (HOST_NO_CLIENT | HOST_ALL_FREE)
typedef enum {
HOST_NO_CLIENT = 0x1,
HOST_ALL_FREE = 0x2,
DEVICE_CONNECTED = 0x4,
DEVICE_DISCONNECTED = 0x8,
DEVICE_ADDRESS_MASK = 0xFF0,
} app_event_t;
static const char *TAG = "example";
static QueueHandle_t app_queue;
static SemaphoreHandle_t ready_to_uninstall_usb;
static EventGroupHandle_t usb_flags;
static void msc_event_cb(const msc_host_event_t *event, void *arg)
{
if (event->event == MSC_DEVICE_CONNECTED) {
ESP_LOGI(TAG, "MSC device connected");
// Obtained USB device address is placed after application events
xEventGroupSetBits(usb_flags, DEVICE_CONNECTED | (event->device.address << 4));
} else if (event->event == MSC_DEVICE_DISCONNECTED) {
xEventGroupSetBits(usb_flags, DEVICE_DISCONNECTED);
ESP_LOGI(TAG, "MSC device disconnected");
}
xQueueSend(app_queue, event, 10);
}
static void print_device_info(msc_host_device_info_t *info)
@ -45,8 +57,8 @@ static void print_device_info(msc_host_device_info_t *info)
printf("Device info:\n");
printf("\t Capacity: %llu MB\n", capacity);
printf("\t Sector size: %u\n", info->sector_size);
printf("\t Sector count: %u\n", info->sector_count);
printf("\t Sector size: %"PRIu32"\n", info->sector_size);
printf("\t Sector count: %"PRIu32"\n", info->sector_count);
printf("\t PID: 0x%4X \n", info->idProduct);
printf("\t VID: 0x%4X \n", info->idVendor);
wprintf(L"\t iProduct: %S \n", info->iProduct);
@ -54,6 +66,12 @@ static void print_device_info(msc_host_device_info_t *info)
wprintf(L"\t iSerialNumber: %S \n", info->iSerialNumber);
}
static bool file_exists(const char *file_path)
{
struct stat buffer;
return stat(file_path, &buffer) == 0;
}
static void file_operations(void)
{
const char *directory = "/usb/esp";
@ -67,7 +85,8 @@ static void file_operations(void)
}
}
ESP_LOGI(TAG, "Writing file");
if (!file_exists(file_path)) {
ESP_LOGI(TAG, "Creating file");
FILE *f = fopen(file_path, "w");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
@ -75,7 +94,9 @@ static void file_operations(void)
}
fprintf(f, "Hello World!\n");
fclose(f);
}
FILE *f;
ESP_LOGI(TAG, "Reading file");
f = fopen(file_path, "r");
if (f == NULL) {
@ -99,15 +120,16 @@ static void handle_usb_events(void *args)
while (1) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
usb_host_device_free_all();
xEventGroupSetBits(usb_flags, HOST_NO_CLIENT);
}
// Give ready_to_uninstall_usb semaphore to indicate that USB Host library
// can be deinitialized, and terminate this task.
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
xSemaphoreGive(ready_to_uninstall_usb);
break;
xEventGroupSetBits(usb_flags, HOST_ALL_FREE);
}
}
@ -116,29 +138,40 @@ static void handle_usb_events(void *args)
static uint8_t wait_for_msc_device(void)
{
msc_host_event_t app_event;
EventBits_t event;
ESP_LOGI(TAG, "Waiting for USB stick to be connected");
xQueueReceive(app_queue, &app_event, portMAX_DELAY);
assert( app_event.event == MSC_DEVICE_CONNECTED );
return app_event.device.address;
event = xEventGroupWaitBits(usb_flags, DEVICE_CONNECTED | DEVICE_ADDRESS_MASK,
pdTRUE, pdFALSE, portMAX_DELAY);
ESP_LOGI(TAG, "connection...");
// Extract USB device address from event group bits
return (event & DEVICE_ADDRESS_MASK) >> 4;
}
static bool wait_for_event(EventBits_t event, TickType_t timeout)
{
return xEventGroupWaitBits(usb_flags, event, pdTRUE, pdTRUE, timeout) & event;
}
void app_main(void)
{
msc_host_device_handle_t msc_device;
msc_host_vfs_handle_t vfs_handle;
msc_host_device_info_t info;
BaseType_t task_created;
ready_to_uninstall_usb = xSemaphoreCreateBinary();
app_queue = xQueueCreate(3, sizeof(msc_host_event_t));
assert(app_queue);
const usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
const gpio_config_t input_pin = {
.pin_bit_mask = BIT64(USB_DISCONNECT_PIN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
ESP_ERROR_CHECK( usb_host_install(&host_config) );
ESP_ERROR_CHECK( gpio_config(&input_pin) );
usb_flags = xEventGroupCreate();
assert(usb_flags);
const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 };
ESP_ERROR_CHECK( usb_host_install(&host_config) );
task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL);
assert(task_created);
@ -150,33 +183,37 @@ void app_main(void)
};
ESP_ERROR_CHECK( msc_host_install(&msc_config) );
uint8_t device_address = wait_for_msc_device();
ESP_ERROR_CHECK( msc_host_install_device(device_address, &msc_device) );
msc_host_print_descriptors(msc_device);
msc_host_device_info_t info;
ESP_ERROR_CHECK( msc_host_get_device_info(msc_device, &info) );
print_device_info(&info);
msc_host_vfs_handle_t vfs_handle;
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 1024,
};
do {
uint8_t device_address = wait_for_msc_device();
ESP_ERROR_CHECK( msc_host_install_device(device_address, &msc_device) );
msc_host_print_descriptors(msc_device);
ESP_ERROR_CHECK( msc_host_get_device_info(msc_device, &info) );
print_device_info(&info);
ESP_ERROR_CHECK( msc_host_vfs_register(msc_device, "/usb", &mount_config, &vfs_handle) );
while (!wait_for_event(DEVICE_DISCONNECTED, 200)) {
file_operations();
}
xEventGroupClearBits(usb_flags, READY_TO_UNINSTALL);
ESP_ERROR_CHECK( msc_host_vfs_unregister(vfs_handle) );
ESP_ERROR_CHECK( msc_host_uninstall_device(msc_device) );
} while (gpio_get_level(USB_DISCONNECT_PIN) != 0);
ESP_LOGI(TAG, "Uninitializing USB ...");
ESP_ERROR_CHECK( msc_host_uninstall() );
xSemaphoreTake(ready_to_uninstall_usb, portMAX_DELAY);
wait_for_event(READY_TO_UNINSTALL, portMAX_DELAY);
ESP_ERROR_CHECK( usb_host_uninstall() );
ESP_LOGI(TAG, "Done");
}