发新帖本帖赏金 200.00元(功能说明)我要提问
返回列表
[MM32生态]

MM32G0001与Petit FAT,谁说小资源MCU玩不起文件系统?

[复制链接]
8791|16
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2024-2-22 15:35 | 显示全部楼层 |阅读模式
本帖最后由 xld0932 于 2024-2-22 17:52 编辑

#申请原创#   @21小跑堂

1.前因
大家熟悉的文件系统有哪些?我首先想到的就是FatFs了,不管是否带有RTOS,移植和参考例程都很多,上手也容易;当然还有其它RTOS自带的文件系统扩展件,比如RT-Thread的DFS、Segger的emFile、uC/OS的uC/FS、FreeRTOS的FreeRTOS-Plus-FAT、ThreadX的FileX等等;但要使用这些文件系统,大家基本上都会选用资源大些的MCU,因为MCU的SRAM和FLASH空间小了,根本就使用不了哈!

最近客户一个项目,功能很简单:周期性的采集传感器的数据,并进行记录;记录使用离线的方式,将数据存放在TF卡里;为什么不使用SPI FLASH进行数据存储呢?第一是因为数据量比较大,市面上最大的SPI FLASH都无法满足要求、第二是SPI FLASH中的数据无法直接读取出来,所以客户就选用TF卡了;但客户对硬件成本很敏感,希望极具性价比;看到这里小伙伴是不是有同感,这样的需求选个资源大些的MCU就搞定了……但成本贵哈!客户使用的外设功能就2个,一个是传感器、另一个就是TF卡,你说用一个48PIN的MCU,都感觉有些浪费资源。

最后在做好了功能验证的前提下,决定选用MM32G0001A1T这块芯片,它有TSSOP20、QFN20和SOP8这三种封装,为了量多有节省客户硬件板面积,我们使用QFN20的封装;此外MM32G0001系列MCU是搭载了高性能Arm Cortex-M0作为内核的32位微控制器,最高工作频率可达到48MHz,具有16KB FLASH程序存储空间和2KB SRAM系统内存空间,丰富的外设满足了客户的产品应用需求,传感器我们使用了1路I2C接口、TF卡我们使用了1路SPI接口。

很多小伙伴已经产生疑惑了,这么小资源的MCU,能带得动文件系统吗?更何况还有其它的应用功能呢?下面见分晓……

2.SD卡接口
我们从SD卡的官网上下载了《Physical Layer Simplified Specification Version 9.10》手册,在3.7章节中介绍和SD卡的引脚及接口定义。对SD卡的操作接口有两种模式,即SD模式和SPI模式;当使用SD模式来操作SD卡时,需要MCU带有SDIO接口外设,一般的MCU还真没有;当使用SPI模式来操作SD卡时,只需要MCU带有SPI接口外设,一般MCU还真带有SPI接口外设,就算没有,使用GPIO来模拟SPI接口通讯时序,也能够实现,但SDIO通过GPIO来模拟通讯时序,显然要比SPI来得复杂。
1.png

所以从这边我们看出了MM32G0001带有硬件SPI接口,完全可以实现对SD卡的操作;且硬件SPI接口实现更方便,通讯速率更快。

3.Petit FAT
Petit FAT是我们大家常用的FatFs文件系统的一个子集,设计它的初衷是应用在内存受限的8位微控制器上的,即使RAM大小小于扇区大小,Petit FAT也可以实现对文件系统的读写操作。Petit FAT具有极小的RAM开销,一个工作空间仅占用44个字节、非常小的代码空间,仅占用2KB~4KB,且代码功能可裁剪、支持FAT12/FAT16/FAT32多种文件系统格式,具体的功能及描述可以参考官网:Petit FAT File System Module (elm-chan.org)
2.png

所以在选用Petit FAT文件系统之后,MM32G0001对文件系统的实现成为了可能!谁也阻挡不了客户极具性价比的心,MM32G0001满足了客户的心愿!接下来我们就需要在MM32G0001上来验证Petit FAT读写TF卡功能了……

4.环境准备
因为前期功能验证,所以客户硬件还没有打板,所以我们需要使用MM32官方的开发板,只有前期验证充分,后面项目在落地的时候就会更顺些!
  • Mini-G0001开发板
  • TF卡模块、TF卡及杜邦线
  • MM32-LINK MINI调试烧录工具
  • USB转TTL串口工具
  • MM32官网下载MM32G0001库函数与例程(V2版本):上海灵动微电子股份有限公司 (mindmotion.com.cn)
3.jpg

5.SD卡SPI模式
在《Physical Layer Simplified Specification Version 9.10》手册的第7章节,专门描述了SPI模式操作SD卡的技术要领。我们要掌握SPI模式下SD卡的初始化流程、SPI模式下SD卡支持的命令、命令发送、CRC校验等等。

5.1.SPI模式下的命令格式:
可以参考《Physical Layer Simplified Specification Version 9.10》手册的7.3.1章节
4.png

代码实现:
uint8_t SD_CalcCRC7(uint64_t Bits, uint8_t BitCount)
{
    uint8_t i = 0, CRC7 = 0, XORB = 0;

    for (i = 1; i <= BitCount; i++)
    {
        XORB = ((CRC7 >> 6) & 1) ^ ((Bits >> (BitCount - i)) & 1);

        CRC7 = (CRC7 << 1) & 0x7F;
        CRC7^= (XORB << 3) | XORB;
    }

    return (CRC7);
}

uint8_t SD_SendCommand(uint8_t Command, uint32_t Argument)
{
    uint8_t  CRC7 = 0, Response = 0;
    uint64_t Bits = 0;

    Bits   = 0x40 + Command;
    Bits <<= 32;
    Bits  |= Argument;

    CRC7 = SD_CalcCRC7(Bits, 40) << 1;

    SD_SPI_NSS_H();
    SD_SPI_ReadWriteByte(0xFF);

    SD_SPI_NSS_L();

    SD_SPI_ReadWriteByte(0x40 + Command);
    SD_SPI_ReadWriteByte(Argument >> 24);
    SD_SPI_ReadWriteByte(Argument >> 16);
    SD_SPI_ReadWriteByte(Argument >> 8);
    SD_SPI_ReadWriteByte(Argument >> 0);
    SD_SPI_ReadWriteByte(CRC7 + 1);

    do
    {
        Response = SD_SPI_ReadWriteByte(0xFF);

    } while(Response & 0x80);

    return (Response);
}

5.2.SPI模式下支持的命令:
这一小节可以参考《Physical Layer Simplified Specification Version 9.10》手册的7.3.1.3章节,这边就不再展开了。

5.3.SPI模式下的初始化流程
可以参考《Physical Layer Simplified Specification Version 9.10》手册的7.2.1章节
5.png

代码实现:
uint8_t SD_Init(void)
{
    uint8_t R7[5], OCR[4];

    SD_SPI_NSS_H();

    for(uint8_t i = 0; i < 16; i++)
    {
        SD_SPI_ReadWriteByte(0xFF);
    }

    if (SD_SendCommand(CMD0, 0x00000000) !=  R1_IN_IDLE_STATE)
    {
        printf("\r\n");
        printf("\r\nUnusable Card!"); return (2);
    }

    if (SD_SendCommand(CMD8, 0x000001AA) & R1_ILLEGAL_COMMAND)
    {
        printf("\r\n");
        printf("\r\nVer1.X SD Memory Card or Not SD Memory Card");

        //to do...
    }
    else
    {
        printf("\r\n");
        printf("\r\nVer2.00 or later SD Memory Card");

        R7[1] = SD_SPI_ReadWriteByte(0xFF);
        R7[2] = SD_SPI_ReadWriteByte(0xFF);
        R7[3] = SD_SPI_ReadWriteByte(0xFF);
        R7[4] = SD_SPI_ReadWriteByte(0xFF);

        if ((R7[3] == 0x01) && (R7[4] == 0xAA))
        {
            printf("\r\n");
            printf("\r\nCompatible voltage range and check pattern is corrent");

            SD_SendCommand(CMD58, 0x00000000);

            OCR[0] = SD_SPI_ReadWriteByte(0xFF);
            OCR[1] = SD_SPI_ReadWriteByte(0xFF);
            OCR[2] = SD_SPI_ReadWriteByte(0xFF);
            OCR[3] = SD_SPI_ReadWriteByte(0xFF);

            printf("\r\n");
            printf("\r\nCard power up status bit(busy) : %d", SD_GetField(OCR, sizeof(OCR), 1, 31));
            printf("\r\nCard Capacity Status(CCS)      : %d", SD_GetField(OCR, sizeof(OCR), 1, 30));
            printf("\r\nUHS-II Card Status             : %d", SD_GetField(OCR, sizeof(OCR), 1, 29));
            printf("\r\nOver 2TB support Status(CO2T)  : %d", SD_GetField(OCR, sizeof(OCR), 1, 27));
            printf("\r\nSwitch to 1.8V Accepted(S18A)  : %d", SD_GetField(OCR, sizeof(OCR), 1, 24));
            printf("\r\nVDD Voltage Window             : %x", SD_GetField(OCR, sizeof(OCR), 9, 15));

JMP_ACMD41:
            if (SD_SendCommand(CMD55, 0x00000000) == R1_IN_IDLE_STATE)
            {
                if (SD_SendCommand(ACMD41, 1UL << 30) == R1_IN_IDLE_STATE)
                {
                    goto JMP_ACMD41;
                }
                else
                {
                    SD_SendCommand(CMD58, 0x00000000);

                    OCR[0] = SD_SPI_ReadWriteByte(0xFF);
                    OCR[1] = SD_SPI_ReadWriteByte(0xFF);
                    OCR[2] = SD_SPI_ReadWriteByte(0xFF);
                    OCR[3] = SD_SPI_ReadWriteByte(0xFF);

                    printf("\r\n");
                    printf("\r\nCard power up status bit(busy) : %d", SD_GetField(OCR, sizeof(OCR), 1, 31));
                    printf("\r\nCard Capacity Status(CCS)      : %d", SD_GetField(OCR, sizeof(OCR), 1, 30));
                    printf("\r\nUHS-II Card Status             : %d", SD_GetField(OCR, sizeof(OCR), 1, 29));
                    printf("\r\nOver 2TB support Status(CO2T)  : %d", SD_GetField(OCR, sizeof(OCR), 1, 27));
                    printf("\r\nSwitch to 1.8V Accepted(S18A)  : %d", SD_GetField(OCR, sizeof(OCR), 1, 24));
                    printf("\r\nVDD Voltage Window             : %x", SD_GetField(OCR, sizeof(OCR), 9, 15));

                    if (SD_GetField(OCR, sizeof(OCR), 1, 30) != 0)
                    {
                        printf("\r\n");
                        printf("\r\nVer2.00 or later High Capacity or Extended Capacity SD Memory Card");
                    }
                    else
                    {
                        printf("\r\n");
                        printf("\r\nVer2.00 or later Standard Capacity SD Memory Card");
                    }
                }
            }
            else
            {
                goto JMP_ACMD41;
            }
        }
        else
        {
            printf("\r\n");
            printf("\r\nUnusable Card!"); return (1);
        }
    }

    SDGetCID();

    SDGetCSD();

    return (0);
}

运行结果:
6.png

5.4.获取SD卡CID信息:
可以参考《Physical Layer Simplified Specification Version 9.10》手册的5.2章节
7.png

代码实现:
void SDGetCID(void)
{
    uint8_t CID[16], R1 = 0;

    R1 = SD_SendCommand(CMD10, 0x00000000);

    while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
    {
        __ASM("nop");
    }

    for (uint8_t i = 0; i < 16; i++)
    {
        CID[i] = SD_SPI_ReadWriteByte(0xFF);
    }

    SD_SPI_ReadWriteByte(0xFF);
    SD_SPI_ReadWriteByte(0xFF);

    printf("\r\n");

    printf("\r\n%s Response : 0x%02X", __FUNCTION__, R1);

    for (uint8_t i = 0; i < 16; i++)
    {
        if ((i % 8) == 0)
        {
            printf("\r\n");
        }

        printf("0x%02X ", CID[i]);
    }

    printf("\r\n");
    printf("\r\nManufacturer ID       (MID) : 0x%02X",     SD_GetField(CID, sizeof(CID),  8, 120));
    printf("\r\nOEM/Application ID    (OID) : %c%c",       SD_GetField(CID, sizeof(CID),  8, 112),
                                                           SD_GetField(CID, sizeof(CID),  8, 104));
    printf("\r\nProduct name          (PNM) : %c%c%c%c%c", SD_GetField(CID, sizeof(CID),  8,  96),
                                                           SD_GetField(CID, sizeof(CID),  8,  88),
                                                           SD_GetField(CID, sizeof(CID),  8,  80),
                                                           SD_GetField(CID, sizeof(CID),  8,  72),
                                                           SD_GetField(CID, sizeof(CID),  8,  64));
    printf("\r\nProduct revision      (PRV) : 0x%02X",     SD_GetField(CID, sizeof(CID),  8,  56));
    printf("\r\nProduct serial number (PSN) : 0x%X",       SD_GetField(CID, sizeof(CID), 32,  24));
    printf("\r\nManufacturing data    (MDT) : 20%02d-%d",  SD_GetField(CID, sizeof(CID),  8,  12),
                                                           SD_GetField(CID, sizeof(CID),  4,   8));
}

运行结果:
8.png

5.5.获取SD卡CSD信息:
可以参考《Physical Layer Simplified Specification Version 9.10》手册的5.3章节,CSD有3个版本,每个版本对应的CSD各个字段的功能定义都不尽相同,如下图列举出的是实验TF卡的Version 2.0版本的CSD定义字段:
9.png

代码实现:
uint32_t SD_GetField(uint8_t *Source, uint8_t Length, uint8_t Width, uint8_t Start)
{
    uint32_t Value = 0, Index = 0, Offset = 0, BitValue = 0;

    for (uint8_t i = 0; i < Width; i++)
    {
        Index  = (Start + i) / 8;
        Offset = (Start + i) % 8;

        BitValue = (Source[(Length - 1) - Index] >> Offset) & 1;

        Value |= BitValue << i;
    }

    return Value;
}

void SDGetCSD(void)
{
    uint8_t CSD[16], R1 = 0;

    R1 = SD_SendCommand(CMD9, 0x00000000);

    while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
    {
        __ASM("nop");
    }

    for (uint8_t i = 0; i < 16; i++)
    {
        CSD[i] = SD_SPI_ReadWriteByte(0xFF);
    }

    SD_SPI_ReadWriteByte(0xFF);
    SD_SPI_ReadWriteByte(0xFF);

    printf("\r\n");

    printf("\r\n%s Response : 0x%02X", __FUNCTION__, R1);

    for (uint8_t i = 0; i < 16; i++)
    {
        if ((i % 8) == 0)
        {
            printf("\r\n");
        }

        printf("0x%02X ", CSD[i]);
    }

    printf("\r\n");

    uint32_t CSD_STRUCTURE = SD_GetField(CSD, sizeof(CSD), 2, 126);

    switch(CSD_STRUCTURE)
    {
        case 0:
            printf("\r\nCSD Version 1.0 : Standard Capacity");
            break;

        case 1:
            printf("\r\nCSD Version 2.0 : High Capacity and Extended Capacity");
            printf("\r\n");
            printf("\r\ndata read access-time               (TAAC)              : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8, 112));
            printf("\r\ndata read access-time in CLK cycles (NSAC)              : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8, 104));
            printf("\r\nmax. data transfer rate             (TRAN_SPEED)        : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8,  96));
            printf("\r\ncard command classes                (CCC)               : 0x%X",            SD_GetField(CSD, sizeof(CSD), 12,  84));
            printf("\r\nmax. read data block length         (READ_BL_LEN)       : %d, %0.0f Byte",  SD_GetField(CSD, sizeof(CSD),  4,  80), pow(2, SD_GetField(CSD, sizeof(CSD), 4, 80)));
            printf("\r\npartial blocks for read allowed     (READ_BL_PARTIAL)   : %d",              SD_GetField(CSD, sizeof(CSD),  1,  79));
            printf("\r\nwrite block misalignment            (WRITE_BLK_MISALIGN): %d",              SD_GetField(CSD, sizeof(CSD),  1,  78));
            printf("\r\nread  block misalignment            (READ_BLK_MISALIGN) : %d",              SD_GetField(CSD, sizeof(CSD),  1,  77));
            printf("\r\nDSR implemented                     (DSR_IMP)           : %d",              SD_GetField(CSD, sizeof(CSD),  1,  76));
            printf("\r\ndevice size                         (C_SIZE)            : %d",              SD_GetField(CSD, sizeof(CSD), 22,  48));
            printf("\r\nerase single block enable           (ERASE_BLK_EN)      : %d",              SD_GetField(CSD, sizeof(CSD),  1,  46));
            printf("\r\nerase sector size                   (SECTOR_SIZE)       : %d, %d KB",       SD_GetField(CSD, sizeof(CSD),  7,  39), 512 * (SD_GetField(CSD, sizeof(CSD), 7, 39) + 1) / 1024);
            printf("\r\nwrite protect group size            (WP_GRP_SIZE)       : %d",              SD_GetField(CSD, sizeof(CSD),  7,  32));
            printf("\r\nwrite protect group enable          (WP_GRP_ENABLE)     : %d",              SD_GetField(CSD, sizeof(CSD),  1,  31));
            printf("\r\nwrite speed factor                  (R2W_FACTOR)        : %d",              SD_GetField(CSD, sizeof(CSD),  3,  26));
            printf("\r\nmax. write data block length        (WRITE_BL_LEN)      : %d, %0.0f Byte",  SD_GetField(CSD, sizeof(CSD),  4,  22), pow(2, SD_GetField(CSD, sizeof(CSD), 4, 22)));
            printf("\r\npartial blocks for write allowed    (WRITE_BL_PARTIAL)  : %d",              SD_GetField(CSD, sizeof(CSD),  1,  21));
            printf("\r\nFile format group                   (FILE_FORMAT_GRP)   : %d",              SD_GetField(CSD, sizeof(CSD),  1,  15));
            printf("\r\ncopy flag                           (COPY)              : %d",              SD_GetField(CSD, sizeof(CSD),  1,  14));
            printf("\r\npermanent write protection          (PERM_WRITE_PROTECT): %d",              SD_GetField(CSD, sizeof(CSD),  1,  13));
            printf("\r\ntemporary write protection          (TMP_WRITE_PROTECT) : %d",              SD_GetField(CSD, sizeof(CSD),  1,  12));
            printf("\r\nFile format                         (FILE_FORMAT)       : %d",              SD_GetField(CSD, sizeof(CSD),  2,  10));
            printf("\r\nwrite protection until power cycle  (WP_UPC)            : %d",              SD_GetField(CSD, sizeof(CSD),  1,   9));
            printf("\r\n");
            printf("\r\nMemory capacity = (C_SIZE + 1) * 512KByte = %0.3fGB", (double)(SD_GetField(CSD, sizeof(CSD), 22, 48) + 1) * 512 * 1024 / 1024 / 1024 / 1024);
            break;

        case 2:
            printf("\r\nCSD Version 3.0 : Ultra Capacity(SDUC)");
            break;

        case 3:
            printf("\r\nReserved");
            break;

        default:
            break;
    }
}

运行结果:
10.png

SPI模式下SD支持的命令有很多,但并不是都需要实现的,为了减少不必要的代码,我们仅实现有需要的部分,比如初始化部分的CMD0、CMD8、CMD55、CMD58、ACMD41等,在后面对SD卡读写操作的CMD17、CMD24等,而对于获取SD卡的CID、CSD信息的命令,则是可以省略不需要实现的。

6.Petit FAT移植
Petit FAT移植都统一在diskio.c文件中实现,diskio.c文件中提供了3个接口函数,分别为disk_initialize、disk_readp、disk_writep;接下面我们对这3个函数进行说明和移植。

6.1.disk_initialize
这个函数是初始化SD卡存储设备的,就是SD卡的初始化流程,我们可以把SD_Init函数放到此处调用,这个函数是在调用pf_mount挂载设备时,进行初始化调用的。具体实现如下所示:
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url] Initialize Disk Drive
  */
DSTATUS disk_initialize(void)
{
    DSTATUS stat;

    // Put your code here
    stat = SD_Init();

    return (stat);
}

6.2.disk_readp
这个函数是读取扇区数据的,它有4个参数,分别为buff、sector、offset、count,其中buff存放读取到的数据,如果buff是空指针,那后面的读取的数据只做空读操作,读到的数据不会存储到内存空间;sector表示扇区的地址,这边需要注意一下,sector不需要再去乘以BLOCKLEN了!这边是在调试的时候趟过的坑^^,offset是扇区中要开始读取数据的偏移位置,count表示要读取的字节数;这边的(offset + count)不得超过扇区大小,即512。

在实现这个函数时,通过调用SPI模式下的CMD17命令,CMD17是读取一个完整扇区的数据,我们在移植的时候空读/省略存储offset之前读取到的数据,从offset开始存储count字节数据,然后对不满一个扇区的数据再做空读/省略存储操作,需要注意的是,每个扇区读取之后还有一个CRC16的校验码需要读取,具体的移植代码如下所示:
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url] Read Partial Sector
  * @param buff   : Pointer to the destination object
  * @param sector : Sector number (LBA)
  * @param offset : Offset in the sector
  * @param count  : Byte count (bit15:destination)
  */
DRESULT disk_readp(BYTE *buff, DWORD sector, UINT  offset, UINT  count)
{
    DRESULT res;

    // Put your code here
    res = RES_ERROR;

    if (SD_SendCommand(CMD17, sector) == 0)
    {
        while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
        {
            __ASM("nop");
        }

        for (UINT i = 0; i < offset; i++)
        {
            SD_SPI_ReadWriteByte(0xFF);
        }

        if (buff)
        {
            for (UINT i = 0; i < count; i++)
            {
                buff[i] = SD_SPI_ReadWriteByte(0xFF);
            }
        }
        else
        {
            for (UINT i = 0; i < count; i++)
            {
                SD_SPI_ReadWriteByte(0xFF);
            }
        }

        for (UINT i = 0; i < (512 + 2 - offset - count); i++)
        {
            SD_SPI_ReadWriteByte(0xFF);
        }

        res = RES_OK;
    }

#if 0
    printf("\r\n%s sector = %d, offset = %d, count = %d", __FUNCTION__, sector, offset, count);

    printf("\r\n Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");

    for (UINT i = 0; i < offset % 16; i++)
    {
        if ((i % 16) == 0)
        {
            printf("\r\n%09X ", sector * 512 + ((offset + 0) / 16) * 16);
        }

        printf("   ");
    }

    for (UINT i = 0; i < count; i++)
    {
        if (((offset + i) % 16) == 0)
        {
            printf("\r\n%09X ", sector * 512 + ((offset + i) / 16) * 16);
        }

        printf("%02X ", buff[i]);
    }

    printf("\r\n");
#endif

    return (res);
}

6.3.disk_writep
这个函数是把数据写入到扇区,这个函数仅有两个参数,分别为buff和sc;这个需要应用搭配来使用:当buff为空指针时,如果sc为0表示数据包写完了,此时进行结束处理操作流程;如果sc不为0表示即将开始写入数据操作,此时sc表示扇区地址,就做好准备。当buff不为空指针时,此时进行数据写入操作,sc表示当前要写入的数据个数。

在实现这个函数时,通过调用SPI模式下的CMD24命令,CMD24是写入一个完整扇区数据;所以当写入扇区的数据量不满1个扇区字节时,是需要补充写完整的;具体的移植代码如下所示:
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url] Write Partial Sector
  * @param buff : Pointer to the data to be written, NULL:Initiate/Finalize write operation
  * @param sc   : Sector number (LBA) or Number of bytes to send
  */
DRESULT disk_writep(const BYTE *buff, DWORD sc)
{
    DRESULT res;
    static DWORD bw = 0;

    res = RES_ERROR;

    if (!buff)
    {
        if (sc)
        {
            // Initiate write process
            if (SD_SendCommand(CMD24, sc) == 0)
            {
                SD_SPI_ReadWriteByte(0xFF);
                SD_SPI_ReadWriteByte(0xFE);

                bw = 512 + 2;   /* BLOCKLEN + CRC16 */
                res = RES_OK;
#if 0
                printf("\r\n%s Initiate, sc = %5d, bw = %d", __FUNCTION__, sc, bw);
#endif
            }
        }
        else
        {
            // Finalize write process
            for (DWORD i = 0; i < bw; i++)
            {
                SD_SPI_ReadWriteByte(0x00);
            }

            /* Wait Data accepted */
            while ((SD_SPI_ReadWriteByte(0xFF) & 0x1F) != 0x05)
            {
                __ASM("nop");
            }

            while (SD_SPI_ReadWriteByte(0xFF) == 0x00)
            {
                __ASM("nop");
            }

            res = RES_OK;
#if 0
            printf("\r\n%s Finalize, sc = %d, bw = %d", __FUNCTION__, sc, bw);
#endif
        }
    }
    else
    {
        // Send data to the disk
        for (DWORD i = 0; i < sc; i++)
        {
            SD_SPI_ReadWriteByte(buff[i]);
        }

        bw = bw - sc;
        res = RES_OK;
#if 0
        printf("\r\n%s SendData, sc = %d, bw = %d", __FUNCTION__, sc, bw);
#endif
    }

    return (res);
}

7.Petit FAT配置
在pffconf.h文件中,是关于Petit FAT的配置,其中有功能函数的使能开关、FAT支持的格式选择、以及PF_USE_LCC这个宏的配置;下表显示了通过配置选项删除了哪些功能以减少代码空间:
11.png

着重说一下PF_USE_LCC这个宏,这边是在调试的时候趟过的坑^^,其默认值为0;当SD卡中的文件名为“HELLO.txt”时,我在使用pf_open函数打开这个文件,会提示:FR_NO_FILE,原因是因为在SD卡的根目录中,所有的文件名和文件后缀名都是大写的,当使用"HELLO.txt"和“HELLO.TXT”进行文本比较时,肯定不会匹配通过,所以解决的办法有两个:一是将PF_USE_LCC的宏值修改为1,二是在pf_open打开文件时,将文件名和文件后缀名都改为大写。
12.png

14.png

13.png

8.Petit FAT示例
在完成移植和配置后,我们就可以对TF卡中的文件进行读写了,编写了一个读写文件的示例函数,如下所示:
FATFS fs;               /* Work area (file system object) for the volume */

void Petit_FatFs_Sample(void)
{
    BYTE buff[16];      /* File read/write buffer */
    UINT br = 0;        /* File read count  */
    UINT bw = 0;        /* File write count  */
    FRESULT res;        /* Petit FatFs function common result code */

    printf("\r\n");
    printf("\r\n%s", __FUNCTION__);
    printf("\r\n");

    res = pf_mount(&fs);

    if (res == FR_OK)
    {
        printf("\r\npf_mount successed");

        printf("\r\n------------------------------pf_write------------------------------");

        res = pf_open("HELLO.TXT");

        if (res == FR_OK)
        {
            printf("\r\npf_open successed");

            memset(buff, 0, sizeof(buff));
            memcpy(buff, "Hello", 5);

            res = pf_lseek(fs.fptr + 0);
            printf("\r\npf_lseek : %d, ofs = %d", res, 0);

            res = pf_write(buff, strlen((char *)buff), &bw);
            printf("\r\npf_write : %d, bw  = %d,", res, bw);

            /* Finalize the current write operation */
            res = pf_write(0, 0, &bw);
            printf("\r\npf_write : %d, bw  = %d,", res, bw);


            memset(buff, 0, sizeof(buff));
            memcpy(buff, "World", 5);

            res = pf_lseek(fs.fptr + 512);
            printf("\r\npf_lseek : %d, ofs = %d", res, 512);

            res = pf_write(buff, strlen((char *)buff), &bw);
            printf("\r\npf_write : %d, bw  = %d,", res, bw);

            /* Finalize the current write operation */
            res = pf_write(0, 0, &bw);
            printf("\r\npf_write : %d, bw  = %d,", res, bw);
        }
        else
        {
            printf("\r\npf_open : %d", res);
        }


        printf("\r\n------------------------------pf_read------------------------------");

        res = pf_open("HELLO.TXT");

        if (res == FR_OK)
        {
            printf("\r\npf_open successed");

            do
            {
                res = pf_read(buff, sizeof(buff), &br);

                if ((res == FR_OK) && (br != 0))
                {
                    printf("\r\npf_read : %s, br = %d", buff, br);
                }
                else
                {
                    printf("\r\npf_read : %d", res);
                }
            } while (br != 0);
        }
        else
        {
            printf("\r\npf_open : %d", res);
        }

    }
    else
    {
        printf("\r\npf_mount : %d", res);
    }
}

在函数中,先挂载了FAT文件系统,监控打印如下所示:
15.png

然后通过pf_open来打开文件,pf_open通过读取根目录扇区的数据进行比较判断,监控打印如下所示:
16.png

在文件打开成功后,我们就可以通过pf_write和pf_read来读写文件中的数据了。
17.png

9.注意事项
9.1.Petit FAT在使用时还是有一些限制的,它无法创建文件,只能打开现有已经存在的文件、无法追加数据和扩展文件大小、文件的时间戳不会更新、只读属性的文件也无法阻止写入操作
9.2.pf_lseek函数的使用,要在pf_open成功之后,其参数ofs需要是扇区的倍数值,才有效;
9.3.pf_write函数写入操作只能在扇区边上启动和停止,就是说一次写一个扇区(512字节),如果不满一个扇区数据,会通根据移植的disk_writep的功能填入0;另外就是一旦启动写操作,就必须正确完成,期间不允许有其它操作函数,否则写入的数据可能会丢失;
9.4.文件写操作顺序:
  • pf_lseek(ofs)                  在启动定稿操作之前,必须将读/写指针移动到扇区边界,否则它将在第一次定稿操作时向下舍入到扇区边界
  • pf_write(buff, btw, &bw) 启动写入操作,将第一批数据写入文件
  • pf_write(buff, btw, &bw) 写入下一批数据,在进行写入操作时,不能使用任何其它文件函数
  • pf_write(0, 0, &bw)        完成当前写入操作,如果读/写指针不在扇区边界上,则扇区中的其余字节将被填充为零


10.程序空间编译比较
没有添加Petit FAT时:
18.png
添加Petit FAT示例后:
19.png

11.附件
《Physical Layer Simplified Specification Version 9.10》手册: Part1 Physical Layer Simplified Specification Version 9.10 December 1, 2023.PDF (5.85 MB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 200.00 元 2024-02-26
理由:恭喜通过原创审核!期待您更多的原创作品~(蓝v达人打赏已提升)

评论
21小跑堂 2024-2-26 13:47 回复TA
MM32G0001小资源MCU借助Petit FAT,玩转文件系统,读写SD储存卡。文章整体结构完善,内容充实,资料详细,优质原创! 
xld0932|  楼主 | 2024-2-22 15:48 | 显示全部楼层
本帖最后由 xld0932 于 2024-2-22 15:53 编辑

使用特权

评论回复
sf116| | 2024-2-22 16:11 | 显示全部楼层

使用特权

评论回复
[鑫森淼焱垚]| | 2024-2-23 09:18 | 显示全部楼层
好贴,点赞

使用特权

评论回复
coody| | 2024-2-23 10:39 | 显示全部楼层
我最早使用1K内存的单片机访问SD卡播放MP3(VS1003解码),还驱动一个OLED,MP3码率320kbps,文件系统支持长文件名,外挂FLASH装字库。

使用特权

评论回复
mbutterfly| | 2024-2-23 11:46 | 显示全部楼层
666

使用特权

评论回复
沧海一笑| | 2024-2-23 22:53 | 显示全部楼层
up......

使用特权

评论回复
呐咯密密| | 2024-2-26 10:20 | 显示全部楼层
很不错的内容,这才是好文章,质量很高。

使用特权

评论回复
xld0932|  楼主 | 2024-2-26 10:36 | 显示全部楼层
呐咯密密 发表于 2024-2-26 10:20
很不错的内容,这才是好文章,质量很高。

使用特权

评论回复
丙丁先生| | 2024-2-27 11:46 | 显示全部楼层
高分

使用特权

评论回复
丙丁先生| | 2024-2-27 11:49 | 显示全部楼层
MM32G0001与Petit FAT   
这个开发板有活动吗?

使用特权

评论回复
丙丁先生| | 2024-2-27 11:49 | 显示全部楼层
MM32G0001与Petit FAT   
这个开发板有活动吗?

使用特权

评论回复
xld0932|  楼主 | 2024-2-27 14:48 | 显示全部楼层
丙丁先生 发表于 2024-2-27 11:49
MM32G0001与Petit FAT   
这个开发板有活动吗?

不知道,我的环境是自备的

使用特权

评论回复
伏尔加的鱼| | 2024-2-28 12:52 | 显示全部楼层
学习了

使用特权

评论回复
gyh974| | 2024-3-1 13:51 | 显示全部楼层
有原理图?SD卡哪几个脚要连接MCU?

使用特权

评论回复
springvirus| | 2024-3-20 17:30 | 显示全部楼层
文章着实不错!!!

使用特权

评论回复
发新帖 本帖赏金 200.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

67

主题

2992

帖子

29

粉丝