返回列表 发新帖我要提问本帖赏金: 200.00元(功能说明)

[MM32生态] MM32G0001与Petit FAT,谁说小资源MCU玩不起文件系统?

[复制链接]
13810|19
 楼主| 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

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

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

  7.         CRC7 = (CRC7 << 1) & 0x7F;
  8.         CRC7^= (XORB << 3) | XORB;
  9.     }

  10.     return (CRC7);
  11. }

  12. uint8_t SD_SendCommand(uint8_t Command, uint32_t Argument)
  13. {
  14.     uint8_t  CRC7 = 0, Response = 0;
  15.     uint64_t Bits = 0;

  16.     Bits   = 0x40 + Command;
  17.     Bits <<= 32;
  18.     Bits  |= Argument;

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

  20.     SD_SPI_NSS_H();
  21.     SD_SPI_ReadWriteByte(0xFF);

  22.     SD_SPI_NSS_L();

  23.     SD_SPI_ReadWriteByte(0x40 + Command);
  24.     SD_SPI_ReadWriteByte(Argument >> 24);
  25.     SD_SPI_ReadWriteByte(Argument >> 16);
  26.     SD_SPI_ReadWriteByte(Argument >> 8);
  27.     SD_SPI_ReadWriteByte(Argument >> 0);
  28.     SD_SPI_ReadWriteByte(CRC7 + 1);

  29.     do
  30.     {
  31.         Response = SD_SPI_ReadWriteByte(0xFF);

  32.     } while(Response & 0x80);

  33.     return (Response);
  34. }

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

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

  4.     SD_SPI_NSS_H();

  5.     for(uint8_t i = 0; i < 16; i++)
  6.     {
  7.         SD_SPI_ReadWriteByte(0xFF);
  8.     }

  9.     if (SD_SendCommand(CMD0, 0x00000000) !=  R1_IN_IDLE_STATE)
  10.     {
  11.         printf("\r\n");
  12.         printf("\r\nUnusable Card!"); return (2);
  13.     }

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

  18.         //to do...
  19.     }
  20.     else
  21.     {
  22.         printf("\r\n");
  23.         printf("\r\nVer2.00 or later SD Memory Card");

  24.         R7[1] = SD_SPI_ReadWriteByte(0xFF);
  25.         R7[2] = SD_SPI_ReadWriteByte(0xFF);
  26.         R7[3] = SD_SPI_ReadWriteByte(0xFF);
  27.         R7[4] = SD_SPI_ReadWriteByte(0xFF);

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

  32.             SD_SendCommand(CMD58, 0x00000000);

  33.             OCR[0] = SD_SPI_ReadWriteByte(0xFF);
  34.             OCR[1] = SD_SPI_ReadWriteByte(0xFF);
  35.             OCR[2] = SD_SPI_ReadWriteByte(0xFF);
  36.             OCR[3] = SD_SPI_ReadWriteByte(0xFF);

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

  44. JMP_ACMD41:
  45.             if (SD_SendCommand(CMD55, 0x00000000) == R1_IN_IDLE_STATE)
  46.             {
  47.                 if (SD_SendCommand(ACMD41, 1UL << 30) == R1_IN_IDLE_STATE)
  48.                 {
  49.                     goto JMP_ACMD41;
  50.                 }
  51.                 else
  52.                 {
  53.                     SD_SendCommand(CMD58, 0x00000000);

  54.                     OCR[0] = SD_SPI_ReadWriteByte(0xFF);
  55.                     OCR[1] = SD_SPI_ReadWriteByte(0xFF);
  56.                     OCR[2] = SD_SPI_ReadWriteByte(0xFF);
  57.                     OCR[3] = SD_SPI_ReadWriteByte(0xFF);

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

  65.                     if (SD_GetField(OCR, sizeof(OCR), 1, 30) != 0)
  66.                     {
  67.                         printf("\r\n");
  68.                         printf("\r\nVer2.00 or later High Capacity or Extended Capacity SD Memory Card");
  69.                     }
  70.                     else
  71.                     {
  72.                         printf("\r\n");
  73.                         printf("\r\nVer2.00 or later Standard Capacity SD Memory Card");
  74.                     }
  75.                 }
  76.             }
  77.             else
  78.             {
  79.                 goto JMP_ACMD41;
  80.             }
  81.         }
  82.         else
  83.         {
  84.             printf("\r\n");
  85.             printf("\r\nUnusable Card!"); return (1);
  86.         }
  87.     }

  88.     SDGetCID();

  89.     SDGetCSD();

  90.     return (0);
  91. }

运行结果:
6.png

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

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

  4.     R1 = SD_SendCommand(CMD10, 0x00000000);

  5.     while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
  6.     {
  7.         __ASM("nop");
  8.     }

  9.     for (uint8_t i = 0; i < 16; i++)
  10.     {
  11.         CID[i] = SD_SPI_ReadWriteByte(0xFF);
  12.     }

  13.     SD_SPI_ReadWriteByte(0xFF);
  14.     SD_SPI_ReadWriteByte(0xFF);

  15.     printf("\r\n");

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

  17.     for (uint8_t i = 0; i < 16; i++)
  18.     {
  19.         if ((i % 8) == 0)
  20.         {
  21.             printf("\r\n");
  22.         }

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

  25.     printf("\r\n");
  26.     printf("\r\nManufacturer ID       (MID) : 0x%02X",     SD_GetField(CID, sizeof(CID),  8, 120));
  27.     printf("\r\nOEM/Application ID    (OID) : %c%c",       SD_GetField(CID, sizeof(CID),  8, 112),
  28.                                                            SD_GetField(CID, sizeof(CID),  8, 104));
  29.     printf("\r\nProduct name          (PNM) : %c%c%c%c%c", SD_GetField(CID, sizeof(CID),  8,  96),
  30.                                                            SD_GetField(CID, sizeof(CID),  8,  88),
  31.                                                            SD_GetField(CID, sizeof(CID),  8,  80),
  32.                                                            SD_GetField(CID, sizeof(CID),  8,  72),
  33.                                                            SD_GetField(CID, sizeof(CID),  8,  64));
  34.     printf("\r\nProduct revision      (PRV) : 0x%02X",     SD_GetField(CID, sizeof(CID),  8,  56));
  35.     printf("\r\nProduct serial number (PSN) : 0x%X",       SD_GetField(CID, sizeof(CID), 32,  24));
  36.     printf("\r\nManufacturing data    (MDT) : 20%02d-%d",  SD_GetField(CID, sizeof(CID),  8,  12),
  37.                                                            SD_GetField(CID, sizeof(CID),  4,   8));
  38. }

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

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

  4.     for (uint8_t i = 0; i < Width; i++)
  5.     {
  6.         Index  = (Start + i) / 8;
  7.         Offset = (Start + i) % 8;

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

  9.         Value |= BitValue << i;
  10.     }

  11.     return Value;
  12. }

  13. void SDGetCSD(void)
  14. {
  15.     uint8_t CSD[16], R1 = 0;

  16.     R1 = SD_SendCommand(CMD9, 0x00000000);

  17.     while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
  18.     {
  19.         __ASM("nop");
  20.     }

  21.     for (uint8_t i = 0; i < 16; i++)
  22.     {
  23.         CSD[i] = SD_SPI_ReadWriteByte(0xFF);
  24.     }

  25.     SD_SPI_ReadWriteByte(0xFF);
  26.     SD_SPI_ReadWriteByte(0xFF);

  27.     printf("\r\n");

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

  29.     for (uint8_t i = 0; i < 16; i++)
  30.     {
  31.         if ((i % 8) == 0)
  32.         {
  33.             printf("\r\n");
  34.         }

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

  37.     printf("\r\n");

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

  39.     switch(CSD_STRUCTURE)
  40.     {
  41.         case 0:
  42.             printf("\r\nCSD Version 1.0 : Standard Capacity");
  43.             break;

  44.         case 1:
  45.             printf("\r\nCSD Version 2.0 : High Capacity and Extended Capacity");
  46.             printf("\r\n");
  47.             printf("\r\ndata read access-time               (TAAC)              : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8, 112));
  48.             printf("\r\ndata read access-time in CLK cycles (NSAC)              : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8, 104));
  49.             printf("\r\nmax. data transfer rate             (TRAN_SPEED)        : 0x%02X",          SD_GetField(CSD, sizeof(CSD),  8,  96));
  50.             printf("\r\ncard command classes                (CCC)               : 0x%X",            SD_GetField(CSD, sizeof(CSD), 12,  84));
  51.             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)));
  52.             printf("\r\npartial blocks for read allowed     (READ_BL_PARTIAL)   : %d",              SD_GetField(CSD, sizeof(CSD),  1,  79));
  53.             printf("\r\nwrite block misalignment            (WRITE_BLK_MISALIGN): %d",              SD_GetField(CSD, sizeof(CSD),  1,  78));
  54.             printf("\r\nread  block misalignment            (READ_BLK_MISALIGN) : %d",              SD_GetField(CSD, sizeof(CSD),  1,  77));
  55.             printf("\r\nDSR implemented                     (DSR_IMP)           : %d",              SD_GetField(CSD, sizeof(CSD),  1,  76));
  56.             printf("\r\ndevice size                         (C_SIZE)            : %d",              SD_GetField(CSD, sizeof(CSD), 22,  48));
  57.             printf("\r\nerase single block enable           (ERASE_BLK_EN)      : %d",              SD_GetField(CSD, sizeof(CSD),  1,  46));
  58.             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);
  59.             printf("\r\nwrite protect group size            (WP_GRP_SIZE)       : %d",              SD_GetField(CSD, sizeof(CSD),  7,  32));
  60.             printf("\r\nwrite protect group enable          (WP_GRP_ENABLE)     : %d",              SD_GetField(CSD, sizeof(CSD),  1,  31));
  61.             printf("\r\nwrite speed factor                  (R2W_FACTOR)        : %d",              SD_GetField(CSD, sizeof(CSD),  3,  26));
  62.             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)));
  63.             printf("\r\npartial blocks for write allowed    (WRITE_BL_PARTIAL)  : %d",              SD_GetField(CSD, sizeof(CSD),  1,  21));
  64.             printf("\r\nFile format group                   (FILE_FORMAT_GRP)   : %d",              SD_GetField(CSD, sizeof(CSD),  1,  15));
  65.             printf("\r\ncopy flag                           (COPY)              : %d",              SD_GetField(CSD, sizeof(CSD),  1,  14));
  66.             printf("\r\npermanent write protection          (PERM_WRITE_PROTECT): %d",              SD_GetField(CSD, sizeof(CSD),  1,  13));
  67.             printf("\r\ntemporary write protection          (TMP_WRITE_PROTECT) : %d",              SD_GetField(CSD, sizeof(CSD),  1,  12));
  68.             printf("\r\nFile format                         (FILE_FORMAT)       : %d",              SD_GetField(CSD, sizeof(CSD),  2,  10));
  69.             printf("\r\nwrite protection until power cycle  (WP_UPC)            : %d",              SD_GetField(CSD, sizeof(CSD),  1,   9));
  70.             printf("\r\n");
  71.             printf("\r\nMemory capacity = (C_SIZE + 1) * 512KByte = %0.3fGB", (double)(SD_GetField(CSD, sizeof(CSD), 22, 48) + 1) * 512 * 1024 / 1024 / 1024 / 1024);
  72.             break;

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

  76.         case 3:
  77.             printf("\r\nReserved");
  78.             break;

  79.         default:
  80.             break;
  81.     }
  82. }

运行结果:
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挂载设备时,进行初始化调用的。具体实现如下所示:
  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url] Initialize Disk Drive
  3.   */
  4. DSTATUS disk_initialize(void)
  5. {
  6.     DSTATUS stat;

  7.     // Put your code here
  8.     stat = SD_Init();

  9.     return (stat);
  10. }

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的校验码需要读取,具体的移植代码如下所示:
  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url] Read Partial Sector
  3.   * @param buff   : Pointer to the destination object
  4.   * @param sector : Sector number (LBA)
  5.   * @param offset : Offset in the sector
  6.   * @param count  : Byte count (bit15:destination)
  7.   */
  8. DRESULT disk_readp(BYTE *buff, DWORD sector, UINT  offset, UINT  count)
  9. {
  10.     DRESULT res;

  11.     // Put your code here
  12.     res = RES_ERROR;

  13.     if (SD_SendCommand(CMD17, sector) == 0)
  14.     {
  15.         while (SD_SPI_ReadWriteByte(0xFF) != 0xFE)
  16.         {
  17.             __ASM("nop");
  18.         }

  19.         for (UINT i = 0; i < offset; i++)
  20.         {
  21.             SD_SPI_ReadWriteByte(0xFF);
  22.         }

  23.         if (buff)
  24.         {
  25.             for (UINT i = 0; i < count; i++)
  26.             {
  27.                 buff[i] = SD_SPI_ReadWriteByte(0xFF);
  28.             }
  29.         }
  30.         else
  31.         {
  32.             for (UINT i = 0; i < count; i++)
  33.             {
  34.                 SD_SPI_ReadWriteByte(0xFF);
  35.             }
  36.         }

  37.         for (UINT i = 0; i < (512 + 2 - offset - count); i++)
  38.         {
  39.             SD_SPI_ReadWriteByte(0xFF);
  40.         }

  41.         res = RES_OK;
  42.     }

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

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

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

  52.         printf("   ");
  53.     }

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

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

  62.     printf("\r\n");
  63. #endif

  64.     return (res);
  65. }

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

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

  10.     res = RES_ERROR;

  11.     if (!buff)
  12.     {
  13.         if (sc)
  14.         {
  15.             // Initiate write process
  16.             if (SD_SendCommand(CMD24, sc) == 0)
  17.             {
  18.                 SD_SPI_ReadWriteByte(0xFF);
  19.                 SD_SPI_ReadWriteByte(0xFE);

  20.                 bw = 512 + 2;   /* BLOCKLEN + CRC16 */
  21.                 res = RES_OK;
  22. #if 0
  23.                 printf("\r\n%s Initiate, sc = %5d, bw = %d", __FUNCTION__, sc, bw);
  24. #endif
  25.             }
  26.         }
  27.         else
  28.         {
  29.             // Finalize write process
  30.             for (DWORD i = 0; i < bw; i++)
  31.             {
  32.                 SD_SPI_ReadWriteByte(0x00);
  33.             }

  34.             /* Wait Data accepted */
  35.             while ((SD_SPI_ReadWriteByte(0xFF) & 0x1F) != 0x05)
  36.             {
  37.                 __ASM("nop");
  38.             }

  39.             while (SD_SPI_ReadWriteByte(0xFF) == 0x00)
  40.             {
  41.                 __ASM("nop");
  42.             }

  43.             res = RES_OK;
  44. #if 0
  45.             printf("\r\n%s Finalize, sc = %d, bw = %d", __FUNCTION__, sc, bw);
  46. #endif
  47.         }
  48.     }
  49.     else
  50.     {
  51.         // Send data to the disk
  52.         for (DWORD i = 0; i < sc; i++)
  53.         {
  54.             SD_SPI_ReadWriteByte(buff[i]);
  55.         }

  56.         bw = bw - sc;
  57.         res = RES_OK;
  58. #if 0
  59.         printf("\r\n%s SendData, sc = %d, bw = %d", __FUNCTION__, sc, bw);
  60. #endif
  61.     }

  62.     return (res);
  63. }

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卡中的文件进行读写了,编写了一个读写文件的示例函数,如下所示:
  1. FATFS fs;               /* Work area (file system object) for the volume */

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

  8.     printf("\r\n");
  9.     printf("\r\n%s", __FUNCTION__);
  10.     printf("\r\n");

  11.     res = pf_mount(&fs);

  12.     if (res == FR_OK)
  13.     {
  14.         printf("\r\npf_mount successed");

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

  16.         res = pf_open("HELLO.TXT");

  17.         if (res == FR_OK)
  18.         {
  19.             printf("\r\npf_open successed");

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

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

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

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


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

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

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

  35.             /* Finalize the current write operation */
  36.             res = pf_write(0, 0, &bw);
  37.             printf("\r\npf_write : %d, bw  = %d,", res, bw);
  38.         }
  39.         else
  40.         {
  41.             printf("\r\npf_open : %d", res);
  42.         }


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

  44.         res = pf_open("HELLO.TXT");

  45.         if (res == FR_OK)
  46.         {
  47.             printf("\r\npf_open successed");

  48.             do
  49.             {
  50.                 res = pf_read(buff, sizeof(buff), &br);

  51.                 if ((res == FR_OK) && (br != 0))
  52.                 {
  53.                     printf("\r\npf_read : %s, br = %d", buff, br);
  54.                 }
  55.                 else
  56.                 {
  57.                     printf("\r\npf_read : %d", res);
  58.                 }
  59.             } while (br != 0);
  60.         }
  61.         else
  62.         {
  63.             printf("\r\npf_open : %d", res);
  64.         }

  65.     }
  66.     else
  67.     {
  68.         printf("\r\npf_mount : %d", res);
  69.     }
  70. }

在函数中,先挂载了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, 下载次数: 56)
SD_Information信息读取示例程序: LibSamples_MM32G0001_V2.0.2_SD_Information.zip (6.12 MB, 下载次数: 49)
Petit FAT读写示例程序: LibSamples_MM32G0001_V2.0.2_Petit_FatFs.zip (6.17 MB, 下载次数: 57)
  

打赏榜单

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

评论

MM32G0001小资源MCU借助Petit FAT,玩转文件系统,读写SD储存卡。文章整体结构完善,内容充实,资料详细,优质原创!  发表于 2024-2-26 13:47
 楼主| 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 | 显示全部楼层
沧海一笑 发表于 2024-2-23 22:53 | 显示全部楼层
up......
呐咯密密 发表于 2024-2-26 10:20 | 显示全部楼层
很不错的内容,这才是好文章,质量很高。
 楼主| xld0932 发表于 2024-2-26 10:36 | 显示全部楼层
呐咯密密 发表于 2024-2-26 10:20
很不错的内容,这才是好文章,质量很高。

 楼主| 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 | 显示全部楼层
文章不错,谢谢分析。
suncat0504 发表于 2025-4-21 11:21 | 显示全部楼层
很棒!突破固有观念,小资源芯片也能玩得起文件系统。
申小林一号 发表于 2025-4-24 18:37 | 显示全部楼层
学习一下
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:King.Xu

77

主题

3023

帖子

38

粉丝
快速回复 在线客服 返回列表 返回顶部