# STM32F407_SDIO **Repository Path**: x-itg/STM32F407_SDIO ## Basic Information - **Project Name**: STM32F407_SDIO - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-25 - **Last Updated**: 2025-04-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 使用STM32读写SD卡在低功耗存储中的应用是比较常见的,但是网上大多数资料都是基于标准库或者基于寄存器的开发。随着嵌入式设备越来越复杂,使用HAL库能够大大降低开发者的学习成本,从而提高开发效率。近年来,ST官方主推以STM32CubeMx为核心代码初始化工具,给开发者节省了配置硬件要花费的精力。 然而,由于HAL是一个硬件抽象层的库,它将不同系列的芯片硬件封装成了统一的接口,但是无法保证能够涵盖所有开发情况。在使用STM32F4开发SD卡读写功能的时候,我发现ST官方提供的HAL存在一些严重Bug,无法直接使用。本文就来填一填ST官方留下的坑。 # 硬件准备 1、STM32F407VET6开发板,带SD卡槽 2、1G逻辑分析仪 # 软件准备 [STM32CubeMX](https://www.st.com.cn/zh/development-tools/stm32cubemx.html) (本项目使用6.12.1版本) [IAR 9.50.2](https://pan.baidu.com/s/1OK0k6JNU_-GRZYnANJ5B-g?pwd=wata)(本项目主要使用IAR,相比于Keil编译速度更快,生成的文件体积更小,若需要Keil版本的代码,可通过STM32CubeMX生成对应版本) # 操作步骤 ## 使用STM32CubeMx生成代码 1、配置RCC ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/60f409cebb3043a2ae6b8c6edf29a171.png) 2、配置调试器 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/403f762cb4df4dbca921acb03616584f.png) 3、配置SDIO,注意这里要配置DMA和SDIO全局中断,其它默认 ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121903.png) ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121120.png) 4、添加一个串口用于调试 ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121909.png) 5、配置时钟树 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c06c3dc7ad5047e8a29e450c04b5d51b.png) 6、生成代码 ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121403.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f2c41de7bdef4cd79894938262b2a523.png) ## 修改代码 1、重定向printf函数输出到串口,用于调试 ```c #include int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } ``` 2、主函数如下 ```c int main(void) { /* USER CODE BEGIN 1 */ /* 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_DMA_Init(); MX_SDIO_SD_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ setvbuf(stdout, NULL, _IONBF, 0); printf("初始化完毕\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } ``` 3、编译下载发现无法输出预期,于是开始了Debug,发现跳转到了Error_Handler ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121499.png) 4、打开Call Stack,发现错误在MX_SDIO_SD_Init();这个函数 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ddde6e2ed59a41caa8af2f1c445b37f7.png) 5、于是继续跟踪,发现这里出错了,查找资料后发现这里生成的代码是有问题的(ST官方代码的第一个大坑) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/af76662ebe744b43b8571162bfebef5f.png) 6、将代码改为如下后重新运行 ```c void MX_SDIO_SD_Init(void) { /* USER CODE BEGIN SDIO_Init 0 */ /* USER CODE END SDIO_Init 0 */ /* USER CODE BEGIN SDIO_Init 1 */ /* USER CODE END SDIO_Init 1 */ hsd.Instance = SDIO; hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; hsd.Init.BusWide = SDIO_BUS_WIDE_1B; // 这里只能是使用SDIO的1Bit总线模式进行初始化 hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; hsd.Init.ClockDiv = 0; if (HAL_SD_Init(&hsd) != HAL_OK) { Error_Handler(); } if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SDIO_Init 2 */ /* USER CODE END SDIO_Init 2 */ } ``` 可以看到输出,说明初始化通过 ![在这里插入图片描述](https://dwgan.top/PicGo/img/202410210121346.png) 7、使用DMA读写SD卡,这里提一点,由于DMA和SDIO模块是分开的,因此当DMA写入完成之后,SDIO的总线可能还处于正忙状态,此时若强行写入SDIO只能导致失败。如果手动添加延时可以一定程度改善,但是无法完全解决这个问题。使用逻辑分析仪调试之后发现只有当SDIO_CMD和SDIO_D0都空闲(高电平)的时候,调用DMA写入才不会失败,因此有了如下的补丁代码。 ```c // 将数据通过DMA写入 if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_8) == 1 && HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_12) == 1) // 补丁,只有当SDIO总线空闲的时候才能够发起写入,否则出错 { HAL_SD_WriteBlocks_DMA(&hsd, buff_w, 0, DMA_NUM_BLOCKS_TO_WRITE); } ``` ## 测试SD卡读写 ```c /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2024 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 "dma.h" #include "sdio.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include #include /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define BLOCK_SIZE 512 // 一个块的字节数节 #define DMA_NUM_BLOCKS_TO_WRITE 64 // 每一次DMA写入块的数量 #define DMA_NUM_BLOCKS_TO_READ 64 // 每一次DMA读出块的数量 #define BUFFER_SIZE_W DMA_NUM_BLOCKS_TO_WRITE*BLOCK_SIZE // 写缓冲区大小 #define BUFFER_SIZE_R DMA_NUM_BLOCKS_TO_READ*BLOCK_SIZE // 读缓冲区大小 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t buff_w[BUFFER_SIZE_W]; uint8_t buff_r[BUFFER_SIZE_R]; uint8_t sdio_write_done=0; uint8_t sdio_read_done=0; /* 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 */ // 重定向printf函数输出到串口 int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { sdio_write_done = 1; } void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { sdio_read_done = 1; } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* 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_DMA_Init(); MX_SDIO_SD_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ setvbuf(stdout, NULL, _IONBF, 0); printf("初始化完毕\n"); // 生成测试数据 printf("正在生成测试数据\n"); for (uint32_t i=0; i https://github.com/dwgan/STM32F407_SDIO