STM32CubeMX教程27 SDIO - 读写SD卡

1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0

keil µVision5 IDE(MDK-Arm

ST-LINK/V2驱动

逻辑分析仪nanoDLA

野火DAP仿真器

XCOM V2.6串口助手

2、实验目标

使用STM32CubeMX软件配置STM32F407开发板SDIO读写4线SD卡,实现轮询方式读写SD卡、以中断方式读取SD卡和以DMA方式读取SD卡

3、轮询方式读取SD卡流程

3.0、前提知识

安全数码卡(Secure Digital Memory Card),简称SD卡,是嵌入式设备上常用的一种存储介质,通常可以将SD卡分为标准SD卡、miniSD卡和microSD卡(TF卡)三种类型,每种卡形状大小不一,除标准SD卡卡身上拥有一个写保护开关外,其他的功能三张卡一致,如今miniSD卡正逐渐被microSD卡所取代,如下图所示为三种不同类型SD卡形状 (注释1)

按照SD卡容量大小的不同可以将其分为SD、SDHC、SDXC等型号,按照SD卡读写机制速度的不同又可以将其分为Standard、High-speed、UHS-I等型号,具体如下表所示

STM32F407提供了一个SDIO接口可以直接通过HAL库来驱动1/4位总线宽度的SD卡或1/4/8位总线宽度的多媒体卡,其完全兼容SD卡规范版本2.0,但只支持高速SD卡,也即与SD卡进行数据传输最大速度为25MHz

SDIO由APB2接口和SDIO适配器两部分组成,SDIO适配器提供了驱动SD/MMC卡的全部功能,APB2接口则可以访问SDIO适配器寄存器在适当时候向内核发起中断/DMA请求

SDIO适配器由48MHz的SDIOCLK驱动,根据SDIOCLK时钟频率、 SDIO Clock divider bypass 参数和 SDIOCLK clock divide factor 参数就可以确定与SD卡通信时SDIO_CLK的时钟频率,当时钟分频器旁路使能时,SDIO_CLK=SDIOCLK;当时钟分频器旁路不使能时,SDIO_CLK=SDIOCLK / (2+时钟分频因子);

根据上面的描述,由于STM32F407的SDIO只支持高速SD卡,因此时钟分频器旁路常常不使能,这样当时钟分频因子为0时,SDIO_CLK则达到最大速度48MHz / 2 = 24Mhz,但在实际的使用中往往稍微降低该时钟频率,否则可能会出现读写SD卡失败的现象

另外值得提醒的是SD卡初始化的时候应该以不超过400KHz的速率,1位总线宽度进行初始化,否则将初始化失败

如下图所示为STM32F407内部的SDIO接口结构框图 (注释2)

笔者使用的开发板上SD卡槽设计为了4位总线宽度,在硬件设计时需要注意MCU与SD卡通信的1/4根数据线SDIO_D0/1/2/3和命令线SDIO_CMD均需外部上拉,硬件原理图如下图所示

3.1、CubeMX相关配置

3.1.0、工程基本配置

打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示

开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示

详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立

3.1.1、时钟树配置

当在STM32CubeMX中启用SDIO功能后,时钟树中48MHz时钟便可以进行调节,该时钟一般如其名字一样配置为48MHz即可,也即将Main PLL(主锁相环)的Q参数调节为7即可,其他HCLK、PCLK1和PCLK2时钟仍然设置为STM32F407能达到的最高时钟频率,具体如下图所示

3.1.2、外设参数配置

本实验需要初始化开发板上WK_UP、KEY2、KEY1和KEY0用户按键,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应

本实验需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信

单击Pinout & Configuration页面左边功能分类栏目Connectivity/SDIO,将其模式选择为4位宽总线SD卡

Clock transition on which the bit capture is made (时钟跳变沿捕获数据配置):数据捕获边沿设置,可设置为上升沿/下降沿

SDIO Clock divider bypass (时钟分频器旁路使能):使能该参数时,SDIO_CLK=SDIOCLK;否则SDIO_CLK频率由时钟分频因子决定

SDIO Clock output enable when the bus is idle (空闲模式时钟输出使能):节能模式,此实验不使能

SDIO hardware flow control (硬件流控):设置是否使能SDIO的硬件流控,此处不使能

SDIOCLK clock divide factor (时钟分频因子):当不使能时钟分频器旁路时,SDIO_CLK=SDIOCLK / (2+时钟分频因子)

SDIO驱动4位宽总线SD卡的参数配置大多按照默认参数配置即可,但是要注意SD卡读写频率过高可能会导致读写失败,因此这里设置SD_CLK频率为8MHz,另外需要注意默认的SDIO复用引脚和开发板上的实际控制SD的引脚是否一致,具体配置如下图所示

3.1.3、外设中断配置

轮询方式读写SD卡无需配置中断

3.2、生成代码

3.2.0、配置Project Manager页面

单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示

详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节

3.2.1、外设初始化调用流程

在main.c文件main()函数中调用MX_SDIO_SD_Init()对SDIO参数配置,并调用HAL_SD_Init()函数对SD卡初始化,最后将SD卡切换到4位宽总线模式

在stm32f4xx_hal_sd.c文件HAL_SD_Init()中调用HAL_SD_MspInit()函数对SDIO时钟使能和所使用到的引脚功能复用,如果配置了中断或DMA,该函数中还会相应的出现NVIC/DMA相关配置,最后在真正的SD卡初始化函数HAL_SD_InitCard()中对SD卡初始化完毕

具体外设初始化函数调用流程如下图所示

初始化配置中时钟分频因子为4,SD_CLK=8MHz,为什么SD卡还可以初始化成功?

这里读者需要搞清楚真正对SD卡初始化时使用的参数配置是不是我们设置的参数,上面提到真正的SD卡初始化函数为HAL_SD_InitCard(),进入该函数发现实际初始化SD卡时用到的并不是用户配置的参数,而是使用的默认初始化参数,这里时钟分频因子被设置为了0x76,也即118,根据上面提到的公式计算可知48MHz / (118 + 2) = 400KHz,满足SD卡的初始化频率,具体如下图所示

3.2.2、外设中断调用流程

轮询方式读写SD卡无配置中断

3.2.3、添加其他必要代码

笔者使用的STM32CubeMX版本为6.10.0,在生成的SDIO初始化函数MX_SDIO_SD_Init()中需要将参数配置中的SD卡数据总线宽度从默认的4位手动修改为1位(STM32CubeMX软件BUG?),在SD卡初始化时应该以不超过400KHz的速率,1位总线宽度进行初始化,如果不修改这里SD卡将无法成功初始化,如下图所示

在sdio.c中添加SD卡读、写、擦除和输出SD卡信息测试函数

/*显示SD卡的信息*/
void SDCard_ShowInfo(void)
{
	//SD卡信息结构体变量
	HAL_SD_CardInfoTypeDef cardInfo;  
	HAL_StatusTypeDef res = HAL_SD_GetCardInfo(&hsd, &cardInfo);

	if(res!=HAL_OK)
	{
		printf("HAL_SD_GetCardInfo() error\r\n");
		return;
	}

	printf("\r\n*** HAL_SD_GetCardInfo() info ***\r\n");
	printf("Card Type= %d\r\n", cardInfo.CardType);
	printf("Card Version= %d\r\n", cardInfo.CardVersion);
	printf("Card Class= %d\r\n", cardInfo.Class);
	printf("Relative Card Address= %d\r\n", cardInfo.RelCardAdd);
	printf("Block Count= %d\r\n", cardInfo.BlockNbr);
	printf("Block Size(Bytes)= %d\r\n", cardInfo.BlockSize);
	printf("LogiBlockCount= %d\r\n", cardInfo.LogBlockNbr);
	printf("LogiBlockSize(Bytes)= %d\r\n", cardInfo.LogBlockSize);
	printf("SD Card Capacity(MB)= %d\r\n", cardInfo.BlockNbr>>1>>10);
}

//获取SD卡当前状态
void SDCard_ShowStatus(void)
{
	//SD卡状态结构体变量
	HAL_SD_CardStatusTypeDef cardStatus;
	HAL_StatusTypeDef res = HAL_SD_GetCardStatus(&hsd, &cardStatus);

	if(res!=HAL_OK)
	{
		printf("HAL_SD_GetCardStatus() error\r\n");
		return;
	}

	printf("\r\n*** HAL_SD_GetCardStatus() info ***\r\n");
	printf("DataBusWidth= %d\r\n", cardStatus.DataBusWidth);
	printf("CardType= %d\r\n", cardStatus.CardType);
	printf("SpeedClass= %d\r\n", cardStatus.SpeedClass);
	printf("AllocationUnitSize= %d\r\n", cardStatus.AllocationUnitSize);
	printf("EraseSize= %d\r\n", cardStatus.EraseSize);
	printf("EraseTimeout= %d\r\n", cardStatus.EraseTimeout);
}

/*SD卡擦除测试*/
void SDCard_EraseBlocks(void)
{
	uint32_t BlockAddrStart=0;
	uint32_t BlockAddrEnd=10;
	
	printf("\r\n*** Erasing blocks ***\r\n");

	if(HAL_SD_Erase(&hsd, BlockAddrStart, BlockAddrEnd)==HAL_OK)
		printf("Erasing blocks,OK\r\n");
	else
		printf("Erasing blocks,fail\r\n");

	HAL_SD_CardStateTypeDef cardState=HAL_SD_GetCardState(&hsd);
	printf("GetCardState()= %d\r\n", cardState);

	while(cardState != HAL_SD_CARD_TRANSFER)
	{
		HAL_Delay(1);
		cardState=HAL_SD_GetCardState(&hsd);
	}
	printf("Blocks 0-10 is erased.\r\n");
}

/*SD卡写入测试函数*/
void SDCard_TestWrite(void)
{
	printf("\r\n*** Writing blocks ***\r\n");
	
	// BLOCKSIZE为512,在stm32f4xx_hal_sd.h中被定义
	uint8_t pData[BLOCKSIZE]="Hello, welcome to UPC\0";  
	uint32_t BlockAddr=5; 	
	uint32_t BlockCount=1; 
	uint32_t TimeOut=1000;	

	if(HAL_SD_WriteBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut) == HAL_OK)
	{
		printf("Write to block 5, OK\r\n");
		printf("The string is: %s\r\n", pData);
	}
	else
	{
		printf("Write to block 5, fail ***\r\n");
		return;
	}

	for(uint16_t i=0;i<BLOCKSIZE; i++)
		pData[i]=i; 	

	BlockAddr=6;
	if(HAL_SD_WriteBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut) == HAL_OK) 
	{
		printf("Write block 6, OK\r\n");
		printf("Data in [10:15] is: ");
		for (uint16_t j=11; j<=15;j++)
		{
			printf("%d,", pData[j]);
		}
		printf("\r\n");
	}
	else
		printf("Write to block 6, fail ***\r\n");
}

/*SD卡读取测试函数*/
void SDCard_TestRead(void)	
{
	printf("\r\n*** Reading blocks ***\r\n");

	uint8_t pData[BLOCKSIZE];
	uint32_t BlockAddr=5;
	uint32_t BlockCount=1;
	uint32_t TimeOut=1000;

	if(HAL_SD_ReadBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut) == HAL_OK)
	//if(HAL_SD_ReadBlocks_IT(&hsd,pData,BlockAddr,BlockCount) == HAL_OK)
	{
		printf("Read block 5, OK\r\n");
		printf("The string is: %s\r\n", pData);
	}
	else
	{
		printf("Read block 5, fail ***\r\n");
		return;
	}

	BlockAddr=6;
	if(HAL_SD_ReadBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut)== HAL_OK)
	//if(HAL_SD_ReadBlocks_IT(&hsd,pData,BlockAddr,BlockCount) == HAL_OK)
	{
		printf("Read block 6, OK\r\n");
		printf("Data in [10:15] is: ");

		for (uint16_t j=11; j<=15;j++)
		{
			printf("%d,", pData[j]);
		}
		printf("\r\n");
	}
}

在sdio.h中声明定义的这些测试函数

/*在sdio.h中声明*/
void SDCard_TestRead(void);
void SDCard_TestWrite(void);
void SDCard_ShowInfo(void);
void SDCard_EraseBlocks(void);

在main.c文件主循环中添加按键逻辑控制程序,WK_UP按键按下输出SD卡信息,KEY2按键按下擦除SD卡块0-10,KEY1按键按下测试SD卡写功能,KEY0按键按下测试SD卡读功能

具体源代码如下所示

/*WK_UP按键按下*/
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
{
	HAL_Delay(50);
	if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
	{
		SDCard_ShowInfo();
		while(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin));
	}
}

/*KEY2按键按下*/
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
	HAL_Delay(50);
	if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
	{
		SDCard_EraseBlocks();
		while(!HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin));
	}
}

/*KEY1按键按下*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
	HAL_Delay(50);
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
	{
		SDCard_TestWrite();
		while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin));
	}
}

/*KEY0按键按下*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
	HAL_Delay(50);
	if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
	{
		SDCard_TestRead();
		while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin));
	}
}

4、烧录验证

烧录程序,开发板复位后按下WK_UP按键会输出SD卡信息,按下KEY2按键会擦除SD卡的块0-10数据,按下KEY0按键会读取SD卡块5和块6的数据,按下KEY1按键会写入一段字符串到SD卡块5,写入块6从1-256整形数字,具体串口输出信息如下图所示

5、中断方式读取SD卡流程简述

5.1、CubeMX相关配置

工程、时钟树、外设参数等配置与轮询方式读取SD卡一致,中断方式读取SD卡只需要在CubeMX软件中启动SDIO的全局中断

在Pinout & Configuration页面左边System Core/NVIC中勾选SDIO全局中断,然后选择合适的中断优先级即可,如下图所示

5.2、生成代码

修改STM32CubeMX工程重新生成工程代码后,读者应注意再次手动修改MX_SDIO_SD_Init()函数中SD卡数据总线宽度从默认的4位手动修改为1位

在sdio.c中增加以中断方式读写SD卡的测试函数,具体代码如下所示

/*SD卡中断写入测试函数*/
void SDCard_TestWrite_IT(void)
{
	printf("\r\n*** IT Writing blocks ***\r\n");
	uint32_t BlockCount=1; 
	uint16_t BlockAddr=5;
	
	HAL_SD_WriteBlocks_IT(&hsd, TX, BlockAddr, BlockCount);
}

/*SD卡中断读取测试函数*/
void SDCard_TestRead_IT(void)	
{
	printf("\r\n*** IT Reading blocks ***\r\n");
	uint32_t BlockCount=1;
	uint16_t BlockAddr=5;
	
	HAL_SD_ReadBlocks_IT(&hsd, RX, BlockAddr, BlockCount);
}

在sdio.c中新增加SD卡Tx/Rx传输完成回调函数HAL_SD_TxCpltCallback()和HAL_SD_RxCpltCallback(),具体代码如下所示

/*SD Tx传输完成回调*/
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
	printf("IT Write to block 5, OK\r\n");
	printf("The string is: %s\r\n", TX);
}

/*SD Rx传输完成回调*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
	printf("IT Read block 5, OK\r\n");
	printf("The string is: %s\r\n", RX);
}

在sdio.c中定义全部变量发送缓存数组TX和接收缓存数组RX,并在sdio.h中声明修改后的中断方式的SD卡写入测试函数和SD卡读取测试函数,源代码如下

/*sdio.c中定义的发送、接收缓存数组*/
uint8_t TX[BLOCKSIZE] = "Hello, welcome to UPC\0";  
uint8_t RX[BLOCKSIZE];  

/*sdio.h中对函数声明*/
void SDCard_TestRead_IT(void);
void SDCard_TestWrite_IT(void);

最后在main.c文件主循环中实现与轮询读写SD时一致的按键逻辑程序,并用修改后的以中断方式读写SD卡的函数替换以轮询方式读写SD卡的函数即可

5.3、烧录验证

烧录程序,开发板复位后按下WK_UP按键会输出SD卡信息,按下KEY2按键会擦除SD卡的块0-10数据,与轮询方式读写SD卡时现象一致

按下KEY0按键以中断方式读取SD卡的块5数据,读取完成后会进入Rx传输完成回调中,在该回调函数中会从串口输出读取到的SD卡块5的数据

按下KEY1按键会以中断方式写入一段字符串到SD卡块5,写入完成后会进入Tx传输完成回调中,在该回调函数中会从串口输出写入到SD卡块5中的数据

具体串口输出信息如下图所示

6、DMA方式读取SD卡流程简述

6.1、CubeMX相关配置

工程、时钟树、外设参数等配置与轮询方式读取SD卡一致,以DMA方式读取SD卡只需要在CubeMX软件中增加SDIO的DMA请求即可

在Pinout & Configuration页面单击Connectivity/SDIO页面,在Configuration配置页面中点击DMA Settings选项卡对SDIO的DMA进行配置,单击ADD增加SDIO的RX/TX两个DMA请求,SDIO的两个DMA请求除了内存地址递增可以设置外,其他的包括Mode、Use Fifo、Data Width和Burst Size等参数都不可以设置

对DMA参数不理解的可以阅读”STM32CubeMX教程12 DMA 直接内存读取“实验,SDIO的具体DMA配置参数如下图所示

在System Core/NVIC中勾选SDIO全局中断、DMA2 stream3 全局中断和 DMA2 stream6 全局中断,然后选择合适的中断优先级即可,如下图所示

6.2、生成代码

修改STM32CubeMX工程重新生成工程代码后,读者应注意再次手动修改MX_SDIO_SD_Init()函数中SD卡数据总线宽度从默认的4位手动修改为1位

在sdio.c中增加以DMA方式读写SD卡的测试函数,具体代码如下所示

/*SD卡DMA写入测试函数*/
void SDCard_TestWrite_DMA(void)
{
	printf("\r\n*** DMA Writing blocks ***\r\n");
	uint32_t BlockCount=1; 
	uint16_t BlockAddr=6;
	
	for(uint16_t i=0;i<BLOCKSIZE; i++)
		TX[i]=i; 
	
	HAL_SD_WriteBlocks_DMA(&hsd, TX, BlockAddr, BlockCount);
}

/*SD卡DMA读取测试函数*/
void SDCard_TestRead_DMA(void)	
{
	printf("\r\n*** DMA Reading blocks ***\r\n");
	uint32_t BlockCount=1;
	uint16_t BlockAddr=6;
	
	HAL_SD_ReadBlocks_DMA(&hsd, RX, BlockAddr, BlockCount);
}

在sdio.h中对增加的函数声明

/*sdio.h中对函数声明*/
void SDCard_TestWrite_DMA(void);
void SDCard_TestRead_DMA(void);

DMA的回调函数使用的是外设的中断回调函数

当启用了SDIO TX DMA请求和SDIO全局中断,并以 HAL_SD_WriteBlocks_DMA() 写入SD卡块数据完成之后,会调用传输完成回调 HAL_SD_TxCpltCallback()

当启用了SDIO RX DMA请求和SDIO全局中断,并以 HAL_SD_ReadBlocks_DMA() 从SD卡块读取数据完毕之后,会调用读取完成回调函数 HAL_SD_RxCpltCallback()

故直接重新实现HAL_SD_RxCpltCallback/HAL_SD_TxCpltCallback两个函数即可,源代码如下所示

/*DMA Tx传输完成回调*/
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
		printf("DMA Write to block 6, OK\r\n");
		printf("Data in [10:15] is: ");

		for (uint16_t j=10; j<=15;j++)
		{
			printf("%d,", TX[j]);
		}
		printf("\r\n");
}

/*DMA Rx传输完成回调*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
		printf("DMA Read block 6, OK\r\n");
		printf("Data in [10:15] is: ");

		for (uint16_t j=10; j<=15;j++)
		{
			printf("%d,", RX[j]);
		}
		printf("\r\n");
}

最后在main.c文件主循环中实现与轮询读写SD时一致的按键逻辑程序,并用修改后的以DMA方式读写SD卡的函数替换以中断方式读写SD卡的函数即可

6.3、实验现象

烧录程序,开发板复位后按下WK_UP按键会输出SD卡信息,按下KEY2按键会擦除SD卡的块0-10数据,与轮询方式读写SD卡时现象一致

按下KEY0按键以DMA的方式读取SD卡块6数据,读取完成后会进入Rx传输完成回调中,在该回调函数中会从串口输出读取到的SD卡块6的数据

按下KEY1按键会以DMA的方式写入1-256的数字到SD卡块6(一个字节写入一个数字),写入完成后会进入Tx传输完成回调中,在该回调函数中会从串口输出写入到SD卡块6中的数据

具体串口输出信息如下图所示

7、常用函数

/*读块*/
HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

/*写块*/
HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

/*擦除块*/
HAL_StatusTypeDef HAL_SD_Erase(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd)

/*获取SD卡信息*/
HAL_StatusTypeDef HAL_SD_GetCardInfo(SD_HandleTypeDef *hsd, HAL_SD_CardInfoTypeDef *pCardInfo)

/*获取SD卡状态*/
HAL_StatusTypeDef HAL_SD_GetCardStatus(SD_HandleTypeDef *hsd, HAL_SD_CardStatusTypeDef *pStatus)

/*以中断方式读块*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_IT(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

/*以中断方式写块*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_IT(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

/*以DMA方式读块*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

/*以DMA方式写块*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

/*SD卡Tx传输完成回调*/
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)

/*SD卡Rx传输完成回调*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)

8、注释详解

注释1:图片来源自 维基百科-SD卡

注释2:图片来源自 STM32F407中文参考手册 RM009

参考资料

STM32Cube高效开发教程(高级篇)

热门相关:布衣官道   扑倒老公大人:龙总,我爱你!   暖君   大物姐夫,奶大小姨子   性女迷梦