mirror of
https://github.com/espressif/esp-idf.git
synced 2024-09-20 00:36:01 -04:00
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:
commit
a9e40f6045
@ -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
|
||||
|
8
examples/peripherals/usb/device/tusb_msc/CMakeLists.txt
Normal file
8
examples/peripherals/usb/device/tusb_msc/CMakeLists.txt
Normal 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)
|
180
examples/peripherals/usb/device/tusb_msc/README.md
Normal file
180
examples/peripherals/usb/device/tusb_msc/README.md
Normal 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>
|
||||
```
|
@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "tusb_msc_storage.c" "tusb_msc_main.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES fatfs wear_levelling console
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_tinyusb: "0.0.1"
|
||||
idf: "^5.1"
|
455
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c
Normal file
455
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c
Normal 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));
|
||||
}
|
201
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.c
Normal file
201
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.c
Normal 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);
|
||||
}
|
118
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.h
Normal file
118
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.h
Normal 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
|
6
examples/peripherals/usb/device/tusb_msc/partitions.csv
Normal file
6
examples/peripherals/usb/device/tusb_msc/partitions.csv
Normal 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,
|
|
@ -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')
|
14
examples/peripherals/usb/device/tusb_msc/sdkconfig.defaults
Normal file
14
examples/peripherals/usb/device/tusb_msc/sdkconfig.defaults
Normal 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
|
Loading…
Reference in New Issue
Block a user