STM32 SDIO驱动SD卡学习及实战指南

目录

一、SDIO寄存器

1.1 SDIO电源控制寄存器(SDIO_POWER)

1.2 SDIO时钟控制寄存器(SDIO_CLKCR)

1.3 SDIO参数寄存器(SDIO_ARG)

1.4 SDIO命令寄存器(SDIO_CMD)

1.5 SDIO命令响应寄存器(SDIO_RESPCMD)

1.6 SDIO响应 1..4 寄存器(SDIO_RESPx)

1.7 SDIO数据定时器寄存器(SDIO_DTIMER)

1.8 SDIO数据长度寄存器(SDIO_DLEN)

1.9 SDIO数据控制寄存器(SDIO_DCTRL)

1.10 SDIO数据计数器寄存器(SDIO_DCOUNT)

1.11 SDIO状态寄存器(SDIO_STA)

1.12 SDIO清除中断寄存器(SDIO_ICR)

1.13 SDIO中断屏蔽寄存器(SDIO_MASK)

1.14 SDIO FIFO计数器寄存器(SDIO_FIFOCNT)

1.15 SDIO数据FIFO寄存器(SDIO_FIFO)

1.16 SDIO寄存器映像

二、开发思路

三、具体实例


书接上回:

STM32——SDIO的学习(驱动SD卡)(理论篇)_宇努力学习的博客-CSDN博客

在写程序前在最后了解一下SDIO的寄存器

一、SDIO寄存器

设备通过可以在AHB上操作的32位控制寄存器与系统通信。
必须以字(32位)的方式操作这些外设寄存器。

1.1 SDIO电源控制寄存器(SDIO_POWER)

地址偏移: 0x00
复位值: 0x0000 0000

注意: 写数据后的7个HCLK时钟周期内,不能写入这个寄存器.

1.2 SDIO时钟控制寄存器(SDIO_CLKCR)

地址偏移: 0x04
复位值: 0x0000 0000
SDIO_CLKCR寄存器控制SDIO_CK输出时钟

注意:

1 当SD/SDIO卡或多媒体卡在识别模式, SDIO_CK的频率必须低于400kHz。
2 当所有卡都被赋予了相应的地址后,时钟频率可以改变到卡总线允许的最大频率。
3 写数据后的7个HCLK时钟周期内不能写入这个寄存器。对于SD I/O卡,在读等待期间可以停
止SDIO_CK,此时SDIO_CLKCR寄存器不控制SDIO_CK
 

1.3 SDIO参数寄存器(SDIO_ARG)

地址偏移: 0x08
复位值: 0x0000 0000
SDIO_ARG寄存器包含32位命令参数,它将作为命令的一部分发送到卡中。

1.4 SDIO命令寄存器(SDIO_CMD)

地址偏移: 0x0C
复位值: 0x0000 0000
SDIO_CMD寄存器包含命令索引和命令类型位。命令索引是作为命令的一部分发送到卡中。命
令类型位控制命令通道状态机(CPSM)。
 

注意:

1 写数据后的7个HCLK时钟周期内不能写入这个寄存器。
2 多媒体卡可以发送2种响应: 48位长的短响应,或136位长的长响应。 SD卡和SD I/O卡只能发
送短响应,参数可以根据响应的类型而变化,软件将根据发送的命令区分响应的类型。 CE-ATA
设备只发送短响应
 

1.5 SDIO命令响应寄存器(SDIO_RESPCMD)

地址偏移: 0x10
复位值: 0x0000 0000
SDIO_RESPCMD寄存器包含最后收到的命令响应中的命令索引。如果传输的命令响应不包含
命令索引(长响应或OCR响应),尽管它应该包含111111b(响应中的保留域值),但RESPCMD域
的内容未知。

 

1.6 SDIO响应 1..4 寄存器(SDIO_RESPx)

地址偏移: 0x14 + 4*(x-1),其中 x = 1..4
复位值: 0x0000 0000
SDIO_RESP1/2/3/4寄存器包含卡的状态,即收到响应的部分信息。
 

1.7 SDIO数据定时器寄存器(SDIO_DTIMER)

地址偏移: 0x24
复位值: 0x0000 0000
SDIO_DTIMER寄存器包含以卡总线时钟周期为单位的数据超时时间。
一个计数器从SDIO_DTIMER寄存器加载数值,并在数据通道状态机(DPSM)进入Wait_R或繁忙
状态时进行递减计数,当DPSM处在这些状态时,如果计数器减为0,则设置超时标志。
 

注意 在写入数据控制寄存器进行数据传输之前,必须先写入数据定时器寄存器和数据长度寄存器。

1.8 SDIO数据长度寄存器(SDIO_DLEN)

地址偏移: 0x28
复位值: 0x0000 0000
SDIO_DLEN寄存器包含需要传输的数据字节长度。当数据传输开始时,这个数值被加载到数据
计数器中。
 

 

注意 对于块数据传输,数据长度寄存器中的数值必须是数据块长度(见SDIO_DCTRL)的倍数。在写
入数据控制寄存器进行数据传输之前,必须先写入数据定时器寄存器和数据长度寄存器。

1.9 SDIO数据控制寄存器(SDIO_DCTRL)

地址偏移: 0x2C
复位值: 0x0000 0000
SDIO_DCTRL寄存器控制数据通道状态机(DPSM)。

 

注意 写数据后的7个HCLK时钟周期内不能写入这个寄存器。
 

1.10 SDIO数据计数器寄存器(SDIO_DCOUNT)

地址偏移: 0x30
复位值: 0x0000 0000
当DPSM从空闲状态进入Wait_R或Wait_S状态时, SDIO_DCOUNT寄存器从数据长度寄存器加
载数值(见SDIO_DLEN),在数据传输过程中,该计数器的数值递减直到减为0,然后DPSM进入
空闲状态并设置数据状态结束标志DATAEND。

 

注意 只能在数据传输结束时读这个寄存器

1.11 SDIO状态寄存器(SDIO_STA)

地址偏移: 0x34
复位值: 0x0000 0000
SDIO_STA是一个只读寄存器,它包含两类标志:
● 静态标志(位[23:22、 10:0]):写入SDIO中断清除寄存器(见SDIO_ICR),可以清除这些位。
● 动态标志(位[21:11]):这些位的状态变化根据它们对应的那部分逻辑而变化(例如: FIFO满
和空标志变高或变低随FIFO的数据写入变化)。

 

1.12 SDIO清除中断寄存器(SDIO_ICR)

地址偏移: 0x38
复位值: 0x0000 0000
SDIO_ICR是一个只写寄存器,在对应寄存器位写’1’将清除SDIO_STA状态寄存器中的对应位。
 

 

 

1.13 SDIO中断屏蔽寄存器(SDIO_MASK)

地址偏移: 0x3C
复位值: 0x0000 0000
在对应位置’1’, SDIO_MASK中断屏蔽寄存器决定哪一个状态位产生中断。

 

 

 

1.14 SDIO FIFO计数器寄存器(SDIO_FIFOCNT)

地址偏移: 0x48
复位值: 0x0000 0000
SDIO_FIFOCNT寄存器包含还未写入FIFO或还未从FIFO读出的数据字数目。当在数据控制寄
存器(SDIO_DCTRL)中设置了数据传输使能位DTEN,并且DPSM处于空闲状态时, FIFO计数
器从数据长度寄存器(见SDIO_DLEN)加载数值。如果数据长度未与字对齐(4的倍数),则最后剩
下的1~3个字节被当成一个字处理。
 

1.15 SDIO数据FIFO寄存器(SDIO_FIFO)

地址偏移: 0x80
复位值: 0x0000 0000
接收和发送FIFO是32位的宽度读或写一组寄存器,它在连续的32个地址上包含32个寄存器,
CPU可以使用FIFO读写多个操作数。

1.16 SDIO寄存器映像

下表是SDIO寄存器的总结。
 

 

 

二、开发思路

        略掉。我们按照ST官方文档来学习。从程序中学习他们的开发流程。我觉得这时最快的方式。在外面都学会了以后开创直接的东西的时候才需要按照基础知识到程序的流程。

三、具体实例

使用之前先来看看怎么取消初始化。 

/**
  * @brief  DeInitializes the SDIO interface.
  * @param  None
  * @retval None
  */
void SD_LowLevel_DeInit(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  
  /*!< Disable SDIO Clock */
  SDIO_ClockCmd(DISABLE);
  
  /*!< Set Power State to OFF */
  SDIO_SetPowerState(SDIO_PowerState_OFF);

  /*!< DeInitializes the SDIO peripheral */
  SDIO_DeInit();
  
  /*!< Disable the SDIO AHB Clock */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, DISABLE);

  /*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /*!< Configure PD.02 CMD line */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}

 先关闭时钟和电源。

然后对SDIO外设的相关寄存器进行设置。

/**
  * @brief  Deinitializes the SDIO peripheral registers to their default reset values.
  * @param  None
  * @retval None
  */
void SDIO_DeInit(void)
{
  SDIO->POWER = 0x00000000;
  SDIO->CLKCR = 0x00000000;
  SDIO->ARG = 0x00000000;
  SDIO->CMD = 0x00000000;
  SDIO->DTIMER = 0x00000000;
  SDIO->DLEN = 0x00000000;
  SDIO->DCTRL = 0x00000000;
  SDIO->ICR = 0x00C007FF;
  SDIO->MASK = 0x00000000;
}

其实就是按照流程给对应寄存器写对应的值。

然后关闭AHB总线

这时得注意有没有其它设备使用这条总线。

然后配置使用的引脚模式为浮空输入。

上面的五个引脚分别是四根数据线和一根时钟线。

下面那根是控制线。

看完取消使能再看使能

/**
  * @brief  Initializes the SD Card and put it into StandBy State (Ready for 
  *         data transfer).
  * @param  None
  * @retval None
  */
void SD_LowLevel_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;

  /*!< GPIOC and GPIOD Periph clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);

  /*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /*!< Configure PD.02 CMD line */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
  
  /*!< Enable the SDIO AHB Clock */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE);

  /*!< Enable the DMA2 Clock */
  RCC_AHBPeriphClockCmd(SD_SDIO_DMA_CLK, ENABLE);
}

和取消相反,先使能引脚对应的时钟总线。

将四个数据引脚 和时钟引脚初始化为复用推挽输出。

使能控制引脚和SDIO与DMA的总线。

/**
  * @brief  Allows to erase memory area specified for the given card.
  * @param  startaddr: the start address.
  * @param  endaddr: the end address.
  * @retval SD_Error: SD Card Error code.
  */
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;

  /*!< Check if the card coomnd class supports erase command */
  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);
  }

  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    startaddr /= 512;
    endaddr /= 512;
  }
  
  /*!< According to sd-card spec 1.0 ERASE_GROUP_START (CMD32) and erase_group_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 argument as addr  */
    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 argument as addr  */
    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++)
  {}

  /*!< Wait till the card is in programming state */
  errorstatus = IsCardProgramming(&cardstate);
  delay = SD_DATATIMEOUT;
  while ((delay > 0) && (errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
  {
    errorstatus = IsCardProgramming(&cardstate);
    delay--;
  }

  return(errorstatus);
}

上面这个函数是擦除SD卡某个区域的。

下面的函数是初始化整个SD卡,并让其进入待机状态。

/**
  * @brief  Initializes the SD Card and put it into StandBy State (Ready for data 
  *         transfer).
  * @param  None
  * @retval SD_Error: SD Card Error code.
  */
SD_Error SD_Init(void)
{
  __IO SD_Error errorstatus = SD_OK;
  
  NVIC_Configuration();
  
  /* SDIO Peripheral Low Level Init */
  SD_LowLevel_Init();

  SDIO_DeInit();

  errorstatus = SD_PowerON();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    return(errorstatus);
  }

  errorstatus = SD_InitializeCards();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    return(errorstatus);
  }

  /*!< Configure the SDIO peripheral */
  /*!< SDIO_CK = SDIOCLK / (SDIO_TRANSFER_CLK_DIV + 2) */
  SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
  SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
  SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
  SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  SDIO_Init(&SDIO_InitStructure);

  /*----------------- Read CSD/CID MSD registers ------------------*/
  errorstatus = SD_GetCardInfo(&SDCardInfo);

  if (errorstatus == SD_OK)
  {
    /*----------------- Select Card --------------------------------*/
    errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
  }

  if (errorstatus == SD_OK)
  {
    errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
  }  

  return(errorstatus);
}

 对单个数据块做读写测试。之前学习系统移植的时候,我们了解到SD卡是以512个字节为一个块的。所以这个Blocksize就是一个宏定义的512

/*
 * 函数名:SD_SingleBlockTest
 * 描述  :	单个数据块读写测试
 * 输入  :无
 * 输出  :无
 */
void SD_SingleBlockTest(void)
{  
  /* Fill the buffer to send */
  Fill_Buffer(Buffer_Block_Tx, BLOCK_SIZE, 0x320F);

  if (Status == SD_OK)
  {
    /* Write block of 512 bytes on address 0 */
    Status = SD_WriteBlock(Buffer_Block_Tx, 0x00, BLOCK_SIZE);
		
    /* Check if the Transfer is finished */
    Status = SD_WaitWriteOperation();	  
    while(SD_GetStatus() != SD_TRANSFER_OK); 
  }

  if (Status == SD_OK)
  {
    /* Read block of 512 bytes from address 0 */
    Status = SD_ReadBlock(Buffer_Block_Rx, 0x00, BLOCK_SIZE);//读取数据
    /* Check if the Transfer is finished */
    Status = SD_WaitReadOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  /* Check the correctness of written data */
  if (Status == SD_OK)
  {
    TransferStatus1 = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE);	//比较
  }
  
  if(TransferStatus1 == PASSED)
    printf("》单块读写测试成功!\n" );
 
  else  
  	printf("》单块读写测试失败!\n " );  
}

 这里是自己实现的三个小函数。方便在操作时使用对应功能。

/*
 * 函数名:Buffercmp
 * 描述  :比较两个缓冲区中的数据是否相等
 * 输入  :-pBuffer1, -pBuffer2 : 要比较的缓冲区的指针
 *         -BufferLength 缓冲区长度
 * 输出  :-PASSED 相等
 *         -FAILED 不等
 */
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    if (*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }

    pBuffer1++;
    pBuffer2++;
  }

  return PASSED;
}


/*
 * 函数名:Fill_Buffer
 * 描述  :在缓冲区中填写数据
 * 输入  :-pBuffer 要填充的缓冲区
 *         -BufferLength 要填充的大小
 *         -Offset 填在缓冲区的第一个值
 * 输出  :无 
 */
void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{
  uint16_t index = 0;

  /* Put in global buffer same values */
  for (index = 0; index < BufferLength; index++ )
  {
    pBuffer[index] = index + Offset;
  }
}

/*
 * 函数名:eBuffercmp
 * 描述  :检查缓冲区的数据是否为0
 * 输入  :-pBuffer 要比较的缓冲区
 *         -BufferLength 缓冲区长度        
 * 输出  :PASSED 缓冲区的数据全为0
 *         FAILED 缓冲区的数据至少有一个不为0 
 */
TestStatus eBuffercmp(uint8_t* pBuffer, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    /* In some SD Cards the erased state is 0xFF, in others it's 0x00 */
    if ((*pBuffer != 0xFF) && (*pBuffer != 0x00))//擦除后是0xff或0x00
    {
      return FAILED;
    }

    pBuffer++;
  }

  return PASSED;
}

对多块读写如下:

/*
 * 函数名:SD_MultiBlockTest
 * 描述  :	多数据块读写测试
 * 输入  :无
 * 输出  :无
 */
void SD_MultiBlockTest(void)
{
  /* Fill the buffer to send */
  Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);

  if (Status == SD_OK)
  {
    /* Write multiple block of many bytes on address 0 */
    Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    /* Check if the Transfer is finished */
    Status = SD_WaitWriteOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  if (Status == SD_OK)
  {
    /* Read block of many bytes from address 0 */
    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    /* Check if the Transfer is finished */
    Status = SD_WaitReadOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  /* Check the correctness of written data */
  if (Status == SD_OK)
  {
    TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
  }
  
  if(TransferStatus2 == PASSED)	  
  	printf("》多块读写测试成功!\n " );

  else 
  	printf("》多块读写测试失败!\n  " );  

}

以上驱动程序来自ST官方驱动库。应用层测试demo来自硬石头。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 SDIO驱动SD卡学习及实战指南

发表评论