1、准备材料
STM32CubeMX软件(Version 6.10.0)
keil µVision5 IDE(MDK-Arm)
2、实验目标
使用STM32CubeMX软件配置STM32F407开发板USB_OTG_FS为工作在Mass Storage Class(大容量存储类)模式下的USB_DEVICE(USB从机),使其作为SD卡读卡器在Windows系统文件资源管理器中直接对SD卡进行读写操作
3、实验流程
3.0、前提知识
关于USB的相关知识请读者阅读STM32CubeMX教程29 USB_HOST - 使用FatFs文件系统读写U盘实验“3、USB概述”小节内容,USB_SALVE 从机接口硬件原理图请读者阅读其“4.0、前提知识”小节内容
当USB工作在USB_DEVICE时可以将其模式配置为以下6种模式中的任何一种,本实验只会介绍其中的大容量存储设备,其他的一概不涉及,具体的6种模式如下图所述
将USB设备接口配置工作在Mass Storage Class模式下,主要是为了将没有USB接口的大容量的存储设备(eg:SD卡)通过该接口,利用USB连接与USB主机之间建立联系,然后便可以通过USB主机对该大容量存储设备进行控制
对于USB_OTG_FS工作在任意USB外设模式下来说,在CubeMX中一般需要对Configuration下Parameter Settings、Device Descriptor 和User Constants三个参数页面参数进行配置(虽然这些参数一般无需修改,保持默认即可)
其中Parameter Settings 选项卡下的参数会根据不同的外设工作模式出现对应该外设的一些重要参数设置,不同外设出现的参数不尽相同
Device Descriptor 选项卡下的参数则较为固定,不同外设之间往往只会改变Device Descriptor FS下的参数,该选项卡下的所有参数主要用于描述该USB外设,正因为其参数则较为固定,因此这里笔者直接列出了各个参数的含义,在之后的其他外设实验中便不再重复说明,具体含义如下图所示
User Constants 选项卡主要用于设定一些用户需要的常量参数,增加之后会以宏定义的形式出现在main.h文件中
在完成该实验之后读者也可以验证下设备描述符中内容和Windwos中读取到的USB设备描述符是否一致
右键单击弹出的U盘驱动器,单击属性,双击硬件选项卡中的STM Product USB Device,在弹出的页面中单击详细信息,最后在属性里面找到父系,可以在下面找到设备描述中的VID和PID,将其转换为十进制之后发现和我们设定值一致,具体如下图所示
3.1、CubeMX相关配置
3.1.0、工程基本配置
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示
开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示
详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”
3.1.1、时钟树配置
将时钟树中48MHz时钟配置为48MHz,也即将Main PLL(主锁相环)的Q参数调节为7,其他HCLK、PCLK1和PCLK2时钟仍然设置为STM32F407能达到的最高时钟频率,具体如下图所示
3.1.2、外设参数配置
本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”
另外由于需要通过SDIO读写SD卡,USB_OTG_FS工作在从机模式下,因此还需要配置SDIO和USB_OTG_FS
单击Pinout & Configuration页面左边功能分类栏目中Connectivity/SDIO,将其模式配置为4位宽总线SD卡,在下方参数配置Configuration/Parameter Settings中将参数 SDIOCLK clock divide factor 配置为4即可,具体参数含义请读者阅读STM32CubeMX教程27 SDIO - 读写SD卡实验内容,具体配置如下图所示
单击Pinout & Configuration页面左边功能分类栏目中Connectivity/USB_OTG_FS,将其模式配置为仅从机(Device_Only),其他所有参数保持默认即可,具体配置如下图所示
单击Pinout & Configuration页面左边功能分类栏目中Middleware and Software Packs/USB DEVICE,将其模式配置为Mass Storage Class(大容量存储类),其他所有参数保持默认即可,具体配置如下图所示
USBD_MAX_NUM_INTERFACES (支持的最大接口数):可选1 ~ 255,不应超过可用内存的总大小
USBD_MAX_NUM_CONFIGURATION (支持的最大配置数):可选1 ~ 255,不应超过可用内存的总大小
USBD_MAX_STR_DESC_SIZ (字符串描述符的最大大小) :可选1 bytes ~ 64 Kbytes,用于设定Device Descriptor页面中对该USB设备的一些描述字符串最大长度
USBD_SELF_POWERED (启用自供电) :可选Enable、Disable,此处选择Enable表示USB设备有自己的电源供应,不需要从USB总线上获取电力
USBD_DEBUG_LEVEL (USBD调试级别):可选0、1、2、3,具体调试级别如下所示
- 0 : No debug message is shown
- 1 : only User message are shown
- 2 : User + Error messages are shown
- 3 : All message and interal debug message are shown
MSC_MEDIA_PACKET (媒体I/O缓冲区大小):可选1 bytes ~ 32 Kbytes,在USB大容量存储设备中,数据传输通常是以数据包为单位进行的,该宏定义了每个数据包的大小
3.1.3、外设中断配置
当在Middleware and SoftwarePacks中配置了USB_DEVICE的模式不为Disable时,便会自动开启USB_OTG的全局中断,且不可关闭,用户配置合适的中断优先级即可,具体配置如下图所示
3.2、生成代码
3.2.0、配置Project Manager页面
单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示
详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节
3.2.1、设初始化调用流程
打开生成的工程,观察目录结构,由于启用了USB_DEVICE,因此在工程目录种增加了USB设备库文件目录USB_Device_Library,在USB_DEVICE/Target目录下增加了usbd_conf.c参数配置文件,在USB_DEVICE/App目录下增加了usb_device.c初始化文件、usbd_desc.c描述文件和usbd_storage_if.c外设接口文件
其中USB_Device_Library目录下所有文件、usbd_conf.c、usbd_desc.c和usb_device.c几个文件均不需要在生成的工程代码中做任何修改,用户唯一需要修改的是在usbd_storage_if.c外设接口文件中实现的大容量存储设备的接口函数,如下图所示为USB_DEVICE工作在Mass Storage Class下生成工程文件目录
究竟是在哪里将需要我们在usbd_storage_if.c外设接口文件中重新实现的接口函数与USBD实例化对象联系起来的呢?
在 usb_device.c 文件中只有MX_USB_DEVICE_Init()一个函数,该函数体内执行了四个函数对USB_DEVICE进行了初始化和启动操作
其中通过调用USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS)函数,将一个USBD_StorageTypeDef类型的结构体与USBD实例化对象联系了起来,该USBD_StorageTypeDef类型的结构体中包含的正是usbd_storage_if.c外设接口文件中的所有接口函数指针
3.2.2、外设中断调用流程
未使用外设任何中断
3.2.3、添加其他必要代码
STM32CubeMX工程生成工程代码后,读者应注意手动修改MX_SDIO_SD_Init()函数中SD卡数据总线宽度从默认的4位手动修改为1位,否则SD卡将初始化失败
根据上面的描述,唯一需要用户修改的地方是usbd_storage_if.c文件中的7个函数,与W25Q128芯片移植FatFs时类似,读者可以参考STM32CubeMX教程26 FatFs 文件系统 - W25Q128读写实验“3.2、生成代码“小节内容,如下所示为重新实现后的七个函数源代码
/*usbd_storage_if.c*/
/*初始化函数无需修改,因为SD卡初始化在SDIO初始化函数中已完成*/
int8_t STORAGE_Init_FS(uint8_t lun)
{
/* USER CODE BEGIN 2 */
UNUSED(lun);
return (USBD_OK);
/* USER CODE END 2 */
}
/*获取存储介质容量*/
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
/* USER CODE BEGIN 3 */
HAL_SD_CardInfoTypeDef cardInfo;
//使用SDIO库函数
HAL_StatusTypeDef res = HAL_SD_GetCardInfo(&hsd, &cardInfo);
if(res == HAL_OK)
{
*block_num = cardInfo.BlockNbr; //块的个数
*block_size = cardInfo.BlockSize; //块大小=512字节
}
else
{
*block_num = STORAGE_BLK_NBR; //0x10000
*block_size = STORAGE_BLK_SIZ; //块大小=512字节
}
return (USBD_OK);
/* USER CODE END 3 */
}
/*返回存储介质是否准备好,无需修改*/
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
/* USER CODE BEGIN 4 */
UNUSED(lun);
return (USBD_OK);
/* USER CODE END 4 */
}
/*返回存储介质是否写保护,,无需修改*/
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
/* USER CODE BEGIN 5 */
UNUSED(lun);
return (USBD_OK);
/* USER CODE END 5 */
}
/*读取存储介质*/
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 6 */
//读取超时时间10000ms
uint32_t Timeout = 10000;
HAL_StatusTypeDef res = HAL_OK;
res = HAL_SD_ReadBlocks(&hsd, buf, blk_addr, blk_len, Timeout);
HAL_SD_CardStateTypeDef status = HAL_SD_CARD_RECEIVING;
if(res == HAL_OK)
{
//等待传输完成
while(status != HAL_SD_CARD_TRANSFER)
status = HAL_SD_GetCardState(&hsd);
return (USBD_OK);
}
else
return (USBD_FAIL);
/* USER CODE END 6 */
}
/*向存储介质写入数据*/
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 7 */
//写入超时时间10000ms
uint32_t Timeout = 10000;
HAL_StatusTypeDef res = HAL_OK;
res = HAL_SD_WriteBlocks(&hsd, buf, blk_addr, blk_len, Timeout);
HAL_SD_CardStateTypeDef status = HAL_SD_CARD_SENDING;
if (res == HAL_OK)
{
//等待传输完成
while(status != HAL_SD_CARD_TRANSFER)
status = HAL_SD_GetCardState(&hsd);
return (USBD_OK);
}
else
return (USBD_FAIL);
/* USER CODE END 7 */
}
/*返回最大支持LUN数量,无需修改*/
int8_t STORAGE_GetMaxLun_FS(void)
{
/* USER CODE BEGIN 8 */
return (STORAGE_LUN_NBR - 1);
/* USER CODE END 8 */
}
4、烧录验证
烧录程序,开发板上电后,使用USB线将Windows电脑与开发板上的USB_SLAVE接口连接,连接成功后电脑上会弹出与U盘插入时一致的弹窗(第一次可能会自动安装驱动,稍微等待等待),之后就可以在文件资源管理器中找到新的可用卷
单击打开该卷便可以像操作U盘一样对开发板上插入的SD卡进行文件管理,如下图所示笔者的SD卡里还保存着之前实验所写入的信息,读者可以自行尝试写入或删除文件
读者还可以自己将本实验与STM32CubeMX教程28 SDIO - 使用FatFs文件系统读写SD卡实验结合来验证使用FatFs读写SD卡操作是否真正成功,笔者通过开发板上的KEY0按键删除了原本写入SD卡中的test.txt文件,复位开发板重新识别SD卡之后,在Windwos资源管理器里查看发现test.txt文件确实被删除了,具体操作如下图所示
5、常用函数
请阅读STM32CubeMX教程28 SDIO - 使用FatFs文件系统读写SD卡实验“7、常用函数”小节