在使用默认的 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 设置块大小失败, 不知道是什么原因