联盛德 HLK-W806 (十三): 运行FatFs读写FAT和exFat格式的SD卡/TF卡

目录

  • 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明
    https://www.cnblogs.com/milton/p/15534747.html

  • 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明
    https://www.cnblogs.com/milton/p/15572019.html

  • 联盛德 HLK-W806 (三): 免按键自动下载和复位
    https://www.cnblogs.com/milton/p/15609031.html

  • 联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD
    https://www.cnblogs.com/milton/p/15614304.html

  • 联盛德 HLK-W806 (五): W801开发板上手报告
    https://www.cnblogs.com/milton/p/15621540.html

  • 联盛德 HLK-W806 (六): I2C驱动SSD1306 128x64 OLED液晶屏
    https://www.cnblogs.com/milton/p/15647938.html

  • 联盛德 HLK-W806 (七): 兼容开发板 LuatOS Air103
    https://www.cnblogs.com/milton/p/15676414.html

  • 联盛德 HLK-W806 (八): 4线SPI驱动SSD1306/SSD1315 128x64 OLED液晶屏
    https://www.cnblogs.com/milton/p/15679117.html

  • 联盛德 HLK-W806 (九): 软件SPI和硬件SPI驱动ST7789V液晶LCD
    https://www.cnblogs.com/milton/p/15703529.html

  • 联盛德 HLK-W806 (十): 在 CDK IDE开发环境中使用WM-SDK-W806
    https://www.cnblogs.com/milton/p/15706286.html

  • 联盛德 HLK-W806 (十一): 软件SPI和硬件SPI驱动ST7567液晶LCD
    https://www.cnblogs.com/milton/p/15707325.html

  • 联盛德 HLK-W806 (十二): Makefile组织结构和编译流程说明
    https://www.cnblogs.com/milton/p/15713468.html

  • 联盛德 HLK-W806 (十三): 运行FatFs读写FAT和exFat格式的SD卡/TF卡
    https://www.cnblogs.com/milton/p/15798251.html

关于SD卡和FatFs

SD卡和FatFs的介绍已经在

Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
https://www.cnblogs.com/milton/p/15367647.html

中详细说明, 对其工作机制和通信机制有兴趣的可以阅读. FatFs的作者写了一篇非常不错的介绍,

How to Use MMC/SDC
http://elm-chan.org/docs/mmc/mmc_e.html

, 非常详细, 值得一读.

FatFs的移植

FatFs的文件结构

FatFs的文件结构如下:

├── diskio.c        # 需要负责移植的开发人员实现的方法列表, 需要修改
├── diskio.h        # 对外提供的头文件, 里面定义了状态和返回值的枚举
├── ff.c            # FastFs的主要逻辑实现, 不需要修改
├── ffconf.h        # 配置文件, 根据自己的环境和需求作修改
├── ff.h            # FastFs头文件, 不需要修改
├── ffsystem.c      # 与操作系统相关的实现, 不需要修改
├── ffunicode.c     # 各个编码的字符集, 之前版本都在子目录, 现在合并到一个文件里, 方便多了
├── LICENSE.txt

移植需要实现的方法

FatFs已经将Fat格式的操作作了抽象化, 在新环境下运行FatFs, 只需要实现 diskio.c 中的几个方法

DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);

还需要实现RTC接口, 这样才能在创建文件时写入正确的时间

DWORD get_fattime (void);

W801/W806基于SPI的实现

完整的代码在演示用例下, 使用了最新的R0.14b版本的FatFs

  • GitHub wm-sdk-w806/tree/main/demo/fatfs
    https://github.com/IOsetting/wm-sdk-w806/tree/main/demo/fatfs

  • Gitee wm-sdk-w806/tree/main/demo/fatfs
    https://gitee.com/iosetting/wm-sdk-w806/tree/main/demo/fatfs

其中对以上方法的实现是 fatfs_mmc.c 这个文件, 主要分成三个部分:

SPI基础方法

/* SPI transmit a byte */
static void MMC_SPI_TxByte(uint8_t data)
{
    HAL_SPI_Transmit(&hspi, &data, 1, SPI_TIMEOUT);
}

/* SPI transmit buffer */
static void MMC_SPI_TxBuffer(uint8_t *buffer, uint16_t len)
{
    HAL_SPI_Transmit(&hspi, buffer, len, SPI_TIMEOUT);
}

/* SPI receive a byte */
static uint8_t MMC_SPI_RxByte(void)
{
    uint8_t dummy, data;
    dummy = 0xFF;
    HAL_SPI_TransmitReceive(&hspi, &dummy, &data, 1, SPI_TIMEOUT);
    return data;
}

SD卡通信方法

/* wait SD ready */
static uint8_t MMC_ReadyWait(void)
{
    uint8_t res;
    uint32_t tickstart = HAL_GetTick();
    /* if SD goes ready, receives 0xFF, timeout 500 */
    do 
    {
        res = MMC_SPI_RxByte();
    } while ((res != 0xFF) && (HAL_GetTick() - tickstart < 500));
    return res;
}

/* power on */
static void MMC_PowerOn(void) 
{
    uint8_t args[6];
    uint32_t cnt = 0x1FFF;

    /* transmit bytes to wake up */
    MMC_CS_HIGH;
    for(int i = 0; i < 10; i++)
    {
        MMC_SPI_TxByte(0xFF);
    }

    /* slave select */
    MMC_CS_LOW;

    /* make idle state */
    args[0] = CMD0;        /* CMD0:GO_IDLE_STATE */
    args[1] = 0;
    args[2] = 0;
    args[3] = 0;
    args[4] = 0;
    args[5] = 0x95;        /* CRC */

    MMC_SPI_TxBuffer(args, sizeof(args));

    /* wait response */
    while ((MMC_SPI_RxByte() != 0x01) && cnt)
    {
        cnt--;
    }

    MMC_CS_HIGH;
    MMC_SPI_TxByte(0XFF);

    PowerFlag = 1;
}

/* power off */
static void MMC_PowerOff(void) 
{
    PowerFlag = 0;
}

/* check power flag */
static uint8_t MMC_CheckPower(void) 
{
    return PowerFlag;
}

/* receive data block */
static bool MMC_RxDataBlock(BYTE *buff, UINT len)
{
    uint8_t token;
    uint32_t tickstart = HAL_GetTick();

    /* loop until receive a response or timeout, timeout 200ms */
    do 
    {
        token = MMC_SPI_RxByte();
    } while((token == 0xFF) && (HAL_GetTick() - tickstart < 200));

    /* invalid response */
    if(token != 0xFE) return false;

    /* receive data */
    do {
        *buff++ = MMC_SPI_RxByte();
    } while(len--);

    /* discard CRC */
    MMC_SPI_RxByte();
    MMC_SPI_RxByte();

    return true;
}

/* transmit data block */

static bool MMC_TxDataBlock(const uint8_t *buff, BYTE token)
{
    uint8_t resp = 0;
    uint8_t i = 0;

    /* wait SD ready */
    if (MMC_ReadyWait() != 0xFF) return false;

    /* transmit token */
    MMC_SPI_TxByte(token);

    /* if it's not STOP token, transmit data */
    if (token != 0xFD)
    {
        MMC_SPI_TxBuffer((uint8_t*)buff, 512);

        /* discard CRC */
        MMC_SPI_RxByte();
        MMC_SPI_RxByte();

        /* receive response */
        while (i <= 64)
        {
            resp = MMC_SPI_RxByte();

            /* transmit 0x05 accepted */
            if ((resp & 0x1F) == 0x05) break;
            i++;
        }

        /* recv buffer clear */
        while (MMC_SPI_RxByte() == 0);
    }

    /* transmit 0x05 accepted */
    if ((resp & 0x1F) == 0x05) return true;

    return false;
}

/* transmit command */
static BYTE MMC_SendCmd(BYTE cmd, uint32_t arg)
{
    uint8_t crc, res;

    /* wait SD ready */
    if (MMC_ReadyWait() != 0xFF) return 0xFF;

    /* transmit command */
    MMC_SPI_TxByte(cmd);                     /* Command */
    MMC_SPI_TxByte((uint8_t)(arg >> 24));     /* Argument[31..24] */
    MMC_SPI_TxByte((uint8_t)(arg >> 16));     /* Argument[23..16] */
    MMC_SPI_TxByte((uint8_t)(arg >> 8));     /* Argument[15..8] */
    MMC_SPI_TxByte((uint8_t)arg);             /* Argument[7..0] */

    /* prepare CRC */
    if(cmd == CMD0) crc = 0x95;    /* CRC for CMD0(0) */
    else if(cmd == CMD8) crc = 0x87;    /* CRC for CMD8(0x1AA) */
    else crc = 1;

    /* transmit CRC */
    MMC_SPI_TxByte(crc);

    /* Skip a stuff byte when STOP_TRANSMISSION */
    if (cmd == CMD12) MMC_SPI_RxByte();

    /* receive response */
    uint8_t n = 10;
    do {
        res = MMC_SPI_RxByte();
    } while ((res & 0x80) && --n);

    return res;
}

diskio.c 接口方法的实现

因为篇幅比较长, 这里不贴完整代码了, 具体就是根据SD卡的标准, 维护一个状态变量, 在开始时加电初始化, 判断卡类型, 之后才能进行读写操作.

RTC 接口的实现

因为W806自带了RTC, 所以这个功能很容易实现, 只需要启动PMU后初始化一下RTC就可以使用. 这个方法返回的是4个字节的DWORD, 需要按照格式准备数据.

/**
 * DWORD, 4-bytes, format:
 * 
 * [25,31], year, [0,127] from 1980
 * [21,24], month, [1,12]
 * [16,20], day, [1,31]
 * [11,15], hour, [0,23]
 * [5,10], minute, [0,59]
 * [0,4], second, [0,59]
*/
DWORD MMC_get_fattime(void)
{
    DWORD val;
    val = (rtc_time.Year - 80) << 25;
    val += rtc_time.Month << 21;
    val += rtc_time.Date << 16;
    val += rtc_time.Hours << 11;
    val += rtc_time.Minutes << 5;
    val += rtc_time.Seconds;
    return val;
}

FatFs参数的调整

在当前的移植中, 对以下参数进行了调整

  • FF_USE_STRFUNC 0 -> 1 因为要用 f_puts 方法往文件里写字符串
  • FF_USE_LFN 0 -> 1 开启长文件名支持, 否则文件名只支持8+3格式
  • FF_LBA64 0 -> 1 这个选项和下面的选项, 是用于开启对exfat格式的支持, 这样才能正常挂载和读写64GB的TF卡
  • FF_FS_EXFAT 0 -> 1

演示用例说明

接线

需要6根接线, 连线方式在演示用例的main.c中有说明

/******************************************************************************
 * \brief       Demo code of FatFs on SD Card with RTC
 * \remarks     test-board: HLK-W806-KIT-V1.0
 * 
 *    This test will perform the following tests:
 *      1. Start RTC with time 2022-1-12 12:28:10
 *      2. Mount SD Card
 *      3. Scan SD Card and list files
 *      4. Check if w806test_long_name.txt exists
 *      5. Delete w806test_long_name.txt if it exists
 *      6. Open w806test_long_name.txt, write and close
 *      7. Open w806test_long_name.txt, read and close
 *      8. Display time in main loop
 * 
 *    Pin Wiring:
 *        B14   -> CS/DAT3
 *        B15   -> SCK, SCL, CLK
 *        B16   -> MISO/DAT0/DO
 *        B17   -> MOSI/CMD/DI
 *        GND   -> VSS
 *        3.3V  -> VDD
 * 
******************************************************************************/

演示代码的使用

正确连线后, 编译演示用例并烧录代码,

  • 连接串口观察输出
  • 在读卡器中插入SD卡(或TF卡)
  • 按RESET键
  • 可以观察到依次进行的初始化挂载, 展示文件列表, 检查并删除测试文件, 写入测试文件, 读取测试文件, 最后卸载SD/TF卡的过程

实际演示输出

2GB FAT TF卡

enter main␍␊
MPU_Init␍␊
RTC_Init␍␊
SPI_Init␍␊
MMC_disk_initialize␍␊
␍␊
CMD0␍␊
CMD8... succeeded, SDC V2+␍␊
ACMD41 ACMD41_HCS.. succeeded␍␊
CMD58 80 FF 80 00 type:04␍␊
f_mount succeeded␍␊
total:1922368KB, free:1922360KB␍␊
/w806test_long_name.txt 51␍␊
Test for w806test_long_name.txt...␊
Size: 51␊
Timestamp: 2022/01/12, 12:28␊
Attributes: ----A␊
file deleted␍␊
open to write: w806test_long_name.txt␍␊
close: w806test_long_name.txt␍␊
open to read: w806test_long_name.txt␍␊
read: w806test_long_name.txt␍␊
W806 SD Card I/O Example via SPI␊
-- Planet 4032-877␍␊
done␍␊
close: w806test_long_name.txt
Unmount sd card␍␊
f_unmount succeeded

64GB exFat TF卡

enter main␍␊
MPU_Init␍␊
RTC_Init␍␊
SPI_Init␍␊
MMC_disk_initialize␍␊
␍␊
CMD0␍␊
CMD8... succeeded, SDC V2+␍␊
ACMD41 ACMD41_HCS.. succeeded␍␊
CMD58 C0 FF 80 00 type:0C␍␊
f_mount succeeded␍␊
Total:61036544KB, Free:61029120KB␍␊
/first.txt 54␍␊
/System Volume Information/WPSettings.dat 12␍␊
/System Volume Information/IndexerVolumeGuid 76␍␊
/? 504400␍␊
/? 12608␍␊
Test for w806test_long_name.txt...␊
Size: 51␊
Timestamp: 2022/01/12, 12:28␊
Attributes: ----A␊
file deleted␍␊
open to write: w806test_long_name.txt␍␊
close: w806test_long_name.txt␍␊
open to read: w806test_long_name.txt␍␊
read: w806test_long_name.txt␍␊
W806 SD Card I/O Example via SPI␊
-- Planet 4032-877␍␊
done␍␊
close: w806test_long_name.txt␍␊
Unmount sd card␍␊
f_unmount succeeded

结束

以上是对W801/W806上移植R0.14b版本FatFs的说明, 演示用例因为是作为演示使用, 所以代码并未根据实际需要进行组织, 并且使用了大量的printf.
如果在项目中使用, fatfs_mmc.c 基本可以复用, 但是在main.c中的方法需要重新组织.