USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)

目录

链接快速定位

前沿

1 描述符讲解

1.1 设备描述符

1.2 配置描述符

1.3 接口描述符

1.4 端点描述符

1.5 字符串描述符

2 基本命令简介

2.1 Command Block Wrapper (CBW)

2.1.1 SCSI协议简介

2.2 Command Status Wrapper (CSW)

2.3 命令解析

3 关键函数讲解

3.1 U盘接口函数讲解

3.1.1 初始化函数讲解

3.1.2 读函数讲解

3.1.3 写函数讲解

3.1.4 获取状态函数讲解

3.2 GPIO配置讲解

3.3 非零端点函数讲解

3.4 中断函数讲解--USB复位函数讲解

3.5 主函数讲解

3.6 盘符个数修改

4 运行演示


链接快速定位

USB -- 初识USB协议(一)

源码下载请参考链接:USB -- STM32-FS-USB-Device驱动代码简述(二)

USB -- STM32F103虚拟串口bulk传输讲解(三)

USB -- STM32F103自定义HID设备及HID上位机中断传输讲解(四)

MassStorage数据手册

Mass Storage Class Specification Overview

前沿

        前面两节主要是对USB的基本概念做了简单讲解,学习USB的最本质目的还是要回到USB的应用方向,接下来的几章主要讲解USB的各类应用,包括:

  • 虚拟串口(环回测试)-- bulk传输(USB -- STM32F103虚拟串口bulk传输讲解(三))
  • 自定义HID -- 中断传输(USB -- STM32F103自定义HID设备及HID上位机中断传输讲解(四))
  • U盘 -- bulk传输(本章讲解)
  • 在线固件升级(DFU)-- 控制传输
  • 语音录制及播放 -- 同步传输
  • 照相机 -- 同步传输
  • 复合设备 -- 中断+bulk传输

        主要还是带领大家找相关的资料及相关的工具和代码。

1 描述符讲解

        描述符是USB能够正常通信的前提,没有描述符,USB就不知道当前是什么样的设备,所以描述符在USB整个通信过程中占有十分重要的地位,所以这里重点讲解一下USB的各类描述符。

        ST的例程为我们配置好了应用的描述符,我们不需要关注也能正常运行程序,但是我们这里讲解一下怎么通过查找资料来知道具体描述符的含义

1.1 设备描述符

        以下是STM32F103系列的U盘的设备描述符代码(每个字节对应的含义可以在USB -- 初识USB协议(一)中查看):

const uint8_t MASS_DeviceDescriptor[MASS_SIZ_DEVICE_DESC] =
  {
    0x12,   /* bLength  */
    0x01,   /* bDescriptorType */
    0x00,   /* bcdUSB, version 2.00 */
    0x02,
    0x00,   /* bDeviceClass : each interface define the device class */
    0x00,   /* bDeviceSubClass */
    0x00,   /* bDeviceProtocol */
    0x40,   /* bMaxPacketSize0 0x40 = 64 */
    0x83,   /* idVendor     (0483) */
    0x04,
    0x20,   /* idProduct */
    0x57,
    0x00,   /* bcdDevice 2.00*/
    0x02,
    1,              /* index of string Manufacturer  */
    /**/
    2,              /* index of string descriptor of product*/
    /* */
    3,              /* */
    /* */
    /* */
    0x01    /*bNumConfigurations */
  };

        设备描述符顾名思义就是描述USB设备的基本信息,主要包括以下信息:

  • USB版本号
  • USB设备类型
  • USB设备协议
  • 端点0最大传输大小(最大64byte)
  • 厂商向USBIF申请的VID(需付费,由芯片厂商提供)
  • 设备PID(厂商自定义)
  • 制造商的字符串描述符索引(描述生产这个产品的厂商)
  • 产品的字符串描述符索引(描述是什么产品)
  • 设备序号的字符串描述符索引(主要是芯片的UID)
  • 配置的数量(一般为1)

        设备类型可以通过USB官网Class查询,我们这里是CDC设备,所以bDeviceClass=0x08,其他两项任意填写。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第1张图片

        但实际这里的bDeviceClass=0x00,是因为0x00表示class可以在接口与描述符中定义。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第2张图片

        制造商的字符串描述符索引、产品的字符串描述符索引和设备序号的字符串描述符索引主要是制造商、产品和设备序列号在字符串描述符的索引位置。

        比如这里举个例子,以下三个是字符串描述符,分别是制造商描述符、产品描述符和序列号描述符:

const uint8_t MASS_StringVendor[MASS_SIZ_STRING_VENDOR] =
  {
    MASS_SIZ_STRING_VENDOR, /* Size of manufacturer string */
    0x03,           /* bDescriptorType = String descriptor */
    /* Manufacturer: "STMicroelectronics" */
    'S', 0, 'T', 0, 'M', 0, 'i', 0, 'c', 0, 'r', 0, 'o', 0, 'e', 0,
    'l', 0, 'e', 0, 'c', 0, 't', 0, 'r', 0, 'o', 0, 'n', 0, 'i', 0,
    'c', 0, 's', 0
  };
const uint8_t MASS_StringProduct[MASS_SIZ_STRING_PRODUCT] =
  {
    MASS_SIZ_STRING_PRODUCT,
    0x03,
    /* Product name: "STM32F10x:USB Mass Storage" */
    'S', 0, 'T', 0, 'M', 0, '3', 0, '2', 0, ' ', 0, 'M', 0, 'a', 0, 's', 0,
    's', 0, ' ', 0, 'S', 0, 't', 0, 'o', 0, 'r', 0, 'a', 0, 'g', 0, 'e', 0

  };

uint8_t MASS_StringSerial[MASS_SIZ_STRING_SERIAL] =
  {
    MASS_SIZ_STRING_SERIAL,
    0x03,
    /* Serial number*/
    'S', 0, 'T', 0, 'M', 0, '3', 0, '2', 0
  };

    由下面代码可知,他们的位置也是根据索引号排列的,所以主机请求index为1的描述符,那么设备就发送制造商描述符给主机,告诉主机USB的制造商是什么。

ONE_DESCRIPTOR String_Descriptor[5] =
  {
    {(uint8_t*)MASS_StringLangID, MASS_SIZ_STRING_LANGID},
    {(uint8_t*)MASS_StringVendor, MASS_SIZ_STRING_VENDOR},
    {(uint8_t*)MASS_StringProduct, MASS_SIZ_STRING_PRODUCT},
    {(uint8_t*)MASS_StringSerial, MASS_SIZ_STRING_SERIAL},
    {(uint8_t*)MASS_StringInterface, MASS_SIZ_STRING_INTERFACE},
  };

        如果设备描述符的索引值修改了,那么String_Descriptor数组存放数据的顺序也需要相应的修改。

        VID可以通过USB官网:USB_Members查看,比如这里的是ST的VID,0x0483的10进制是1155,我们在USB官网查看,如下:

1.2 配置描述符

        配置描述符顾名思义就是描述USB设备的配置信息,主要包括以下信息:

  • USB接口的数量(会在接口描述符讲解)
  • USB的配置值(一般为1,根据设备描述符配置数量决定)
  • USB的供电信息
  • USB的最大电流
const uint8_t MASS_ConfigDescriptor[] =
  {
    0x09,   /* bLength: Configuration Descriptor size */
    0x02,   /* bDescriptorType: Configuration */
    MASS_SIZ_CONFIG_DESC,

    0x00,
    0x01,   /* bNumInterfaces: 1 interface */
    0x01,   /* bConfigurationValue: */
    /*      Configuration value */
    0x00,   /* iConfiguration: */
    /*      Index of string descriptor */
    /*      describing the configuration */
    0xC0,   /* bmAttributes: */
    /*      Self powered */
    0x32,   /* MaxPower 100 mA */
  }

1.3 接口描述符

        这里的接口描述符有1个,由配置描述符指定个数。我们举例说明一下含义。

uint8_t MASS_InterfaceDescriptor[ ] = 
{
    /******************** Descriptor of Mass Storage interface ********************/
    /* 09 */
    0x09,   /* bLength: Interface Descriptor size */
    0x04,   /* bDescriptorType: */
    /*      Interface descriptor type */
    0x00,   /* bInterfaceNumber: Number of Interface */
    0x00,   /* bAlternateSetting: Alternate setting */
    0x02,   /* bNumEndpoints*/
    0x08,   /* bInterfaceClass: MASS STORAGE Class */
    0x06,   /* bInterfaceSubClass : SCSI transparent*/
    0x50,   /* nInterfaceProtocol */
    4,          /* iInterface: */
 }

         首先下载MassStorage数据手册,打开“usbmassbulk_10.pdf”文件,找到4.3小节,这里有对接口描述符的全部解释。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第3张图片

        bInterfaceSubClass通过Mass Storage Class Specification Overview手册查看,这里选择的是SCSI传输协议。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第4张图片

        nInterfaceProtocol选择的是仅Bulk传输。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第5张图片

1.4 端点描述符

        这里用到2个端点,具体的解释可参见USB -- 初识USB协议(一)。

uint8_t MASS_EndpointDescriptor[ ] = 
{
    0x07,   /*Endpoint descriptor length = 7*/
    0x05,   /*Endpoint descriptor type */
    0x81,   /*Endpoint address (IN, address 1) */
    0x02,   /*Bulk endpoint type */
    0x40,   /*Maximum packet size (64 bytes) */
    0x00,
    0x00,   /*Polling interval in milliseconds */
    /* 25 */
    0x07,   /*Endpoint descriptor length = 7 */
    0x05,   /*Endpoint descriptor type */
    0x02,   /*Endpoint address (OUT, address 2) */
    0x02,   /*Bulk endpoint type */
    0x40,   /*Maximum packet size (64 bytes) */
    0x00,
    0x00     /*Polling interval in milliseconds*/
 }

1.5 字符串描述符

        在设备描述符中已经对制造商描述符、产品描述符和设备序号描述符做了说明,这里仅对语言ID描述符做简单说明。

        语言ID描述符相对很简单,就是告诉主机用的哪种语言编码,这里一般选择0x0409,使用美国的编码。一般使用最多的也是美国编码。

const uint8_t MASS_StringLangID[MASS_SIZ_STRING_LANGID] =
  {
    MASS_SIZ_STRING_LANGID,
    0x03,
    0x09,
    0x04
  }
  ;      /* LangID = 0x0409: U.S. English */

2 基本命令简介

        基本命令格式通过手册MassStorage数据手册查看,主要包括CBW和CSW两种命令。

2.1 Command Block Wrapper (CBW)

        CBW命令格式如下:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第6张图片

  • dCBWSignature:固定为0x43425355(小端),表明一个CBW命令
  • dCBWTag:主机发送的命令块标记。设备应在相关CSW的dCSWTag字段中将该字段的内容回显给主机。dCSWTag将CSW与对应的CBW正相关。
  • dCBWDataTransferLength:在执行此命令期间,主机希望在“批量输入”或“批量输出”端点上传输的数据字节数(由“方向”位指示)。如果该字段为零,则设备和主机不得在CBW和相关CSW之间传输任何数据,并且设备应忽略bmCBWFlags中的Direction位的值。
  • bmCBWFlags:改字段的位定义如下:
    • bit7 - 表示方向:
      • 0,数据out,表示主机到设备
      • 1,数据in,表示设备到主机
    • bit6 - 废弃的,须为0
    • bits 5..0 - 保留位,须为0
  • bCBWLUN:要将命令块发送到的设备逻辑单元号(LUN)。对于支持多个LUN的设备,主机应将此命令块寻址到的LUN放置在此字段中。否则,主机应将该字段设置为零。这个位也就是我们常见的C盘和D盘等逻辑单元号。
  • bCBWCBLength:CBWCB的有效长度(以字节为单位)。这定义了命令块的有效长度。唯一的合法值是1到16(01h到10h)。保留所有其他值。
  • CBWCB:要由设备执行的命令块。设备应将该字段中的第一个bCBWBLength字节解释为由bInterface SubClass标识的命令集定义的命令块。如果设备支持的命令集使用长度小于16(10h)字节的命令块,则应首先传输有效字节,从偏移量为15(Fh)的字节开始。设备应忽略偏移量(15+bCBWCCBLength-1)处超过字节的CBWCB字段的内容。

        简单来说,前14个字节都是CBW的协议的内容,最后16个字节(CBWCB域)则是存放的具体协议,具体协议由接口描述符的bInterfaceSubClass标识,bInterfaceSubClass=0x06,则标识CBWCB内容为SCSI协议,所以下一小节简单讲解一下SCSI协议。

2.1.1 SCSI协议简介

         SCSI命令格式可通过《SCSI Primary Commands-4.pdf》文档进行查看,这里只讲一下6-byte的命令,SCSI命令格式如下:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第7张图片

       这里只讲一下第一个字节的OPERATION CODE,他定义了具体的操作类型,我们的MassStorage主要也就使用了这些SCSI协议的命令。具体命令的含义需要查看SCSI手册。

#define SCSI_FORMAT_UNIT                            0x04
#define SCSI_INQUIRY                                0x12
#define SCSI_MODE_SELECT6                           0x15
#define SCSI_MODE_SELECT10                          0x55
#define SCSI_MODE_SENSE6                            0x1A
#define SCSI_MODE_SENSE10                           0x5A
#define SCSI_ALLOW_MEDIUM_REMOVAL                   0x1E
#define SCSI_READ6                                  0x08
#define SCSI_READ10                                 0x28
#define SCSI_READ12                                 0xA8
#define SCSI_READ16                                 0x88

#define SCSI_READ_CAPACITY10                        0x25
#define SCSI_READ_CAPACITY16                        0x9E

#define SCSI_REQUEST_SENSE                          0x03
#define SCSI_START_STOP_UNIT                        0x1B
#define SCSI_TEST_UNIT_READY                        0x00
#define SCSI_WRITE6                                 0x0A
#define SCSI_WRITE10                                0x2A
#define SCSI_WRITE12                                0xAA
#define SCSI_WRITE16                                0x8A

#define SCSI_VERIFY10                               0x2F
#define SCSI_VERIFY12                               0xAF
#define SCSI_VERIFY16                               0x8F

#define SCSI_SEND_DIAGNOSTIC                        0x1D
#define SCSI_READ_FORMAT_CAPACITIES                 0x23

        查看《SCSI Block Command - 2.pdf》手册的table12可知道每个字节的具体含义。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第8张图片

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第9张图片

       

        更多关于SCSI协议的内容请参见《SCSI Primary Commands-4.pdf》和《SCSI Block Command - 2.pdf》文档,这里提供下载包。

2.2 Command Status Wrapper (CSW)

        CBW命令格式如下:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第10张图片

  • dCSWSignature:固定为0x53425355(小端),表明一个CSW命令
  • dCSWTag:设备应将该字段设置为相关CBW的dCBWTag中接收的值。
  • dCSWDataResidue:对于Data Out,设备应在dCSWDataResiduce中报告dCBWDataTransferLength中规定的预期数据量与设备处理的实际数据量之间的差异。对于Data In,应在dCSWDataResiduce中报告dCBWDataTransferLength中规定的预期数据量与设备发送的相关数据的实际量之间的差异。dCSWDataResiduce不得超过dCBWDataTransferLength中发送的值。
  • bCSWStatus:bCSStatus表示命令的成功或失败。如果命令成功完成,设备应将该字节设置为零。根据下表,非零值应指示命令执行期间的故障:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第11张图片

        关于CBW和CSW命令更详细的解释可以参见MassStorage数据手册的第五章。

2.3 命令解析

        以下是通过逻辑分析仪抓取的一段CBW和CSW命令的截图,就这个截图结合程序进行简单的讲解,使读者遇到相应的命令可以去相应的位置查找。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第12张图片

        这是一个完成的交互流程,包括CBW、SISC和CSW语句。

CBW:55 53 42 43 10 B0 6E 7B 24 00 00 00 80 00 06 12 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00

  • dCBWSignature字段:固定为0x43425355(小端),表明一个CBW命令
  • dCBWTag字段:0x7B6EB010,设备应将该字段设置为相关CBW的dCBWTag中接收的值
  • dCBWDataTransferLength字段:0x00000024,也就是下条指令--SCSI指令的长度
  • bmCBWFlags字段:0x80,表示数据In,请求的是设备到主机的数据
  • bCBWLUN字段:0x00,表示操作的是第0个磁盘
  • bCBWCBLength字段:0x06,表示下发的SCSI指令长度为6个字节
  • CBWCB字段:SCSI指令,第一个字节的0x12表示 INQUIRY命令,后面跟的是具体的操作和信息

SCSI:00 80 02 02 20 00 00 00 53 54 4D 20 20 20 20 20 53 44 20 46 6C 61 73 68 20 44 69 73 6B 20 20 20 31 2E 30 20

        这个是Standard inquiry data,通过定义的数组返回。

uint8_t Standard_Inquiry_Data[] =
  {
    0x00,          /* Direct Access Device */
    0x80,          /* RMB = 1: Removable Medium */
    0x02,          /* Version: No conformance claim to standard */
    0x02,

    36 - 4,          /* Additional Length */
    0x00,          /* SCCS = 1: Storage Controller Component */
    0x00,
    0x00,
    /* Vendor Identification */
    'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',
    /* Product Identification */
    'S', 'D', ' ', 'F', 'l', 'a', 's', 'h', ' ',
    'D', 'i', 's', 'k', ' ', ' ', ' ',
    /* Product Revision Level */
    '1', '.', '0', ' '
  };

CSW:55 53 42 53 10 B0 6E 7B 00 00 00 00 00

  • dCSWSignature字段:固定为0x53425355(小端),表明一个CSW命令
  • dCSWTag字段:0x7B6EB010,设备应将该字段设置为相关CBW的dCBWTag中接收的值
  • dCSWDataResidue字段:0x00000000,对于Data Out,设备应在dCSWDataResiduce中报告dCBWDataTransferLength中规定的预期数据量与设备处理的实际数据量之间的差异。对于Data In,应在dCSWDataResiduce中报告dCBWDataTransferLength中规定的预期数据量与设备发送的相关数据的实际量之间的差异。dCSWDataResiduce不得超过dCBWDataTransferLength中发送的值。
  • bCSWStatus字段:0x00,表示好的状态。

3 关键函数讲解

3.1 U盘接口函数讲解

        U盘接口函数主要包括初始化SDIO接口、读写SD卡信息等函数,他们是ST官网写好的,我们直接使用即可,这里讲解一下如果SD卡变成了EEPROM或者其他FLASH的时候,怎么修改接口函数。

3.1.1 初始化函数讲解

        ST提供两个初始化函数(默认两个盘符),一个是SD卡的初始化,另一个是NAND_FLASH的初始化,我们这里只是使用的SD卡,所以我们只关注SD_Init函数,后面章节讲解怎么修改盘符个数(这里的lun是Logical Unit Number的意思,因为只有一个盘符,所以默认为0)。

        如果我们需要把SD卡改为其它的FLASH,我们需要修改SD_Init函数的内容。

/*******************************************************************************
* Function Name  : MAL_Init
* Description    : Initializes the Media on the STM32
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Init(uint8_t lun)
{
  uint16_t status = MAL_OK;

  switch (lun)
  {
    case 0:
      Status = SD_Init();
      break;
#ifdef USE_STM3210E_EVAL
    case 1:
      NAND_Init();
      break;
#endif
    default:
      return MAL_FAIL;
  }
  return status;
}

        SD_Init函数主要是初始化SD卡的GPIO和配置信息,这里不展开讲,有兴趣的读者可以自行研究。

/**
  * @brief  Initializes the SD Card and put it into StandBy State (Ready for data 
  *         transfer).
  * @param  None
  * @retval SD_Error: SD Card Error code.
  */
SD_Error SD_Init(void)
{
  __IO SD_Error errorstatus = SD_OK;
  
  /* SDIO Peripheral Low Level Init */
  SD_LowLevel_Init();

  SDIO_DeInit();

  errorstatus = SD_PowerON();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    return(errorstatus);
  }

  errorstatus = SD_InitializeCards();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    return(errorstatus);
  }

  /*!< Configure the SDIO peripheral */
  /*!< SDIO_CK = SDIOCLK / (SDIO_TRANSFER_CLK_DIV + 2) */
  SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
  SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
  SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
  SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  SDIO_Init(&SDIO_InitStructure);

  /*----------------- Read CSD/CID MSD registers ------------------*/
  errorstatus = SD_GetCardInfo(&SDCardInfo);

  if (errorstatus == SD_OK)
  {
    /*----------------- Select Card --------------------------------*/
    errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
  }

  if (errorstatus == SD_OK)
  {
    errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
  }  

  return(errorstatus);
}

3.1.2 读函数讲解

        同样读函数我们也只关心lun=0的位置,此函数主要使用SDIO接口对SD卡进行读取操作。我们需要更改其它的存储介质,需要对读函数进行修改。

/*******************************************************************************
* Function Name  : MAL_Read
* Description    : Read sectors
* Input          : None
* Output         : None
* Return         : Buffer pointer
*******************************************************************************/
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{

  switch (lun)
  {
    case 0:

      SD_ReadMultiBlocks((uint8_t*)Readbuff, Memory_Offset, Transfer_Length, 1);
#if defined(USE_STM3210E_EVAL) || defined(USE_STM32L152D_EVAL)
      Status = SD_WaitReadOperation();
      while(SD_GetStatus() != SD_TRANSFER_OK)
      {
      }
      
      if ( Status != SD_OK )
      {
        return MAL_FAIL;
      }
#endif /* USE_STM3210E_EVAL */      
      break;
#ifdef USE_STM3210E_EVAL
    case 1:
      NAND_Read(Memory_Offset, Readbuff, Transfer_Length);
      ;
      break;
#endif
    default:
      return MAL_FAIL;
  }
  return MAL_OK;
}

3.1.3 写函数讲解

         同样写函数我们也只关心lun=0的位置,此函数主要使用SDIO接口对SD卡进行写操作。我们需要更改其它的存储介质,需要对写函数进行修改。

/*******************************************************************************
* Function Name  : MAL_Write
* Description    : Write sectors
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{

  switch (lun)
  {
    case 0:
    Status = SD_WriteMultiBlocks((uint8_t*)Writebuff, Memory_Offset, Transfer_Length,1);
#if defined(USE_STM3210E_EVAL) || defined(USE_STM32L152D_EVAL)
    Status = SD_WaitWriteOperation();  
    while(SD_GetStatus() != SD_TRANSFER_OK);
      if ( Status != SD_OK )
      {
        return MAL_FAIL;
      }      
#endif /* USE_STM3210E_EVAL ||USE_STM32L152D_EVAL*/      
      break;
#ifdef USE_STM3210E_EVAL
    case 1:
      NAND_Write(Memory_Offset, Writebuff, Transfer_Length);
      break;
#endif /* USE_STM3210E_EVAL */  
    default:
      return MAL_FAIL;
  }
  return MAL_OK;
}

3.1.4 获取状态函数讲解

        同样获取状态函数我们也只关心lun=0的位置,此函数主要使用SDIO接口对SD卡状态的获取,比如SD卡的版本信息,SD卡的大小等。我们需要更改其它的存储介质,需要对获取状态函数进行修改。

/*******************************************************************************
* Function Name  : MAL_GetStatus
* Description    : Get status
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_GetStatus (uint8_t lun)
{
#ifdef USE_STM3210E_EVAL
  NAND_IDTypeDef NAND_ID;
  uint32_t DeviceSizeMul = 0, NumberOfBlocks = 0;
#else
#if !defined(USE_STM32L152D_EVAL)
  SD_CSD SD_csdata;
#endif
  uint32_t DeviceSizeMul = 0;
#endif /* USE_STM3210E_EVAL */

#ifdef USE_STM32L152D_EVAL

  uint32_t NumberOfBlocks = 0;
#endif

  if (lun == 0)
  {
#if defined (USE_STM3210E_EVAL)  || defined(USE_STM32L152D_EVAL)
    if (SD_Init() == SD_OK)
    {
      SD_GetCardInfo(&mSDCardInfo);
      SD_SelectDeselect((uint32_t) (mSDCardInfo.RCA << 16));
      DeviceSizeMul = (mSDCardInfo.SD_csd.DeviceSizeMul + 2);

      if(mSDCardInfo.CardType == SDIO_HIGH_CAPACITY_SD_CARD)
      {
        Mass_Block_Count[0] = (mSDCardInfo.SD_csd.DeviceSize + 1) * 1024;
      }
      else
      {
        NumberOfBlocks  = ((1 << (mSDCardInfo.SD_csd.RdBlockLen)) / 512);
        Mass_Block_Count[0] = ((mSDCardInfo.SD_csd.DeviceSize + 1) * (1 << DeviceSizeMul) << (NumberOfBlocks/2));
      }
      Mass_Block_Size[0]  = 512;

      Status = SD_SelectDeselect((uint32_t) (mSDCardInfo.RCA << 16)); 
      Status = SD_EnableWideBusOperation(SDIO_BusWide_4b); 
      if ( Status != SD_OK )
      {
        return MAL_FAIL;
      }
     
#else

    uint32_t temp_block_mul = 0;
    SD_GetCSDRegister(&SD_csdata);
    DeviceSizeMul = SD_csdata.DeviceSizeMul + 2;
    temp_block_mul = (1 << SD_csdata.RdBlockLen)/ 512;
    Mass_Block_Count[0] = ((SD_csdata.DeviceSize + 1) * (1 << (DeviceSizeMul))) * temp_block_mul;
    Mass_Block_Size[0] = 512;
    Mass_Memory_Size[0] = (Mass_Block_Count[0] * Mass_Block_Size[0]);
#endif /* USE_STM3210E_EVAL */
      Mass_Memory_Size[0] = Mass_Block_Count[0] * Mass_Block_Size[0];
      STM_EVAL_LEDOn(LED2);
      return MAL_OK;

#if defined (USE_STM3210E_EVAL) || defined(USE_STM32L152D_EVAL)
    }
#endif /* USE_STM3210E_EVAL */
  }
#ifdef USE_STM3210E_EVAL
  else
  {
    FSMC_NAND_ReadID(&NAND_ID);
    if (NAND_ID.Device_ID != 0 )
    {
      /* only one zone is used */
      Mass_Block_Count[1] = NAND_ZONE_SIZE * NAND_BLOCK_SIZE * NAND_MAX_ZONE ;
      Mass_Block_Size[1]  = NAND_PAGE_SIZE;
      Mass_Memory_Size[1] = (Mass_Block_Count[1] * Mass_Block_Size[1]);
      return MAL_OK;
    }
  }
#endif /* USE_STM3210E_EVAL */
  STM_EVAL_LEDOn(LED2);
  return MAL_FAIL;
}

3.2 GPIO配置讲解

        这里对USB的D+外部上拉电阻进行了配置,如果读者使用的开发板和ST官网的外部上拉不一致,需要修改这个位置的IO口定义。这里也需要注意,ST开发板使用的是PNP型三极管作为上拉电阻的使能信号,所以这里GPIO输出低D+上拉,GPIO输出高D+为低。

3.3 非零端点函数讲解

        非0端点传输函数主要在“usb_endp.c”文件中实现,我们打开usb_endp.c文件,看到只有两个函数,分别是对IN端点和OUT端点的处理,这两个端点函数为我们封装好了CBW和CSW指令及SCSI协议相关的内容,我们直接使用,如果感兴趣可以自行研究。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第13张图片

3.4 中断函数讲解--USB复位函数讲解

        USB复位函数十分的重要,因为在USB主机识别到设备的时候,首先发出复位指令,USB设备收到复位指令之后,会去初始化一些使用到的端点(端点0必须初始化),然后再通过相应的端点实现设备与主机间的通信。

        下图是利用DSVIEW抓取的USB枚举过程的波形,每次在USB设备枚举之前,都会产生一个复位信号。

        下面代码是USB复位信号来临之后,USB所做的一些事情,主要就是初始化了端点的状态,为后续枚举过程做准备。

/*******************************************************************************
* Function Name  : MASS_Reset
* Description    : Mass Storage reset routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void MASS_Reset()
{
  /* Set the device as not configured */
  Device_Info.Current_Configuration = 0;

  /* Current Feature initialization */
  pInformation->Current_Feature = MASS_ConfigDescriptor[7];

  SetBTABLE(BTABLE_ADDRESS);

  /* Initialize Endpoint 0 */
  SetEPType(ENDP0, EP_CONTROL);
  SetEPTxStatus(ENDP0, EP_TX_NAK);
  SetEPRxAddr(ENDP0, ENDP0_RXADDR);
  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  SetEPTxAddr(ENDP0, ENDP0_TXADDR);
  Clear_Status_Out(ENDP0);
  SetEPRxValid(ENDP0);

  /* Initialize Endpoint 1 */
  SetEPType(ENDP1, EP_BULK);
  SetEPTxAddr(ENDP1, ENDP1_TXADDR);
  SetEPTxStatus(ENDP1, EP_TX_NAK);
  SetEPRxStatus(ENDP1, EP_RX_DIS);

  /* Initialize Endpoint 2 */
  SetEPType(ENDP2, EP_BULK);
  SetEPRxAddr(ENDP2, ENDP2_RXADDR);
  SetEPRxCount(ENDP2, Device_Property.MaxPacketSize);
  SetEPRxStatus(ENDP2, EP_RX_VALID);
  SetEPTxStatus(ENDP2, EP_TX_DIS);


  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  SetEPRxValid(ENDP0);

  /* Set the device to response on default address */
  SetDeviceAddress(0);

  bDeviceState = ATTACHED;

  CBW.dSignature = BOT_CBW_SIGNATURE;
  Bot_State = BOT_IDLE;

  USB_NotConfigured_LED();
}

3.5 主函数讲解

        主函数代码相对简单,就是实现了USB的初始化。

/*******************************************************************************
* Function Name  : main.
* Description    : Main routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
int main(void)
{
  Set_System();
  Set_USBClock();
  Led_Config();
  USB_Interrupts_Config();
  USB_Init();
  while (bDeviceState != CONFIGURED);

  USB_Configured_LED();

  while (1)
  {}
}

3.6 盘符个数修改

        由于博主的开发板没有挂NAND_FLASH,所以会导致程序一直执行不下去,这里有两种方式解决。

1. 修改盘符个数,并屏蔽NAND_FLASH初始化代码:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第14张图片

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第15张图片

2. 不修改盘符个数,屏蔽NAND_FLASH初始化代码和NAND_FLASH获取状态代码:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第16张图片

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第17张图片

        通过逻辑分析仪抓取波形,发现默认A1 FE 00 00 00 00 01 00命令请求回复的是01,说明存在两个盘符。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第18张图片

        查看计算机,发现确实存在两个盘符,只是有一个盘符没有实体,所以不能进行操作。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第19张图片

4 运行演示

        能够查看到U盘显示的前提是SDIO接口初始化正确,并且插入SD卡,现象如下:

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第20张图片

        如果SDIO接口初始化失败,或者没有接SD卡,可以出现以下问题,这个说明USB的枚举是通过了的,只是没有读取到FLASH。

USB -- STM32F103 U盘(MassStorage)SDIO接口SCSI协议Bulk传输讲解(五)_第21张图片

接下来讲解STM32 USB的在线固件升级(DFU)实验,敬请期待。。。

你可能感兴趣的:(usb,stm32,嵌入式硬件,单片机)