STM32F4读写SD卡:填一填ST官方HAL库的坑
使用STM32读写SD卡在低功耗存储中的应用是比较常见的但是网上大多数资料都是基于标准库或者基于寄存器的开发。随着嵌入式设备越来越复杂使用HAL库能够大大降低开发者的学习成本从而提高开发效率。近年来ST官方主推以STM32CubeMx为核心代码初始化工具给开发者节省了配置硬件要花费的精力。然而由于HAL是一个硬件抽象层的库它将不同系列的芯片硬件封装成了统一的接口但是无法保证能够涵盖所有开发情况。在使用STM32F4开发SD卡读写功能的时候我发现ST官方提供的HAL存在一些严重Bug无法直接使用。本文就来填一填ST官方留下的坑。硬件准备1、STM32F407VET6开发板带SD卡槽2、1G逻辑分析仪软件准备STM32CubeMX (本项目使用6.12.1版本)IAR 9.50.2本项目主要使用IAR相比于Keil编译速度更快生成的文件体积更小若需要Keil版本的代码可通过STM32CubeMX生成对应版本操作步骤使用STM32CubeMx生成代码1、配置RCC2、配置调试器3、配置SDIO注意这里要配置DMA和SDIO全局中断其它默认4、添加一个串口用于调试5、配置时钟树6、生成代码修改代码1、重定向printf函数输出到串口用于调试#includestdio.hintfputc(intch,FILE*f){HAL_UART_Transmit(huart1,(uint8_t*)ch,1,HAL_MAX_DELAY);returnch;}2、主函数如下intmain(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_Handler4、打开Call Stack发现错误在MX_SDIO_SD_Init();这个函数5、于是继续跟踪发现这里出错了查找资料后发现这里生成的代码是有问题的ST官方代码的第一个大坑6、将代码改为如下后重新运行voidMX_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.InstanceSDIO;hsd.Init.ClockEdgeSDIO_CLOCK_EDGE_RISING;hsd.Init.ClockBypassSDIO_CLOCK_BYPASS_DISABLE;hsd.Init.ClockPowerSaveSDIO_CLOCK_POWER_SAVE_DISABLE;hsd.Init.BusWideSDIO_BUS_WIDE_1B;// 这里只能是使用SDIO的1Bit总线模式进行初始化hsd.Init.HardwareFlowControlSDIO_HARDWARE_FLOW_CONTROL_DISABLE;hsd.Init.ClockDiv0;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 */}可以看到输出说明初始化通过CubeMx1.16.1修复了这个bug7、使用DMA读写SD卡这里提一点由于DMA和SDIO模块是分开的因此当DMA写入完成之后SDIO的总线可能还处于正忙状态此时若强行写入SDIO只能导致失败。如果手动添加延时可以一定程度改善但是无法完全解决这个问题。使用逻辑分析仪调试之后发现只有当SDIO_CMD和SDIO_D0都空闲高电平的时候调用DMA写入才不会失败因此有了如下的补丁代码。// 将数据通过DMA写入if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)1)// 补丁只有当SDIO总线空闲的时候才能够发起写入否则出错{HAL_SD_WriteBlocks_DMA(hsd,buff_w,0,DMA_NUM_BLOCKS_TO_WRITE);}可通过逻辑分析仪观察波形可以看到只有当SDIO_D0引脚为高的时候才能够发起SDIO通信否则将不会触发DMA发送完成中断后续数据将无法继续传输这个坑目前还没在国内网站上看到有好的解决方法。。。测试SD卡读写/* 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 ------------------------------------------------------------------*/#includemain.h#includedma.h#includesdio.h#includeusart.h#includegpio.h/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#includestdio.h#includestring.h/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*//* USER CODE BEGIN PD */#defineBLOCK_SIZE512// 一个块的字节数节#defineDMA_NUM_BLOCKS_TO_WRITE64// 每一次DMA写入块的数量#defineDMA_NUM_BLOCKS_TO_READ64// 每一次DMA读出块的数量#defineBUFFER_SIZE_WDMA_NUM_BLOCKS_TO_WRITE*BLOCK_SIZE// 写缓冲区大小#defineBUFFER_SIZE_RDMA_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_tbuff_w[BUFFER_SIZE_W];uint8_tbuff_r[BUFFER_SIZE_R];uint8_tsdio_write_done0;uint8_tsdio_read_done0;/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/voidSystemClock_Config(void);/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */// 重定向printf函数输出到串口intfputc(intch,FILE*f){HAL_UART_Transmit(huart1,(uint8_t*)ch,1,HAL_MAX_DELAY);returnch;}voidHAL_SD_TxCpltCallback(SD_HandleTypeDef*hsd){sdio_write_done1;}voidHAL_SD_RxCpltCallback(SD_HandleTypeDef*hsd){sdio_read_done1;}/* USER CODE END 0 *//** * brief The application entry point. * retval int */intmain(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_ti0;isizeof(buff_w);i){buff_w[i]i;}// 写入SD卡printf(正在写入数据\n);// 将数据通过DMA写入if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)1)// 补丁只有当SDIO总线空闲的时候才能够发起写入否则出错{HAL_SD_WriteBlocks_DMA(hsd,buff_w,0,DMA_NUM_BLOCKS_TO_WRITE);}while(sdio_write_done0);printf(数据写入完成\n);// 读取SD卡数据并且通过串口输出printf(正在读取数据\n);// 将数据读出到buff_r中if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)1HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_12)1){HAL_SD_ReadBlocks_DMA(hsd,buff_r,0,DMA_NUM_BLOCKS_TO_READ);}while(sdio_read_done0);if(0memcmp(buff_w,buff_r,sizeof(buff_r))){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 */}/** * brief System Clock Configuration * retval None */voidSystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct{0};RCC_ClkInitTypeDef RCC_ClkInitStruct{0};/** Configure the main internal regulator output voltage */__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);/** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */RCC_OscInitStruct.OscillatorTypeRCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIStateRCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValueRCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.PLL.PLLStateRCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSourceRCC_PLLSOURCE_HSI;RCC_OscInitStruct.PLL.PLLM8;RCC_OscInitStruct.PLL.PLLN168;RCC_OscInitStruct.PLL.PLLPRCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ7;if(HAL_RCC_OscConfig(RCC_OscInitStruct)!HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks */RCC_ClkInitStruct.ClockTypeRCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSourceRCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDividerRCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDividerRCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDividerRCC_HCLK_DIV2;if(HAL_RCC_ClockConfig(RCC_ClkInitStruct,FLASH_LATENCY_5)!HAL_OK){Error_Handler();}}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//** * brief This function is executed in case of error occurrence. * retval None */voidError_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 */}#ifdefUSE_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 */voidassert_failed(uint8_t*file,uint32_tline){/* 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 */结果如下初始化完毕 正在生成测试数据 正在写入数据 数据写入完成 正在读取数据 数据是一致的 写入的数据如下 读出的数据如下总结ST官方的代码有3大坑1、SDIO初始化的坑必须要使用1bit总线的SDIO来初始化SD卡否则会导致初始化失败2、采用轮询方式或者中断方式读写SDIO有问题这里建议采用DMA进行读写3、使用DMA读写SD卡的时候需要实现检查当前SDIO是空闲的否则会出错代码https://github.com/dwgan/STM32F407_SDIO
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2472948.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!