SD卡存儲容量的計算過程(附帶修正STM32官方庫裏SD卡例程的一個BUG)

前言

  1. SD卡底層驅動代碼量不小,功能稍微有點複雜,其他的功能不說了;本博文主要介紹SD卡V1.0和V2.0版本的SD卡的容量結算;
  2. 在對SD卡進行FATFS文件系統(最新R0.13c版本)移植時,接口函數DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void *buff )會獲取SD卡的三個重要信息作爲f_fdiskf_mkfs函數爲整個SD卡分區掛載文件系統的依據;
  3. 下面的代碼來自於STM32官方提供的固件庫的SD卡例程,但是:例程裏邊有錯誤的地方需要修改,如果不修改有可能會影響到FatFS系統移植時分區的問題,在博文的最後有特別指出;
  4. CSD寄存器(Card-Specific Data register 卡特性數據寄存器)
  5. 看懂本文需要有SD卡開發經驗和FATFS文件系統移植經驗基礎的同志;

1.獲取並處理CSD寄存器數據

步驟:

  • 獲取SD卡CSD寄存器數據。執行SD_GetCardInfo函數,將CSD寄存器的值全部賦值到CSD結構體;要獲取的數據如下表(來自SD卡V2.0協議)
    對於V1.0的卡來說,要獲取下面幾組數據:
    在這裏插入圖片描述
    對於V2.0的卡來說,只需獲取一個數據:
    在這裏插入圖片描述
    所對應的代碼下面列出來,但是注意我們所要用到的V1.0和V2.0的參數本身不多,所有我對結構體做出適當的修剪:
/** 
  * @brief  Card Specific Data: CSD Register   
  */ 
typedef struct
{
  /***省略若干行***/
  __IO uint8_t  RdBlockLen;           /*!< Max. read data block length V1.0計算公式要用到*/   
  /***省略若干行***/
  __IO uint32_t DeviceSize;           /*!< Device Size V1.0和V2.0計算公式都要用到*/   
  /***省略若干行***/
  __IO uint8_t  DeviceSizeMul;        /*!< Device size multiplier */
  /***省略若干行***/
} SD_CSD;

/** 
  * @brief SD Card information 
  * 這個結構體包含了SD卡的CSD寄存器和CID寄存器,這裏我們只討論CSD寄存器
  */
typedef struct
{
  SD_CSD SD_csd;			
  SD_CID SD_cid;					
  uint32_t CardCapacity;  /*!< Card Capacity */   
  uint32_t CardBlockSize; /*!< Card Block Size */
  uint16_t RCA;
  uint8_t CardType;
} SD_CardInfo;  //此結構體將上面兩個結構體都包含了;


/**
  * @brief  Returns information about specific card.
  * @param  cardinfo: pointer to a SD_CardInfo structure that contains all SD card 
  *         information.
  * @retval SD_Error: SD Card Error code.
  */
SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo)
{
  SD_Error errorstatus = SD_OK;
  uint8_t tmp = 0;

/*************************************************************/
/*****************SD_CardInfo結構體填充****************/
/*************************************************************/	

  cardinfo->CardType = (uint8_t)CardType;
  cardinfo->RCA = (uint16_t)RCA;

/**********************************************/
/*****************CSD結構體填充****************/
/**********************************************/	
	
  /*!< Byte 0 */
  tmp = (uint8_t)((CSD_Tab[0] & 0xFF000000) >> 24);
  cardinfo->SD_csd.CSDStruct = (tmp & 0xC0) >> 6;
  cardinfo->SD_csd.SysSpecVersion = (tmp & 0x3C) >> 2;
  cardinfo->SD_csd.Reserved1 = tmp & 0x03;

  /*!< Byte 1 */
  tmp = (uint8_t)((CSD_Tab[0] & 0x00FF0000) >> 16);
  cardinfo->SD_csd.TAAC = tmp;

  /*!< Byte 2 */
  tmp = (uint8_t)((CSD_Tab[0] & 0x0000FF00) >> 8);
  cardinfo->SD_csd.NSAC = tmp;

  /*!< Byte 3 */
  tmp = (uint8_t)(CSD_Tab[0] & 0x000000FF);
  cardinfo->SD_csd.MaxBusClkFrec = tmp;

  /*!< Byte 4 */
  tmp = (uint8_t)((CSD_Tab[1] & 0xFF000000) >> 24);
  cardinfo->SD_csd.CardComdClasses = tmp << 4;

  /*!< Byte 5 */
  tmp = (uint8_t)((CSD_Tab[1] & 0x00FF0000) >> 16);
  cardinfo->SD_csd.CardComdClasses |= (tmp & 0xF0) >> 4;
  cardinfo->SD_csd.RdBlockLen = tmp & 0x0F;

  /*!< Byte 6 */
  tmp = (uint8_t)((CSD_Tab[1] & 0x0000FF00) >> 8);
  cardinfo->SD_csd.PartBlockRead = (tmp & 0x80) >> 7;
  cardinfo->SD_csd.WrBlockMisalign = (tmp & 0x40) >> 6;
  cardinfo->SD_csd.RdBlockMisalign = (tmp & 0x20) >> 5;
  cardinfo->SD_csd.DSRImpl = (tmp & 0x10) >> 4;
  cardinfo->SD_csd.Reserved2 = 0; /*!< Reserved */

  if ((CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1) || (CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0))
  {
    cardinfo->SD_csd.DeviceSize = (tmp & 0x03) << 10;

    /*!< Byte 7 */
    tmp = (uint8_t)(CSD_Tab[1] & 0x000000FF);
    cardinfo->SD_csd.DeviceSize |= (tmp) << 2;

    /*!< Byte 8 */
    tmp = (uint8_t)((CSD_Tab[2] & 0xFF000000) >> 24);
    cardinfo->SD_csd.DeviceSize |= (tmp & 0xC0) >> 6;

    cardinfo->SD_csd.MaxRdCurrentVDDMin = (tmp & 0x38) >> 3;
    cardinfo->SD_csd.MaxRdCurrentVDDMax = (tmp & 0x07);

    /*!< Byte 9 */
    tmp = (uint8_t)((CSD_Tab[2] & 0x00FF0000) >> 16);
    cardinfo->SD_csd.MaxWrCurrentVDDMin = (tmp & 0xE0) >> 5;
    cardinfo->SD_csd.MaxWrCurrentVDDMax = (tmp & 0x1C) >> 2;
    cardinfo->SD_csd.DeviceSizeMul = (tmp & 0x03) << 1;
    /*!< Byte 10 */
    tmp = (uint8_t)((CSD_Tab[2] & 0x0000FF00) >> 8);
    cardinfo->SD_csd.DeviceSizeMul |= (tmp & 0x80) >> 7;
    
    cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) ;
    cardinfo->CardCapacity *= (1 << (cardinfo->SD_csd.DeviceSizeMul + 2));
    cardinfo->CardBlockSize = 1 << (cardinfo->SD_csd.RdBlockLen);
    cardinfo->CardCapacity *= cardinfo->CardBlockSize;
  }
  else if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    /*!< Byte 7 */
    tmp = (uint8_t)(CSD_Tab[1] & 0x000000FF);
    cardinfo->SD_csd.DeviceSize = (tmp & 0x3F) << 16;

    /*!< Byte 8 */
    tmp = (uint8_t)((CSD_Tab[2] & 0xFF000000) >> 24);

    cardinfo->SD_csd.DeviceSize |= (tmp << 8);

    /*!< Byte 9 */
    tmp = (uint8_t)((CSD_Tab[2] & 0x00FF0000) >> 16);

    cardinfo->SD_csd.DeviceSize |= (tmp);

    /*!< Byte 10 */
    tmp = (uint8_t)((CSD_Tab[2] & 0x0000FF00) >> 8);
    
    cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) * 512 * 1024;
    cardinfo->CardBlockSize = 512;    
  }


  cardinfo->SD_csd.EraseGrSize = (tmp & 0x40) >> 6;
  cardinfo->SD_csd.EraseGrMul = (tmp & 0x3F) << 1;

  /*!< Byte 11 */
  tmp = (uint8_t)(CSD_Tab[2] & 0x000000FF);
  cardinfo->SD_csd.EraseGrMul |= (tmp & 0x80) >> 7;
  cardinfo->SD_csd.WrProtectGrSize = (tmp & 0x7F);

  /*!< Byte 12 */
  tmp = (uint8_t)((CSD_Tab[3] & 0xFF000000) >> 24);
  cardinfo->SD_csd.WrProtectGrEnable = (tmp & 0x80) >> 7;
  cardinfo->SD_csd.ManDeflECC = (tmp & 0x60) >> 5;
  cardinfo->SD_csd.WrSpeedFact = (tmp & 0x1C) >> 2;
  cardinfo->SD_csd.MaxWrBlockLen = (tmp & 0x03) << 2;

  /*!< Byte 13 */
  tmp = (uint8_t)((CSD_Tab[3] & 0x00FF0000) >> 16);
  cardinfo->SD_csd.MaxWrBlockLen |= (tmp & 0xC0) >> 6;
  cardinfo->SD_csd.WriteBlockPaPartial = (tmp & 0x20) >> 5;
  cardinfo->SD_csd.Reserved3 = 0;
  cardinfo->SD_csd.ContentProtectAppli = (tmp & 0x01);

  /*!< Byte 14 */
  tmp = (uint8_t)((CSD_Tab[3] & 0x0000FF00) >> 8);
  cardinfo->SD_csd.FileFormatGrouop = (tmp & 0x80) >> 7;
  cardinfo->SD_csd.CopyFlag = (tmp & 0x40) >> 6;
  cardinfo->SD_csd.PermWrProtect = (tmp & 0x20) >> 5;
  cardinfo->SD_csd.TempWrProtect = (tmp & 0x10) >> 4;
  cardinfo->SD_csd.FileFormat = (tmp & 0x0C) >> 2;
  cardinfo->SD_csd.ECC = (tmp & 0x03);

  /*!< Byte 15 */
  tmp = (uint8_t)(CSD_Tab[3] & 0x000000FF);
  cardinfo->SD_csd.CSD_CRC = (tmp & 0xFE) >> 1;
  cardinfo->SD_csd.Reserved4 = 1;

  /***省略若干行***/

  return(errorstatus);
}

  • 通過上面一頓操作我們將想要的信息整理了出來,分別是:
    用於V1.0計算的:C_SIZE,C_SIZE_MULT,READ_BL_LEN
    用於V2.0計算的:C_SIZE;(注意兩個C_SIZE的位寬是不一樣的)

2. V1.0計算公式

在這裏插入圖片描述
代碼如下:

//代碼來自上面歷程
	cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) ;
    cardinfo->CardCapacity *= (1 << (cardinfo->SD_csd.DeviceSizeMul + 2));
    cardinfo->CardBlockSize = 1 << (cardinfo->SD_csd.RdBlockLen);
    cardinfo->CardCapacity *= cardinfo->CardBlockSize;  //最終計算的結果,以字節爲單位;

3. V2.0計算公式

在這裏插入圖片描述
代碼如下:

	//代碼來自上面歷程
	cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) * 512 * 1024; //最終計算的結果,以字節爲單位;
    cardinfo->CardBlockSize = 512;  

特別注意:尤其是V2.0的SD卡最大可以達到32G類型,防止溢出;

4. 但是這裏有一個問題(內存卡小於等於4GB的請忽略下面)

問題:在對SD卡進行FatFS系統移植的時候,我在實驗中發現STM32官方提供的SD卡程序只能支持0~4G以內的SD卡(其實不能說是BUG,嚴格的說是一個移植不兼容的問題);詳細問題情況如下:

  1. 我的內存卡是
    16GB(標籤)== 1610001000*1000=16000000000字節 = 16000000000/1024/1024/1024 = 14.9GB(實際);
    但是實際上廠家並不會這麼精準(唉),所最終我的內存卡的容量最終如下圖(我們暫且取14.6GB):
    在這裏插入圖片描述
  2. 下面對這個SD卡直接用上面移植來的SD卡例程和FATFS文件系統對其進行平均4分區,分區後如下:
    在這裏插入圖片描述
    會發現:有4個相等的有效分區,和一個12G的未分配區間,爲什麼會產生這個問題?14.6GB的內存不應該是都是3.65GB嗎?
    原因:看下面這段代碼,這段代碼是FATFS文件系統中一個命令控制函數,FATFS文件系統用它來獲取分區的依據;
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res = RES_PARERR;

	switch (pdrv) {
	case DEV_SPI_FLASH :

		return res;
	case DEV_SDHC :
		
		switch(cmd)
		{
			
				/* Generic command (Used by FatFs) */
			case CTRL_SYNC:					 res = RES_OK;break;		//確保設備完成了等待寫過程,也就是設備數據緩衝區內的數據寫入了存儲介質;
			case GET_SECTOR_COUNT:	 												//獲取存儲設備中可用扇區數值返回到buff所指向的DWORD中;
						*(DWORD*)buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;    
						res = RES_OK;break;
			case GET_SECTOR_SIZE:		 												//獲取存儲設備中扇區大小返回到buff所指向的DWORD中;
						*(DWORD*)buff = SDCardInfo.CardBlockSize;//SDCardInfo.CardBlockSize;	//扇區大小等於塊大小;
						res = RES_OK;
						break;
			
			case GET_BLOCK_SIZE:					//GET_BLOCK_SIZE:以扇區爲單位,將擦除塊大小返回到buff指向的DWORD變量;
						*(DWORD*)buff = 1;
						res = RES_OK;
						break;
			case CTRL_TRIM:					 			//告訴設備此扇區塊(cmd中包含的地址)不再需要,可以擦除;

						res = RES_OK;
						break;

			default:;break;
		}

		return res;
	}

	return RES_PARERR;
}

(DWORD)buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize; 是問題的原因,在準確點說是: SDCardInfo.CardCapacity這個結構體成員的值發生了錯誤,然而導致這個問題的原因是如下代碼:

	cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) * 512 * 1024; //最終計算的結果,以字節爲單位;
    cardinfo->CardBlockSize = 512;  

對,就是V2.0的計算公式;然而再準確的說,原因是:cardinfo->SD_csd.DeviceSize這個成員變量的數據類型,如下定義:

__IO uint32_t DeviceSize;           /*!< Device Size V1.0和V2.0計算公式都要用到*/   

通過計算可知:uint32_t的最大表示值爲0xFFFF FFFF = (4GB-1Byte),如果超出4G,則勢必會發生溢出,從而導致數據錯誤,再引起分區錯誤;

解決辦法:修改如圖兩個地方即可:
在這裏插入圖片描述
在這裏插入圖片描述
修改後再分區:
在這裏插入圖片描述
基本上完成了4平分,(最後的13M,強迫症表示接受不了!!!)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章