本篇,將使用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卡的句柄、數據、塊地址、塊數量、超時ms3、寫數據
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) // SD卡的句柄、數據、塊地址、塊數量、超時ms4、讀數據_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"); // 掛載成功elsereturn; // 掛載失敗,退出函數}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-exFatprintf("SD卡已有文件系統:FAT32\n");if (myFatFs.fs_type == 0x04) // exFAT; 1-FAT12、2-FAT16、3-FAT32、4-exFatprintf("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-SECUREDprintf("卡版本:%d \r\n", pCardInfo.CardVersion); // 版本返回:0-CARD_V1、1-CARD_V2printf("塊數量:%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線,才能被重新識別。
如有錯漏 ,望指正~~~!