xld0932 发表于 2024-2-22 15:35

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

本帖最后由 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来得复杂。

所以从这边我们看出了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)

所以在选用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)

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章节

代码实现:
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_tCRC7 = 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章节

代码实现:
uint8_t SD_Init(void)
{
    uint8_t R7, OCR;

    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 = SD_SPI_ReadWriteByte(0xFF);
      R7 = SD_SPI_ReadWriteByte(0xFF);
      R7 = SD_SPI_ReadWriteByte(0xFF);
      R7 = SD_SPI_ReadWriteByte(0xFF);

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

            SD_SendCommand(CMD58, 0x00000000);

            OCR = SD_SPI_ReadWriteByte(0xFF);
            OCR = SD_SPI_ReadWriteByte(0xFF);
            OCR = SD_SPI_ReadWriteByte(0xFF);
            OCR = 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 = SD_SPI_ReadWriteByte(0xFF);
                  OCR = SD_SPI_ReadWriteByte(0xFF);
                  OCR = SD_SPI_ReadWriteByte(0xFF);
                  OCR = 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);
}
运行结果:

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

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

    R1 = SD_SendCommand(CMD10, 0x00000000);

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

    for (uint8_t i = 0; i < 16; i++)
    {
      CID = 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);
    }

    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));
}
运行结果:

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

代码实现:
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, R1 = 0;

    R1 = SD_SendCommand(CMD9, 0x00000000);

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

    for (uint8_t i = 0; i < 16; i++)
    {
      CSD = 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);
    }

    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\nreadblock 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;
    }
}
运行结果:

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挂载设备时,进行初始化调用的。具体实现如下所示:
/**
* @brief 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的校验码需要读取,具体的移植代码如下所示:
/**
* @brief 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, UINToffset, UINTcount)
{
    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 = 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    0123456789ABCDEF");

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

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

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

在实现这个函数时,通过调用SPI模式下的CMD24命令,CMD24是写入一个完整扇区数据;所以当写入扇区的数据量不满1个扇区字节时,是需要补充写完整的;具体的移植代码如下所示:
/**
* @brief 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);
      }

      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这个宏的配置;下表显示了通过配置选项删除了哪些功能以减少代码空间:

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



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

void Petit_FatFs_Sample(void)
{
    BYTE buff;      /* 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文件系统,监控打印如下所示:

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

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

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时:
添加Petit FAT示例后:

11.附件
《Physical Layer Simplified Specification Version 9.10》手册:
SD_Information信息读取示例程序:
Petit FAT读写示例程序:

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

文章着实不错!!!

wooda 发表于 2025-1-15 13:54

可以可以,表扬表扬。
要是几个注意事项里面的限制能花比较小的代价弥补,就更完美了

goyhuan 发表于 2025-1-16 07:08

如果petit省资源,不知道是牺牲了什么功能或性能为代价?

小小蚂蚁举千斤 发表于 2025-1-22 23:09

非常齐全的功能介绍

qiuming 发表于 2025-4-17 11:50

文章不错,谢谢分析。
页: [1] 2
查看完整版本: MM32G0001与Petit FAT,谁说小资源MCU玩不起文件系统?