kopia lustrzana https://github.com/espressif/esp-idf
397 wiersze
14 KiB
C
397 wiersze
14 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
|
*/
|
|
|
|
/* DESCRIPTION:
|
|
* This example contains code to make ESP32-S3 based device recognizable by USB-hosts as a USB Mass Storage Device.
|
|
* It either allows the embedded application i.e. example to access the partition or Host PC accesses the partition over USB MSC.
|
|
* They can't be allowed to access the partition at the same time.
|
|
* For different scenarios and behaviour, Refer to README of this example.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include "esp_console.h"
|
|
#include "esp_check.h"
|
|
#include "esp_partition.h"
|
|
#include "driver/gpio.h"
|
|
#include "tinyusb.h"
|
|
#include "tusb_msc_storage.h"
|
|
#ifdef CONFIG_EXAMPLE_STORAGE_MEDIA_SDMMC
|
|
#include "diskio_impl.h"
|
|
#include "diskio_sdmmc.h"
|
|
#endif
|
|
|
|
static const char *TAG = "example_main";
|
|
|
|
/* TinyUSB descriptors
|
|
********************************************************************* */
|
|
#define EPNUM_MSC 1
|
|
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
|
|
|
|
enum {
|
|
ITF_NUM_MSC = 0,
|
|
ITF_NUM_TOTAL
|
|
};
|
|
|
|
enum {
|
|
EDPT_CTRL_OUT = 0x00,
|
|
EDPT_CTRL_IN = 0x80,
|
|
|
|
EDPT_MSC_OUT = 0x01,
|
|
EDPT_MSC_IN = 0x81,
|
|
};
|
|
|
|
static uint8_t const desc_configuration[] = {
|
|
// Config number, interface count, string index, total length, attribute, power in mA
|
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
|
|
|
// Interface number, string index, EP Out & EP In address, EP size
|
|
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64),
|
|
};
|
|
|
|
static tusb_desc_device_t descriptor_config = {
|
|
.bLength = sizeof(descriptor_config),
|
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
|
.bcdUSB = 0x0200,
|
|
.bDeviceClass = TUSB_CLASS_MISC,
|
|
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
|
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
|
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
|
|
.idProduct = 0x4002,
|
|
.bcdDevice = 0x100,
|
|
.iManufacturer = 0x01,
|
|
.iProduct = 0x02,
|
|
.iSerialNumber = 0x03,
|
|
.bNumConfigurations = 0x01
|
|
};
|
|
|
|
static char const *string_desc_arr[] = {
|
|
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
|
|
"TinyUSB", // 1: Manufacturer
|
|
"TinyUSB Device", // 2: Product
|
|
"123456", // 3: Serials
|
|
"Example MSC", // 4. MSC
|
|
};
|
|
/*********************************************************************** TinyUSB descriptors*/
|
|
|
|
#define BASE_PATH "/data" // base path to mount the partition
|
|
|
|
#define PROMPT_STR CONFIG_IDF_TARGET
|
|
static int console_unmount(int argc, char **argv);
|
|
static int console_read(int argc, char **argv);
|
|
static int console_write(int argc, char **argv);
|
|
static int console_size(int argc, char **argv);
|
|
static int console_status(int argc, char **argv);
|
|
static int console_exit(int argc, char **argv);
|
|
const esp_console_cmd_t cmds[] = {
|
|
{
|
|
.command = "read",
|
|
.help = "read BASE_PATH/README.MD and print its contents",
|
|
.hint = NULL,
|
|
.func = &console_read,
|
|
},
|
|
{
|
|
.command = "write",
|
|
.help = "create file BASE_PATH/README.MD if it does not exist",
|
|
.hint = NULL,
|
|
.func = &console_write,
|
|
},
|
|
{
|
|
.command = "size",
|
|
.help = "show storage size and sector size",
|
|
.hint = NULL,
|
|
.func = &console_size,
|
|
},
|
|
{
|
|
.command = "expose",
|
|
.help = "Expose Storage to Host",
|
|
.hint = NULL,
|
|
.func = &console_unmount,
|
|
},
|
|
{
|
|
.command = "status",
|
|
.help = "Status of storage exposure over USB",
|
|
.hint = NULL,
|
|
.func = &console_status,
|
|
},
|
|
{
|
|
.command = "exit",
|
|
.help = "exit from application",
|
|
.hint = NULL,
|
|
.func = &console_exit,
|
|
}
|
|
};
|
|
|
|
// mount the partition and show all the files in BASE_PATH
|
|
static void _mount(void)
|
|
{
|
|
ESP_LOGI(TAG, "Mount storage...");
|
|
ESP_ERROR_CHECK(tinyusb_msc_storage_mount(BASE_PATH));
|
|
|
|
// List all the files in this directory
|
|
ESP_LOGI(TAG, "\nls command output:");
|
|
struct dirent *d;
|
|
DIR *dh = opendir(BASE_PATH);
|
|
if (!dh) {
|
|
if (errno == ENOENT) {
|
|
//If the directory is not found
|
|
ESP_LOGE(TAG, "Directory doesn't exist %s", BASE_PATH);
|
|
} else {
|
|
//If the directory is not readable then throw error and exit
|
|
ESP_LOGE(TAG, "Unable to read directory %s", BASE_PATH);
|
|
}
|
|
return;
|
|
}
|
|
//While the next entry is not readable we will print directory files
|
|
while ((d = readdir(dh)) != NULL) {
|
|
printf("%s\n", d->d_name);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// unmount storage
|
|
static int console_unmount(int argc, char **argv)
|
|
{
|
|
if (tinyusb_msc_storage_in_use_by_usb_host()) {
|
|
ESP_LOGE(TAG, "storage is already exposed");
|
|
return -1;
|
|
}
|
|
ESP_LOGI(TAG, "Unmount storage...");
|
|
ESP_ERROR_CHECK(tinyusb_msc_storage_unmount());
|
|
return 0;
|
|
}
|
|
|
|
// read BASE_PATH/README.MD and print its contents
|
|
static int console_read(int argc, char **argv)
|
|
{
|
|
if (tinyusb_msc_storage_in_use_by_usb_host()) {
|
|
ESP_LOGE(TAG, "storage exposed over USB. Application can't read from storage.");
|
|
return -1;
|
|
}
|
|
ESP_LOGD(TAG, "read from storage:");
|
|
const char *filename = BASE_PATH "/README.MD";
|
|
FILE *ptr = fopen(filename, "r");
|
|
if (ptr == NULL) {
|
|
ESP_LOGE(TAG, "Filename not present - %s", filename);
|
|
return -1;
|
|
}
|
|
char buf[1024];
|
|
while (fgets(buf, 1000, ptr) != NULL) {
|
|
printf("%s", buf);
|
|
}
|
|
fclose(ptr);
|
|
return 0;
|
|
}
|
|
|
|
// create file BASE_PATH/README.MD if it does not exist
|
|
static int console_write(int argc, char **argv)
|
|
{
|
|
if (tinyusb_msc_storage_in_use_by_usb_host()) {
|
|
ESP_LOGE(TAG, "storage exposed over USB. Application can't write to storage.");
|
|
return -1;
|
|
}
|
|
ESP_LOGD(TAG, "write to storage:");
|
|
const char *filename = BASE_PATH "/README.MD";
|
|
FILE *fd = fopen(filename, "r");
|
|
if (!fd) {
|
|
ESP_LOGW(TAG, "README.MD doesn't exist yet, creating");
|
|
fd = fopen(filename, "w");
|
|
fprintf(fd, "Mass Storage Devices are one of the most common USB devices. It use Mass Storage Class (MSC) that allow access to their internal data storage.\n");
|
|
fprintf(fd, "In this example, ESP chip will be recognised by host (PC) as Mass Storage Device.\n");
|
|
fprintf(fd, "Upon connection to USB host (PC), the example application will initialize the storage module and then the storage will be seen as removable device on PC.\n");
|
|
fclose(fd);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Show storage size and sector size
|
|
static int console_size(int argc, char **argv)
|
|
{
|
|
if (tinyusb_msc_storage_in_use_by_usb_host()) {
|
|
ESP_LOGE(TAG, "storage exposed over USB. Application can't access storage");
|
|
return -1;
|
|
}
|
|
uint32_t sec_count = tinyusb_msc_storage_get_sector_count();
|
|
uint32_t sec_size = tinyusb_msc_storage_get_sector_size();
|
|
printf("Storage Capacity %lluMB\n", ((uint64_t) sec_count) * sec_size / (1024 * 1024));
|
|
return 0;
|
|
}
|
|
|
|
// exit from application
|
|
static int console_status(int argc, char **argv)
|
|
{
|
|
printf("storage exposed over USB: %s\n", tinyusb_msc_storage_in_use_by_usb_host() ? "Yes" : "No");
|
|
return 0;
|
|
}
|
|
|
|
// exit from application
|
|
static int console_exit(int argc, char **argv)
|
|
{
|
|
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED);
|
|
tinyusb_msc_storage_deinit();
|
|
printf("Application Exiting\n");
|
|
exit(0);
|
|
return 0;
|
|
}
|
|
|
|
// callback that is delivered when storage is mounted/unmounted by application.
|
|
static void storage_mount_changed_cb(tinyusb_msc_event_t *event)
|
|
{
|
|
ESP_LOGI(TAG, "Storage mounted to application: %s", event->mount_changed_data.is_mounted ? "Yes" : "No");
|
|
}
|
|
|
|
#ifdef CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle)
|
|
{
|
|
ESP_LOGI(TAG, "Initializing wear levelling");
|
|
|
|
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
|
|
if (data_partition == NULL) {
|
|
ESP_LOGE(TAG, "Failed to find FATFS partition. Check the partition table.");
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
return wl_mount(data_partition, wl_handle);
|
|
}
|
|
#else // CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
static esp_err_t storage_init_sdmmc(sdmmc_card_t **card)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
bool host_init = false;
|
|
sdmmc_card_t *sd_card;
|
|
|
|
ESP_LOGI(TAG, "Initializing SDCard");
|
|
|
|
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
|
|
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 40MHz for SDMMC)
|
|
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
|
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
|
|
|
// This initializes the slot without card detect (CD) and write protect (WP) signals.
|
|
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
|
|
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
|
|
|
// For SD Card, set bus width to use
|
|
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
|
|
slot_config.width = 4;
|
|
#else
|
|
slot_config.width = 1;
|
|
#endif // CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
|
|
|
|
// On chips where the GPIOs used for SD card can be configured, set the user defined values
|
|
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
|
slot_config.clk = CONFIG_EXAMPLE_PIN_CLK;
|
|
slot_config.cmd = CONFIG_EXAMPLE_PIN_CMD;
|
|
slot_config.d0 = CONFIG_EXAMPLE_PIN_D0;
|
|
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
|
|
slot_config.d1 = CONFIG_EXAMPLE_PIN_D1;
|
|
slot_config.d2 = CONFIG_EXAMPLE_PIN_D2;
|
|
slot_config.d3 = CONFIG_EXAMPLE_PIN_D3;
|
|
#endif // CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
|
|
|
|
#endif // CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
|
|
|
// Enable internal pullups on enabled pins. The internal pullups
|
|
// are insufficient however, please make sure 10k external pullups are
|
|
// connected on the bus. This is for debug / example purpose only.
|
|
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
|
|
|
|
// not using ff_memalloc here, as allocation in internal RAM is preferred
|
|
sd_card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));
|
|
ESP_GOTO_ON_FALSE(sd_card, ESP_ERR_NO_MEM, clean, TAG, "could not allocate new sdmmc_card_t");
|
|
|
|
ESP_GOTO_ON_ERROR((*host.init)(), clean, TAG, "Host Config Init fail");
|
|
host_init = true;
|
|
|
|
ESP_GOTO_ON_ERROR(sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *) &slot_config),
|
|
clean, TAG, "Host init slot fail");
|
|
|
|
while (sdmmc_card_init(&host, sd_card)) {
|
|
ESP_LOGE(TAG, "The detection pin of the slot is disconnected(Insert uSD card). Retrying...");
|
|
vTaskDelay(pdMS_TO_TICKS(3000));
|
|
}
|
|
|
|
// Card has been initialized, print its properties
|
|
sdmmc_card_print_info(stdout, sd_card);
|
|
*card = sd_card;
|
|
|
|
return ESP_OK;
|
|
|
|
clean:
|
|
if (host_init) {
|
|
if (host.flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
|
|
host.deinit_p(host.slot);
|
|
} else {
|
|
(*host.deinit)();
|
|
}
|
|
}
|
|
if (sd_card) {
|
|
free(sd_card);
|
|
sd_card = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif // CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
|
|
void app_main(void)
|
|
{
|
|
ESP_LOGI(TAG, "Initializing storage...");
|
|
|
|
#ifdef CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
static wl_handle_t wl_handle = WL_INVALID_HANDLE;
|
|
ESP_ERROR_CHECK(storage_init_spiflash(&wl_handle));
|
|
|
|
const tinyusb_msc_spiflash_config_t config_spi = {
|
|
.wl_handle = wl_handle,
|
|
.callback_mount_changed = storage_mount_changed_cb, /* First way to register the callback. This is while initializing the storage. */
|
|
.mount_config.max_files = 5,
|
|
};
|
|
ESP_ERROR_CHECK(tinyusb_msc_storage_init_spiflash(&config_spi));
|
|
ESP_ERROR_CHECK(tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, storage_mount_changed_cb)); /* Other way to register the callback i.e. registering using separate API. If the callback had been already registered, it will be overwritten. */
|
|
#else // CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
static sdmmc_card_t *card = NULL;
|
|
ESP_ERROR_CHECK(storage_init_sdmmc(&card));
|
|
|
|
const tinyusb_msc_sdmmc_config_t config_sdmmc = {
|
|
.card = card,
|
|
.callback_mount_changed = storage_mount_changed_cb, /* First way to register the callback. This is while initializing the storage. */
|
|
.mount_config.max_files = 5,
|
|
};
|
|
ESP_ERROR_CHECK(tinyusb_msc_storage_init_sdmmc(&config_sdmmc));
|
|
ESP_ERROR_CHECK(tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, storage_mount_changed_cb)); /* Other way to register the callback i.e. registering using separate API. If the callback had been already registered, it will be overwritten. */
|
|
#endif // CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
|
|
|
//mounted in the app by default
|
|
_mount();
|
|
|
|
ESP_LOGI(TAG, "USB MSC initialization");
|
|
const tinyusb_config_t tusb_cfg = {
|
|
.device_descriptor = &descriptor_config,
|
|
.string_descriptor = string_desc_arr,
|
|
.string_descriptor_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),
|
|
.external_phy = false,
|
|
.configuration_descriptor = desc_configuration,
|
|
};
|
|
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
|
|
ESP_LOGI(TAG, "USB MSC initialization DONE");
|
|
|
|
esp_console_repl_t *repl = NULL;
|
|
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
|
/* Prompt to be printed before each line.
|
|
* This can be customized, made dynamic, etc.
|
|
*/
|
|
repl_config.prompt = PROMPT_STR ">";
|
|
repl_config.max_cmdline_length = 64;
|
|
esp_console_register_help_command();
|
|
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
|
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
|
|
for (int count = 0; count < sizeof(cmds) / sizeof(esp_console_cmd_t); count++) {
|
|
ESP_ERROR_CHECK(esp_console_cmd_register(&cmds[count]));
|
|
}
|
|
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
|
}
|