【CubeMX+STM32】SD卡 U盘文件系统 USB+FATFS
本篇,将使用CubeMX+Keil, 创建一个 USB+TF卡存储+FatFS 的虚拟U盘读写工程。
目录
一、简述
二、CubeMX 配置 SDIO + DMA + FatFs + USB
三、Keil 编辑代码
四、实验效果
串口助手,实现效果:
U盘,识别效果:
一、简述
上几篇,已循序渐进讲解了SD、SDIO、基础读写、DMA读写、FatFs。
这里不再啰嗦,有兴趣的可翻看之前的篇章:
1、【CubeMX+STM32】SD卡 基础读写
2、【CubeMX+STM32】SD卡 DMA读写
3、【CubeMX+STM32】SD卡 文件系统 FatFs+SDIO+DMA
本篇,再向前一步。
通过 CubeMX+Keil ,把SD卡模拟成U盘 ( SDIO+DAM+FatFs+USB),可以用代码进行文件读写,也可以插上USB线在电脑端进行文件读写。
对USB详细原理有兴趣的朋友,可参考大神的书籍《USB应用分析精粹》。
SD卡的接线原画图:
带SDIO的STM32芯片,都是一样的接法,不管是F1、F4。
对于第9脚:检测插入,文中另有详述。
USB端口接线原理图:
本篇所用电路, 如下图,也是STM32作为设备端的通用接法。
- Type-C母座已连接STM32的PA11、PA12, 且PA12 (DP线) 经1.5K电阻上拉到3.3V。
- 优点:电路简单;缺点:只能作设备端,不能作主机端。
注意:如果板子上有两个USB口,注意区分哪个是USB设备端口,哪个是USB转TTL口;
二、CubeMX 配置 SDIO + DMA + FatFs + USB
新建工程部分,略过 。
工程要点:STACK设置>=0x1000,配置好printf重定向。
- 新建工程 参考:【STM32+CubeMX】 新建一个工程(STM32F407)
- UART1 参考1:【STM32+CubeMX】USART1 DMA收发、printf
- UART1 参考2: STM32串口通信 -- bsp_UART.c 文件的移植和函数使用-CSDN博客
1、配置 SDIO、DMA
- Mode : SD 4 bits Wide bus ;
- 参数 :F4系列不用修改配置,默认即可。F103系列,时钟分频系数 SDIOCLK Clock divide factor这一项,默认0,修改为8, 不然会通信失败。
- DMA Settings : 添加SDIO_RX、SDIO_TX这两项; 本页其它参数默认;
2、配置 FatFs 文件系统
- Mode: 打勾 SD Card
- 参数 CODE_PAGE:简体中文
- 参数 USE_LFN:长文件名称(缓存放在STACK,因此STACK得设置大一些,如>0x1000)
- 参数 FS_EXFAT:ENABLE (挂载、格式化时,会自动选择合适的FAT16、FAT32、exFAT)
3、FatFS 使用DMA
4、FatFS 是否使用检测引脚
让我们先回看一下原理图:
SD卡座的第9脚,用于检测是否已插入SD卡,如果已插入SD卡,CD脚会输出低电平。
不建议使用这个功能!因为:SD卡与U盘不同,没有完善的保护电路,不应该进行热插拔!而且,做SD卡项目调试时,默认状态应该是一直插着SD卡的。如果项目有需要,也可以自己写几行引脚电平检测,当引脚电平为低时,再对SD卡进行操作,这样更灵活。
回到FatFs配置界面 ,下方配置里,可以设置是否使用检测引脚:
- 如果你的SD卡已连接检测引脚,想使用CubeMX生成管理,可以在这里指定引脚。
- 如果不需要这个检测功能,就让它默认空着即可。不管是否已连接此引脚,都可空着。
CubeMX生成时注意:
当不指定检测引脚,在最后生成工程时,会有弹窗警告,不用管它,到时点击Yes即可,将会正常生成工程,代码上不会有任何影响。
如果不想有弹窗,可以随便指定一个空闲的引脚,这样在生成时就不会弹警告了,但是需要在生成的工程代码里,注释掉引脚检测功能。操作有点烦人,不建议此方法,如有需要,自行搜索操作的方法。
完成上面的配置后,工程已经能够使用代码进行文件系统挂载、读写了。
5、配置 USB
6、添加USB类 (大容量存储)
7、配置中断、优先级
- 系统时基的中断优先级,修改为 0 ; (如果不改,电脑端将无法识别成U盘)
- SDIO global interrupt: 打勾
- 修改下图四项的中断优先级; 中断值:SDIO < DMA < USB (值越小优先级越高)
8、时钟设置
进入时钟树配置页面。
如果之前没配置过SDIO、USB,这时就会弹窗:是否自动配置所需时钟?
选择:NO ,手动修改即可。
不推荐 Yes,因为它将针对已使能的SDIO、USB进行必须值的配置,而已设置好的系统时钟,将会被修改成其它值。
F4系列,如果板子用的是25M晶振,使用下图配置即可;如果是8M晶振,修改晶振、分频两处为8即可。
重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟,这个时钟配置成 48M ! 因此,箭头的Q值设置为 7;
好了,SD卡 模拟成U盘 所需的 SDIO + FatFS + DMA + USB 已完成配置。
重新生成工程,这时,会有弹窗提示,因为我们没有指定SD卡的检测引脚。
点击 Yes 确认,继续生成即可!
三、Keil 编辑代码
1、打开keil 工程,先重新编译一次。
工程生成后,第一次编译会比较耗时,耐心等待。
- 正常情况,编译是0 Error的。
- 如果有Error, 应该是新建工程时,路径、名称有中文了,重新开建工程,改为英文即可。
2、重要修改:SD卡的初始化,使用 1-bit 模式
CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下:
- 右击 main.c 文件中函数 MX_SDIO_SD_Init(),
- 在弹出菜单中:Go To Ddfinition Of ...; 将跳转到SD卡初始化函数内部;
跳转到 sdio.c文件内的 MX_SDIO_SD_Init() 初始化函数,
- 把函数内的 4B,改为 1B ;(如下图)
因为初始化时需要低速率,改用1线通信。如果不修改,初始化过程会导致程序卡死。
重要:CubeMX每次重新生成后,都要手动修改一次。
至此,工程已经能够使用代码对SD卡文件进行读写了。
下面再添加USB的支持。
3、打开 sd_diskio.h
可以打开 sd_diskio.c,文件空白位置右击,再跳转到其头文件 sd_diskio.h;
进入sd_diskio.h后:
- 在27行,添加头文件引用:#include "ff_gen_drv.h" ;
4、修改 usbd_storage_if.c文件
这个文件,需要修改的地方比较多, 目的是为了把SD卡的文件系统与USB大容量类挂钩起来。
- 增加两个头文件引脚
- 修改四个函数
详细如下,需要修改的位置,已用中文注释。没中文注释的,不用管,保持原样。
可对照修改,也可以直接复制替换原文件。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : usbd_storage_if.c
* @version : v1.0_Cube
* @brief : Memory management layer.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usbd_storage_if.h"
/* USER CODE BEGIN INCLUDE */
#include "sdio.h" // 增加这行
#include "sd_diskio.h" // 增加这行
/* USER CODE END INCLUDE */
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
* @brief Usb device.
* @{
*/
/** @defgroup USBD_STORAGE
* @brief Usb mass storage device module
* @{
*/
/** @defgroup USBD_STORAGE_Private_TypesDefinitions
* @brief Private types.
* @{
*/
/* USER CODE BEGIN PRIVATE_TYPES */
/* USER CODE END PRIVATE_TYPES */
/**
* @}
*/
/** @defgroup USBD_STORAGE_Private_Defines
* @brief Private defines.
* @{
*/
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 0x10000
#define STORAGE_BLK_SIZ 0x200
/* USER CODE BEGIN PRIVATE_DEFINES */
/* USER CODE END PRIVATE_DEFINES */
/**
* @}
*/
/** @defgroup USBD_STORAGE_Private_Macros
* @brief Private macros.
* @{
*/
/* USER CODE BEGIN PRIVATE_MACRO */
/* USER CODE END PRIVATE_MACRO */
/**
* @}
*/
/** @defgroup USBD_STORAGE_Private_Variables
* @brief Private variables.
* @{
*/
/* USER CODE BEGIN INQUIRY_DATA_FS */
/** USB Mass storage Standard Inquiry Data. */
const int8_t STORAGE_Inquirydata_FS[] = /* 36 */
{
/* LUN 0 */
0x00,
0x80,
0x02,
0x02,
(STANDARD_INQUIRY_DATA_LEN - 5),
0x00,
0x00,
0x00,
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '.', '0', '1' /* Version : 4 Bytes */
};
/* USER CODE END INQUIRY_DATA_FS */
/* USER CODE BEGIN PRIVATE_VARIABLES */
/* USER CODE END PRIVATE_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_STORAGE_Exported_Variables
* @brief Public variables.
* @{
*/
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE BEGIN EXPORTED_VARIABLES */
/* USER CODE END EXPORTED_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_STORAGE_Private_FunctionPrototypes
* @brief Private functions declaration.
* @{
*/
static int8_t STORAGE_Init_FS(uint8_t lun);
static int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
static int8_t STORAGE_IsReady_FS(uint8_t lun);
static int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
static int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_GetMaxLun_FS(void);
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
/**
* @}
*/
USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
{
STORAGE_Init_FS,
STORAGE_GetCapacity_FS,
STORAGE_IsReady_FS,
STORAGE_IsWriteProtected_FS,
STORAGE_Read_FS,
STORAGE_Write_FS,
STORAGE_GetMaxLun_FS,
(int8_t *)STORAGE_Inquirydata_FS
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes the storage unit (medium) over USB FS IP
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Init_FS(uint8_t lun)
{
/* USER CODE BEGIN 2 */
UNUSED(lun);
return (USBD_OK);
/* USER CODE END 2 */
}
/**
* @brief Returns the medium capacity.
* @param lun: Logical unit number.
* @param block_num: Number of total block number.
* @param block_size: Block size.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
/* USER CODE BEGIN 3 */
UNUSED(lun);
// *block_num = STORAGE_BLK_NBR; // 注释掉
// *block_size = STORAGE_BLK_SIZ; // 注释掉
*block_num = hsd.SdCard.BlockNbr; // 增加这行,用于获取SD卡的块数量
*block_size = hsd.SdCard.BlockSize; // 增加这行,用于获取SD卡的块大小
return (USBD_OK);
/* USER CODE END 3 */
}
/**
* @brief Checks whether the medium is ready.
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
/* USER CODE BEGIN 4 */
// UNUSED(lun); // 注释掉
return (SD_Driver.disk_status(lun)); // 增加这行; 检查存储设备(如SD卡)指定逻辑单元的状态; 这行是非必需的,但增加后能更准确地反映设备的状态,避免在设备未准备好时进行读写操作,从而提高系统的可靠性和稳定性。
// return (USBD_OK); // 注释掉
/* USER CODE END 4 */
}
/**
* @brief Checks whether the medium is write protected.
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
/* USER CODE BEGIN 5 */
UNUSED(lun);
return (USBD_OK);
/* USER CODE END 5 */
}
/**
* @brief Reads data from the medium.
* @param lun: Logical unit number.
* @param buf: data buffer.
* @param blk_addr: Logical block address.
* @param blk_len: Blocks number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 6 */
// UNUSED(lun); // 注释掉
// UNUSED(buf); // 注释掉
// UNUSED(blk_addr); // 注释掉
// UNUSED(blk_len); // 注释掉
// return (USBD_OK); // 注释掉
return (SD_Driver.disk_read(lun, buf, blk_addr, blk_len)); // 增加这行
/* USER CODE END 6 */
}
/**
* @brief Writes data into the medium.
* @param lun: Logical unit number.
* @param buf: data buffer.
* @param blk_addr: Logical block address.
* @param blk_len: Blocks number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 7 */
// UNUSED(lun); // 注释掉
// UNUSED(buf); // 注释掉
// UNUSED(blk_addr); // 注释掉
// UNUSED(blk_len); // 注释掉
// return (USBD_OK); // 注释掉
return (SD_Driver.disk_write(lun, buf, blk_addr, blk_len)); // 增加这行
/* USER CODE END 7 */
}
/**
* @brief Returns the Max Supported LUNs.
* @param None
* @retval Lun(s) number.
*/
int8_t STORAGE_GetMaxLun_FS(void)
{
/* USER CODE BEGIN 8 */
return (STORAGE_LUN_NBR - 1);
/* USER CODE END 8 */
}
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
/**
* @}
*/
/**
* @}
*/
至此,整个工程需要修改的地方,都已修改好了。
先编译、保存一下。
下面,开始编写测试代码,用代码的方式进行文件读写测试。
5、常用函数
我们上几篇介绍SD卡的读写时,共用过6个函数,如下表。
这6个函数,在本工程中,还是可用的,本篇不啰嗦示范。
1、获取SD卡信息
HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体
HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息
2、读数据
HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000); // SD卡的句柄、数据、块地址、块数量、超时ms
3、写数据
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) // SD卡的句柄、数据、块地址、块数量、超时ms
4、读数据_DMA
HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2); // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量;
5、写数据_DMA
HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2); // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;
6、擦除数据
HAL_SD_Erase(&hsd, 7, 8) // SD卡的句柄、块起始地址、块结束地址
而用代码进行FatFS文件系统的操作、读写,会用到下面6个函数.
函数参数的具体作用,可以通过 Kimi 进行查询。
FRESULT f_res;
1、挂载文件系统
f_res = f_mount(&myFatFs, "0:", 1); // 在SD卡上挂载文件系统; 参数:文件系统对象、驱动器路径、读写模式(0只读、1读写)
2、格式化
static uint8_t aMountBuffer[4096]; // 格式化时所需的临时缓存; 块大小512的倍数; 值越大格式化越快, 如果内存不够,可改为512或者1024; 当需要在函数内定义这种大缓存时,要用static修饰,令缓存存放在全局数据区内,不然,可能会导致stack溢出。
f_res = f_mkfs("0:", 0, 0, aMountBuffer, sizeof(aMountBuffer)); // 格式化SD卡; 参数:驱动器路径、文件系统(0自动\1FAT12\2FAT16\)、簇大小(0为自动选择)、格式化临时缓冲区、缓冲区大小
3、打开文件
f_res = f_open(&myFile, "0:Test.txt", FA_CREATE_ALWAYS | FA_WRITE); // 打开文件; 参数:要操作的文件对象、路径和文件名称、打开模式;
4、关闭文件
f_close(&myFile); // 不再读写,关闭文件
5、文件写入数据
f_res = f_write(&myFile, aWriteBuf, sizeof(aWriteBuf), &num); // 向文件内写入数据; 参数:文件对象、数据缓存、申请写入的字节数、实际写入的字节数
6、文件读取数据
f_res = f_read(&myFile, aReadData, sizeof(aReadData), &num); // 从文件中读取数据; 参数:文件对象、数据缓冲区、请求读取的最大字节数、实际读取的字节数
6、具体操作示例代码
第一步:编写FatFs文件系统操作、读写的示范函数
在main()的上方,/* USER CODE BEGIN 0 */ 下面,编写以下代码(建议直接复制):
这个函数的作用是:判断是否需要格式化、挂载文件系统、创建文件、写入数据、读出数据。
// SD卡的FatFS文件系统挂载、格式化、读写测试
void FatFsTest(void)
{
static FATFS myFatFs; // FatFs 文件系统对象; 这个结构体占用598字节,有点大,需用static修饰(存放在全局数据区), 避免stack溢出
static FIL myFile; // 文件对象; 这个结构体占用570字节,有点大,需用static修饰(存放在全局数据区), 避免stack溢出
static FRESULT f_res; // 文件操作结果
static uint32_t num; // 文件实际成功读写的字节数
static uint8_t aReadData[1024] = {0}; // 读取缓冲区; 这个数组占用1024字节,需用static修饰(存放在全局数据区), 避免stack溢出
static uint8_t aWriteBuf[] = "测试; This is FatFs Test ! \r\n"; // 要写入的数据
// 重要的延时:避免烧录期间的复位导致文件读写、格式化等错误
HAL_Delay(1000); // 重要:稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位,短暂运行了程序,导致下列读写数据不完整。
// 1、挂载测试:在SD卡挂载文件系统
printf("\r\n\r\n");
printf("1、挂载 FatFs 测试 ****** \r\n");
f_res = f_mount(&myFatFs, "0:", 1); // 在SD卡上挂载文件系统; 参数:文件系统对象、驱动器路径、读写模式(0只读、1读写)
if (f_res == FR_NO_FILESYSTEM) // 检查是否已有文件系统,如果没有,就格式化创建创建文件系统
{
printf("SD卡没有文件系统,开始格式化…...\r\n");
static uint8_t aMountBuffer[4096]; // 格式化时所需的临时缓存; 块大小512的倍数; 值越大格式化越快, 如果内存不够,可改为512或者1024; 当需要在函数内定义这种大缓存时,要用static修饰,令缓存存放在全局数据区内,不然,可能会导致stack溢出。
f_res = f_mkfs("0:", 0, 0, aMountBuffer, sizeof(aMountBuffer)); // 格式化SD卡; 参数:驱动器、文件系统(0-自动\1-FAT12\2-FAT16\3-FAT32\4-exFat)、簇大小(0为自动选择)、格式化临时缓冲区、缓冲区大小; 格式化前必须先f_mount(x,x,1)挂载,即必须用读写方式挂载; 如果SD卡已格式化,f_mkfs()的第2个参数,不能用0自动,必须指定某个文件系统。
if (f_res == FR_OK) // 格式化 成功
{
printf("SD卡格式化:成功 \r\n");
f_res = f_mount(NULL, "0:", 1); // 格式化后,先取消挂载
f_res = f_mount(&myFatFs, "0:", 1); // 重新挂载
if (f_res == FR_OK)
printf("FatFs 挂载成功 \r\n"); // 挂载成功
else
return; // 挂载失败,退出函数
}
else
{
printf("SD卡格式化:失败 \r\n"); // 格式化 失败
return;
}
}
else if (f_res != FR_OK) // 挂载异常
{
printf("FatFs 挂载异常: %d; 检查MX_SDIO_SD_Init()是否已修改1B\r", f_res);
return;
}
else // 挂载成功
{
if (myFatFs.fs_type == 0x03) // FAT32; 1-FAT12、2-FAT16、3-FAT32、4-exFat
printf("SD卡已有文件系统:FAT32\n");
if (myFatFs.fs_type == 0x04) // exFAT; 1-FAT12、2-FAT16、3-FAT32、4-exFat
printf("SD卡已有文件系统:exFAT\n");
printf("FatFs 挂载成功 \r\n"); // 挂载成功
}
// 2、写入测试:打开或创建文件,并写入数据
printf("\r\n");
printf("2、写入测试:打开或创建文件,并写入数据 ****** \r\n");
f_res = f_open(&myFile, "0:text.txt", FA_CREATE_ALWAYS | FA_WRITE); // 打开文件; 参数:要操作的文件对象、路径和文件名称、打开模式;
if (f_res == FR_OK)
{
printf("打开文件 成功 \r\n");
printf("写入测试:");
f_res = f_write(&myFile, aWriteBuf, sizeof(aWriteBuf), &num); // 向文件内写入数据; 参数:文件对象、数据缓存、申请写入的字节数、实际写入的字节数
if (f_res == FR_OK)
{
printf("写入成功 \r\n");
printf("已写入字节数:%d \r\n", num); // printf 写入的字节数
printf("已写入的数据:%s \r\n", aWriteBuf); // printf 写入的数据; 注意,这里以字符串方式显示,如果数据是非ASCII可显示范围,则无法显示
}
else
{
printf("写入失败 \r\n"); // 写入失败
printf("错误编号: %d\r\n", f_res); // printf 错误编号
}
f_close(&myFile); // 不再读写,关闭文件
}
else
{
printf("打开文件 失败: %d\r\n", f_res);
}
// 3、读取测试:打开已有文件,读取其数据
printf("3、读取测试:打开刚才的文件,读取其数据 ****** \r\n");
f_res = f_open(&myFile, "0:text.txt", FA_OPEN_EXISTING | FA_READ); // 打开文件; 参数:文件对象、路径和名称、操作模式; FA_OPEN_EXISTING:只打开已存在的文件; FA_READ: 以只读的方式打开文件
if (f_res == FR_OK)
{
printf("打开文件 成功 \r\n");
f_res = f_read(&myFile, aReadData, sizeof(aReadData), &num); // 从文件中读取数据; 参数:文件对象、数据缓冲区、请求读取的最大字节数、实际读取的字节数
if (f_res == FR_OK)
{
printf("读取数据 成功 \r\n");
printf("已读取字节数:%d \r\n", num); // printf 实际读取的字节数
printf("读取到的数据:%s\r\n", aReadData); // printf 实际数据; 注意,这里以字符串方式显示,如果数据是非ASCII可显示范围,则无法显示
}
else
{
printf("读取 失败 \r\n"); // printf 读取失败
printf("错误编号:%d \r\n", f_res); // printf 错误编号
}
}
else
{
printf("打开文件 失败 \r\n"); // printf 打开文件 失败
printf("错误编号:%d\r\n", f_res); // printf 错误编号
}
f_close(&myFile); // 不再读写,关闭文件
f_mount(NULL, "0:", 1); // 不再使用文件系统,取消挂载文件系统
}
编写完成后,位置如下图:
第二步:编写SD卡信息获取函数
在刚才函数的下方,再编写一个SD卡信息获取函数(建议直接复制)。
这个函数的作用是:获取SD卡的基础信息、块数量 、块大小、卡容量。
// 获取SD卡信息
// 注意: 本函数需要在f_mount()执行后再调用,因为CubeMX生成的FatFs代码, 会在f_mount()函数内对SD卡进行初始化
void SDCardInfo(void)
{
HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体
uint8_t status = HAL_SD_GetCardState(&hsd); // SD卡状态标志值
if (status == HAL_SD_CARD_TRANSFER)
{
HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息
printf("\r\n");
printf("*** 获取SD卡信息 *** \r\n");
printf("卡类型:%d \r\n", pCardInfo.CardType); // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECURED
printf("卡版本:%d \r\n", pCardInfo.CardVersion); // 版本返回:0-CARD_V1、1-CARD_V2
printf("块数量:%d \r\n", pCardInfo.BlockNbr); // 可用的块数量
printf("块大小:%d \r\n", pCardInfo.BlockSize); // 每个块的大小; 单位:字节
printf("卡容量:%lluG \r\n", ((uint64_t)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024); // 计算卡的容量; 单位:GB
}
}
第三步:在 main()函数内,调用刚才那两个函数
调用位置,如下图:
至此,测试代码编写完成,可以编译、烧录了。
四、实验效果
串口助手输出:
电脑U盘识别效果:
重要:每次烧录程序后,需要手动插拔一次USB线,才能被重新识别。
如有错漏 ,望指正~~~!