运动控制器27:SD的配置和读写程序解读

初始化和去初始化SD卡

SD_Init

  • 该函数首先将SDIO模块设置为SPI模式SD_LowLevel_Init,在初始化结束以后,再将数据总线设置为4位数据模式。
  • 然后调用SD_PowerON对SD卡进行上电,进行时钟设置和相对位置的设置,以及卡的类型判断。
  • 第三步调用SD_InitializeCards初始化,读出CID和CSD,这些是卡的身份证号码。
  • 身份证验证通过以后,我们将读取的CID和CSD赋值给结构体,用于后面的程序调用和输出显示。
  • 选中该卡以后,我们配置数据总线为4位,SPI完成了任务,1位模式退出。
SD_Error SD_Init(void)
{
   SD_Error errorstatus = SD_OK;
   //外设的端口设置,打开各引脚的时钟,配置为SPI,CLK,MO,MI和CS引脚,以及一个检测引脚。
   //初始化SPI以及打开SPI模块。
   SD_LowLevel_Init();
 
    //SD上电,配置SD卡正常工作的所有参数,主要是时钟和电压检测
    //第一个调用的函数SDIO_Init,其中总线为1位宽度,并配置好时钟。
    //然后调用 SDIO_SetPowerState给SDIO模块上电,然后 SDIO_ClockCmd打开时钟开关,此时可以正常工作了。
    //发送CMD0,把卡进入闲置模式,发送的是 SDIO_SendCommand命令,先将结构体进行填充
    //用CmdError获取一下SD的状态,判断返回为SD_OK后,发送下一条命令
    //CMD8用来获取卡的一些基本信息,返回的是SDIO_Response_Short响应。
    //用CmdResp7Error来获取SD的状态信息,如果是OK,则将卡的版本和容量类型进行读取。
    //发送CMD55表示下面要发送的是APP命令,然后发送ACMD41带RCA=0的应用命令,将SD的RCA进行配置。
    //判断一些状态以后,此函数完成上电操作。
   SDIO_DeInit();
   errorstatus = SD_PowerON();
   if (errorstatus != SD_OK)
   {
     /*!< CMD Response TimeOut (wait for CMDSENT flag) */
     return(errorstatus);
   }
   
   //第三个重要的操作,初始化卡,具体可以参考手册的20.4.4卡识别过程
   //通知所有的激活卡卡发送CID:ALL_SEND_CID,然后等待R2的响应,响应会发回到SDIO的寄存器SDIO_RESP1
   //我们将寄存器的值进行读出后放到  CID_Tab中
   //然后将激活的卡赋予一个地址,用CMD3给此卡分配CRA地址
   //如果卡设置了保护区域,则还需要配置CSD,用CMD9发送以后等待响应,响应的格式还是R2
   //这样,我们就获取了CSD,同CID存入  CSD_Tab中SD_InitializeCards执行完毕。
   errorstatus = SD_InitializeCards();
   if (errorstatus != SD_OK)
   {
     /*!< CMD Response TimeOut (wait for CMDSENT flag) */
     return(errorstatus);
   }
 
   //再调用一次SDIO的初始化配置,前面已经设置过一次了,SPI模式。
   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);
 
   //获取卡信息,包括SD_cid和SD_csd,具体来说,就是收到的127位CID和CSD中获取数据,并赋值给CID和CSD的结构体。
   if (errorstatus == SD_OK)
   {
     errorstatus = SD_GetCardInfo(&SDCardInfo);
   }
 
   //然后发送CMD7命令选择该卡
   if (errorstatus == SD_OK)
   {
     errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
   }
 
    //用SPI配置完成以后,再使能总线宽度为4位,里面对  SDIO_Init进行了重新的初始化。
   if (errorstatus == SD_OK)
   {
     errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
   }  
 
   return(errorstatus);
 }

SD卡读操作

读单块ReadBlock

  • 读单块我们必须设置为512字节,这是SD卡决定的,不能修改。
  • 入口参数包括:从那个地址开始,连续读512字节ReadAddr,以及读取的数据存放的地址readbuff
  • 在进行任何步骤的时候,我们都要双向的查询SD卡和SDIO模块的状态,是否可以进行下一步
 SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, 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;
     ReadAddr /= 512;
   }
   
    //SDIO的数据这里是第一次用,进行一次配置,用BLOCK模式,将SD卡中的数据读入SDIO中。 
   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_DPSM = SDIO_DPSM_Enable;
   SDIO_DataConfig(&SDIO_DataInitStructure);
 
   //发送CMD17进行连续一块的命令,CMD18为连续多块的操作,入口参数当然是块的首地址
   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);
   }
 
    //确认接收的各个状态位都没有错误后,获取FIFO的状态,如果也没有错误,则将8个FIFO中的数据全部读出以后,保存到tempbuff,并且将tempbuff的位置+8。    
    while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
   {
     if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
     {
       for (count = 0; count < 8; count++)
       {
         *(tempbuff + count) = SDIO_ReadData();
       }
       tempbuff += 8;
     }
   }
 
   if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
   {
     SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
     errorstatus = SD_DATA_TIMEOUT;
     return(errorstatus);
   }
   else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
   {
     SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
     errorstatus = SD_DATA_CRC_FAIL;
     return(errorstatus);
   }
   else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
   {
     SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
     errorstatus = SD_RX_OVERRUN;
     return(errorstatus);
   }
   else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
   {
     SDIO_ClearFlag(SDIO_FLAG_STBITERR);
     errorstatus = SD_START_BIT_ERR;
     return(errorstatus);
   }
   //最后一次操作,如果FIFO中还有数据,也继续读出来,一般8位读完以后,当然就没数据啦。
   while (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)
   {
     *tempbuff = SDIO_ReadData();
     tempbuff++;
   }
    //成功读取以后,将所有的标志都清空,等待下一次传输。
   SDIO_ClearFlag(SDIO_STATIC_FLAGS);

    //如果配置为DMA模式,则512数据收到以后,将自动存入到readbuff中。
 #elif defined (SD_DMA_MODE)
     SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
     SDIO_DMACmd(ENABLE);
     SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
 #endif
 
   return(errorstatus);
 }

读多块ReadMultiBlocks

和读单块稍微有不同的是,单块可以用轮询方式,多块我们用DMA方式更好,程序如下:

 SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)
 {
   SD_Error errorstatus = SD_OK;
   TransferError = SD_OK;
   TransferEnd = 0;
   StopCondition = 1;
   SDIO->DCTRL = 0x0;
 
   if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
   {
     BlockSize = 512;
     ReadAddr /= 512;
   }
 
   //上面的参数基本上和单块是一样的,这里我们先发送读多块的命令,块大小还是512.
   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);
   }
     
    //将数据结构体进行配置,这里配置的数据长度为NumberOfBlocks * BlockSize;
   SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
   SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * 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);
 
    //如上文,读多块用的是CMD18命令
   SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
   SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_MULT_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_MULT_BLOCK);
   if (errorstatus != SD_OK)
   {
     return(errorstatus);
   }
 
   //中断配置,所有的数据传输完成后进行中断,开启DMA配置
   SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
   SDIO_DMACmd(ENABLE);
   SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize));
 
   return(errorstatus);
 }

DMA的配置如下:在中断函数中,我们设置了清SDIO_IT_DATAEND标志和中断配置关闭,并返回卡的状态SD_OK;

 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_Cmd(DMA2_Channel4, DISABLE);
 
   //确认SDIO对应的DMA通道为DMA2_Channel4
   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
   DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;
   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
   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);
 
   DMA_Cmd(DMA2_Channel4, ENABLE); 
 }

写单块和写多块

和读单块与多块的流程差不多,不再介绍

SD擦除

先检查CSD中的擦除命令,是否可以擦除
if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
然后获取响应寄存器中的值
if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)
如果SD卡为大容量卡,则按照512一块进行操作,然后发送开始地址和截止地址
然后用CMD32和CMD33发送收尾地址,首地址发送的代码如下:

/*!< 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 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);

然后进入等待状态,我们这里有一个延时函数,如果不用此延时,直接用
errorstatus = IsCardProgramming(&cardstate);
不知道是否OK,延时的计算如下:

maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);
 for (delay = 0; delay < maxdelay; delay++)
   {}

IsCardProgramming用来检查看是否在编程状态,也就是在进行擦除运算中。
此时发送CMD13,并发送CRA后,等待响应后,用respR1 = SDIO_GetResponse(SDIO_RESP1);获取状态,然后我们根据SDIO_RESP1的内容进行判断SD卡的运行状态。

你可能感兴趣的:(运动控制器27:SD的配置和读写程序解读)