STM32CubeMX教程30 USB_DEVICE - MSC外设_读卡器
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、常用函数”小节
参考资料
STM32CubeMX教程30 USB_DEVICE - MSC外设_读卡器的更多相关文章
- [译]Vulkan教程(30)深度缓存
[译]Vulkan教程(30)深度缓存 Depth buffering 深度缓存 Introduction 入门 The geometry we've worked with so far is pr ...
- PS网页设计教程——30个优秀的PS网页设计教程的中文翻译教程
PS网页设计教程--30个优秀的PS网页设计教程的中文翻译教程 作为编码者,美工基础是偏弱的.我们可以参考一些成熟的网页PS教程,提高自身的设计能力.套用一句话,"熟读唐诗三百首,不会作 ...
- Directx教程(30) 如何保证渲染物体不会变形
原文:Directx教程(30) 如何保证渲染物体不会变形 在Directx11教程(6)中, 我们曾经实现过这个功能,但那时是在SystemClass中,处理WM_SIZE时候,重新调用m ...
- Unix/Linux环境C编程入门教程(30) 字符串操作那些事儿
函数介绍 rindex(查找字符串中最后一个出现的指定字符) 相关函数 index,memchr,strchr,strrchr 表头文件 #include<string.h> 定义函数 c ...
- Cocos2d-x教程(30)-3.x版本号物理引擎的使用
转载时请注明原文出处 : http://blog.csdn.net/u012945598/article/details/38417333 在Cocos2d-x 2.x的版本号中,开发人员能够直接使用 ...
- Unix/Linux环境C编程新手教程(30) 字符串操作那些事儿
函数介绍 rindex(查找字符串中最后一个出现的指定字符) 相关函数 index,memchr,strchr,strrchr 表头文件 #include<string.h> 定义函数 c ...
- RHadoop教程翻译系列 _Mapreduce(1)_第一个Mapreduce任务
如果单从概念上来说,Mapreduce和R中的函数lapply, tapply并无差别,它们都是把元素转化成列,然后计算索引(Mapreduce中的键),最后合并成一个定义好的组合.首先,让我们看一个 ...
- 面向对象程序设计-C++_课时30运算符重载——基本规则_课时31运算符重载——原型_课时32运算符重载——赋值_课时33运算符重载——类型转换
区分初始化,赋值 #include <iostream> using namespace std; class Fi { public: Fi() {}//1构造函数 }; class F ...
- Linux+Redis实战教程_day03_Redis-set【重点】_有序set(了解)
2.redis-set[重点] Java HashSet 无序,不重复. Redis操作中,涉及到两个大数据集合的并集,交集,差集运算. 赋值: l sadd key values[value1.v ...
- VS连接SQL Server数据库,增删改查详细教程(C#代码)_转载
工具: 1.Visual Studio (我使用的是vs2013) 2.SQL Server (我使用的是sql server2008) 操作: 1.打开SQL Server,打开后会看到数据库的初 ...
随机推荐
- 为什么加了@Transactional注解,事务没有回滚?
在昨天的<事务管理入门>一文发布之后,有读者联系说根据文章尝试,加了@Transactional注解之后,事务并没有回滚.经过一顿沟通排查之后,找到了原因,在此记录一下,给后面如果碰到类似 ...
- 使用element-plus的el-scrollbar时滚动条没有显示出来但是页面可以滚动的解决办法
如果使用 Element UI 的 el-scrollbar 组件时,滚动条没有显示出来但页面可以滚动,可以尝试调用其 update 方法来更新滚动条. 在适当的时机(例如在数据加载完成后或组件更新后 ...
- 定期发送邮件功能-outlook与腾讯邮箱
一.背景:定期发送邮件功能挺好用的,可以帮忙我们在特殊的时间点发送邮件,以及实现无人推送的功能 二.outlook的实现1.首先编辑好邮件保存至草稿 2.选项-延迟传递,设置不早于传递的时间,点击发送 ...
- context 从入门到深入了解
1. 前言 在 Go 语言中,上下文 context.Context 用来设置截止日期,同步信号,传递值的功能,它与 goroutine 关系密切,被用来解决 goroutine 之间 退出通知,元数 ...
- TCP 三次握手和四次挥手详解
转载请注明出处: TCP协议(Transmission Control Protocol) 面向连接的,可靠的,基于字节流的传输层通信协议 特点: 基于连接的:数据传输之前需要建立连接 全双工的:双向 ...
- VSCode + GCC编译器(MinGW)开发环境中文字符乱码问题踩坑与解决办法
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- Go-数据类型-数字
Go数字类型 整数 int 类 int(在32机器上为int32,在64位机器上为int64) int8 int16 int32 int64 unit类 uint(在32机器上为uint32,在64位 ...
- [转帖]Linux运维常用150个命令
Linux运维常用150个命令 转载自:www.cnblogs.com/bananaaa/p/7774467.html 命令 功能说明 线上查询及帮助命令(2个) man 查看命令帮助,命令的词典,更 ...
- Jmeter学习之六_进行https证书处理的工作
Jmeter 进行https证书处理的工作 背景 继续学习中,想着能够抓取一下https相关的信息 所以计划些一下处理过程 但是感觉自己这一块比较薄弱. 场景设计这一块应该是专业人去搞, 我这边先只是 ...
- 【转帖】eBay 流量管理之 Kubernetes 网络硬核排查案例
https://www.infoq.cn/article/L4vyfdyvHYM5EV8d3CdD 一.引子 在 eBay 新一代基于 Kubernetes 的云平台 Tess 环境中,流量管理的实现 ...