文章目錄
stm32 CubeMx 實現SD卡/SD nand FATFS讀寫測試
1. 前言
2. 環境介紹
2.1 軟硬件說明
2.2 外設原理圖
3. 工程搭建
3.1 CubeMx 配置
3.2 讀寫測試
3.2.1 添加讀寫測試代碼
3.3 FATFS文件操作
3.3.1 修改讀寫測試代碼
3.4 配置問題記錄
3.4.1 CubeMx生成代碼bug
3.4.2 SD插入檢測引腳配置
4. 結束語
1. 前言
SD卡/SD nand是嵌入式開發中常為使用的大容量存儲設備,SD nand雖然當前價格比SD卡高,但勝在價格、封裝以及穩定性上有優勢,實際操作和SD卡沒什么區別。
關于 SD卡/SDnand 的驅動,有了CubeMx之后其實基本上都自動生成了對應的驅動了,基本上把驅動配置一下之后,自己寫一些應用就可以完成基本的讀寫了,同時關于FATFS文件系統,也可以直接采用CubeMx配置,也不用自己移植,因此使用STM32開發這些還是比較爽的!不過使用過程中也有一些坑,自動生成的驅動有時候也還是有一些bug,因此還是需要大家對對應驅動有一定的了解。
本文將主要分享關于使用 CubeMx 配置 stm32 的工程,通過SDIO總線完成 SD卡/SD nand 的讀寫,并配置FATFS,采用文件操作實現對 SD卡/SD nand 的讀寫操作;此外還將分享博主在調試過程中遇到的一些問題,比如CubeMx自動生成的驅動存在的bug等,以及分享關于驅動部分的代碼分析!
2. 環境介紹
2.1 軟硬件說明
硬件環境:
主控:stm32f103vet6
SD nand: CSNP1GCR01-AOW【樣品CS創世SD NAND由深圳市雷龍發展有限公司免費提供的,感興趣的可到雷龍官網申請】
軟件環境:
CubeMx版本:Version 6.6.1
注意:當前最新版本 V6.8.0,生成的工程配置存在bug,具體細節在后文描述
2.2 外設原理圖
SD卡槽原理圖部分如下:
?
?
3. 工程搭建
3.1 CubeMx 配置
?
- 2.搜索對應的芯片型號,在對應列表下方選擇對應芯片
- 3.配置時鐘方案,采用外部高速時鐘,無源晶振方案
?
- 4.配置調試器,由于我采用SWD調試接口,因此選擇 Serial Wrie 串行總線
?
- 5.配置SDIO外設,由于我們所使用的SD nand支持4線傳輸,因此此處選擇4線寬度;如果你所使用的SD nand或SD卡不支持4線傳輸,此處應選擇1線寬度;支持4線寬度的SD卡肯定可以使用1線寬度,因此如果你實在不知道你的SD卡支持幾線寬度,你可以直接選擇1線寬度!4線和1線寬度的差別也就在于速度上相差了4倍!
- (注意這里暫時不需要對SDIO的參數進行配置,后面我們再回來配置!)
?
- 6.完成時鐘樹配置:
- 配置外部晶振頻率
- 調整時鐘選擇,SYSCLK由PLL產生,PLL由外部時鐘倍頻產生
- 配置SDIO外設時鐘,注意此處SDIO外設比較特殊,有兩個時鐘!具體原因見后文!
?
- 7.
- 修改SDIO參數配置,主要是修改SDIOCLK的分頻
- 由于我們上述配置的SDIO時鐘為 72M,而SD卡支持的通訊速率在0MHz至25MHz之間,因此我們需要分頻,配置 SDIO Clock divider bypass 為 Disable
- 此處設置 SDIOCLK clock divide factor CLKDIV分頻系數為 8,這個受限于具體的SD卡支持的最大速度。如果設置值較小,可能由于SDIO_CK速度過高,SD卡/SDnand不支持,導致通訊失敗,因此建議先將此值設大點(或查看SD卡/SDnand手冊,或先設一個較大值,軟件完成SD信息讀取后再配置)
- 注意這個配置的時鐘是用于SD讀寫通訊時候的時鐘,而不是SD卡信息識別過程時的速度!
?
編輯
?
8.勾選 FATFS 配置,選擇 SD Card
編輯
?
9.配置SD卡檢測引腳,有以下兩種方案
- 方案一:選擇一個輸入IO,作為觸發引腳
編輯
?
- 方案二:不配置輸入IO,最后生成代碼的時候無視警報即可,生成的代碼會自動取消輸入檢測判斷
編輯
?
10.配置調試串口,用來打印信息,此處我選擇USART1,大家可根據自己硬件環境自行選擇
編輯
?
11.配置工程信息
- 配置工程名
- 選擇工程路徑
- 配置應用程序結構,我習慣選擇 Basic 結構
- 選擇IDE工具及版本
- 修改堆棧大小,適當改大一點,怕不夠用
編輯
?
12.勾選將外設初始化放置在獨立的.c和.h文件,這樣每個外設的初始化是獨立的,方便閱讀移植!
編輯
?
13.生成代碼
編輯
?
3.2 SDIO時鐘配置說明
在上述CubeMx時鐘配置中,外設的時鐘一般都是只有一路過去,但是在此處我們會發現SDIO的時鐘在時鐘樹中有兩個!沒弄清楚還會以為這是CubeMx出現bug了!
編輯
?
其實這是SDIO外設的特殊點,我們查看數據手冊上的時鐘樹,便可以發現,實際上是真的有兩路時鐘,分別是:1)SDIOCLK;2)至SDIO的AHB接口;
?
之后,我們看到數據手冊的SDIO章節,我們可以看到SDIO外設分為:1)AHB總線接口 和 2)SDIO適配器兩大塊,且使用不同的時鐘,這也就是我們在時鐘樹配置中可以看到有兩路時鐘配置的原因了!
從下圖我們可以知道,SDIO外設不同于其他外設,其外設模塊部分與中斷、DMA是分開的,并采用不同的時鐘!
?
關于AHB總線接口及SDIO適配器更多細節,大家可自行閱讀參考手冊部分章節內容,此處不做贅述。
此外,關于時鐘配置有一個特別需要注意的,也就是SDIO_CK時鐘信號。SDIO_CK時鐘,也就是我們SDIO外設與SD卡/SD nand通訊的CLK時鐘,從上圖我們可知,SDIO_CK時鐘來自SDIO適配器,也就是來自SDIOCLK,對應CubeMX時鐘配置中的:
?
?
3.2 讀寫測試
3.2.1 添加讀寫測試代碼
- 1.使能 MicroLIB 微庫,否則調用 printf 函數會卡住
?
2.修改編碼規則為 UTF-8,這是由于我們CubeMx中配置的FATFS的編碼格式為 UTF-8導致,如果不修改為 UTF-8 則部分中文會亂碼! //TODO:確認是由FATFS配置導致
?
?
3.添加 printf 重映射 (位置可根據自行決定)
- #include
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
- return (ch);
- }
4.添加 sdcard 信息打印函數,查看卡片信息
- HAL_SD_CardInfoTypeDef SDCardInfo;
- void printf_sdcard_info(void)
- {
- uint64_t CardCap; //SD卡容量
- HAL_SD_CardCIDTypeDef SDCard_CID;
- HAL_SD_GetCardCID(&hsd,&SDCard_CID); //獲取CID
- HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //獲取SD卡信息
- CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //計算SD卡容量
- switch(SDCardInfo.CardType)
- {
- case CARD_SDSC:
- {
- if(SDCardInfo.CardVersion == CARD_V1_X)
- printf("Card Type:SDSC V1\r\n");
- else if(SDCardInfo.CardVersion == CARD_V2_X)
- printf("Card Type:SDSC V2\r\n");
- }
- break;
- case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
- default:break;
- }
- printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID
- printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本號
- printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡類別
- printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相對地址
- printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //塊數量
- printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //塊大小
- printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //邏輯塊數量
- printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //邏輯塊大小
- printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量
- }
5.添加初始化及讀寫測試代碼,注意此處我們沒有直接使用FATFS的讀寫接口,我們先測試生成的SD驅動函數接口
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- BYTE send_buf[512];
- DRESULT ret;
- /* USER CODE END 1 */
- /* ...省略若干自動生成代碼... */
- /* USER CODE BEGIN 2 */
- SD_Driver.disk_initialize(0);
- printf_sdcard_info();
- printf("\r\n\r\n********** 英文讀寫測試 **********\r\n");
- ret = SD_Driver.disk_write(0,
- (BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
- If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
- insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
- your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true\
- friends",20,2);
- printf("sd write result:%d\r\n", ret);
- ret = SD_Driver.disk_read(0, send_buf, 20, 2);
- printf("sd reak result:%d\r\n", ret);
- printf("sd read content:\r\n%s\r\n", send_buf);
- printf("\r\n\r\n********** 中文讀寫測試 **********\r\n");
- ret = SD_Driver.disk_write(0,
- (BYTE *)"開發者社區的明天需要大家一同開源共創,期待下一次你的分享,讓我們一同攜手共進,推動人類科技的發展!!!\r\n\
- 創作不易,轉載請注明出處~\r\n\
- 更多文章敬請關注:愛出名的狗腿子\r\n", 22, 2);
- printf("sd write result:%d\r\n", ret);
- ret = SD_Driver.disk_read(0, send_buf, 22, 2);
- printf("sd reak result:%d\r\n", ret);
- printf("sd read content:\r\n%s\r\n", send_buf);
- /* USER CODE END 2 */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
6.修改燒錄器配置,配置為燒錄后自動運行
?
7.下載測試,這里由于我們采用UTF-8編碼,所以使用的串口上位機也需要支持UTF-8解析,我們這里使用Mobaxterm上位機,測試結果如下:
?
8.main.c 文件全部代碼如下,供大家參考:
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : main.c
- * @brief : Main program body
- ******************************************************************************
- * @attention
- *
- * Copyright (c) 2023 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 "main.h"
- #include "fatfs.h"
- #include "sdio.h"
- #include "usart.h"
- #include "gpio.h"
- /* Private includes ----------------------------------------------------------*/
- /* USER CODE BEGIN Includes */
- #include
- /* USER CODE END Includes */
- /* Private typedef -----------------------------------------------------------*/
- /* USER CODE BEGIN PTD */
- /* USER CODE END PTD */
- /* Private define ------------------------------------------------------------*/
- /* USER CODE BEGIN PD */
- /* USER CODE END PD */
- /* Private macro -------------------------------------------------------------*/
- /* USER CODE BEGIN PM */
- /* USER CODE END PM */
- /* Private variables ---------------------------------------------------------*/
- /* USER CODE BEGIN PV */
- /* USER CODE END PV */
- /* Private function prototypes -----------------------------------------------*/
- void SystemClock_Config(void);
- /* USER CODE BEGIN PFP */
- /* USER CODE END PFP */
- /* Private user code ---------------------------------------------------------*/
- /* USER CODE BEGIN 0 */
- HAL_SD_CardInfoTypeDef SDCardInfo;
- void printf_sdcard_info(void)
- {
- uint64_t CardCap; //SD卡容量
- HAL_SD_CardCIDTypeDef SDCard_CID;
- HAL_SD_GetCardCID(&hsd,&SDCard_CID); //獲取CID
- HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //獲取SD卡信息
- CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //計算SD卡容量
- switch(SDCardInfo.CardType)
- {
- case CARD_SDSC:
- {
- if(SDCardInfo.CardVersion == CARD_V1_X)
- printf("Card Type:SDSC V1\r\n");
- else if(SDCardInfo.CardVersion == CARD_V2_X)
- printf("Card Type:SDSC V2\r\n");
- }
- break;
- case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
- default:break;
- }
- printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID
- printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本號
- printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡類別
- printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相對地址
- printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //塊數量
- printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //塊大小
- printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //邏輯塊數量
- printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //邏輯塊大小
- printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量
- }
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
- return (ch);
- }
- /* USER CODE END 0 */
- /**
- * @brief The application entry point.
- * @retval int
- */
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- BYTE send_buf[512];
- DRESULT ret;
- /* USER CODE END 1 */
- /* MCU Configuration--------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
- /* USER CODE BEGIN Init */
- /* USER CODE END Init */
- /* Configure the system clock */
- SystemClock_Config();
- /* USER CODE BEGIN SysInit */
- /* USER CODE END SysInit */
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_SDIO_SD_Init();
- MX_USART1_UART_Init();
- MX_FATFS_Init();
- /* USER CODE BEGIN 2 */
- SD_Driver.disk_initialize(0);
- printf_sdcard_info();
- printf("\r\n\r\n********** 英文讀寫測試 **********\r\n");
- ret = SD_Driver.disk_write(0,
- (BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
- If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
- insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
- your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true\
- friends",20,2);
- printf("sd write result:%d\r\n", ret);
- ret = SD_Driver.disk_read(0, send_buf, 20, 2);
- printf("sd reak result:%d\r\n", ret);
- printf("sd read content:\r\n%s\r\n", send_buf);
- printf("\r\n\r\n********** 中文讀寫測試 **********\r\n");
- ret = SD_Driver.disk_write(0,
- (BYTE *)"開發者社區的明天需要大家一同開源共創,期待下一次你的分享,讓我們一同攜手共進,推動人類科技的發展!!!\r\n\
- 創作不易,轉載請注明出處~\r\n\
- 更多文章敬請關注:愛出名的狗腿子\r\n", 22, 2);
- printf("sd write result:%d\r\n", ret);
- ret = SD_Driver.disk_read(0, send_buf, 22, 2);
- printf("sd reak result:%d\r\n", ret);
- printf("sd read content:\r\n%s\r\n", send_buf);
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
- /**
- * @brief System Clock Configuration
- * @retval None
- */
- void SystemClock_Config(void)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
- /** Initializes the RCC Oscillators according to the specified parameters
- * in the RCC_OscInitTypeDef structure.
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
- RCC_OscInitStruct.HSIState = RCC_HSI_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
- /** Initializes the CPU, AHB and APB buses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
- {
- Error_Handler();
- }
- }
- /* USER CODE BEGIN 4 */
- /* USER CODE END 4 */
- /**
- * @brief This function is executed in case of error occurrence.
- * @retval None
- */
- void Error_Handler(void)
- {
- /* USER CODE BEGIN Error_Handler_Debug */
- /* User can add his own implementation to report the HAL error return state */
- __disable_irq();
- while (1)
- {
- }
- /* USER CODE END Error_Handler_Debug */
- }
- #ifdef USE_FULL_ASSERT
- /**
- * @brief Reports the name of the source file and the source line number
- * where the assert_param error has occurred.
- * @param file: pointer to the source file name
- * @param line: assert_param error line source number
- * @retval None
- */
- void assert_failed(uint8_t *file, uint32_t line)
- {
- /* USER CODE BEGIN 6 */
- /* User can add his own implementation to report the file name and line number,
- ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
- /* USER CODE END 6 */
- }
- #endif /* USE_FULL_ASSERT */
3.3 FATFS文件操作
移植了FATFS,當然也就可以只用通用的文件系統操作函數完成文件的讀寫,通用的文件系統操作API 在 ff.c 文件內,聲明在 ff.h 文件內,主要使用的API接口如下:
- FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
- FRESULT f_close (FIL* fp); /* Close an open file object */
- FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
- FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */
- FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
- FRESULT f_lseek (FIL* fp, DWORD ofs); /* Move file pointer of a file object */
- FRESULT f_truncate (FIL* fp); /* Truncate file */
- FRESULT f_sync (FIL* fp); /* Flush cached data of a writing file */
- FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
- FRESULT f_closedir (DIR* dp); /* Close an open directory */
- FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
- FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
- FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
- FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
- FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
- FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
- FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
- FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of the file/dir */
- FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change times-tamp of the file/dir */
- FRESULT f_chdir (const TCHAR* path); /* Change current directory */
- FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
- FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
- FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
- FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
- FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
- FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
- FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au); /* Create a file system on the volume */
- FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work); /* Divide a physical drive into some partitions */
- int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
- int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
- int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
- TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
關于API的使用此處不做過多贅述,大家可以自行上官網查閱 FATFS官網,或者網上搜索,或直接看下述示例亦可。
3.3.1 修改讀寫測試代碼
修改3.2.1章節所使用的讀寫測試代碼,此處我們直接使用FATFS文件系統的讀寫函數接口,修改主函數如下,注意需要包含fatfs.h頭文件!
- #include "fatfs.h"
- int main()
- {
- /* USER CODE BEGIN 1 */
- #define USERPath "0:/"
- BYTE write_buf[] = "\r\n\r\n\
- hello world!\r\n\
- 開發者社區的明天需要大家一同開源共創,期待下一次你的分享,讓我們一同攜手共進,推動人類科技的發展!!!\r\n\
- 創作不易,轉載請注明出處~\r\n\
- 更多文章敬請關注:愛出名的狗腿子\r\n\r\n\
- ";
- BYTE read_buf[1024] = {0};
- UINT num;
- FRESULT ret;
- /* USER CODE END 1 */
- /* ... 省略初始化代碼... */
- /* USER CODE BEGIN 2 */
- /* 掛載文件系統,掛載的時候會完成對應硬件設備(SD卡/SDnand)初始化 */
- ret = f_mount(&SDFatFS, USERPath, 1);
- if (ret != FR_OK) {
- printf("f_mount error!\r\n");
- goto mount_error;
- } else if(ret == FR_NO_FILESYSTEM) { /* 檢測是否存在文件系統,如果沒有則進行格式化 */
- printf("未檢測到FATFS文件系統,執行格式化...\r\n");
- ret = f_mkfs(USERPath, 0, 0);
- if(ret == FR_OK) {
- printf("格式化成功!\r\n");
- f_mount(NULL, USERPath, 1); /* 先取消掛載,后重新掛載 */
- ret = f_mount(&SDFatFS, USERPath, 1);
- } else {
- printf("格式化失敗!\r\n");
- goto mount_error;
- }
- } else {
- printf("f_mount success!\r\n");
- }
- /* 讀寫測試 */
- printf("\r\n ========== write test ==========\r\n");
- ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
- if(ret == FR_OK) {
- printf("open file sucess!\r\n");
- ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
- if(ret == FR_OK) {
- printf("write "%s" success!\r\nwrite len:%d\r\n", write_buf, num);
- } else {
- printf("write error! ret:%d \r\n", ret);
- goto rw_error;
- }
- f_close(&SDFile);
- } else {
- printf("open file error!\r\n");
- goto rw_error;
- }
- printf("\r\n ========== read test ==========\r\n");
- ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ);
- if(ret == FR_OK) {
- printf("open file sucess!\r\n");
- ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num);
- if(ret == FR_OK) {
- printf("read data:"%s"!\r\nread len:%d\r\n", read_buf, num);
- } else {
- printf("read error! ret:%d \r\n", ret);
- goto rw_error;
- }
- } else {
- printf("open file error!\r\n");
- goto rw_error;
- }
- rw_error:
- f_close(&SDFile);
- mount_error:
- f_mount(NULL, USERPath, 1);
- /* USER CODE END 2 */
- while (1) {
- }
- }
#define USERPath "0:/" 表示掛載的位置,這是由于FATFS初始化的時候鏈接的根目錄為 0:/ ,所以掛載的文件系統需要在此目錄下,當然也可以是此目錄下的路徑,如0:/hello,但不能是其他目錄,如 1:/
?
測試結果如下:
?
main.c完整內容如下:
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : main.c
- * @brief : Main program body
- ******************************************************************************
- * @attention
- *
- * Copyright (c) 2023 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 "main.h"
- #include "fatfs.h"
- #include "sdio.h"
- #include "usart.h"
- #include "gpio.h"
- /* Private includes ----------------------------------------------------------*/
- /* USER CODE BEGIN Includes */
- #include
- #include "fatfs.h"
- /* USER CODE END Includes */
- /* Private typedef -----------------------------------------------------------*/
- /* USER CODE BEGIN PTD */
- /* USER CODE END PTD */
- /* Private define ------------------------------------------------------------*/
- /* USER CODE BEGIN PD */
- /* USER CODE END PD */
- /* Private macro -------------------------------------------------------------*/
- /* USER CODE BEGIN PM */
- /* USER CODE END PM */
- /* Private variables ---------------------------------------------------------*/
- /* USER CODE BEGIN PV */
- /* USER CODE END PV */
- /* Private function prototypes -----------------------------------------------*/
- void SystemClock_Config(void);
- /* USER CODE BEGIN PFP */
- /* USER CODE END PFP */
- /* Private user code ---------------------------------------------------------*/
- /* USER CODE BEGIN 0 */
- HAL_SD_CardInfoTypeDef SDCardInfo;
- void printf_sdcard_info(void)
- {
- uint64_t CardCap; //SD卡容釿
- HAL_SD_CardCIDTypeDef SDCard_CID;
- HAL_SD_GetCardCID(&hsd,&SDCard_CID); //獲取CID
- HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //獲取SD卡信恿
- CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //計算SD卡容釿
- switch(SDCardInfo.CardType)
- {
- case CARD_SDSC:
- {
- if(SDCardInfo.CardVersion == CARD_V1_X)
- printf("Card Type:SDSC V1\r\n");
- else if(SDCardInfo.CardVersion == CARD_V2_X)
- printf("Card Type:SDSC V2\r\n");
- }
- break;
- case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
- default:break;
- }
- printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制?商ID
- printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本號
- printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡類劌
- printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相對地坿
- printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //塊數釿
- printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //塊大尿
- printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //邏輯塊數釿
- printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //邏輯塊大尿
- printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容釿
- }
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
- return (ch);
- }
- /* USER CODE END 0 */
- /**
- * @brief The application entry point.
- * @retval int
- */
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- #define USERPath "0:/"
- BYTE write_buf[] = "\r\n\r\n\
- hello world!\r\n\
- 開發者社區的明天需要大家一同開源共創,期待下一次你的分享,讓我們一同攜手共進,推動人類科技的發展!!!\r\n\
- 創作不易,轉載請注明出處~\r\n\
- 更多文章敬請關注:愛出名的狗腿子\r\n\r\n\
- ";
- BYTE read_buf[1024] = {0};
- UINT num;
- FRESULT ret;
- /* USER CODE END 1 */
- /* MCU Configuration--------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
- /* USER CODE BEGIN Init */
- /* USER CODE END Init */
- /* Configure the system clock */
- SystemClock_Config();
- /* USER CODE BEGIN SysInit */
- /* USER CODE END SysInit */
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_SDIO_SD_Init();
- MX_USART1_UART_Init();
- MX_FATFS_Init();
- /* USER CODE BEGIN 2 */
- /* 掛載文件系統,掛載的時候會完成對應硬件設備(SD卡/SDnand)初始化 */
- ret = f_mount(&SDFatFS, USERPath, 1);
- if (ret != FR_OK) {
- printf("f_mount error!\r\n");
- goto mount_error;
- } else if(ret == FR_NO_FILESYSTEM) { /* 檢測是否存在文件系統,如果沒有則進行格式化 */
- printf("未檢測到FATFS文件系統,執行格式化...\r\n");
- ret = f_mkfs(USERPath, 0, 0);
- if(ret == FR_OK) {
- printf("格式化成功!\r\n");
- f_mount(NULL, USERPath, 1); /* 先取消掛載,后重新掛載 */
- ret = f_mount(&SDFatFS, USERPath, 1);
- } else {
- printf("格式化失敗!\r\n");
- goto mount_error;
- }
- } else {
- printf("f_mount success!\r\n");
- }
- /* 讀寫測試 */
- printf("\r\n ========== write test ==========\r\n");
- ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
- if(ret == FR_OK) {
- printf("open file sucess!\r\n");
- ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
- if(ret == FR_OK) {
- printf("write "%s" success!\r\nwrite len:%d\r\n", write_buf, num);
- } else {
- printf("write error! ret:%d \r\n", ret);
- goto rw_error;
- }
- f_close(&SDFile);
- } else {
- printf("open file error!\r\n");
- goto rw_error;
- }
- printf("\r\n ========== read test ==========\r\n");
- ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ);
- if(ret == FR_OK) {
- printf("open file sucess!\r\n");
- ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num);
- if(ret == FR_OK) {
- printf("read data:"%s"!\r\nread len:%d\r\n", read_buf, num);
- } else {
- printf("read error! ret:%d \r\n", ret);
- goto rw_error;
- }
- } else {
- printf("open file error!\r\n");
- goto rw_error;
- }
- rw_error:
- f_close(&SDFile);
- mount_error:
- f_mount(NULL, USERPath, 1);
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
- /**
- * @brief System Clock Configuration
- * @retval None
- */
- void SystemClock_Config(void)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
- /** Initializes the RCC Oscillators according to the specified parameters
- * in the RCC_OscInitTypeDef structure.
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
- RCC_OscInitStruct.HSIState = RCC_HSI_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
- /** Initializes the CPU, AHB and APB buses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
- {
- Error_Handler();
- }
- }
- /* USER CODE BEGIN 4 */
- /* USER CODE END 4 */
- /**
- * @brief This function is executed in case of error occurrence.
- * @retval None
- */
- void Error_Handler(void)
- {
- /* USER CODE BEGIN Error_Handler_Debug */
- /* User can add his own implementation to report the HAL error return state */
- __disable_irq();
- while (1)
- {
- }
- /* USER CODE END Error_Handler_Debug */
- }
- #ifdef USE_FULL_ASSERT
- /**
- * @brief Reports the name of the source file and the source line number
- * where the assert_param error has occurred.
- * @param file: pointer to the source file name
- * @param line: assert_param error line source number
- * @retval None
- */
- void assert_failed(uint8_t *file, uint32_t line)
- {
- /* USER CODE BEGIN 6 */
- /* User can add his own implementation to report the file name and line number,
- ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
- /* USER CODE END 6 */
- }
- #endif /* USE_FULL_ASSERT */
3.4 配置問題記錄
3.4.1 CubeMx生成代碼bug
測試發現,使用CubeMx當前最新版本:V6.8.0版本,生成代碼會存在以下問題:
- SD卡/SDnand 卡片信息讀取成功,但是讀寫測試失敗
經過仔細分析代碼后發現,出現的問題在 MX_SDIO_SD_Init() 此初始化函數內的配置項錯誤導致,具體分析如下:
- 我們在CubeMx里面配置的時候選擇的是4線寬度模式 SD 4bit Wide bus
- v6.8.0版本CubeMx生成的 MX_SDIO_SD_Init() SD初始化函數內,hsd.Init.BusWide = SDIO_BUS_WIDE_4B;
- 看上去沒有什么問題,配置4線模式,對應的初始化項也使用4線模式,但是不然,我們繼續分析 MX_SDIO_SD_Init() 此初始配置的調用
- MX_SDIO_SD_Init() 此函數在main函數內初始化的時候調用,此函數只配置了 hsd 結構體,并未配置給SDIO硬件寄存器
- 之后調用 SD_Driver.disk_initialize(0); 函數的時候才真正開始進行SDIO外設配置
BSP_SD_Init()
??->HAL_SD_Init()
????->HAL_SD_InitCard()
在 HAL_SD_InitCard() 函數內使用Init結構體配置SDIO外設,總線寬度1bit,時鐘速度<400k,以進行卡片的初始化識別。
??????-> SD_InitCard()
????????-> SDIO_Init(hsd->Instance, hsd->Init)
??????-> SDMMC_CmdBlockLength(hsd->Instance, BLOCKSIZE)
· 在 SD_InitCard() 函數內實現SD卡的初始化識別,之后調用 SDIO_Init() 將 MX_SDIO_SD_Init() 內對 hsd 的配置配置給SDIO外設,此處的作用主要是提升SDIO外設時鐘速率為我們配置的速率;
· v6.8.0版本的代碼此時hsd.Init.BusWide = SDIO_BUS_WIDE_4B; ,因此v6.8.0版本代碼后續SDIO外設使用4線通訊;
· 之后調用 SDMMC_CmdBlockLength() 設置塊大小,由于SDIO外設已切換到4線模式,而SD卡/SDnand此時仍然處于1線模式,因此配置會出錯
?? -> HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B)
根據前面獲取到的SD卡SCR寄存器值,判斷是否支持4線模式,如果支持則發送配置命令通知SD卡/SDnand進入4線模式,之后修改SDIO外設總線寬度為4線模式
6.通過以上分析可知,MX_SDIO_SD_Init() 函數內對 hsd.Init.BusWide = SDIO_BUS_WIDE_4B; 的配置會導致對SD卡塊大小的配置失敗,從而導致后續讀寫時失敗,報錯為塊大小設置失敗!
7.綜上,針對當前最新版本 V6.8.0 版本CubeMx的處理方法是:手動修改此 hsd.Init.BusWide 配置為 SDIO_BUS_WIDE_1B 或更換低版本CubeMx,本人更換V6.6.1版本后無此bug。
3.4.2 SD插入檢測引腳配置
使用CubeMx配置FATFS 選擇 SD Card 之后,有一個配置參數,用來配置SD Card的輸入檢測引腳。如果我們在硬件上有設計SD卡的卡槽插入檢測引腳插入連接到了MCU的IO,則可配置對應IO為輸入模式,并設置對應IO為輸入檢測引腳,比如,我們設置PD12為輸入檢測引腳,則配置如下:
對應代碼如下,輸入檢測 IO 低電平有效!
?
?
如果硬件上,沒有此插入檢測引腳,則可以在CubeMx內不進行配置,只是在生成代碼的時候會提示警報而已,可以不用關心,生成的代碼項會自動屏蔽插入檢測!
?
4. 結束語
- 以上便是本文的全部內容了,歡迎大家評論區留言討論!
- 使用CubeMx雖然能幫助我們快速生成驅動,但是對于SD卡/SD nand的驅動流程,我們還是需要有清晰的認識,推薦閱讀: SD Nand 與 SD卡 SDIO模式應用流程
————————————————
【本文轉載自CSDN,作者: 愛出名的狗腿子】
-
SD卡
+關注
關注
2文章
553瀏覽量
63520 -
FATFS
+關注
關注
0文章
43瀏覽量
18211
發布評論請先 登錄
相關推薦
評論