【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配置界面 ,下方配置里,可以設置是否使用檢測引腳:

  1. 如果你的SD卡已連接檢測引腳,想使用CubeMX生成管理,可以在這里指定引腳。
  2. 如果不需要這個檢測功能,就讓它默認空著即可。不管是否已連接此引腳,都可空著。

?

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線,才能被重新識別。

如有錯漏 ,望指正~~~!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/895373.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/895373.shtml
英文地址,請注明出處:http://en.pswp.cn/news/895373.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

docker nginx 配置文件詳解

在平常的開發工作中&#xff0c;我們經常需要訪問靜態資源&#xff08;圖片、HTML頁面等&#xff09;、訪問文件目錄、部署項目時進行負載均衡等。那么我們就會使用到Nginx&#xff0c;nginx.conf 的配置至關重要。那么今天主要結合訪問靜態資源、負載均衡等總結下 nginx.conf …

Ubuntu 下 nginx-1.24.0 源碼分析 - ngx_atomic_cmp_set 函數

目錄 修正 執行 ./configure 命令時&#xff0c;輸出&#xff1a; checking for OS Linux 6.8.0-52-generic x86_64 checking for C compiler ... found using GNU C compiler gcc version: 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04) 所以當前環境是 x86_64 于是在 src…

在 Go 中實現事件溯源:構建高效且可擴展的系統

事件溯源&#xff08;Event Sourcing&#xff09;是一種強大的架構模式&#xff0c;它通過記錄系統狀態的變化&#xff08;事件&#xff09;來重建系統的歷史狀態。這種模式特別適合需要高可擴展性、可追溯性和解耦的系統。在 Go 語言中&#xff0c;事件溯源可以通過一些簡單的…

大數據Orc文件生成與讀取

ORC(Optimized Row Columnar)是Hadoop生態系統中一種高效的列式存儲文件格式,其主要特性包括高效壓縮、快速讀取、以及能夠存儲結構化數據。本文將展示如何使用Java編寫代碼來生成和讀取ORC文件。 一、ORC文件介紹 ORC是一種為Hadoop生態系統優化的列式存儲格式,具有以下…

解讀 Flink Source 接口重構后的 KafkaSource

前言 Apache Kafka 和 Apache Flink 的結合&#xff0c;為構建實時流處理應用提供了一套強大的解決方案[1]。Kafka 作為高吞吐量、低延遲的分布式消息隊列&#xff0c;負責數據的采集、緩沖和分發&#xff1b;而 Flink 則是功能強大的流處理引擎&#xff0c;負責對數據進行實時…

【推理llm論文精讀】DeepSeek V3技術論文_精工見效果

先附上原始論文和效果對比https://arxiv.org/pdf/2412.19437 摘要 (Abstract) DeepSeek-V3是DeepSeek-AI團隊推出的最新力作&#xff0c;一個強大的混合專家&#xff08;Mixture-of-Experts&#xff0c;MoE&#xff09;語言模型。它擁有671B的總參數量&#xff0c;但每個tok…

如何使用Java語言在Idea和Android中分別建立服務端和客戶端實現局域網聊天

手把手教你用Java語言在Idea和Android中分別建立服務端和客戶端實現局域網聊天 目錄 文章目錄 手把手教你用**Java**語言在**Idea**和**Android**中分別建立**服務端**和**客戶端**實現局域網聊天**目錄**[toc]**基本實現****問題分析****服務端**Idea:結構預覽Server類代碼解…

java韓順平最新教程,Java工程師進階

簡介 HikariCP 是用于創建和管理連接&#xff0c;利用“池”的方式復用連接減少資源開銷&#xff0c;和其他數據源一樣&#xff0c;也具有連接數控制、連接可靠性測試、連接泄露控制、緩存語句等功能&#xff0c;另外&#xff0c;和 druid 一樣&#xff0c;HikariCP 也支持監控…

如何在 IDE 里使用 DeepSeek?

近期&#xff0c;阿里云百煉平臺重磅推出 DeepSeek-V3、DeepSeek-R1、DeepSeek-R1-Distill-Qwen-32B 等 6 款模型&#xff0c;進一步豐富其 AI 模型矩陣。與此同時&#xff0c;通義靈碼也緊跟步伐&#xff0c;全新上線模型選擇功能&#xff0c;支持基于百煉的 DeepSeek-V3 和 D…

vue中附件下載及打印功能

1.附件dom 注&#xff1a;fileList是由后臺返回的附件數組&#xff0c;數組中包含附件名稱fileName,附件地址url&#xff0c;附件id等信息 <el-form-item label"附件" style"width: 100% !important;" v-if"modelTypeborrowDetail"><d…

chromium-mojo

https://chromium.googlesource.com/chromium/src//refs/heads/main/mojo/README.md 相關類&#xff1a;https://zhuanlan.zhihu.com/p/426069459 Core:https://source.chromium.org/chromium/chromium/src//main:mojo/core/README.md;bpv1;bpt0 embedder:https://source.chr…

網絡安全技術復習總結

1|0第一章 概論 1.網絡安全發展階段包括四個階段&#xff1a;通信安全、計算機安全、網絡安全、網絡空間安全。 2.2017年6月1日&#xff0c;我國第一部全面規范網絡空間安全的基礎性法律《中華人民共和國網絡安全法》正式實施。 3.2021年 6月10日&#xff0c;《中華人民共和…

基于華為云鏡像加速器的Docker環境搭建與項目部署指南

基于華為云鏡像加速器的Docker環境搭建與項目部署指南 一、安裝Docker1.1 更新系統包1.2 安裝必要的依賴包1.3 移除原有的Docker倉庫配置(如果存在)1.4 添加華為云Docker倉庫1.5 安裝Docker CE1.6 啟動Docker服務1.7 驗證Docker是否安裝成功1.8 添加華為云鏡像加速器地址二、…

在SpringBoot服務器端采購上,如何選擇操作系統、Cpu、內存和帶寬、流量套餐

在Spring Boot服務器端采購時&#xff0c;選擇操作系統、CPU、內存、帶寬和流量套餐需根據應用需求、預算和性能要求綜合考慮。以下是具體建議&#xff1a; 1. 操作系統 Linux發行版&#xff08;如Ubuntu、CentOS&#xff09;&#xff1a;適合大多數Spring Boot應用&#xff…

DedeBIZ系統審計小結

之前簡單審計過DedeBIZ系統&#xff0c;網上還沒有對這個系統的漏洞有過詳盡的分析&#xff0c;于是重新審計并總結文章&#xff0c;記錄下自己審計的過程。 https://github.com/DedeBIZ/DedeV6/archive/refs/tags/6.2.10.zip &#x1f4cc;DedeBIZ 系統并非基于 MVC 框架&…

業務開發 | 基礎知識 | Maven 快速入門

Maven 快速入門 1.Maven 全面概述 Apache Maven 是一種軟件項目管理和理解工具。基于項目對象模型的概念&#xff08;POM&#xff09;&#xff0c;Maven 可以從中央信息中管理項目的構建&#xff0c;報告和文檔。 2.Maven 基本功能 因此實際上 Maven 的基本功能就是作為 Ja…

人工智能之推薦系統實戰系列(協同過濾,矩陣分解,FM與DeepFM算法)

一.推薦系統介紹和應用 (1)推薦系統通俗解讀 推薦系統就是來了就別想走了。例如在大數據時代中京東越買越想買&#xff0c;抖音越刷越是自己喜歡的東西&#xff0c;微博越刷越過癮。 (2).推薦系統發展簡介 1)推薦系統無處不在&#xff0c;它是根據用戶的行為決定推薦的內容…

2.11 sqlite3數據庫【數據庫的相關操作指令、函數】

練習&#xff1a; 將 epoll 服務器 客戶端拿來用 客戶端&#xff1a;寫一個界面&#xff0c;里面有注冊登錄 服務器&#xff1a;處理注冊和登錄邏輯&#xff0c;注冊的話將注冊的賬號密碼寫入數據庫&#xff0c;登錄的話查詢數據庫中是否存在賬號&#xff0c;并驗證密碼是否正確…

Python(十九)實現各大跨境船公司物流查詢數據處理優化

一、前言 之前已經實現了常用 跨境物流船司 基礎信息查詢功能&#xff0c;如下所示 實現各大跨境船公司[COSCO/ZIM/MSK/MSC/ONE/PIL]的物流信息查詢&#xff1a;https://blog.csdn.net/Makasa/article/details/145484999?spm1001.2014.3001.5501 然后本章在其基礎上做了一些…

CentOS開機自啟動服務內容設置

CentOS開機自啟動服務內容設置 1. 開機后自動配置時鐘同步2. 開機自啟動服務腳本3. 配置開機自動添加路由 1. 開機后自動配置時鐘同步 # cat /etc/rc.local /usr/sbin/ntpdate pool.ntp.org >> /var/log/ntpdate.log需要設置/etc/rc.local的一個權限&#xff1a; # ll …