目录
链接快速定位
前沿
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的各类应用,包括:
主要还是带领大家找相关的资料及相关的工具和代码。
描述符是USB能够正常通信的前提,没有描述符,USB就不知道当前是什么样的设备,所以描述符在USB整个通信过程中占有十分重要的地位,所以这里重点讲解一下USB的各类描述符。
ST的例程为我们配置好了应用的描述符,我们不需要关注也能正常运行程序,但是我们这里讲解一下怎么通过查找资料来知道具体描述符的含义。
以下是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官网Class查询,我们这里是CDC设备,所以bDeviceClass=0x08,其他两项任意填写。
但实际这里的bDeviceClass=0x00,是因为0x00表示class可以在接口与描述符中定义。
制造商的字符串描述符索引、产品的字符串描述符索引和设备序号的字符串描述符索引主要是制造商、产品和设备序列号在字符串描述符的索引位置。
比如这里举个例子,以下三个是字符串描述符,分别是制造商描述符、产品描述符和序列号描述符:
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官网查看,如下:
配置描述符顾名思义就是描述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个,由配置描述符指定个数。我们举例说明一下含义。
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小节,这里有对接口描述符的全部解释。
bInterfaceSubClass通过Mass Storage Class Specification Overview手册查看,这里选择的是SCSI传输协议。
nInterfaceProtocol选择的是仅Bulk传输。
这里用到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*/
}
在设备描述符中已经对制造商描述符、产品描述符和设备序号描述符做了说明,这里仅对语言ID描述符做简单说明。
语言ID描述符相对很简单,就是告诉主机用的哪种语言编码,这里一般选择0x0409,使用美国的编码。一般使用最多的也是美国编码。
const uint8_t MASS_StringLangID[MASS_SIZ_STRING_LANGID] =
{
MASS_SIZ_STRING_LANGID,
0x03,
0x09,
0x04
}
; /* LangID = 0x0409: U.S. English */
基本命令格式通过手册MassStorage数据手册查看,主要包括CBW和CSW两种命令。
CBW命令格式如下:
简单来说,前14个字节都是CBW的协议的内容,最后16个字节(CBWCB域)则是存放的具体协议,具体协议由接口描述符的bInterfaceSubClass标识,bInterfaceSubClass=0x06,则标识CBWCB内容为SCSI协议,所以下一小节简单讲解一下SCSI协议。
SCSI命令格式可通过《SCSI Primary Commands-4.pdf》文档进行查看,这里只讲一下6-byte的命令,SCSI命令格式如下:
这里只讲一下第一个字节的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可知道每个字节的具体含义。
更多关于SCSI协议的内容请参见《SCSI Primary Commands-4.pdf》和《SCSI Block Command - 2.pdf》文档,这里提供下载包。
CBW命令格式如下:
关于CBW和CSW命令更详细的解释可以参见MassStorage数据手册的第五章。
以下是通过逻辑分析仪抓取的一段CBW和CSW命令的截图,就这个截图结合程序进行简单的讲解,使读者遇到相应的命令可以去相应的位置查找。
这是一个完成的交互流程,包括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
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
U盘接口函数主要包括初始化SDIO接口、读写SD卡信息等函数,他们是ST官网写好的,我们直接使用即可,这里讲解一下如果SD卡变成了EEPROM或者其他FLASH的时候,怎么修改接口函数。
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);
}
同样读函数我们也只关心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;
}
同样写函数我们也只关心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;
}
同样获取状态函数我们也只关心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;
}
这里对USB的D+外部上拉电阻进行了配置,如果读者使用的开发板和ST官网的外部上拉不一致,需要修改这个位置的IO口定义。这里也需要注意,ST开发板使用的是PNP型三极管作为上拉电阻的使能信号,所以这里GPIO输出低D+上拉,GPIO输出高D+为低。
非0端点传输函数主要在“usb_endp.c”文件中实现,我们打开usb_endp.c文件,看到只有两个函数,分别是对IN端点和OUT端点的处理,这两个端点函数为我们封装好了CBW和CSW指令及SCSI协议相关的内容,我们直接使用,如果感兴趣可以自行研究。
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();
}
主函数代码相对简单,就是实现了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)
{}
}
由于博主的开发板没有挂NAND_FLASH,所以会导致程序一直执行不下去,这里有两种方式解决。
1. 修改盘符个数,并屏蔽NAND_FLASH初始化代码:
2. 不修改盘符个数,屏蔽NAND_FLASH初始化代码和NAND_FLASH获取状态代码:
通过逻辑分析仪抓取波形,发现默认A1 FE 00 00 00 00 01 00命令请求回复的是01,说明存在两个盘符。
查看计算机,发现确实存在两个盘符,只是有一个盘符没有实体,所以不能进行操作。
能够查看到U盘显示的前提是SDIO接口初始化正确,并且插入SD卡,现象如下:
如果SDIO接口初始化失败,或者没有接SD卡,可以出现以下问题,这个说明USB的枚举是通过了的,只是没有读取到FLASH。
接下来讲解STM32 USB的在线固件升级(DFU)实验,敬请期待。。。