在使用默认的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。
如果想要极限速度的话:
CPU主频如果设置为80MHz,MMC_CTL设置为0xC3
CPU主频如果设置为240MHz,MMC_CTL设置为0xD3
我这边测试SD卡读写正常。
@SK2024 OK,Got it.
很好!收下了!
我读取小容量卡的时候,CMD16设置块大小失败,不知道是什么原因