【前言】
驱动MCU的SD卡,在日常的开发中,最为通用,用于数据的保存,固件升级等等都有很大的作用。这次我花了三天的时间对STM32F769-DISC1进行了驱动的学习,由于我是新手,官方的开发板提供了基于BSP的驱动示例,但是没有STM32CubeMX的工程,所以需要自行移植他的BSP里的驱动,或者使用STM32CubeMX进行配置驱动。STM32CubeMX配置出来,还是需要手工进行调整一些参数才行正常驱动,遇到了很多的坑,特此在这里记录并分享。
【软件环境】
1、操作系统 win11
2、配置工具:STM32CubeMX 6.13.0
3、代码编辑vscode
【配置过程】
1、创建基于STM32F769NIHx的基本工程。
2、打开USART1,选择PA9、PA10为串口用的IO。其他使用默认配置。
3、打开RCC的外部晶振
4、打开SDMMC2,选择SD 4 bits Wide Bus。时钟分频为4分频。
5、确认GPIO与原理图一样。
6、添加DMA设置,添加RX、TX两个传输通道,添加后不用修改这里有参数,保持黙认即可。
7、使能DMA中断,三个中断都需要使能。
8、调整时钟配置,选择HSE为输入源,配置如下图:
这里一定要注间,SDMMC2为48MHz以下的时钟,我就是在这里没有配置好,在坑里爬了2天才找出原因。具体过程可以看我的另一篇帖子:
【STM32F769】调试SD驱动,由于其时钟配置不对引起的错误以及排查记录 https://forum.eepw.com.cn/thread/391294/1
9、打开工程选项,输入项目名称,选择Cmake工程,根据以后自己的应用调节栈大小。
10、生成工程,并使用vscode打开工程。
11、修改sdmmc.c的MX_SDMMC2_SD_Init函数,将最先的初始化需要修数据宽度为1b,要不会选成错误:
12、在usart.c的最后添加printf重定向代码:
14、在stm32f7xx_it.c的最后,添加dma回调函数,并声明全局变量传输标志:
/* USER CODE BEGIN 1 */ extern __IO uint32_t SDWriteStatus; extern __IO uint32_t SDReadStatus ; void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { /* Prevent unused argument(s) compilation warning */ UNUSED(hsd); SDWriteStatus = 1; } void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { UNUSED(hsd); SDReadStatus = 1; } void HAL_SD_ErrorCallback(SD_HandleTypeDef *hsd) { UNUSED(hsd); } void HAL_SD_AbortCallback(SD_HandleTypeDef *hsd) { UNUSED(hsd); } /* USER CODE END 1 */
13、添加测试函数,首先打印卡的信息,然后进行读写测试,代码如下:
#include <stdio.h> #include "sd_test1.h" #include "main.h" extern SD_HandleTypeDef hsd2; #define BLOCK_START_ADDR 0 /* Block start address */ /* BLOCKSIZE = 512 Bytes, defined in stm32f7xx_hal_sd.h */ #define NUM_OF_BLOCKS 50 /* Total number of blocks */ #define BUFFER_WORDS_SIZE ((BLOCKSIZE * NUM_OF_BLOCKS) >> 2) /* Total data size in bytes */ /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ uint32_t aTxBuffer[BUFFER_WORDS_SIZE]; uint32_t aRxBuffer[BUFFER_WORDS_SIZE]; __IO uint32_t SDWriteStatus = 0, SDReadStatus = 0; __IO uint32_t SdmmcTest = 0; /* Private function prototypes -----------------------------------------------*/ static void Fill_Buffer(uint32_t *pBuffer, uint32_t uwBufferLenght, uint32_t uwOffset); static uint8_t Buffercmp(uint32_t *pBuffer1, uint32_t *pBuffer2, uint16_t BufferLength); /* Private functions ---------------------------------------------------------*/ uint8_t BSP_SD_GetCardState(void) { return ((HAL_SD_GetCardState(&hsd2) == HAL_SD_CARD_TRANSFER) ? SD_TRANSFER_OK : SD_TRANSFER_BUSY); } void sd_test1(void) { uint8_t SD_state = MSD_OK; HAL_SD_CardInfoTypeDef SDCardInfo; // 获取SD卡信息 if (HAL_SD_GetCardInfo(&hsd2, &SDCardInfo) != HAL_OK) { // 获取信息失败处理 printf("Failed to get SD card information!\n"); while (1) ; } // 打印SD卡信息 printf("SD Card Information:\n"); printf("Card Type: %ld\n", SDCardInfo.CardType); printf("Card Version: %ld\n", SDCardInfo.CardVersion); printf("Card Capacity: %ld\n", SDCardInfo.BlockSize); printf("Card Block Size: %ld\n", SDCardInfo.BlockNbr); printf("Card Block Nbr: %ld\n", SDCardInfo.Class); printf("Card LogBlockSize: %ld\n", SDCardInfo.LogBlockSize); SD_state = HAL_SD_Erase(&hsd2, BLOCK_START_ADDR, (BLOCKSIZE * NUM_OF_BLOCKS)) ; while (BSP_SD_GetCardState() != SD_TRANSFER_OK) { } if (SD_state
【实验效果】
下载到开发板后,通过串口打印,日志如下:
【经验总结】
1、配置时钟需要特别注意 PLLQ要配置48MHz以下。
2、修改初始化为1Bit。
3、需要添加dma的中断回调函数来获取传输完成后的标志位。
总之,厉时3天多,终于把坑填上了。