运动控制器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卡的运行状态。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容