Merge branch 'usb_msc' into 'master'

usb: Add MSC device Example

Closes IDF-580

See merge request espressif/esp-idf!20881
This commit is contained in:
Saurabh Kumar Bansal 2022-12-04 14:02:47 +08:00
commit a9e40f6045
11 changed files with 1074 additions and 1 deletions

View File

@ -77,7 +77,7 @@ However, the driver also provides default descriptors. You can install the drive
- bcdDevice
- Manufacturer
- Product name
- Name of CDC device if it is On
- Name of CDC or MSC device if it is On
- Serial number
If you want to use your own descriptors with extended modification, you can define them during the driver installation process.
@ -141,6 +141,66 @@ USB Serial Console
The driver allows to redirect all standard application streams (stdinm stdout, stderr) to the USB Serial Device and return them to UART using :cpp:func:`esp_tusb_init_console`/:cpp:func:`esp_tusb_deinit_console` functions.
USB Mass Storage Device (MSC)
-----------------------------
If the MSC CONFIG_TINYUSB_MSC_ENABLED option is enabled and SPI Flash Wear Levelling WL_SECTOR_SIZE is set to 512 and WL_SECTOR_MODE is set to PERF in Menuconfig, the USB MSC Device can be initialized as shown below (see example below).
.. code-block:: c
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,
.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
};
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &descriptor_config,
.string_descriptor = string_desc_arr,
.external_phy = false,
.configuration_descriptor = desc_configuration,
};
tinyusb_driver_install(&tusb_cfg);
The mandatory callbacks that are required to be implemented are
.. code-block:: c
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
bool tud_msc_test_unit_ready_cb(uint8_t lun)
void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
Application Examples
--------------------
@ -160,3 +220,5 @@ The table below describes the code examples available in the directory :example:
- How to set up {IDF_TARGET_NAME} chip to work as a USB MIDI Device
* - :example:`peripherals/usb/device/tusb_hid`
- How to set up {IDF_TARGET_NAME} chip to work as a USB Human Interface Device
* - :example:`peripherals/usb/device/tusb_msc`
- How to set up {IDF_TARGET_NAME} chip to work as a USB Mass Storage Device

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tusb_msc)

View File

@ -0,0 +1,180 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# TinyUSB Mass Storage Device Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
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.
This example contains code to make ESP based device recognizable by USB-hosts as a USB Mass Storage Device.
It either allows the embedded application ie 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.
The access to the underlying block device is provided by functions in tusb_msc_storage.c
In this example, data is read/written from/to SPI Flash through wear-levelling APIs. Wear leveling is a technique that helps to distribute wear and tear among sectors more evenly without requiring any attention from the user. As a result, it helps in extending the life of each sector of the Flash memory.
As a USB stack, a TinyUSB component is used.
## How to use example
### Scenarios
1. USB which accesses the ESP MSC Partition is unplugged initially and the board is powered-on.
- Result: Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition.
2. USB which accesses the ESP MSC Partition is already plugged-in at boot time.
- Result: Host PC recongnize it as removable device and can access the partition over USB MSC. Application example can't perform any operation on partition.
3. USB which accesses the ESP MSC Partition is plugged-in at boo-up. After boot-up, it is ejected on Host PC manually by user.
- Result: Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition.
4. USB which accesses the ESP MSC Partition is plugged-in at boot-up. It is then unplugged(removed) from Host PC manually by user.
- Result: The behaviour is different for bus-powered devices and self-powered devices
- (a) Bus-Powered devices - Both Host PC as well as application example can't access the partition over USB MSC. Here, the device will be Powered-off.
- (b) Self-Powered devices - Here, the device can be powered-on even after unplugging the device from Host PC. These behaviour can be further categorize in two ways:
- (i) Self-Powered Devices without VBUS monitoring - Both Host PC as well as application example can't access the partition over USB MSC.
- (ii) Self-Powered Devices with VBUS monitoring - Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition. Here, in ``tinyusb_config_t`` user must set ``self_powered`` to ``true`` and ``vbus_monitor_io`` to GPIO number (``VBUS_MONITORING_GPIO_NUM``) that will be used for VBUS monitoring.
### Hardware Required
Any ESP board that have USB-OTG supported.
### Pin Assignment
_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
Next, for Self-Powered Devices with VBUS monitoring, user must set ``self_powered`` to ``true`` and ``vbus_monitor_io`` to GPIO number (``VBUS_MONITORING_GPIO_NUM``) that will be used for VBUS monitoring.
### 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 (311) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (332) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (332) example_msc_main: Initializing storage...
I (342) example_msc_storage: Initializing wear levelling
I (372) example_msc_main: USB MSC initialization
I (372) tusb_desc:
┌─────────────────────────────────┐
│ USB Device Descriptor Summary │
├───────────────────┬─────────────┤
│bDeviceClass │ 239 │
├───────────────────┼─────────────┤
│bDeviceSubClass │ 2 │
├───────────────────┼─────────────┤
│bDeviceProtocol │ 1 │
├───────────────────┼─────────────┤
│bMaxPacketSize0 │ 64 │
├───────────────────┼─────────────┤
│idVendor │ 0x303a │
├───────────────────┼─────────────┤
│idProduct │ 0x4002 │
├───────────────────┼─────────────┤
│bcdDevice │ 0x100 │
├───────────────────┼─────────────┤
│iManufacturer │ 0x1 │
├───────────────────┼─────────────┤
│iProduct │ 0x2 │
├───────────────────┼─────────────┤
│iSerialNumber │ 0x3 │
├───────────────────┼─────────────┤
│bNumConfigurations │ 0x1 │
└───────────────────┴─────────────┘
I (532) TinyUSB: TinyUSB Driver installed
I (532) example_msc_main: USB MSC initialization DONE
I (542) example_msc_main: Mount storage...
I (542) example_msc_storage: Initializing FAT
I (552) example_msc_main:
ls command output:
README.MD
.fseventsd
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
esp32s3> I (912) example_msc_main: tud_mount_cb MSC START: Expose Over USB
I (912) example_msc_main: Unmount storage...
I (2032) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
I (2032) example_msc_main: tud_msc_capacity_cb() size(1024000), sec_size(512)
esp32s3>
esp32s3>
esp32s3> help
help
Print the list of registered commands
read
read BASE_PATH/README.MD and print its contents
write
create file BASE_PATH/README.MD if it does not exist
size
show storage size and sector size
expose
Expose Storage to Host
status
Status of storage exposure over USB
exit
exit from application
esp32s3>
esp32s3> read
E (19102) example_msc_main: storage exposed over USB. Application can't read from storage.
Command returned non-zero error code: 0xffffffff (ESP_FAIL)
esp32s3> write
E (22412) example_msc_main: storage exposed over USB. Application can't write to storage.
Command returned non-zero error code: 0xffffffff (ESP_FAIL)
esp32s3> size
E (24962) example_msc_main: storage exposed over USB. Application can't access storage
Command returned non-zero error code: 0xffffffff (ESP_FAIL)
esp32s3> status
storage exposed over USB: Yes
esp32s3>
esp32s3>
esp32s3> I (49692) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
I (49692) example_msc_main: tud_msc_start_stop_cb() invoked, power_condition=0, start=0, load_eject=1
I (49702) example_msc_main: tud_msc_start_stop_cb: MSC EJECT: Mount on Example
I (49712) example_msc_main: Mount storage...
I (49712) example_msc_storage: Initializing FAT
I (49712) example_msc_main:
ls command output:
README.MD
esp32s3>
esp32s3>
esp32s3> status
storage exposed over USB: No
esp32s3> read
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.
In this example, ESP chip will be recognised by host (PC) as Mass Storage Device.
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.
esp32s3> write
esp32s3> size
storage size(1024000), sec_size(512)
esp32s3>
esp32s3> expose
I (76402) example_msc_main: Unmount storage...
esp32s3> I (76772) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
I (76772) example_msc_main: tud_msc_capacity_cb() size(1024000), sec_size(512)
esp32s3>
esp32s3> status
storage exposed over USB: Yes
esp32s3>
esp32s3>
```

View File

@ -0,0 +1,5 @@
idf_component_register(
SRCS "tusb_msc_storage.c" "tusb_msc_main.c"
INCLUDE_DIRS .
REQUIRES fatfs wear_levelling console
)

View File

@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "0.0.1"
idf: "^5.1"

View File

@ -0,0 +1,455 @@
/*
* SPDX-FileCopyrightText: 2022 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 ie 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.
* The access to the underlying block device is provided by functions in tusb_msc_storage.c
* For different scenarios and behaviour, Refer to README of this example.
*/
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include "esp_log.h"
#include "esp_console.h"
#include "tinyusb.h"
#include "class/msc/msc_device.h"
#include "tusb_msc_storage.h"
#include "driver/gpio.h"
static const char *TAG = "example_msc_main";
#define PROMPT_STR CONFIG_IDF_TARGET
/********* TinyUSB MSC callbacks ***************/
/** SCSI ASC/ASCQ codes. **/
/** User can add and use more codes as per the need of the application **/
#define SCSI_CODE_ASC_MEDIUM_NOT_PRESENT 0x3A /** SCSI ASC code for 'MEDIUM NOT PRESENT' **/
#define SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE 0x20 /** SCSI ASC code for 'INVALID COMMAND OPERATION CODE' **/
#define SCSI_CODE_ASCQ 0x00
static void _mount(void);
static void _unmount(void);
static bool is_eject = false;
// 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;
ESP_LOGD(TAG, "tud_msc_inquiry_cb() invoked");
const char vid[] = "TinyUSB";
const char pid[] = "Flash Storage";
const char rev[] = "0.1";
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;
ESP_LOGD(TAG, "tud_msc_test_unit_ready_cb() invoked");
if (is_eject) {
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, SCSI_CODE_ASC_MEDIUM_NOT_PRESENT, SCSI_CODE_ASCQ);
return false;
} else {
ESP_LOGD(TAG, "tud_msc_test_unit_ready_cb: MSC START: Expose Over USB");
_unmount();
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;
size_t size = storage_get_size();
size_t sec_size = storage_get_sector_size();
ESP_LOGI(TAG, "tud_msc_capacity_cb() size(%d), sec_size(%d)", size, sec_size);
*block_count = size / sec_size;
*block_size = sec_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;
ESP_LOGI(TAG, "tud_msc_start_stop_cb() invoked, power_condition=%d, start=%d, load_eject=%d", power_condition, start, load_eject);
if (load_eject && !start) {
is_eject = true;
ESP_LOGI(TAG, "tud_msc_start_stop_cb: MSC EJECT: Mount on Example");
_mount();
}
return true;
}
// Invoked when received SCSI READ10 command
// - Address = lba * BLOCK_SIZE + offset
// - Application fill the buffer (up to bufsize) with address contents and return number of read byte.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
{
ESP_LOGD(TAG, "tud_msc_read10_cb() invoked, lun=%d, lba=%lu, offset=%lu, bufsize=%lu", lun, lba, offset, bufsize);
size_t addr = lba * storage_get_sector_size() + offset;
esp_err_t err = storage_read_sector(addr, bufsize, buffer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "storage_read_sector failed: 0x%x", err);
return 0;
}
return bufsize;
}
// Invoked when received SCSI WRITE10 command
// - Address = lba * BLOCK_SIZE + offset
// - Application write data from buffer to address contents (up to bufsize) and return number of written byte.
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
ESP_LOGD(TAG, "tud_msc_write10_cb() invoked, lun=%d, lba=%lu, offset=%lu", lun, lba, offset);
size_t addr = lba * storage_get_sector_size() + offset;
esp_err_t err = storage_write_sector(addr, bufsize, buffer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "storage_write_sector failed: 0x%x", err);
return 0;
}
return bufsize;
}
/**
* Invoked when received an SCSI command not in built-in list below.
* - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE
* - READ10 and WRITE10 has their own callbacks
*
* \param[in] lun Logical unit number
* \param[in] scsi_cmd SCSI command contents which application must examine to response accordingly
* \param[out] buffer Buffer for SCSI Data Stage.
* - For INPUT: application must fill this with response.
* - For OUTPUT it holds the Data from host
* \param[in] bufsize Buffer's length.
*
* \return Actual bytes processed, can be zero for no-data command.
* \retval negative Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding
* endpoint and return failed status in command status wrapper phase.
*/
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
{
int32_t ret;
ESP_LOGD(TAG, "tud_msc_scsi_cb() invoked. bufsize=%d", bufsize);
switch (scsi_cmd[0]) {
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
/* SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL is the Prevent/Allow Medium Removal
command (1Eh) that requests the library to enable or disable user access to
the storage media/partition. */
ESP_LOGI(TAG, "tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL");
ret = 0;
break;
default:
ESP_LOGW(TAG, "tud_msc_scsi_cb() invoked: %d", scsi_cmd[0]);
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ);
ret = -1;
break;
}
return ret;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
is_eject = true;
ESP_LOGI(TAG, "tud_umount_cb: Mount on Example");
_mount();
}
// Invoked when device is mounted (configured)
void tud_mount_cb(void)
{
ESP_LOGI(TAG, "tud_mount_cb MSC START: Expose Over USB");
_unmount();
}
/************* Application Code *******************/
/************* TinyUSB descriptors ****************/
#define EPNUM_MSC 1
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
#define VBUS_MONITORING_GPIO_NUM GPIO_NUM_4
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
};
#define BASE_PATH "/data" // base path to mount the partition
static bool is_mount = false;
// mount the partition and show all the files in BASE_PATH
static void _mount(void)
{
ESP_LOGI(TAG, "Mount storage...");
if (!is_mount) {
ESP_ERROR_CHECK(storage_mount(BASE_PATH));
is_mount = true;
}
// 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 the partition
static void _unmount(void)
{
if (!is_mount) {
ESP_LOGD(TAG, "storage exposed over USB...");
return;
}
ESP_LOGI(TAG, "Unmount storage...");
ESP_ERROR_CHECK(storage_unmount());
is_mount = false;
is_eject = false;
}
static int f_unmount(int argc, char **argv)
{
_unmount();
return 0;
}
// read BASE_PATH/README.MD and print its contents
static int f_read(int argc, char **argv)
{
if (!is_mount) {
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 f_write(int argc, char **argv)
{
if (!is_mount) {
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 f_size(int argc, char **argv)
{
if (!is_mount) {
ESP_LOGE(TAG, "storage exposed over USB. Application can't access storage");
return -1;
}
size_t size = storage_get_size();
size_t sec_size = storage_get_sector_size();
printf("storage size(%d), sec_size(%d)\n", size, sec_size);
return 0;
}
// exit from application
static int f_status(int argc, char **argv)
{
printf("storage exposed over USB: %s\n", is_mount ? "No" : "Yes");
return 0;
}
// exit from application
static int f_exit(int argc, char **argv)
{
printf("Application Exiting\n");
exit(0);
return 0;
}
void app_main(void)
{
// Configure GPIO Pin for vbus monitorung
const gpio_config_t vbus_gpio_config = {
.pin_bit_mask = BIT64(VBUS_MONITORING_GPIO_NUM),
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = true,
.pull_down_en = false,
};
ESP_ERROR_CHECK(gpio_config(&vbus_gpio_config));
ESP_LOGI(TAG, "Initializing storage...");
ESP_ERROR_CHECK(storage_init());
ESP_LOGI(TAG, "USB MSC initialization");
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &descriptor_config,
.string_descriptor = string_desc_arr,
.external_phy = false,
.configuration_descriptor = desc_configuration,
.self_powered = true,
.vbus_monitor_io = VBUS_MONITORING_GPIO_NUM,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB MSC initialization DONE");
//mounted in the app by default
_mount();
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));
const esp_console_cmd_t cmd_read = {
.command = "read",
.help = "read BASE_PATH/README.MD and print its contents",
.hint = NULL,
.func = &f_read,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_read) );
const esp_console_cmd_t cmd_write = {
.command = "write",
.help = "create file BASE_PATH/README.MD if it does not exist",
.hint = NULL,
.func = &f_write,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_write) );
const esp_console_cmd_t cmd_size = {
.command = "size",
.help = "show storage size and sector size",
.hint = NULL,
.func = &f_size,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_size) );
const esp_console_cmd_t cmd_umount = {
.command = "expose",
.help = "Expose Storage to Host",
.hint = NULL,
.func = &f_unmount,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_umount) );
const esp_console_cmd_t cmd_status = {
.command = "status",
.help = "Status of storage exposure over USB",
.hint = NULL,
.func = &f_status,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_status) );
const esp_console_cmd_t cmd_exit = {
.command = "exit",
.help = "exit from application",
.hint = NULL,
.func = &f_exit,
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_exit) );
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

View File

@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
// DESCRIPTION:
// This file contains the code for accessing the storage medium ie SPI Flash.
#include "esp_log.h"
#include "esp_err.h"
#include "esp_vfs_fat.h"
#include "diskio_impl.h"
#include "diskio_wl.h"
#include "wear_levelling.h"
#include "esp_partition.h"
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
static bool s_fat_mounted;
static const char *s_base_path;
static const char *TAG = "example_msc_storage";
esp_err_t storage_init(void)
{
ESP_LOGI(TAG, "Initializing wear levelling");
esp_err_t err;
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;
}
err = wl_mount(data_partition, &s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to mount wear levelling layer (0x%x)", err);
return err;
}
return ESP_OK;
}
static inline size_t esp_vfs_fat_get_allocation_unit_size(
size_t sector_size, size_t requested_size)
{
size_t alloc_unit_size = requested_size;
const size_t max_sectors_per_cylinder = 128;
const size_t max_size = sector_size * max_sectors_per_cylinder;
alloc_unit_size = MAX(alloc_unit_size, sector_size);
alloc_unit_size = MIN(alloc_unit_size, max_size);
return alloc_unit_size;
}
esp_err_t storage_mount(const char *base_path)
{
const size_t workbuf_size = 4096;
void *workbuf = NULL;
esp_err_t err;
if (s_fat_mounted) {
return ESP_OK;
}
ESP_LOGI(TAG, "Initializing FAT");
// connect driver to FATFS
BYTE pdrv = 0xFF;
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
ESP_LOGE(TAG, "the maximum count of volumes is already mounted");
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
char drv[3] = {(char)('0' + pdrv), ':', 0};
err = ff_diskio_register_wl_partition(pdrv, s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ff_diskio_register_wl_partition failed pdrv=%d (0x%x)", pdrv, err);
goto fail;
}
FATFS *fs;
err = esp_vfs_fat_register(base_path, drv, 2, &fs);
if (err == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_vfs_fat_register failed (0x%x)", err);
goto fail;
}
// Try to mount partition
FRESULT fresult = f_mount(fs, drv, 1);
if (fresult != FR_OK) {
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
if (!((fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR))) {
err = ESP_FAIL;
goto fail;
}
workbuf = ff_memalloc(workbuf_size);
if (workbuf == NULL) {
err = ESP_ERR_NO_MEM;
goto fail;
}
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
CONFIG_WL_SECTOR_SIZE,
4096);
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
const MKFS_PARM opt = {(BYTE)FM_FAT, 0, 0, 0, 0};
fresult = f_mkfs("", &opt, workbuf, workbuf_size); // Use default volume
if (fresult != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult);
goto fail;
}
free(workbuf);
workbuf = NULL;
ESP_LOGI(TAG, "Mounting again");
fresult = f_mount(fs, drv, 0);
if (fresult != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult);
goto fail;
}
}
s_fat_mounted = true;
s_base_path = base_path;
return ESP_OK;
fail:
free(workbuf);
esp_vfs_fat_unregister_path(base_path);
ff_diskio_unregister(pdrv);
s_fat_mounted = false;
ESP_LOGW(TAG, "Failed to mount storage (0x%x)", err);
return err;
}
esp_err_t storage_unmount(void)
{
if (!s_fat_mounted) {
return ESP_OK;
}
BYTE pdrv = ff_diskio_get_pdrv_wl(s_wl_handle);
if (pdrv == 0xff) {
return ESP_ERR_INVALID_STATE;
}
char drv[3] = {(char)('0' + pdrv), ':', 0};
f_mount(0, drv, 0);
ff_diskio_unregister(pdrv);
ff_diskio_clear_pdrv_wl(s_wl_handle);
esp_err_t err = esp_vfs_fat_unregister_path(s_base_path);
s_base_path = NULL;
s_fat_mounted = false;
return err;
}
size_t storage_get_size(void)
{
assert(s_wl_handle != WL_INVALID_HANDLE);
return wl_size(s_wl_handle);
}
size_t storage_get_sector_size(void)
{
assert(s_wl_handle != WL_INVALID_HANDLE);
return wl_sector_size(s_wl_handle);
}
esp_err_t storage_read_sector(size_t addr, size_t size, void *dest)
{
assert(s_wl_handle != WL_INVALID_HANDLE);
return wl_read(s_wl_handle, addr, dest, size);
}
esp_err_t storage_write_sector(size_t addr, size_t size, const void *src)
{
assert(s_wl_handle != WL_INVALID_HANDLE);
if (s_fat_mounted) {
ESP_LOGE(TAG, "can't write, FAT mounted");
return ESP_ERR_INVALID_STATE;
}
size_t sector_size = wl_sector_size(s_wl_handle);
if (addr % sector_size != 0 || size % sector_size != 0) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = wl_erase_range(s_wl_handle, addr, size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "wl_erase_range failed (0x%x)", err);
return err;
}
return wl_write(s_wl_handle, addr, src, size);
}

View File

@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include "esp_err.h"
/* Public functions
********************************************************************* */
/**
* @brief Initialize the Storage.
*
* First find 'first partition' based on one or more parameters (ie DATA, FAT).
* Mount WL for defined partition.
* Initialize the instance of WL instance.
* Once the storage is initialized, other storage functions can be used.
*
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NOT_FOUND, if Failed to find FATFS partition;
* - ESP_ERR_INVALID_ARG, if WL allocation was unsuccessful;
* - ESP_ERR_NO_MEM, if there was no memory to allocate WL components;
*/
esp_err_t storage_init(void);
/**
* @brief Mount the storage partition locally on the firmware application.
*
* Get the available drive number. Register spi flash partition.
* Connect POSIX and C standard library IO function with FATFS.
* Mounts the partition.
* This API is used by the firmware application. If the storage partition is
* mounted by this API, host (PC) can't access the storage via MSC.
*
* @param base_path path prefix where FATFS should be registered
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered;
*/
esp_err_t storage_mount(const char *base_path);
/**
* @brief Unmount the storage partition from the firmware application.
*
* Unmount the partition. Unregister diskio driver.
* Unregister the SPI flash partition.
* Finally, Un-register FATFS from VFS.
* After this function is called, storage device can be seen (recongnized) by host (PC).
*
* @return esp_err_t
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
*/
esp_err_t storage_unmount(void);
/**
* @brief Get size of the WL storage
*
* @return usable size, in bytes
*/
size_t storage_get_size(void);
/**
* @brief Get sector size of the WL instance
*
* @return sector size, in bytes
*/
size_t storage_get_sector_size(void);
/**
* @brief Read data from the WL storage
*
* @param addr Address of the data to be read, relative to the
* beginning of the partition.
* @param size Size of data to be read, in bytes.
* @param dest Pointer to the buffer where data should be stored.
* Pointer must be non-NULL and buffer must be at least 'size' bytes long.
* @return esp_err_t
* - ESP_OK, if data was read successfully;
* - ESP_ERR_INVALID_ARG, if src_offset exceeds partition size;
* - ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t storage_read_sector(size_t addr, size_t size, void *dest);
/**
* @brief Write data to the WL storage
*
* Before writing data to flash, corresponding region of flash needs to be erased.
* This is done internally using wl_erase_range function.
*
* @param addr Address where the data should be written, relative to the
* beginning of the partition.
* @param size Size of data to be written, in bytes.
* @param src Pointer to the source buffer. Pointer must be non-NULL and
* buffer must be at least 'size' bytes long.
* @return esp_err_t
* - ESP_OK, if data was written successfully;
* - ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size;
* - ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t storage_write_sector(size_t addr, size_t size, const void *src);
/*********************************************************************** Public functions*/
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,6 @@
# 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, 1M,
storage, data, fat, , 1M,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, fat, , 1M,

View File

@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32s2
@pytest.mark.usb_device
def test_usb_device_msc_example(dut: Dut) -> None:
dut.expect('USB MSC initialization DONE')
dut.expect('Mount storage')
dut.expect('Initializing FAT')
dut.write(' help')
dut.expect('read')
dut.expect('write')
dut.expect('size')
dut.expect('expose')
dut.expect('status')
dut.write(' status')
dut.expect('storage exposed over USB')

View File

@ -0,0 +1,14 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_TINYUSB=y
CONFIG_TINYUSB_MSC_ENABLED=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_MODE_PERF=y
CONFIG_FATFS_LFN_HEAP=y