在使用默認的SDK的SDIO與FATFS過程中,發現原SDK存在如下幾個問題:
【1】無法識別大小為2G以下的卡
【2】fatfs無法正常掛載2G以下的卡
【3】將主頻修改至240M後,卡片讀寫會出現問題
因此我對原來SDK的SDIO驅動和Fatfs的移植部分進行了修改,並解決了這幾個問題。
【1】問題描述,在SDK的wm_main.c中存在如下的函數,是用來設置CPU的總線頻率,最高為240M。為了更好的釋放性能,我將其調整至了240M後出現了SD卡的讀寫不正常的問題。
tls_sys_clk_set(CPU_CLK_240M);
【2】問題的原因:SDIO在mmc模式下,且使用的SD卡片為默認速度配置的話,最高的輸出頻率只能為20M。W801的SDIO掛載在CPU的時鐘總線上,通過分頻得到,原來SDK中的默認分頻數為1/6,當修改主頻後,SDIO的總線頻率也就變成了40M,超過上限無法正常讀寫。
【3】解決的辦法:解決辦法有兩種,一個是修改分頻寄存器的值,一個是修改SD卡的速度配置,我這裡選擇的是前者。SDIO分頻的值對應表如下圖所示;
因此當cpu主頻設置為240M時,需要講分頻改為1/12才能不超過20M的上限,修改的位置在下面這個初始化配置函數中進行修改:
int wm_sdh_config(void)
{
tls_sys_clk sysclk;
tls_sys_clk_get(&sysclk);
SDIO_HOST->MMC_CARDSEL = 0xC0 | (sysclk.cpuclk / 2 - 1);//0xd3; //enable module, enable mmcclk
SDIO_HOST->MMC_CTL = 0xEB; //原來SDK中為D3,這裡要實現分頻為1/12,需要修改為EB,當主頻變化時,這裡也需要對應改變保證不超過20M
SDIO_HOST->MMC_INT_MASK = 0x100; //unmask sdio data interrupt.
SDIO_HOST->MMC_CRCCTL = 0xC0; //
SDIO_HOST->MMC_TIMEOUTCNT = 0xff;
return 0;
}
【修改後試驗結果】
【1】SD卡的分類:
SD卡由mmc卡發展而來,根據協議版本,容量大小可以分為若幹種,因為W801最高支持sd2.0協議,因此我們面對的如下4類卡片:
1.mmc卡
2.SD卡:協議版本為SD1.0,容量大小0-2G
3.SDSC卡:協議版本為SD2.0,容量大小0-2G
4.SDHC卡:協議版本為SD2.0,容量大小2-32G
【2】不同SD卡的讀寫區別
2G以內的SD卡是字節尋址的,而2G以上的卡只能為塊尋址。舉個例子,當地址為0x01時,2G內的卡會判定為是從字節地址0x01開始讀寫,而2G以外的卡會判定成為從第一個塊開始讀寫。
【3】SD卡的初始化與識別
根據SD2.0協議,上述四類卡片的識別過程如下圖所示,我也寫了一個中文版的導圖附上。
如上面說到的初始化過程,主要通過cmd8以及acmd41返回的OCR寄存器的值來識別那四類卡片。這裡我們看一下SDK的原代碼可以發現,原SDK中對於CMD8命令是否響應未做區分,如果未讀到response則直接判定為初始化失敗。同時後續也未對OCR寄存器的CCS位做判斷,導致其無法判斷是為2G容量以內的SDSC卡還是2-32G的SDHC卡,將其統一識別為了SDHC卡,如上面的不同SD卡讀寫區別中可以看到2G以內的卡,是按字節尋址而2G以外的卡,為塊尋址,因為原SDK不做區分導致2G以內的卡能成功初始化,但是會出現讀寫失敗的問題。驅動有問題也會導致fatfs出現問題。
int wm_sd_card_initialize(uint32_t *rca)
{
int ret = -1;
uint32_t respCmd[4];
int recnt = 5;
wm_sdh_config();
//======================================================
// set up
// Test: Init sequence, With response check
// CMD 0 Reset Card
// CMD 8 Get voltage (Only 2.0 Card response to this)
// CMD55 Indicate Next Command are Application specific
// ACMD41 Get Voltage windows
// CMD 2 CID reg
// CMD 3 Get RCA.
//======================================================
begin:
wm_sdh_send_cmd(0, 0, 0x04); //Send CMD0
sm_sdh_wait_interrupt(0, -1);
delay_cnt(1000);
wm_sdh_send_cmd(8, 0x1AA, 0x44); //Send CMD8
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD8 respCmd", (char *)respCmd, 5);
if(respCmd[0] != 0x1AA || (respCmd[1] & 0xFF) != 8)
{
TEST_DEBUG("CMD8 Error\n");
printf("CMD8 Error\n");
if(recnt--)
goto begin;
goto end; //這裡當5次讀取cmd8後的response未成功時,會直接跳轉到初始化失敗,而未做區分
}
while(1)
{
wm_sdh_send_cmd(55, 0, 0x44); //Send CMD55
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD55 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 55)
{
printf("respCmd Error\n");
goto end;
}
wm_sdh_send_cmd(41, 0xC0100000, 0x44); //Send ACMD41
sm_sdh_wait_interrupt(0, -1);
sm_sdh_wait_interrupt(3, 1000); //由於sd規範中,Acmd41返回的crc永遠是11111,也就是應該忽略crc;這裡的crc錯誤應該忽略。
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("ACMD41 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 0x3F) //sd規範定義固定為0x3F,所以導致crc錯誤
{
printf("respCmd Error - 2\n");
goto end;
}
if(respCmd[0] >> 31 & 0x1)
{
TEST_DEBUG("card is ready\n"); //這裡未對CCS位進行判斷,無法區分SDSC與SDHC
printf("card is ready\n");
break;
}
}
wm_sdh_send_cmd(2, 0, 0x54); //Send CMD2
sm_sdh_wait_interrupt(0, -1);
sm_sdh_wait_interrupt(3, 1000);
wm_sdh_get_response(respCmd, 4);
sh_dumpBuffer("CMD2 respCmd", (char *)respCmd, 16);
if((respCmd[3] >> 24 & 0xFF) != 0x3F) //sd規範定義固定為0x3F,所以導致crc錯誤
{
printf("respCmd Error - 3\n");
goto end;
}
wm_sdh_send_cmd(3, 0, 0x44); //Send CMD3
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD3 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 3)
{
printf("respCmd Error - 4\n");
goto end;
}
*rca = respCmd[0] >> 16;
TEST_DEBUG("RCA = %x\n", *rca);
ret = 0;
end:
return ret;
}
【1】修改wm_sdio_host.h,在該頭文件的SD_CardInfo_t結構體中實際上已經定義了一個CardType的變量用來標識卡片類型,這裡我們只需要添加3個宏定義來標明卡片類型。因為mmc卡片不太常用,因此我並沒有增加mmc卡片的識別。
#define CardType_SD 0x01
#define CardType_SDSC 0x02
#define CardType_SDHC 0x03
//該結構體已經在頭文件中定義,可以看到初始SDK已經定義了一個CardType的變量用來標識卡片類型
typedef struct
{
long long CardCapacity;
u32 CardBlockSize;
u16 RCA;
u8 CardType;
} SD_CardInfo_t;
【2】修改wm_sdio_host.c中的wm_sd_card_initialize函數,修改如下:
int wm_sd_card_initialize(uint32_t *rca)
{
int ret = -1;
uint32_t respCmd[4];
int recnt = 5;
u8 temp_type = 0x00;
wm_sdh_config();
//======================================================
// set up
// Test: Init sequence, With response check
// CMD 0 Reset Card
// CMD 8 Get voltage (Only 2.0 Card response to this)
// CMD55 Indicate Next Command are Application specific
// ACMD41 Get Voltage windows
// CMD 2 CID reg
// CMD 3 Get RCA.
//======================================================
begin:
wm_sdh_send_cmd(0, 0, 0x04); //Send CMD0
sm_sdh_wait_interrupt(0, -1);
delay_cnt(1000);
wm_sdh_send_cmd(8, 0x1AA, 0x44); //Send CMD8
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD8 respCmd", (char *)respCmd, 5);
if(respCmd[0] != 0x1AA || (respCmd[1] & 0xFF) != 8)
{
TEST_DEBUG("CMD8 Error\n");
if(recnt--)
goto begin;
temp_type =0x01; // 未收到回複則說明為SD1.0卡片
}
while(1)
{
wm_sdh_send_cmd(55, 0, 0x44); //Send CMD55
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD55 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 55)
goto end;
wm_sdh_send_cmd(41, 0xC0100000, 0x44); //Send ACMD41
sm_sdh_wait_interrupt(0, -1);
sm_sdh_wait_interrupt(3, 1000); //由於sd規範中,Acmd41返回的crc永遠是11111,也就是應該忽略crc;這裡的crc錯誤應該忽略。
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("ACMD41 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 0x3F) //sd規範定義固定為0x3F,所以導致crc錯誤
goto end;
if(respCmd[0] >> 31 & 0x1)
{
TEST_DEBUG("card is ready\n");
// 根據CCS位來判斷為哪一類型的卡片
if ((respCmd[0] >> 30 == 0x3) && (temp_type == 0x0))
{
SDCardInfo.CardType = CardType_SDHC;
printf("\nCardtype[%d]: SDHC\n", SDCardInfo.CardType);
}
else if ((respCmd[0] >> 30 == 0x2) && (temp_type == 0x0))
{
SDCardInfo.CardType = CardType_SDSC;
printf("\nCardtype[%d]: SDSC\n", SDCardInfo.CardType);
}
else if (temp_type == 0x1)
{
SDCardInfo.CardType = CardType_SD;
printf("\nCardtype[%d]: SD\n", SDCardInfo.CardType);
}
break;
}
}
wm_sdh_send_cmd(2, 0, 0x54); //Send CMD2
sm_sdh_wait_interrupt(0, -1);
sm_sdh_wait_interrupt(3, 1000);
wm_sdh_get_response(respCmd, 4);
sh_dumpBuffer("CMD2 respCmd", (char *)respCmd, 16);
if((respCmd[3] >> 24 & 0xFF) != 0x3F) //sd規範定義固定為0x3F,所以導致crc錯誤
goto end;
wm_sdh_send_cmd(3, 0, 0x44); //Send CMD3
sm_sdh_wait_interrupt(0, -1);
wm_sdh_get_response(respCmd, 2);
sh_dumpBuffer("CMD3 respCmd", (char *)respCmd, 5);
if((respCmd[1] & 0xFF) != 3)
goto end;
*rca = respCmd[0] >> 16;
TEST_DEBUG("RCA = %x\n", *rca);
ret = 0;
end:
return ret;
}
【3】對於wm_sdio_host_demo.c的修改,這個demo沒有對不同卡片進行區別采用不同操作,這裡我們通過卡片類型來細化,使得所有卡片都能通過塊讀寫的方式來進行讀寫。這裡的兩個讀寫函數都需要修改,這裡展示sdh_card_wr_sb函數的修改,另一個函數同理。
static int sdh_card_wr_sb(uint32_t rca, uint8_t bus_width, const uint32_t tsize)
{
int ret = -1;
int i = 0;
char* buf = NULL;
char* bufR = NULL;
buf = tls_mem_alloc(512);
if(buf == NULL)
goto end;
bufR = tls_mem_alloc(512);
if(bufR == NULL)
goto end;
random_get_bytes(buf, 512);
TEST_DEBUG("bus width %s\n", bus_width == 0 ? "1bit" : "4bits");
ret = wm_sd_card_set_bus_width(rca, bus_width);
if(ret)
goto end;
ret = wm_sd_card_set_blocklen(0x200); //512
if(ret)
goto end;
for(i=0; i<(tsize/512); i++)
{
//判斷是否是SDHC卡,若是則為塊尋址,否則為字節尋址,需要乘以512
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_block_write(rca, i, buf);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
ret = wm_sd_card_block_write(rca, i * 512, buf);
}
if(ret)
goto end;
}
ret = wm_sd_card_query_status(rca, NULL);
if(ret)
goto end;
for(i=0; i<(tsize/512); i++)
{
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_block_read(rca, i, bufR);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
ret = wm_sd_card_block_read(rca, i * 512, bufR);
}
if(ret)
goto end;
if(memcmp(buf, bufR, 512))
{
ret = -2;
goto end;
}
}
ret = 0;
end:
if(buf)
{
tls_mem_free(buf);
}
if(bufR)
{
tls_mem_free(bufR);
}
TEST_DEBUG("ret %d\n", ret);
return ret;
}
【4】fatfs的diskio.c的修改,原來移植的fatfs文件系統讀寫函數也未對塊讀寫和字節讀寫做區分,會無法成功掛載2G以內的卡需要對disk_write和disk_write函數做如下修改:
static int MMC_disk_write( BYTE *buff, LBA_t sector, UINT count)
{
int ret, i;
int buflen = BLOCK_SIZE*count;
BYTE *wrbuff = buff;
if (((u32)buff)&0x3)
{
wrbuff = tls_mem_alloc(buflen);
if (wrbuff == NULL) /*non aligned 4*/
{
return -1;
}
memcpy(wrbuff, buff, buflen);
}
for( i = 0; i < TRY_COUNT; i++ )
{
if(count == 1)
{
//判斷是否是SDHC卡,若是則為塊尋址,否則為字節尋址,需要乘以512
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_block_write(fs_rca, sector, (char *)wrbuff);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
ret = wm_sd_card_block_write(fs_rca, sector * BLOCK_SIZE, (char *)wrbuff);
}
}
else if(count > 1)
{
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_blocks_write(fs_rca, sector, (char *)wrbuff, buflen);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
for (int j = 0; j < count; j++)
{
ret = wm_sd_card_blocks_write(fs_rca, sector + j* BLOCK_SIZE, (char *)wrbuff, BLOCK_SIZE);
}
}
}
if( ret == 0 )
{
break;
}
}
if(wrbuff != buff)
{
tls_mem_free(wrbuff);
}
return ret;
}
static int MMC_disk_read( BYTE *buff, LBA_t sector, UINT count)
{
int ret, i;
int buflen = BLOCK_SIZE*count;
BYTE *rdbuff = buff;
if (((u32)buff)&0x3) /*non aligned 4*/
{
rdbuff = tls_mem_alloc(buflen);
if (rdbuff == NULL)
{
return -1;
}
}
for( i=0; i<TRY_COUNT; i++ )
{
if(count == 1)
{
//判斷是否是SDHC卡,若是則為塊尋址,否則為字節尋址,需要乘以512
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_block_read(fs_rca, sector, (char *)rdbuff);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
ret = wm_sd_card_block_read(fs_rca, sector * BLOCK_SIZE, (char *)rdbuff);
}
}
else if(count > 1)
{
if (SDCardInfo.CardType == CardType_SDHC)
{
ret = wm_sd_card_blocks_read(fs_rca, sector, (char *)rdbuff, buflen);
}
else if ((SDCardInfo.CardType == CardType_SDSC) || (SDCardInfo.CardType == CardType_SD))
{
for (int j = 0; j < count; j++)
{
ret = wm_sd_card_blocks_read(fs_rca, sector + j* BLOCK_SIZE, (char *)rdbuff, BLOCK_SIZE);
}
}
}
if( ret == 0 )
break;
}
if(rdbuff != buff)
{
if(ret == 0)
{
memcpy(buff, rdbuff, buflen);
}
tls_mem_free(rdbuff);
}
return ret;
}
【1】使用的SD卡:我手頭目前只有兩張SD卡,一張為4G的SDHC卡,另一張為128MB的SD卡,如下圖所示。
【2】回顯的結果:如下圖所示,都能成功讀寫並掛載Fatfs
我個人測試下來沒有啥問題了,如果有啥bug的話可以在評論區交流哈
鏈接:https://pan.baidu.com/s/1YFtnyO1YzOUhDGhLEiWuXw
提取碼:e4fx
修改後的代碼,能發群739265828 文件嗎? 這裡看不出哪些改了。
@lutherluo 那個群滿了,我在5群,這裡給個百度雲鏈接吧
鏈接:https://pan.baidu.com/s/1YFtnyO1YzOUhDGhLEiWuXw
提取碼:e4fx
文章寫的很棒!需要注意的是,CPU主頻是除以2再進行分頻的,也就是240MHZ/2/6 = 20MHZ。
![image.png image.png](/uploads/20220426/ac01cb13b8a879116ccdf8c3dcc724b2.png)
如果想要極限速度的話:
CPU主頻如果設置為80MHz,MMC_CTL設置為0xC3
CPU主頻如果設置為240MHz,MMC_CTL設置為0xD3
我這邊測試SD卡讀寫正常。
@SK2024 OK,Got it.
很好!收下了!
我讀取小容量卡的時候,CMD16設置塊大小失敗,不知道是什麼原因