细说STM32单片机使用DMA模式直接访问SD卡的方法及其应用

目录

一、示例:以DMA方式读写SD卡

1、示例功能与CubeMX项目设置

(1)RCC、GPIO、USART6、SYS、CodeGenrator

(2)SDIO

(3)NVIC 

2、主程序与外设初始化

(1)初始主程序

(2)DMA初始化

(3)SDIO接口和SD卡初始化

3、程序功能实现

(1)主程序

(2)中断ISR和回调函数

(3)DMA方式写数据

(4)DMA方式读取数据

(5)其它代码

二、运行与调试


        阅读本文需要的基本知识可以参考本文作者发布的文章:

        细说STM32单片机使用轮询模式直接访问SD卡的方法及其应用-CSDN博客  https://wenchm.blog.csdn.net/article/details/149218456?spm=1011.2415.3001.5331

        SD的HAL驱动程序提供了DMA方式读写SD卡的函数,即HAL_SD_WriteBlocks_DMA()和HAL_SD_ReadBlocks_DMA()。当SD卡读写数据量比较大时,使用DMA方式可以减少处理器负荷,提高运行效率。

        本文用一个示例演示DMA方式读写SD卡的操作。        

一、示例:以DMA方式读写SD卡

1示例功能与CubeMX项目设置

        通过串口在串口助手上互动,使用DMA模式测试SD卡的信息、擦除、读写。 

(1)RCC、GPIO、USART6、SYS、CodeGenrator

        与参考文章相同。

(2)SDIO

 

        对SDIO进行DMA设置,SDIO有SDIO_RX(接收)和SDIO_TX(发送)两个DMA请求,需要分别关联DMA流。两个DMA流的模式都自动设置为PeripheralFlow Control(外设流程控制),都会自动使用FIFO,数据宽度自动设置为Word,Memory端开启地址自增功能。

        对SDIO进行DMA配置后,还必须打开SDIO的全局中断。如果不打开SDIO的全局中断,在使用DMA方式进行读写时,有时会出现失败的情况。SDIO全局中断的抢占优先级应高于或等于DMA流的抢占优先级。

 

(3)NVIC 

 

 

2、主程序与外设初始化

(1)初始主程序

        完成设置后,CubeMX会自动生成代码。初始的主程序代码如下,代码中增加了DMA初始化函数MX_DMA_Init():

//main.c

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
uint8_t SDBuf_TX[BLOCKSIZE]; //Data Sending Cache, BLOCKSIZE=512
uint8_t SDBuf_RX[BLOCKSIZE]; //Data Received Cache
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
    //此处及之后的内容见下文
}

        上述程序定义了两个uint8_t类型的全局数组SDBuf_TX和SDBuf_RX,其大小都是BLOCKSIZE,也就是512字节。因为以DMA方式读写数据是非阻塞式的,需要使用全局变量作为存储数据的缓冲区。 

(2)DMA初始化

        函数MX_DMA_Init()用于DMA初始化,在自动生成的文件dma.h和dma.c中定义和实现。这个函数的源代码如下,其功能就是使能DMA2控制器时钟,配置两个DMA流的中断优先级。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    dma.c
  * @brief   This file provides code for the configuration
  *          of all the requested memory to memory DMA transfers.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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 ------------------------------------------------------------------*/
#include "dma.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/*----------------------------------------------------------------------------*/
/* Configure DMA                                                              */
/*----------------------------------------------------------------------------*/

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/**
  * Enable DMA controller clock
  */
void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

(3)SDIO接口和SD卡初始化

        文件sdio.c中的初始化函数MX_SDIO_SD_Init()以及相关变量和函数的代码如下:

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "sdio.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

/* SDIO init function */

void MX_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.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
  if (HAL_SD_Init(&hsd) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SDIO_Init 2 */

  /* USER CODE END SDIO_Init 2 */

}

void HAL_SD_MspInit(SD_HandleTypeDef* sdHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(sdHandle->Instance==SDIO)
  {
  /* USER CODE BEGIN SDIO_MspInit 0 */

  /* USER CODE END SDIO_MspInit 0 */
    /* SDIO clock enable */
    __HAL_RCC_SDIO_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /**SDIO GPIO Configuration
    PC8     ------> SDIO_D0
    PC12     ------> SDIO_CK
    PD2     ------> SDIO_CMD
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* SDIO DMA Init */
    /* SDIO_RX Init */
    hdma_sdio_rx.Instance = DMA2_Stream3;
    hdma_sdio_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_sdio_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_sdio_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_sdio_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_sdio_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_sdio_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_sdio_rx.Init.Mode = DMA_PFCTRL;
    hdma_sdio_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_sdio_rx.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_sdio_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_sdio_rx.Init.MemBurst = DMA_MBURST_INC4;
    hdma_sdio_rx.Init.PeriphBurst = DMA_PBURST_INC4;
    if (HAL_DMA_Init(&hdma_sdio_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(sdHandle,hdmarx,hdma_sdio_rx);

    /* SDIO_TX Init */
    hdma_sdio_tx.Instance = DMA2_Stream6;
    hdma_sdio_tx.Init.Channel = DMA_CHANNEL_4;
    hdma_sdio_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_sdio_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_sdio_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_sdio_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_sdio_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_sdio_tx.Init.Mode = DMA_PFCTRL;
    hdma_sdio_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_sdio_tx.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_sdio_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_sdio_tx.Init.MemBurst = DMA_MBURST_INC4;
    hdma_sdio_tx.Init.PeriphBurst = DMA_PBURST_INC4;
    if (HAL_DMA_Init(&hdma_sdio_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(sdHandle,hdmatx,hdma_sdio_tx);

    /* SDIO interrupt Init */
    HAL_NVIC_SetPriority(SDIO_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(SDIO_IRQn);
  /* USER CODE BEGIN SDIO_MspInit 1 */

  /* USER CODE END SDIO_MspInit 1 */
  }
}

void HAL_SD_MspDeInit(SD_HandleTypeDef* sdHandle)
{

  if(sdHandle->Instance==SDIO)
  {
  /* USER CODE BEGIN SDIO_MspDeInit 0 */

  /* USER CODE END SDIO_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SDIO_CLK_DISABLE();

    /**SDIO GPIO Configuration
    PC8     ------> SDIO_D0
    PC12     ------> SDIO_CK
    PD2     ------> SDIO_CMD
    */
    HAL_GPIO_DeInit(GPIOC, GPIO_PIN_8|GPIO_PIN_12);

    HAL_GPIO_DeInit(GPIOD, GPIO_PIN_2);

    /* SDIO DMA DeInit */
    HAL_DMA_DeInit(sdHandle->hdmarx);
    HAL_DMA_DeInit(sdHandle->hdmatx);

    /* SDIO interrupt Deinit */
    HAL_NVIC_DisableIRQ(SDIO_IRQn);
  /* USER CODE BEGIN SDIO_MspDeInit 1 */

  /* USER CODE END SDIO_MspDeInit 1 */
  }
}

        MSP初始化函数HAL_SD_MspInit()中增加了DMA初始化功能,上述程序定义了两个DMA流对象与SDIO的两个DMA请求关联。

3、程序功能实现

(1)主程序

        在主程序中修改和添加代码,完成的主程序代码如下:

// 继续上面的main.c代码

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SDIO_SD_Init();
  MX_USART6_UART_Init();

  /* USER CODE BEGIN 2 */

  uint8_t startstr[] = "Demo13_2: SD card R/W-DMA. \r\n";
  HAL_UART_Transmit(&huart6,startstr,sizeof(startstr),0xFFFF);

  uint8_t startstr1[] = "Read/write SD card via DMA. \r\n\r\n";
  HAL_UART_Transmit(&huart6,startstr1,sizeof(startstr1),0xFFFF);

  //show menu
  printf("[S2]KeyUp   =SD card info. \r\n");
  printf("[S3]KeyDown =Erase 0-10 blocks. \r\n");
  printf("[S4]KeyLeft =Write block. \r\n");
  printf("[S1]KeyRight=Read block. \r\n\r\n");

  while(1)
  {
	  KEYS waitKey=ScanPressedKey(KEY_WAIT_ALWAYS);	//Waiting for the key

	  if (waitKey==KEY_UP)
		{
			SDCard_ShowInfo();
			printf("Reselect menu item or reset. \r\n");
		}
		else if (waitKey== KEY_DOWN)
		{
			SDCard_EraseBlocks();			//EraseBlocks 0-10
			printf("Reselect menu item or reset. \r\n");
		}
		else if (waitKey== KEY_LEFT)
			SDCard_TestWrite_DMA();
		else if (waitKey== KEY_RIGHT)
			SDCard_TestRead_DMA();

		HAL_Delay(500);
  }
  /* USER CODE END 2 */

//以下的代码内容在下文接续

        主程序显示了4个菜单项,通过4个按键选择操作。SDCard_ShowInfo()和SDCard_EraseBlocks()的代码与前一示例的完全相同,就不再显示和解释了。函数SDCard_TestWrite_DMA()以DMA方式向SD卡写入数据,函数SDCard_TestRead_DMA()以DMA方式从SD卡读取数据。

(2)中断ISR和回调函数

        在示例中,SDIO的全局中断开启了,两个DMA流中断也是自动打开的,在文件stm32f4xx_it.c中,自动生成了这3个中断的ISR,代码如下:

//stm32f4xx_it.h

/* Exported functions prototypes ---------------------------------------------*/
// 此处的函数声明省略

void SDIO_IRQHandler(void);
void DMA2_Stream3_IRQHandler(void);
void DMA2_Stream6_IRQHandler(void);
/**
  * @brief This function handles SDIO global interrupt.
  */
void SDIO_IRQHandler(void)
{
  /* USER CODE BEGIN SDIO_IRQn 0 */

  /* USER CODE END SDIO_IRQn 0 */
  HAL_SD_IRQHandler(&hsd);
  /* USER CODE BEGIN SDIO_IRQn 1 */

  /* USER CODE END SDIO_IRQn 1 */
}

/**
  * @brief This function handles DMA2 stream3 global interrupt.
  */
void DMA2_Stream3_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream3_IRQn 0 */

  /* USER CODE END DMA2_Stream3_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_sdio_rx);
  /* USER CODE BEGIN DMA2_Stream3_IRQn 1 */

  /* USER CODE END DMA2_Stream3_IRQn 1 */
}

/**
  * @brief This function handles DMA2 stream6 global interrupt.
  */
void DMA2_Stream6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream6_IRQn 0 */

  /* USER CODE END DMA2_Stream6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_sdio_tx);
  /* USER CODE BEGIN DMA2_Stream6_IRQn 1 */

  /* USER CODE END DMA2_Stream6_IRQn 1 */
}

        这些代码都是由IDE自动生成的。

        SDIO有两个最主要的回调函数,HAL_SD_TxCpltCallback()是在中断方式或DMA方式发送数据完成时执行的回调函数,HAL_SD_RxCpltCallback()是在中断方式或DMA方式接收数据完成时执行的回调函数。本示例需要重新实现这两个回调函数,为了便于使用全局数组SDBuf_TX和SDBuf_RX,这两个回调函数放在文件main.c里实现。

(3)DMA方式写数据

        函数HAL_SD_WriteBlocks_DMA()以DMA方式向SD卡写入数据。在文件main.c中自定义的函数SDCard_TestWrite_DMA()就用这个函数测试向SD卡写入数据,同时,还需要实现回调函数HAL_SD_TxCpltCallback()。

// 继续上面的main.c的代码
// 此处代码在/* USER CODE BEGIN 4/END 4 */代码对内

/* USER CODE BEGIN 4 */
/* HAL_SD_GetCardInfo(), Display SD card information */
void	SDCard_ShowInfo()
{
	HAL_SD_CardInfoTypeDef cardInfo;  //SD card information structure
	HAL_StatusTypeDef res=HAL_SD_GetCardInfo(&hsd, &cardInfo);

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

	printf("*** HAL_SD_GetCardInfo() info *** \r\n\r\n");

	printf("Card Type= %ld \r\n", cardInfo.CardType);
	printf("Card Version= %ld \r\n", cardInfo.CardVersion);
	printf("Card Class= %ld \r\n", cardInfo.Class);
	printf("Relative Card Address= %ld \r\n", cardInfo.RelCardAdd);
	printf("Block Count= %ld \r\n", cardInfo.BlockNbr);
	printf("Block Size(Bytes)= %ld \r\n", cardInfo.BlockSize);
	printf("LogiBlockCount= %ld \r\n", cardInfo.LogBlockNbr);
	printf("LogiBlockSize(Bytes)= %ld \r\n", cardInfo.LogBlockSize);

	uint32_t cap= (cardInfo.BlockNbr*cardInfo.BlockSize);		//bytes
	cap = cap/(1e6);											//MB

	printf("SD Card Capacity(MB)= %ld \r\n", cap);
}

/* HAL_SD_Erase(), Erase SD card Blocks*/
void	SDCard_EraseBlocks()
{
	uint32_t BlockAddrStart=0; 	// Block 0,Addresses using block number
	uint32_t BlockAddrEnd=10; 	// Block 10

	printf("\r\n*** Erasing blocks *** \r\n\r\n");

	if (HAL_SD_Erase(&hsd,BlockAddrStart, BlockAddrEnd)==HAL_OK)
	{
		printf("Erasing blocks,OK. \r\n");
		printf("Blocks 0-10 is erased. \r\n");
	}
	else
		printf("Erasing blocks,fail. \r\n");

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

	// The following code has nothing to do with erasure and can be deleted.[wen]
	while(cardState != HAL_SD_CARD_TRANSFER)   //Wait for return to transmission status
	{
		HAL_Delay(1);
		cardState=HAL_SD_GetCardState(&hsd);
		return;			//Otherwise, it will not come out after entering the loop
	}
}

/* HAL_SD_WriteBlocks_DMA(), Write SD card */
void	SDCard_TestWrite_DMA()	//DMA mode, test write
{
	printf("\r\n*** DMA Writing blocks *** \r\n\r\n");

	for(uint16_t i=0;i<BLOCKSIZE; i++)
		SDBuf_TX[i]=i; 			//generate data

	printf("Writing block 6. \r\n");
	printf("Data in [10:15] is: %d ",SDBuf_TX[10]);

	for (uint16_t j=11; j<=15;j++)
	{
		printf(", %d", SDBuf_TX[j]);
	}
	printf("\r\nHAL_SD_WriteBlocks_DMA() is to be called. \r\n");

	uint32_t BlockAddr=6;		//Block Address
	uint32_t BlockCount=1;		//Block Count
	HAL_SD_WriteBlocks_DMA(&hsd,SDBuf_TX,BlockAddr,BlockCount);  //can erase block automatically
}

/* HAL_SD_ReadBlocks_DMA(), Read SD card */
void	SDCard_TestRead_DMA()  //test read
{
	printf("\r\n*** DMA Reading blocks *** \r\n\r\n");
	printf("\r\nHAL_SD_ReadBlocks_DMA() is to be called. \r\n");

	uint32_t BlockAddr=6;		//Block Address
	uint32_t BlockCount=1;		//Block Count
	HAL_SD_ReadBlocks_DMA(&hsd,SDBuf_RX,BlockAddr,BlockCount);
}

/* SD write callback func */
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
	printf("DMA write complete. \r\n");
	printf("HAL_SD_TxCpltCallback() is called. \r\n");
	printf("Reselect menu item or reset. \r\n");
}

/* SD read callback func */
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
	printf("DMA read complete. \r\n");
	printf("HAL_SD_RxCpltCallback() is called. \r\n");
	printf("Data in [10:15] is: %d\r\n",SDBuf_RX[10]);

	for (uint16_t j=11; j<=15;j++)
	{
		printf(", %d", SDBuf_RX[j]);
	}
	printf("\r\nReselect menu item or reset. \r\n");
}

        上述程序首先将数组SDBuf_TX填满数据,然后调用函数HAL_SD_WriteBlocks_DMA()将数组SDBuf_TX里的数据写入SD卡的Block6。函数HAL_SD_WriteBlocks_DMA()可以一次写入多个数据块,写入之前无须擦除块,函数里会自动擦除。

        函数HAL_SD_WriteBlocks_DMA()的原型定义详见参考文章。

        函数HAL_SD_WriteBlocks_DMA()返回HAL_OK只表示DMA操作正确,并不表示数据写入完成了。以DMA方式向SD卡写入数据完成后,会执行回调函数HAL_SD_TxCpltCallback(),重新实现的这个回调函数的代码很简单,就是显示信息,表示DMA写入已完成。运行时会发现,此回调函数能被正常调用,表示DMA方式写入数据成功。

(4)DMA方式读取数据

        函数HAL_SD_ReadBlocks_DMA()以DMA方式读取SD卡的数据,SDCard_TestRead_DMA()使用这个函数测试读取SD卡数据,同时,还需要实现回调函数HAL_SD_RxCpltCallback()。

        函数SDCard_TestRead_DMA()调用函数HAL_SD_ReadBlocks_DMA()读取Block6的数据,保存到全局数组SDBuf_RX里。同样地,函数HAL_SD_ReadBlocks_DMA()返回HAL_OK只是表示DMA操作正确,并不表示数据读取完成了。

        以DMA方式读取SD卡数据完成后,会执行回调函数HAL_SD_RxCpltCallback(),重新实现的这个回调函数显示了数组SDBuf_RX里的部分内容。在开发板上运行测试,如果Block 6被写入了数据,会发现读出的数据与写入的数据一致;如果Block 6被擦除后没有写入数据,读出的数组内容全部是0。

        SD卡被擦除后,存储单元的内容可能是0,也可能是0xFF会因SD卡的不同而不一样。

(5)其它代码

        串口: 

// 继续上面的main.c的代码
// 此处代码在/* USER CODE BEGIN 4/END 4 */代码对内

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}
/* USER CODE END 4 */

         main.h中声明私有函数:

/* USER CODE BEGIN EFP */
void SDCard_ShowInfo();
void SDCard_EraseBlocks();
void SDCard_TestWrite_DMA();
void SDCard_TestRead_DMA();
/* USER CODE END EFP */

二、运行与调试

        下载并运行,本示例的项目仍然使用1线数据线,下载后在串口助手上不能显示开始菜单(这个现象不正常),按下开发板的复位键后显示开始菜单:

        S2: 

         S3:

        S4: 

        S1: 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenchm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值