Implementing USB Functionality on ESP32S3

Implementing USB Functionality on ESP32S3

The previous article implemented the online functionality of DAPlink, and now I want to achieve the drag-and-drop download function. The prerequisite for this function is to simulate the ESP32S3 as a USB drive, allowing the ESP32S3 to read files from the USB drive. Today, we will implement the ESP32S3 as a USB drive.

Analysis of ESP32S3 Storage Functionality

The ESP32S3 I am using does not come with an SD card, but fortunately, it has 16M of flash memory, and I can allocate 10M for the USB drive storage functionality through partitioning.

esp_partition API

From the official documentation of ESP32S, the esp_partition component provides high-level API functions for accessing partitions defined in the partition table. There are three core APIs:

1.esp_partition_find_first(): Find a partition based on parameters;

2.esp_partition_read: Read data from a partition;

3.esp_partition_write(): Write data to a partition;

Note: When writing data, you must first erase the data in the write area.

4.esp_partition_erase_range(): Erase data from part of a partition.

With these four APIs, you can perform read and write operations on the storage area. Please refer to the official documentation for specific parameters.

FAT File System

ESP-IDF can directly support the FAT file system, with core APIs:

1.esp_vfs_fat_spiflash_mount_rw_wl(): Initializes the FAT file system in SPI flash and registers it with VFS’s convenient API;

2.esp_vfs_fat_spiflash_unmount_rw_wl(): Format the FAT file system;

3.esp_vfs_fat_info(): Get the size of the file system and the remaining space;

Using these APIs along with the C standard library, you can perform file read and write operations on the MCU side.

Implementation

Modify Partition Table

Use a custom partition table partitions.csv

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 2M,
storage,  data, fat,     ,        10M,

In this, a new storage partition has been added, with type data, subtype fat, and size of 10M. The first three parameters are helpful for finding the partition later.

In menuconfig, select partition table to use the custom partition table, and its CSV is partitions.csv.

Implementing USB Functionality on ESP32S3

Implement MSC Functionality

Add the msc_storage_template.c demo from cherryUSB to the compilation system. Since this demo is suitable for RT-Thread, it cannot be used directly in ESP32. Therefore, you need to modify the following three functions in the file:

1.msc_storage_init(): Add initialization for the storage partition;

2.usbd_msc_get_cap(): Set the number of blocks and block size for the USB drive;

3.usbd_msc_sector_read(): Implement the read functionality for blocks;

4.usbd_msc_sector_write(): Implement the write functionality for blocks;

msc_storage_init()

void msc_storage_init(uint8_t busid, uintptr_t reg_base)
{
    /*Find partition Type = data SubType = fat*/
    partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
    if(partition == NULL)
    {
        printf("No partition found!\n");
        return;
    }
    usbd_desc_register(busid, msc_storage_descriptor);
    usbd_add_interface(busid, usbd_msc_init_intf(busid, &intf0, MSC_OUT_EP, MSC_IN_EP));
    usbd_initialize(busid, reg_base, usbd_event_handler);
}

usbd_msc_get_cap()

void usbd_msc_get_cap(uint8_t busid, uint8_t lun, uint32_t *block_num, uint32_t *block_size)
{
    *block_num = partition->size / CONFIG_USBDEV_MSC_MAX_BUFSIZE;
    *block_size = CONFIG_USBDEV_MSC_MAX_BUFSIZE;
}

The block size is set to CONFIG_USBDEV_MSC_MAX_BUFSIZE, and the number of blocks is the partition size divided by the block size;

usbd_msc_sector_read()

int usbd_msc_sector_read(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length)
{
    esp_partition_read_raw(partition, sector*CONFIG_USBDEV_MSC_MAX_BUFSIZE, buffer, length);
    return 0;
}

The two parameters in esp_partition_read_raw() are the offset address relative to the partition base address.

usbd_msc_sector_write()

int usbd_msc_sector_write(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length)
{
    esp_partition_erase_range(partition, sector*CONFIG_USBDEV_MSC_MAX_BUFSIZE, length);
    esp_partition_write_raw(partition, sector*CONFIG_USBDEV_MSC_MAX_BUFSIZE, buffer, length);
    return 0;
}

According to the requirements, the data must be erased before reading.

Call

Call msc_storage_init(0, ESP_USBD_BASE); in the main function.

Testing

Test USB Drive Read/Write

If the compilation is successful, you can download the code and connect the ESP32’s USB to the computer, where you can find that the computer recognizes a USB drive.

Note: On the first use, the computer will likely require you to initialize the USB drive.

Implementing USB Functionality on ESP32S3

Then create a pc.txt file and write the file is created by PC, and find that file creation and writing are successful.

Test Reading Files in ESP32

To read pc.txt in ESP32, you first need to mount a FAT system and then perform read/write operations using the C standard library.

Mounting

/*Configuration information */
const esp_vfs_fat_mount_config_t mount_config = {
  .max_files = 5,                    // Maximum number of files that can be opened at once
  .format_if_mount_failed = false,     // If mounting fails, format it
  .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, // Allocation unit size
};
/*Mount*/
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Filesystem mounted");

Reading

 /*Try to read the file */
FILE * f = fopen("/spiflash/pc.txt", "r");
 if (f == NULL) {
     ESP_LOGE(TAG, "The file does not exist");
     return;
 }

 char line[128] = {0};
fgets(line, sizeof(line), f);
fclose(f);

// strip newline
char *pos = strchr(line, '\n');
if (pos) {
    *pos = '\0';
}
else
{
    ESP_LOGW(TAG, "File format is incorrect");
}

ESP_LOGI(TAG, "File content: '%s'", line);

Result:

I (476) main-task: Filesystem mounted
I (476) main-task: File content: 'the file is created by PC'

pc.txt is correctly recognized and read.

To enable FATFS to support long file names and Chinese characters, the FATFS configuration needs to be modified.

Implementing USB Functionality on ESP32S3

Leave a Comment