STM32CubeMX教程27 SDIO - 读写SD卡

目录

1、准备材料

2、实验目标

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

3.0、前提知识

3.1、CubeMX相关配置

3.1.0、工程基本配置

3.1.1、时钟树配置

3.1.2、外设参数配置

3.1.3、外设中断配置

3.2、生成代码

3.2.0、配置Project Manager页面

3.2.1、外设初始化调用流程

3.2.2、外设中断调用流程

3.2.3、添加其他必要代码

4、烧录验证

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

5.1、CubeMX相关配置

5.2、生成代码

5.3、烧录验证

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

6.1、CubeMX相关配置

6.2、生成代码

6.3、实验现象

7、常用函数

8、注释详解

参考资料


1、准备材料

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

STM32CubeMX软件(Version 6.10.0

keil µVision5 IDE(MDK-Arm

ST-LINK/V2驱动

野火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");elseprintf("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");}elseprintf("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高效开发教程(高级篇)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/651241.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

现货黄金做日内交易和波段交易有何差异?

在现货黄金投资中&#xff0c;日内交易和波段交易都是投资者常用的手段。但投资者其实搞不懂两者有何区别&#xff0c;有时甚至不清楚自己做的是日内交易还是波段交易&#xff0c;下面我们就来讨论一下这两种交易方法的异同。 两者的区别主要是在持仓的时间上。日内交易顾名思义…

Python中的`__all__`魔法函数使用详解

概要 Python是一门灵活而强大的编程语言&#xff0c;提供了各种机制来控制模块的导入和访问。其中&#xff0c;__all__魔法函数是一种用于限制模块导入的机制&#xff0c;可以明确指定哪些变量、函数或类可以被导入。本文将深入探讨__all__的作用、用法以及示例&#xff0c;以…

C++:类和对象(中)

类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成…

ES文档索引、查询、分片、文档评分和分析器技术原理

技术原理 索引文档 索引文档分为单个文档和多个文档。 单个文档 新建单个文档所需要的步骤顺序&#xff1a; 客户端向 Node 1 发送新建、索引或者删除请求。节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3&#xff0c;因为分片 0 的主分片目前被分配在 …

【AI绘画】stablediffusion图生图教程!!

**手把手教你入门绘图超强的AI绘画&#xff0c;用户只需要输入一段图片的文字描述&#xff0c;即可生成精美的绘画。给大家带来了全新保姆级教程资料包 &#xff08;文末可获取&#xff09; ** 目录 一、图生图 1.图生图原理 2.图生图三个步骤 ①图生图基本三步法 ②提示…

Leetcode—2859. 计算 K 置位下标对应元素的和【简单】

2023每日刷题&#xff08;九十一&#xff09; Leetcode—2859. 计算 K 置位下标对应元素的和 内置函数__builtin_popcount方法实现代码 class Solution { public:int sumIndicesWithKSetBits(vector<int>& nums, int k) {int n nums.size();int ans 0;for(int i …

mac电脑安卓文件传输工具:Android File Transfer直装版

Android File Transfer&#xff08;AFT&#xff09;是一款用于在Mac操作系统上与Android设备之间传输文件。它允许用户将照片、音乐、视频和其他文件从他们的Android手机或平板电脑传输到Mac电脑&#xff0c;以及将文件从Mac上传到Android设备。 下载地址&#xff1a;https://w…

怎样编写高性能C/C++程序

本文主要讨论高性能编程&#xff0c;而且是那种“极致性能需求”。按照本人的粗浅认识&#xff0c;应该已经覆盖了绝大多数技术要点&#xff0c;但缺点是不够详细&#xff08;篇幅有限&#xff09;。本文共分为4个部分&#xff1a;总体论述、高性能网络编程、高性能数值计算、常…

【揭秘】ForkJoinTask全面解析

内容摘要 ForkJoinTask的显著优点在于其高效的并行处理能力&#xff0c;它能够将复杂任务拆分成多个子任务&#xff0c;并利用多核处理器同时执行&#xff0c;从而显著提升计算性能&#xff0c;此外&#xff0c;ForkJoinTask还提供了简洁的API和强大的任务管理机制&#xff0c…

常规的管理系统除了适用该有的范儿一定要有!气质上不能输

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 常规的管理系统除了适用该有的范儿一定要有!气质上不能输 在现今快速发展的商业环境中…

Android音量调节修改

前言 今日公司&#xff0c;安卓设备的音量显示不正常&#xff0c;让我来修复这个bug&#xff0c;现在已修复&#xff0c;做个博客&#xff0c;记录一下&#xff0c;以后碰到类似一下子就好解决。 Android音量调节相关 路径 frameworks\base\services\core\java\com\android…

NIO-Selector详解

NIO-Selector详解 Selector概述 Selector选择器&#xff0c;也可以称为多路复⽤器。它是Java NIO的核⼼组件之⼀&#xff0c;⽤于检查⼀个或多个Channel的状态是否处于可读、可写、可连接、可接收等。通过⼀个Selector选择器管理多个Channel&#xff0c;可以实现⼀个线程管理…

Spring boot + Azure OpenAI 服务 1.使用 GPT-35-Turbo

Azure OpenAI 服务使用 GPT-35-Turbo 先决条件 maven 注意 beta.6 版本 <dependency><groupId>com.azure</groupId><artifactId>azure-ai-openai</artifactId><version>1.0.0-beta.6</version></dependency>问答工具类 pack…

C++的关键字,命名空间,缺省参数,函数重载以及原理

文章目录 前言一、C关键字(C98)二、命名空间命名空间介绍命名空间的使用 三、C输入【cin】& 输出【cout】四、缺省参数缺省参数概念缺省参数分类缺省参数的使用小结一下 五、函数重载函数重载介绍函数重载类型 六、C支持函数重载的原理--名字修饰(name Mangling)【重点】 前…

二分算法模版

二分算法模版 实数二分算法模版实数二分模版题 整数二分算法模版向上取整二分模版向下取整二分模版二分模版的注意点二分模版中check函数的实现能够使用二分的条件 二分主要分两类&#xff0c; 一类是对实数进行二分&#xff0c;一类是对整数进行二分 对整数二分又分成2种&…

python-自动化篇-运维-监控-简单实例-道出如何使⽤Python进⾏系统监控?

如何使⽤Python进⾏系统监控&#xff1f; 使⽤Python进⾏系统监控涉及以下⼀般步骤&#xff1a; 选择监控指标&#xff1a; ⾸先&#xff0c;确定希望监控的系统指标&#xff0c;这可以包括 CPU 利⽤率、内存使⽤情况、磁盘空间、⽹络流量、服务可⽤性等。选择监控⼯具&#x…

Java实现加权平均分计算程序WeightedAverageCalculator

成绩加权平均分计算程序&#xff0c;带UI界面和输入保存功能。 因为本人对成绩的加权均分有所关注&#xff0c;但学校的教务系统查分时往往又不显示个人的加权均分&#xff0c;加之每次手动敲计算器计算很麻烦就花了点时间写了一个加权均分计算程序自用&#xff0c;顺便开源。…

STM32标准库——(5)EXTI外部中断

1.中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&#xff…

《WebKit 技术内幕》学习之十五(6):Web前端的未来

6 Chromium OS和Chrome的Web应用 6.1 基本原理 HTML5技术已经不仅仅用来编写网页了&#xff0c;也可以用来实现Web应用。传统的操作系统支持本地应用&#xff0c;那么是否可以有专门的操作系统来支持Web应用呢&#xff1f;当然&#xff0c;现在已经有众多基于Web的操作系统&…

uniapp小程序:内存超过2mb解决方法(简单)message:Error: 上传失败:网络请求错误 代码包大小超过限制。

分析&#xff1a;这种情况是代码文件内存超过2mb无法进行预览上传 解决方法&#xff1a; 1、Hbuilder中点击运行-->运行到小程序模拟器--->运行时是否压缩代码 2、在微信小程序中点击详情--->本地设置&#xff1a; 3、点击预览即可运行了