W801 的 SDIO 驱动与 FATFS 优化

发布于 2022-04-22 18: 09: 10

一. 项目概述

在使用默认的 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 分频的值对应表如下图所示;
9. png
因此当 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; 
}

[修改后试验结果]
10. png

三. SDIO 驱动与 Fatfs 修改

1. SD 卡相关概念

[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
7. jpg

[2] 不同 SD 卡的读写区别
2G 以内的 SD 卡是字节寻址的, 而 2G 以上的卡只能为块寻址. 举个例子, 当地址为 0x01 时, 2G 内的卡会判定为是从字节地址 0x01 开始读写, 而 2G 以外的卡会判定成为从第一个块开始读写.

[3] SD 卡的初始化与识别
根据 SD2. 0 协议, 上述四类卡片的识别过程如下图所示, 我也写了一个中文版的导图附上.
5. png
6. png

2. SDK 的驱动缺陷

如上面说到的初始化过程, 主要通过 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; 
}

3. 驱动修改

[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; 
}

4. 实验结果

[1] 使用的 SD 卡: 我手头目前只有两张 SD 卡, 一张为 4G 的 SDHC 卡, 另一张为 128MB 的 SD 卡, 如下图所示.
8. jpg

[2] 回显的结果: 如下图所示, 都能成功读写并挂载 Fatfs
1. png
2. png
3. png
4. png

最后附上百度云链接

我个人测试下来没有啥问题了, 如果有啥 bug 的话可以在评论区交流哈
链接: https: //pan. baidu. com/s/1YFtnyO1YzOUhDGhLEiWuXw
提取码: e4fx

6 条评论

发布
问题