存储主机方案
概述
USB Mass Storage Class (MSC) 协议建立了主机与大容量存储设备之间的标准通信规范。
Ameba 基于 USB-IF 官方发布的 MSC 协议标准,实现了完备的 USB MSC 主机协议栈。支持通过 SCSI(Small Computer System Interface)命令集与 MSC 设备交互,实现稳定、高速的文件读写。
特性
支持标准的 MSC 设备(如 U 盘、SD 卡读卡器等)
最大支持 32GB 存储容量
支持 FAT32 文件系统的读写操作
支持热插拔
自动解析描述符,自适应速度模式
批量传输长度等参数可配置
应用场景
作为 USB 存储主机,Ameba 可通过 USB 接口挂载外部大容量存储设备,并结合网络实现多种数据交互应用,例如:
智能网络存储/私有云:结合网络协议栈打通本地存储与云端连接,支持文件的自动双向同步及基于 DDNS/VPN 的安全远程访问,实现轻量级家庭私有云。
工业物联网网关:在弱网环境(如矿山、远洋)下网关将传感器采集数据或串口日志缓存至 USB 存储设备,待网络恢复后自动执行断点续传上传云端,确保工业数据的完整性与可靠性。
AI 媒体库与存储扩展:利用外部存储存储海量媒体文件或 AI 知识库,突破设备内部 Flash 容量限制,实现本地离线检索与低成本功能扩展。
硬件安全密钥:设备在启动或权限验证阶段扫描 U 盘内的特定加密证书或 License 文件,校验通过后激活系统高级权限,充当物理隔离的低成本硬件授权锁。
协议简介
MSC 协议定义了在 USB 规范下实现存储设备的传输和控制功能,遵循该标准的常见设备有 U 盘、移动硬盘和读卡器等。
描述符结构
MSC 设备除了遵循标准的 USB 描述符规范(如设备描述符、配置描述符、端点描述符)外,还需通过接口描述符声明其使用的通信协议(如 SCSI),并通过批量端点封装传输指令与数据。
以下展示了基于 Bulk-Only Transport (BOT) 模式并使用 SCSI 命令集的标准 USB MSC 描述符结构:
Device Descriptor
└── Identifies basic device information
Configuration Descriptor
└── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── Endpoint Descriptor(BULK IN)
└── Endpoint Descriptor(BULK OUT)
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── Endpoint Descriptor(BULK IN)
└── Endpoint Descriptor(BULK OUT)
协议文档
USB-IF 官方发布了 MSC 类基础协议及关于 BOT 传输协议规范。开发过程中请参考以下核心文档:
MSC BOT 传输协议规范,请从 USB IF 官网下载 Mass Storage Bulk Only
BOT 传输过程中使用的 SCSI 命令集,请参考 https://www.t10.org/, 其中主要关注下面两个规范:
规范
说明
文档
SCSI Primary Commands (SPC)
所有 SCSI 设备通用的基本命令
SCSI Block Commands (SBC)
针对块存储设备(硬盘、U盘、SSD)的特定命令
软硬件层级结构
下图展示了命令和数据在主机和设备之间所经过的软硬件层级。
以读操作为例,当用户从 U 盘读取一个文件时:
主机端:
应用请求:用户在应用程序(如文件管理器)中发起读文件请求。
文件系统转换:文件系统将文件名与偏移量转换为逻辑块地址(LBA)读取请求,并生成标准的 SCSI READ 命令。
协议封装:主机 MSC 类驱动将 SCSI 命令封装为 MSC 协议规定的命令包格式。
硬件发送:USB 主机控制器驱动通过 USB 物理端口将数据包发送至总线。
设备端:
硬件接收:USB 设备控制器从物理端口接收数据包。
协议解析:MSC 设备类驱动校验数据包完整性,并解析出内部封装的 SCSI 命令。
介质访问:根据解析出的命令参数(如 LBA 地址和长度),从底层存储介质(如 SD 卡)读取对应数据。
返回数据和状态:将读取的数据返回给主机,并回应命令执行状态。
BOT 传输流程
当 MSC 设备连接至主机并完成枚举后,若识别为支持 BOT 模式的大容量存储设备,后续的数据通信将仅通过批量端点进行。批量传输不受严格的时间限制,能够最大程度保证数据的完整性。
根据 MSC BOT 传输协议规范,所有的传输均遵循 "命令 (Command) -> 数据 (Data) -> 状态 (Status)" 的三段式流程:
CBW (Command Block Wrapper):由主机发往设备,包内封装了具体的 SCSI 命令(如 READ, WRITE, INQUIRY 等)。
Data (数据阶段):传输实际的文件或控制数据(传输方向取决于 SCSI 命令类型,对于某些无数据交互的命令,此阶段可省略)。
CSW (Command Status Wrapper):由设备发往主机,用于报告上一条 CBW 命令的执行结果(成功、失败或阶段错误)。
数据传输流程如下:
主机发起请求:主机 MSC 类驱动将 SCSI 命令封装进 CBW 包,通过批量 OUT 端点发送给设备。
设备解析与执行:设备接收 CBW 包并进行合法性检查,解析出 SCSI 命令。若 CBW 有效,设备将根据命令操作底层物理存储介质:
写操作 (如 WRITE):通过批量 OUT 端点接收主机发送的数据流,并写入存储介质。
读操作 (如 READ):从存储介质读取数据,通过批量 IN 端点回传给主机。
无数据命令 (如 TEST UNIT READY):跳过数据阶段,直接进入状态阶段。
设备反馈状态:数据传输完成后(或无需传输数据),设备通过批量 IN 端点发送 CSW 包,向主机报告命令执行结果。
主机确认完成:主机解析接收到的 CSW 包,检查 bCSWStatus 字段以确认命令是否执行成功,从而结束本次操作。
类特定请求
MSC 设备的控制请求分为 标准请求(Standard Requests) 和 类特定请求 (Class-Specific Requests) 。
本节主要介绍 MSC BOT 规范特有的 类特定请求,这些请求用于实现特有的存储功能,只有下面两个:
MSC 类特定请求 |
要求 |
描述 |
|---|---|---|
Bulk-Only Mass Storage Reset |
必选 |
重置设备接口及其相关的端点 |
Get Max LUN |
必选 |
查询该设备支持的最大逻辑单元数量 |
SCSI 命令
MSC BOT 规范下主要的 SCSI 命令如下:
SCSI 命令 |
要求 |
描述 |
|---|---|---|
INQUIRY |
必选 |
主机发送的第一个命令,用于查询设备信息 |
REQUEST_SENSE |
必选 |
当任何命令执行失败时,主机会发送此命令来获取详细的错误信息 |
TEST_UNIT_READY |
必选 |
检查设备是否就绪 |
READ_CAPACITY(10) |
必选 |
查询存储介质的容量 |
READ(10) |
必选 |
核心的读数据命令 |
WRITE(10) |
必选 |
核心的写数据命令 |
MODE_SENSE(6) |
可选 |
查询设备的特定参数,如缓存策略、写保护状态等 |
ALLOW_MEDIUM_REMOVAL |
可选 |
用来允许或禁止移除介质,实现“安全删除硬件”功能 |
START_STOP_UNIT |
可选 |
用于加载/弹出介质 |
VERIFY(10) |
可选 |
请求设备验证指定块的数据是否可读,但不传输数据 |
READ_FORMAT_CAPACITIES |
可选 |
提供比 READ_CAPACITY 更详细的容量和格式信息 |
备注
详细 SCSI 标准可参考官方文档。
类驱动
USB 协议栈提供了基于 BOT(Bulk-Only Transport)传输协议,使用 SCSI 命令集的标准 MSC 主机类驱动。
通道配置
数量 |
描述 |
|---|---|
2 |
控制 IN/OUT 传输 |
1 |
批量 IN 传输 |
1 |
批量 OUT 传输 |
类特定请求实现
驱动已实现了 MSC 的两个类特定请求(Class-Specific Requests)。
MSC 类特定请求 |
说明 |
|---|---|
Bulk-Only Mass Storage Reset |
当 BOT 传输出错时,主机请求重置传输 |
Get Max LUN |
设备连接后,主机查询设备支持逻辑单元数量 |
SCSI 命令实现
驱动已实现的 MSC BOT 规范下的 SCSI 命令如下,开发者可参考现有实现来扩展其他命令。
INQUIRY
REQUEST_SENSE
TEST_UNIT_READY
READ_CAPACITY(10)
READ(10)
WRITE(10)
FatFS 磁盘驱动 API
类驱动提供了类型为 ll_diskio_drv 的 FatFS 磁盘驱动 USB_disk_Driver,实现了以下 API:
API |
描述 |
|---|---|
disk_initialize |
初始化USB磁盘 |
disk_deinitialize |
注销USB磁盘 |
disk_status |
获取USB磁盘状态:RES_OK:就绪,RES_ERROR:未就绪 |
disk_read |
从USB磁盘中读取数据 |
disk_write |
将数据写入USB磁盘,仅当FATFS使能_USE_WRITE时有效 |
disk_ioctl |
支持以下FATFS IO控制指令:
|
API 说明
应用示例
应用设计
加载驱动
调用下面的示例代码完成配置结构体定和回调函数定义,随后调用初始化接口,加载 USB 主机核心驱动及 MSC 类驱动。
static usbh_config_t usbh_cfg = {
.speed = USB_SPEED_HIGH,
.ext_intr_enable = USBH_SOF_INTR,
.isr_priority = INT_PRI_MIDDLE,
.main_task_priority = 3U,
.tick_source = USBH_SOF_TICK,
}
static usbh_msc_cb_t usbh_msc_usr_cb = {
.attach = usbh_msc_cb_attach, /* USB device attach callback */
.setup = usbh_msc_cb_setup, /* USB device setup done, indicate that device is ready for bulk transfer */
};
static usbh_user_cb_t usbh_usr_cb = {
.process = usbh_msc_cb_process
};
int ret = 0;
ret = usbh_init(&usbh_cfg, &usbh_usr_cb); /* Initialize USB host core driver with configuration and user callback. */
if (ret != HAL_OK) {
return;
}
ret = usbh_msc_init(&usbh_msc_usr_cb); /* Initializes the MSC host class with MSC class user callback. */
if (ret != HAL_OK) {
usbd_msc_disk_deinit();
return;
}
文件读写
加载驱动后才可以对 MSC 设备进行文件读取、写入。
static rtos_sema_t msc_attach_sema;
static __IO int msc_is_ready = 0;
static int usbh_msc_cb_attach(void)
{
RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACH\n");
rtos_sema_give(msc_attach_sema);
return HAL_OK;
}
static int usbh_msc_cb_setup(void)
{
RTK_LOGS(TAG, RTK_LOG_INFO, "SETUP\n");
msc_is_ready = 1;
return HAL_OK;
}
static int usbh_msc_cb_process(usb_host_t *host, u8 msg)
{
UNUSED(host);
switch (msg) {
case USBH_MSG_DISCONNECTED:
msc_is_ready = 0;
break;
case USBH_MSG_CONNECTED:
break;
default:
break;
}
return HAL_OK;
}
static u32 filenum = 0;
static u8 *msc_wt_buf;
static u8 *msc_rd_buf;
FATFS fs;
FIL f;
int drv_num = 0;
char logical_drv[4];
char path[64] = {'0'};
u32 br;
u32 bw;
rtos_sema_create(&msc_attach_sema, 0U, 1U);
msc_wt_buf = (u8 *)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);
if (msc_wt_buf == NULL) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to alloc test buf\n");
goto exit_deinit;
}
msc_rd_buf = (u8 *)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);
if (msc_rd_buf == NULL) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to alloc test buf\n");
goto exit_deinit;
}
/* Register USB disk driver to fatfs*/
drv_num = FATFS_RegisterDiskDriver(&USB_disk_Driver);
if (drv_num < 0) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to register\n");
goto exit_deinit;
}
logical_drv[0] = drv_num + '0';
logical_drv[1] = ':';
logical_drv[2] = '/';
logical_drv[3] = 0;
while (1) {
if (msc_is_ready) {
rtos_time_delay_ms(10); /* Wait for MSC device is ready for class-specific communication */
break;
}
}
/* Mount logical drive */
if (f_mount(&fs, logical_drv, 1) != FR_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to mount logical drive\n");
FATFS_UnRegisterDiskDriver(drv_num);
goto exit_deinit;
}
while (1) {
if (rtos_sema_take(msc_attach_sema, RTOS_SEMA_MAX_COUNT) != RTK_SUCCESS) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to take sema\n");
continue; /* Wait for MSC device attach*/
}
}
/* Construct the device file path */
strcpy(path, logical_drv);
sprintf(&path[3], "TEST%ld.DAT", filenum);
/* Open test file */
f_open(&f, path, FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
/* Write the data from a buffer to an opened file */
f_write(&f, (void *)msc_wt_buf, USBH_MSC_TEST_BUF_SIZE, (UINT *)&bw);
/* Move the file pointer to the file head */
f_lseek(&f, 0);
/* Read data from an opened file to a buffer */
f_read(&f, (void *)msc_rd_buf, USBH_MSC_TEST_BUF_SIZE, (UINT *)&br);
/* Close source file */
f_close(&f);
exit_deinit:
rtos_sema_delete(msc_attach_sema);
if (msc_wt_buf) {
rtos_mem_free(msc_wt_buf);
}
if (msc_rd_buf) {
rtos_mem_free(msc_rd_buf);
}
usbh_msc_deinit();
usbh_deinit();
return;
卸载驱动
在不再需要存储功能或系统关机时,按加载的反序释放资源。
/* Deinitialize MSC host class driver. */
usbh_msc_deinit();
/* Deinitialize USB host core driver. */
usbh_deinit();
运行方式
本节介绍了一个完整的 USB 大容量存储(MSC)应用示例,该示例演示了如何通过 MSC 协议栈,将 Ameba 开发板配置为 USB 存储主机。
当开发板连接至支持 FAT32 格式的 U 盘等标准 MSC 设备时,主机可以识别并基于 FatFS 进行简单的文件读写测试。
该示例路径: {SDK}/example/usb/usbh_msc,可为开发者设计自定义 USB 存储主机产品提供完整的设计参考。
配置与编译
编译与烧录
在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程,然后将生成的
Image文件烧录至开发板。# Initialize environment (required for every new terminal) source env.sh or env.bat(Windows system) # Select Target SoC (replace xxx with your specific SoCs) ameba.py soc xxx ameba.py build -a usbh_msc -p
Menuconfig 配置确认
若编译失败,请执行
ameba.py menuconfig,确认已选择USBH MSC。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Host) ---> [*] MSC
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[MSC-I] USBH MSC demo start [MSC-I] Register USB disk [MSC-I] FatFS USB W/R performance test start...
连接设备
用 OTG 线将格式化为 FatFS 的 U 盘连接到开发板的 USB 端口。
识别和测试
Ameba 开发板将识别 MSC 设备并进行读写性能测试。
[USBH-A] Device attached,speed 0 [USBH-A] PID: 0x6545 [USBH-A] VID: 0x0930 [USBH-A] Address 1 assigned [USBH-A] MFG: TOSHIBA [USBH-A] PROD: TransMemory [USBH-A] SN: C03FD5F7715FE3417000DE76 [USBH-A] Enum done, total 1 cfg [USBH-A] Switch to itf: 0 [USBH-A] Class: 0x08 [USBH-A] SubClass: 0x06 [USBH-A] Protocol: 0x50 [MSC-I] ATTACH [MSC-I] Max lun 1 [MSC-I] Lun 0 [MSC-I] SETUP [MSC-I] Open file: 0:/TEST0.DAT [MSC-I] W test: size 512, round 20... [MSC-I] W rate 204.0 KB/s for 20 round @ 49 ms [MSC-I] R test: size = 512 round = 20... [MSC-I] R rate 476.1 KB/s for 20 round @ 21 ms [MSC-I] W test: size 1024, round 20... [MSC-I] W rate 540.5 KB/s for 20 round @ 37 ms [MSC-I] R test: size = 1024 round = 20... [MSC-I] R rate 800.0 KB/s for 20 round @ 25 ms [MSC-I] W test: size 2048, round 20... [MSC-I] W rate 655.7 KB/s for 20 round @ 61 ms [MSC-I] R test: size = 2048 round = 20... [MSC-I] R rate 1212.1 KB/s for 20 round @ 33 ms [MSC-I] W test: size 4096, round 20... [MSC-I] W rate 1095.8 KB/s for 20 round @ 73 ms [MSC-I] R test: size = 4096 round = 20... [MSC-I] R rate 1600.0 KB/s for 20 round @ 50 ms [MSC-I] FatFS USB W/R performance test done [MSC-I] File close OK