大容量存储设备(如:U盘),是我们很常用的设备,本章将简单介绍USB MSC开发流程
前期准备
1.带USB 功能的MCU (笔者使用的NXP RT1062)
2.存储介质(ROM:EMMC 、FLASH、SD卡等 或 RAM)
U盘描述符
MSC描述符很好找到参考,笔者抓包自己的U盘,读取出如下描述符。将读取的描述符,拷贝到工程中用于开发
Device Descriptor:
------------------------------
0x12 bLength
0x01 bDescriptorType
0x0200 bcdUSB
0x00 bDeviceClass
0x00 bDeviceSubClass
0x00 bDeviceProtocol
0x40 bMaxPacketSize0 (64 bytes)
0x058F idVendor
0x6387 idProduct
0x0100 bcdDevice
0x01 iManufacturer "Generic"
0x02 iProduct "Teclast CoolFlash"
0x03 iSerialNumber "B9C88F53"
0x01 bNumConfigurations
Configuration Descriptor:
------------------------------
0x09 bLength
0x02 bDescriptorType
0x0020 wTotalLength (32 bytes)
0x01 bNumInterfaces
0x01 bConfigurationValue
0x00 iConfiguration
0x80 bmAttributes (Bus-powered Device)
0x64 bMaxPower (200 mA)
Interface Descriptor:
------------------------------
0x09 bLength
0x04 bDescriptorType
0x00 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x08 bInterfaceClass (Mass Storage Device Class)
0x06 bInterfaceSubClass (Transparent SCSI subclass)
0x50 bInterfaceProtocol (Bulk only transport)
0x00 iInterface
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x01 bEndpointAddress (OUT endpoint 1)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0200 wMaxPacketSize (512 bytes)
0x00 bInterval
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x82 bEndpointAddress (IN endpoint 2)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0200 wMaxPacketSize (512 bytes)
0x00 bInterval
MSC设备接口描述符(Interface Descriptor)
大容量存储设备类:0x08
子类接口代码:0x06 (SCSI 传输协议指令集)
接口协议代码:0x50
0x08 bInterfaceClass (Mass Storage Device Class)
0x06 bInterfaceSubClass (Transparent SCSI subclass)
0x50 bInterfaceProtocol (Bulk only transport)
Bulk only transport 协议
命令块;数据块;状态块
- 命令块(主机发送,即端点OUT):叫做CBW(Command Block Wrapper),主要描述接下来数据的传输方向和大小
包头0x43425355 “USBC”
/*! @brief Command Block Wrapper(CBW) */
typedef struct _usb_device_msc_cbw
{
uint32_t signature; /*!< Byte 0-3 dCBWSignature*/
uint32_t tag; /*!< Byte 4-7 dCBWTag*/
uint32_t dataTransferLength; /*!< Byte 8-11 dCBWDataTransferLength*/
uint8_t flags; /*!< Byte 12 bmCBWFlags*/
uint8_t logicalUnitNumber; /*!< Byte 13 bCBWLUN*/
uint8_t cbLength; /*!< Byte 14 bCBWCBLength*/
uint8_t cbwcb[16]; /*!< Byte 15-30 CBWCB, CBWCB is used to store UFI command*/
} usb_device_msc_cbw_t;
- 数据块(端点IN和端点OUT)
- 状态块(设备发送,即端点IN):向主机发送状态完成封包,成为CSW(Command Status Wrapper)
包头0x53425355 “USBS”
/*! @brief Command Status Wrapper(CSW) */
typedef struct _usb_device_msc_csw
{
uint32_t signature; /*!< Byte 0-3 dCSWSignature*/
uint32_t tag; /*!< Byte 4-7 dCSWTag*/
uint32_t dataResidue; /*!< Byte 8-11 dCSWDataResidue*/
uint8_t cswStatus; /*!< Byte 12 bCSWStatus*/
} usb_device_msc_csw_t;
批量数据传输中的一些内容
首先U盘传输,命令块中,cbwcb[16] (usb_device_msc_cbw_t结构体),用于UFI命令(cbwcb[0]为命令标识符)。UFI 命令规范是针对USB 移动存储
/*! @brief UFI Commands code*/
#define USB_DEVICE_MSC_INQUIRY_COMMAND (0x12U) //索取器件信息
#define USB_DEVICE_MSC_READ_10_COMMAND (0x28U) //Host读取存储介质中的二进制数据
#define USB_DEVICE_MSC_READ_12_COMMAND (0xA8U) //同 28h
#define USB_DEVICE_MSC_REQUEST_SENSE_COMMAND (0x03U) //请求设备向主机返回执行结果和状态数据
#define USB_DEVICE_MSC_TEST_UNIT_READY_COMMAND (0x00U) //请求设备报告是否处于Ready状态
#define USB_DEVICE_MSC_WRITE_10_COMMAND (0x2AU) //从主机向介质写二进制数据
#define USB_DEVICE_MSC_WRITE_12_COMMAND (0xAAU) //同 2Ah
#define USB_DEVICE_MSC_PREVENT_ALLOW_MEDIUM_REM_COMMAND (0x1EU) //写保护
#define USB_DEVICE_MSC_FORMAT_UNIT_COMMAND (0x04U) //格式化存储单元
#define USB_DEVICE_MSC_READ_CAPACITY_10_COMMAND (0x25U) //要求设备返回当前容量
#define USB_DEVICE_MSC_READ_CAPACITY_16_COMMAND (0x9EU) //同 9Eh
#define USB_DEVICE_MSC_READ_FORMAT_CAPACITIES_COMMAND (0x23U) //查询当前容量和可用空间
#define USB_DEVICE_MSC_MODE_SENSE_10_COMMAND (0x5AU) //向Host传输参数
#define USB_DEVICE_MSC_MODE_SENSE_6_COMMAND (0x1AU) //同 1Ah
#define USB_DEVICE_MSC_MODE_SELECT_10_COMMAND (0x55U) //允许Host对外部设备设置参数
#define USB_DEVICE_MSC_MODE_SELECT_6_COMMAND (0x15U) //同 55h
#define USB_DEVICE_MSC_SEND_DIAGNOSTIC_COMMAND (0x1DU) //执行固件复位并执行诊断
#define USB_DEVICE_MSC_VERIFY_COMMAND (0x2FU) //在存储中验证数据
#define USB_DEVICE_MSC_START_STOP_UNIT_COMMAND (0x1BU) //Start Stop Load Unload
......
/*!
* @brief Process usb msc ufi command.
*
* This function analyse the cbw , get the command code.
*
* @param handle The device msc class handle.
*
* @retval kStatus_USB_Success Free device msc class handle successfully.
*/
static usb_status_t USB_DeviceMscProcessUfiCommand(usb_device_msc_struct_t *mscHandle)
{
usb_status_t status;
usb_device_msc_ufi_struct_t *ufi = NULL;
ufi = &mscHandle->mscUfi;
if (USB_DEVICE_MSC_REQUEST_SENSE_COMMAND != mscHandle->mscCbw->cbwcb[0])
{
ufi->requestSense->senseKey = USB_DEVICE_MSC_UFI_NO_SENSE;
ufi->requestSense->additionalSenseCode = USB_DEVICE_MSC_UFI_NO_SENSE;
ufi->requestSense->additionalSenseQualifer = USB_DEVICE_MSC_UFI_NO_SENSE;
}
ufi->thirteenCase.hostExpectedDataLength = mscHandle->mscCbw->dataTransferLength;
ufi->thirteenCase.hostExpectedDirection = (uint8_t)(mscHandle->mscCbw->flags >> USB_DEVICE_MSC_CBW_DIRECTION_SHIFT);
/*The first byte of all ufi command blocks shall contain an Operation Code, refer to ufi spec*/
switch (mscHandle->mscCbw->cbwcb[0])
{
/* ufi command operation code*/
case USB_DEVICE_MSC_INQUIRY_COMMAND: /*operation code : 0x12*/
status = USB_DeviceMscUfiInquiryCommand(mscHandle);
break;
case USB_DEVICE_MSC_READ_10_COMMAND: /*operation code : 0x28 */
case USB_DEVICE_MSC_READ_12_COMMAND: /*operation code : 0xA8 */
status = USB_DeviceMscUfiReadCommand(mscHandle);
break;
case USB_DEVICE_MSC_REQUEST_SENSE_COMMAND: /*operation code : 0x03*/
status = USB_DeviceMscUfiRequestSenseCommand(mscHandle);
break;
case USB_DEVICE_MSC_TEST_UNIT_READY_COMMAND: /*operation code : 0x00 */
status = USB_DeviceMscUfiTestUnitReadyCommand(mscHandle);
break;
case USB_DEVICE_MSC_WRITE_10_COMMAND: /*operation code : 0x2A */
case USB_DEVICE_MSC_WRITE_12_COMMAND: /*operation code : 0xAA */
status = USB_DeviceMscUfiWriteCommand(mscHandle);
break;
case USB_DEVICE_MSC_PREVENT_ALLOW_MEDIUM_REM_COMMAND: /*operation code :0x1E */
status = USB_DeviceMscUfiPreventAllowMediumCommand(mscHandle);
break;
case USB_DEVICE_MSC_FORMAT_UNIT_COMMAND: /*operation code : 0x04*/
status = USB_DeviceMscUfiFormatUnitCommand(mscHandle);
break;
case USB_DEVICE_MSC_READ_CAPACITY_10_COMMAND: /*operation code : 0x25*/
case USB_DEVICE_MSC_READ_CAPACITY_16_COMMAND: /*operation code : 0x9E*/
status = USB_DeviceMscUfiReadCapacityCommand(mscHandle);
break;
case USB_DEVICE_MSC_MODE_SENSE_10_COMMAND: /* operation code :0x5A*/
case USB_DEVICE_MSC_MODE_SENSE_6_COMMAND: /* operation code : 0x1A */
status = USB_DeviceMscUfiModeSenseCommand(mscHandle);
break;
case USB_DEVICE_MSC_MODE_SELECT_10_COMMAND: /*operation code : 0x55 */
case USB_DEVICE_MSC_MODE_SELECT_6_COMMAND: /*operation code : 0x15 */
status = USB_DeviceMscUfiModeSelectCommand(mscHandle);
break;
case USB_DEVICE_MSC_READ_FORMAT_CAPACITIES_COMMAND: /*operation code : 0x23 */
status = USB_DeviceMscUfiReadFormatCapacityCommand(mscHandle);
break;
case USB_DEVICE_MSC_SEND_DIAGNOSTIC_COMMAND: /*operation code : 0x1D*/
status = USB_DeviceMscUfiSendDiagnosticCommand(mscHandle);
break;
case USB_DEVICE_MSC_VERIFY_COMMAND: /*operation code : 0x2F*/
status = USB_DeviceMscUfiVerifyCommand(mscHandle);
break;
case USB_DEVICE_MSC_START_STOP_UNIT_COMMAND: /*operation code : 0x1B*/
status = USB_DeviceMscUfiStartStopUnitCommand(mscHandle);
break;
default:
status = USB_DeviceMscUfiUnsupportCommand(mscHandle);
break;
}
if ((USB_DEVICE_MSC_UFI_NO_SENSE != ufi->requestSense->senseKey) &&
(USB_DEVICE_MSC_COMMAND_PASSED == mscHandle->mscCsw->cswStatus) &&
(USB_DEVICE_MSC_REQUEST_SENSE_COMMAND != mscHandle->mscCbw->cbwcb[0]))
{
mscHandle->mscCsw->cswStatus = USB_DEVICE_MSC_COMMAND_FAILED;
}
return status;
}
INQUIRY (0x12U)
插入U盘,第一条UFI命令就是 INQUIRY
其中inquiry 表示的数据结构如下(基本就描述该设备的基础信息了,什么设备类型,是否可移除,厂商信息,版本信息等等):
/*! @brief UFI inquiry data format structure*/
typedef struct _usb_device_inquiry_data_fromat_struct
{
uint8_t peripheralDeviceType; /*!< Peripheral Device Type*/
uint8_t rmb; /*!< Removable Media Bit*/
uint8_t versions; /*!< ISO Version, ECMA Version, ANSI Version*/
uint8_t responseDataFormat; /*!< Response Data Format*/
uint8_t additionalLength; /*!< The Additional Length field shall specify the length in bytes of the parameters*/
uint8_t reserved[3]; /*!< reserved*/
uint8_t vendorInformatin[8]; /*!< Vendor Identification*/
uint8_t productId[16]; /*!< Product Identification*/
uint8_t productVersionLevel[4]; /*!< Product Revision Level*/
} usb_device_inquiry_data_fromat_struct_t;
READ FORMAT CAPACITIES (0x23U)
该命令会获取设备最大容量
如图,0x23命令请求,端点IN返回12Byte
端点IN数据信息结构,参考上图红线
[0:3] Capacity List Header
[4:7] Number of Blocks
[8] Descriptor Code
[9:11] Block Length
/*! @brief UFI capacity list header structure*/
typedef struct _usb_device_capacity_list_header_struct
{
uint8_t reserverd[3]; /*!< reserved*/
uint8_t capacityListLength; /*!< Capacity List Length*/
} usb_device_capacity_list_header_struct_t;
/*! @brief UFI current maximum capacity structure*/
typedef struct _usb_device_current_max_capacity_descriptor_struct
{
uint32_t blockNumber; /*!< Number of Blocks*/
uint32_t descriptorCodeBlockLength; /*!< Byte 4 Descriptor Code , byte 5-7 Block Length*/
} usb_device_current_max_capacity_descriptor_struct_t;
READ CAPACITY(0x25)
该命令会获取当前内存容量
如图,命令请求,端点IN返回8Byte
[0:3] 最后逻辑块地址
[4:7] 每块大小Byte
READ_10 READ_12(0x28 0xA8)
该命令获取当前存储设备实际数据
WRITE_10 WRITE_12(0x2A 0xAA)
该命令向当前存储设备写数据
TEST UNIT READY (0x00) REQUEST SENSE(0x03)
这两个指令都是主机频繁发送的命令,0x00 可查询逻辑单元是否准备好了,0x03 查询设备状态等等。
测试
U盘设备枚举成功后,需要将存储介质格式化,至于格式化成什么格式都可以(NFS FAT32 exFAT等),反正文件系统靠主机制作,若设备也想读取,那么就得开发对应的文件系统了,本章不扩展。好了一个U盘设备制作完成