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
.
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.
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.