STM32使用SDIO模式和DMA实现SD卡的读写及擦除操作

对于STM32操作SD卡来说,最重要的就算初始化写操作读操作擦除这几个操作了。

对于初始化部分上一篇文章已经分析,本篇就主要分析写、读、擦除操作。

本篇函数来自于STM32提供的例程。参考野火的程序进行了解释,与野火函数有些不同。

这几种函数完成之后,就是开始实现对SD卡进行操作了。😀 😀 😀


SD卡擦除函数

SD卡擦除函数比较简单,只用到CMD32、CMD33、CMD38指令。

最后需要确保SD卡擦除完成才能退出SD_Erase函数。通过IsCardProgramming函数实现。

/**
 * @brief 控制 SD 卡擦除指定的数据区域
 * @param startaddr: 擦除的开始地址
 * @param endaddr: 擦除的结束地址
 * @retval SD_Error: SD 返回的错误代码
 */
SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr)
{
  SD_Error errorstatus = SD_OK;
  uint32_t delay = 0;
  __IO uint32_t maxdelay = 0;
  uint8_t cardstate = 0;

  /*!< 检查SD卡是否支持擦除操作 */
  if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
  {
    errorstatus = SD_REQUEST_NOT_APPLICABLE;
    return(errorstatus);
  }

  maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);    //延时,根据时钟分频设计来计算

  if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)    //卡上锁
  {
    errorstatus = SD_LOCK_UNLOCK_FAILED;
    return(errorstatus);
  }

  /* SDHC卡,地址参数为块地址,每块512字节,SDSC卡地址为字节地址 */
  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) 
  {
    startaddr /= 512;
    endaddr /= 512;
  }
  
  /*!<  ERASE_GROUP_START (CMD32) and SD_CMD_SD_ERASE_GRP_END(CMD33) */
  if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || 
      (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || 
      (SDIO_HIGH_CAPACITY_SD_CARD == CardType))
  {
    /*!< Send CMD32 SD_ERASE_GRP_START with startaddr  */
    SDIO_CmdInitStructure.SDIO_Argument = startaddr;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
    SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
    SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStructure);
    errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);
    if (errorstatus != SD_OK)
    {
      return(errorstatus);
    }

    /*!< Send CMD33 SD_ERASE_GRP_END with endaddr  */
    SDIO_CmdInitStructure.SDIO_Argument = endaddr;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
    SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
    SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStructure);
    errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);
    if (errorstatus != SD_OK)
    {
      return(errorstatus);
    }
  }

  /*!< Send CMD38 ERASE */
  SDIO_CmdInitStructure.SDIO_Argument = 0;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
  errorstatus = CmdResp1Error(SD_CMD_ERASE);
  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }

  for (delay = 0; delay < maxdelay; delay++){}
  /*!< 等待SD卡的内部时序操作完成 */
  errorstatus = IsCardProgramming(&cardstate);

  while ((errorstatus == SD_OK) && 
         ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
          //SD卡编程 || SD卡接收
  {
    errorstatus = IsCardProgramming(&cardstate);    //检测SD卡是否在读写操作
  }
  return(errorstatus);
}

数据写入操作(单块写入、DMA传输方式)

因为分析DMA传输模式,故有关轮询的函数删掉了。

数据写入函数

/**
 * @brief 向 sd 卡写入一个 BLOCK 的数据(512 字节)
 * @note 本函数使用后需要调用如下两个函数来等待数据传输完成
 * - SD_WaitWriteOperation(): 确认 DMA 已把数据传输到 SDIO 接口
 * - SD_GetStatus(): 确认 SD 卡内部已经把数据写入完毕
 * @param writebuff: 指向要写入的数据
 * @param WriteAddr: 要把数据写入到 sd 卡的地址
 * @param BlockSize: 块大小,sdhc 卡为 512 字节
 * @retval SD_Error: 返回的 sd 错误代码
 */
SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;

  TransferError = SD_OK;
  TransferEnd = 0;
  StopCondition = 0;
  
  SDIO->DCTRL = 0x0;

  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    WriteAddr /= 512;
  }
  
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
  /*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
  /* Set Block Size for Card,cmd16,
   * 若是sdsc卡,可以用来设置块大小,
   * 若是sdhc卡,块大小为512字节,不受cmd16影响 
   */
  /*!< Send CMD 16 */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;   
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
  errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
  if (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++

  /*!< Send CMD24 WRITE_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = WriteAddr;    //写入地址
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
  errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK);
  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }
  /*!< 配置SDIO的写数据寄存器 */
  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;    //可用此参数代替SDIO_DataBlockSize_512b
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;    //写数据
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;    //开启数据通道状态机
  SDIO_DataConfig(&SDIO_DataInitStructure);

  SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);    //数据传输结束中断
  SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize);    //配置DMA
  SDIO_DMACmd(ENABLE);    //使能SDIO的DMA请求

  return(errorstatus);
}

首先,设置SDIO->DCTRL清零,清除之前的传输设置。

发送CMD16指定块的大小。对于标准卡,要写入 BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。

发送CMD24通知 SD 卡要进行数据写入操作,并指定待写入数据的目标地址。

通过结构体配置数据传输的超时、块数量、块大小、传输方向等参数。

调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到 SDIO 的 中断服务函数运行。

调用前面讲解的 SD_LowLevel_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的 DMA 请求。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数使能。

SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。

写入DMA配置

/*
 * 函数名:SD_DMA_RxConfig
 * 描述  :为SDIO发送数据配置DMA2的通道4的请求
 * 输入  :BufferDST:装载了数据的变量指针
 *  	   BufferSize:	缓冲区大小
 * 输出  :无
 */
void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{

  DMA_InitTypeDef DMA_InitStructure;

  DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);

  /*!< DMA2 Channel4 disable */
  DMA_Cmd(DMA2_Channel4, DISABLE);

  /*!< DMA2 Channel4 Config */
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设为写入目标
  DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;	//外设地址不自增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA2_Channel4, &DMA_InitStructure);

  /*!< DMA2 Channel4 enable */
  DMA_Cmd(DMA2_Channel4, ENABLE);  
}

写入操作等待函数

/**
 * @brief 本函数会一直等待到 DMA 传输结束
 * 在 SDIO_WriteBlock() 和 SDIO_WriteMultiBlocks() 函数后必须被调用以确保DMA 数据传输完成
 * @param None.
 * @retval SD_Error: 返回的 sd 错误代码.
 */
SD_Error SD_WaitWriteOperation(void)
{
  SD_Error errorstatus = SD_OK;
  //等待 DMA 是否传输结束
  while ((SD_DMAEndOfTransferStatus() == RESET) 
        && (TransferEnd == 0) && (TransferError == SD_OK))
  {}

  if (TransferError != SD_OK)
  {
    return(TransferError);
  }

  /*!< 清除标志 */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

  return(errorstatus);
}

调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,当 DMA 传输 结束时,该函数会返回 SET 值。另外,while 循环中的判断条件使用的 TransferEnd 和 TransferError是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被设置,传输结束后,根据TransferError 的值来确认是否正确传输,若不正确则直接返回错误代码。SD_WaitWriteOperation 函数最后是清 除相关标志位并返回错误。由于这个函数里的 while 循环的存在,它会确保 DMA 的传输结束。


数据读取操作(单块读取、DMA传输方式)

数据读取函数

/**
 * @brief 向 sd 卡写入一个 BLOCK 的数据(512 字节)
 * @note 本函数使用后需要调用如下两个函数来等待数据传输完成
 * - SD_WaitWriteOperation(): 确认 DMA 已把数据传输到 SDIO 接口
 * - SD_GetStatus(): 确认 SD 卡内部已经把数据写入完毕
 * @param writebuff: 指向要写入的数据
 * @param WriteAddr: 要把数据写入到 sd 卡的地址
 * @param BlockSize: 块大小,sdhc 卡为 512 字节
 * @retval SD_Error: 返回的 sd 错误代码
 */

SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;

  TransferError = SD_OK;
  TransferEnd = 0;    //传输结束标志位,在中断服务中置1
  StopCondition = 0;
  
  SDIO->DCTRL = 0x0;
  
  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    ReadAddr /= 512;
  }
  //+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
  /*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
  /* Set Block Size for Card,cmd16,
   * 若是sdsc卡,可以用来设置块大小,
   * 若是sdhc卡,块大小为512字节,不受cmd16影响 
   */
  /*!< Send CMD 16 */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;   
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
  errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
  if (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
  //+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
  
  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;    //数据传输方向
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
  SDIO_DataConfig(&SDIO_DataInitStructure);

  /*!< Send CMD17 READ_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
  errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);
  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }

  SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);    //启用或禁用SDIO中断,第一个为指定禁用值
  SDIO_DMACmd(ENABLE);    //启用或禁用SDIO DMA请求。 
  SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);

  return(errorstatus);
}

数据读操作和写操作类似!

首先,设置SDIO->DCTRL清零,清除之前的传输设置。

发送CMD16指定块的大小。对于标准卡,要写入 BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。

通过结构体配置数据传输的超时、块数量、块大小、传输方向等参数。

发送CMD17,SD卡在接收到命令后就会通过数据线把书记传输到SDIO的数据FIFO中。

调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到 SDIO 的 中断服务函数运行。

调用前面讲解的 SD_LowLevel_DMA_RxConfig 函数,配置使能 SDIO 从 SD 卡读取数据的 DMA 请求。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数使能。

SD 卡发出的数据将会传输到 STM32 的 SDIO 外设,而 SDIO 外设激发 DMA 请求,把 数据搬运到内存中。

读取DMA配置

 /*
  * 函数名:SD_DMA_RxConfig
  * 描述  :为SDIO接收数据配置DMA2的通道4的请求
  * 输入  :BufferDST:用于装载数据的变量指针
  *	      : BufferSize:	缓冲区大小
  * 输出  :无
  */
void SD_LowLevel_DMA_RxConfig( uint32_t *BufferDST, uint32_t BufferSize )
{
    DMA_InitTypeDef DMA_InitStructure;
 
    DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);//清除DMA标志位
 
    /*!< DMA2 Channel4 disable */
    DMA_Cmd(DMA2_Channel4, DISABLE);    //SDIO为第四通道
 
    /*!< DMA2 Channel4 Config */
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;    //外设地址,fifo
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;    //目标地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;    //外设为源地址
    DMA_InitStructure.DMA_BufferSize = BufferSize / 4;    //除以4,把字转成字节单位
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;    //使能外设地址不自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;    //使能存储目标地址自增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;    //外设数据大小为字,32位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;    //外设数据大小为字,32位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;    //不循环,循环模式主要用在adc上
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;    //通道优先级高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;    //非 存储器至存储器模式
    DMA_Init(DMA2_Channel4, &DMA_InitStructure);
 
    /*!< DMA2 Channel4 enable */
    DMA_Cmd(DMA2_Channel4, ENABLE);
}

读取操作等待函数

/**
 * @brief 本函数会一直等待到 DMA 传输结束
 * SDIO_ReadMultiBlocks() 函数后必须被调用以确保 DMA 数据传输完成
 * @param None.
 * @retval SD_Error: 返回的 sd 错误代码.
 */
SD_Error SD_WaitWriteOperation(void)
{
  SD_Error errorstatus = SD_OK;
    
  //等待 DMA 传输结束
  while ((SD_DMAEndOfTransferStatus() == RESET) 
        && (TransferEnd == 0) && (TransferError == SD_OK))
  {}

  if (TransferError != SD_OK)
  {
    return(TransferError);
  }

  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

  return(errorstatus);
}

其中,写入读取操作分为单块和多块,但是相差不大,故本篇文章仅仅分析了单块读取操作!


SDIO的中断服务操作

中断服务操作在 stm32f10x_it.c 文件中。需要注意!!!!!

在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。

中断服务接口

stm32f10x_it.c 文件中需要设置:(其实设置到sdio用户自定文件中也可!)

void SDIO_IRQHandler(void) 
{		
    /* Process All SDIO Interrupt Sources */									
 	SD_ProcessIRQSrc();
}	

中断服务函数

SD_Error SD_ProcessIRQSrc(void)
{
  if (StopCondition == 1)    //发送读、写命令时设置为1
  {
    SDIO->ARG = 0x0;    //命令参数寄存器
    SDIO->CMD = 0x44C;    //命令寄存器 0100-0100-1100
                          /*    CPSMEN[10]    WAITRESP[7:6]  CMDINDEX[5:0]
                           *    0100          01             001100
                           *    开启命令状态机  短响应         命令索引:CMD12
                           */
    TransferError = CmdResp1Error(SD_CMD_STOP_TRANSMISSION);
  }
  else
  {
    TransferError = SD_OK;
  }
  SDIO_ClearITPendingBit(SDIO_IT_DATAEND);    //清除中断
  SDIO_ITConfig(SDIO_IT_DATAEND, DISABLE);    //关闭SDIO中断使能
  TransferEnd = 1;
  return(TransferError);
}

函数首先判断 StopCondition !多块读写中被置为1,单块读写中是置为0 。SD卡要求多块读写命令有 CMD12 结束,SD卡收到 CMD12 时才停止多块的传输!发送命令直接采用向寄存器写入命令和参数的方式。此外,根据传输情况设置全局变量 TransferError、TransferEnd 。


至此,大部分的STM32 的 SDIO 功能就基本上看到了。可根据此执行一些简单的读写操作了。

晚安 ~

 

物联沃分享整理
物联沃-IOTWORD物联网 » STM32使用SDIO模式和DMA实现SD卡的读写及擦除操作

发表评论