存储主机方案

概述

USB Mass Storage Class (MSC) 协议建立了主机与大容量存储设备之间的标准通信规范。

Ameba 基于 USB-IF 官方发布的 MSC 协议标准,实现了完备的 USB MSC 主机协议栈。支持通过 SCSI(Small Computer System Interface)命令集与 MSC 设备交互,实现稳定、高速的文件读写。

Ameba USB 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 传输协议规范。开发过程中请参考以下核心文档:

软硬件层级结构

下图展示了命令和数据在主机和设备之间所经过的软硬件层级。

../../../_images/usb_msc_arch_zh.svg

以读操作为例,当用户从 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 命令的执行结果(成功、失败或阶段错误)。

../../../_images/usb_msc_bot_flow_zh.svg

数据传输流程如下:

  • 主机发起请求:主机 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控制指令:

  • CTRL_SYNC:强制将缓冲区的数据写入物理存储介质,对于USB磁盘,可认为是写穿透的,不需要SYNC

  • GET_SECTOR_COUNT:获取sector数量

  • GET_SECTOR_SIZE:获取sector大小

  • GET_BLOCK_SIZE:获取block大小

API 说明

驱动 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