STM32CubeMX教程27 SDIO 您所在的位置:网站首页 sd卡最大多少gb STM32CubeMX教程27 SDIO

STM32CubeMX教程27 SDIO

2024-07-16 00:12| 来源: 网络整理| 查看: 265

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 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 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 printf("DMA Write to block 6, OK\r\n"); printf("Data in [10:15] is: "); for (uint16_t j=10; j printf("DMA Read block 6, OK\r\n"); printf("Data in [10:15] is: "); for (uint16_t j=10; j


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有