谁说小资源MCU玩不起文件系统?这款国产芯片了解一下!

原创 嵌入式ARM 2025-01-15 12:00
一、背景


大家熟悉的文件系统都有哪些?我首先想到的就是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就搞定了……但成本贵哈!客户使用的外设功能就两个,一个是传感器,另一个就是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,能带得动文件系统吗?更何况还有其它的应用功能呢?下面见分晓!

二、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接口实现更方便,通讯速率更快。

三、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卡功能了。


四、环境准备


因为前期功能验证,客户硬件还没有打板,所以我们需要使用MM32官方的开发板,只有前期验证充分,后面项目在落地的时候就会更顺些。


  • Mini-G0001开发板
  • TF卡模块、TF卡及杜邦线
  • MM32-LINK MINI调试烧录工具
  • USB转TTL串口工具
  • MM32官网下载MM32G0001库函数与例程(V2版本):上海灵动微电子股份有限公司(mindmotion.com.cn)



五、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_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章节。



代码实现:


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


运行结果:



5.4 获取SD卡CID信息


这里可以参考《Physical Layer Simplified Specification Version 9.10》手册的5.2章节。



代码实现:


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


运行结果:


5.5 获取SD卡CSD信息

此处可以参考《Physical Layer Simplified Specification Version 9.10》手册的5.3章节,CSD有三个版本,每个版本对应的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[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; }}

运行结果:



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


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


七、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打开文件时,将文件名和文件后缀名都改为大写。





八、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文件系统,监控打印如下所示:


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


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


九、注意事项


首先,Petit FAT在使用时还是有一些限制的,它无法创建文件,只能打开现有已经存在的文件,无法追加数据和扩展文件大小,文件的时间戳不会更新,只读属性的文件也无法阻止写入操作。

其次,pf_lseek函数的使用,要在pf_open成功之后,其参数ofs需要是扇区的倍数值才有效。

第三,pf_write函数写入操作,只能在扇区边上启动和停止。也就是说,一次写一个扇区(512字节),如果不满一个扇区数据,会通根据移植的disk_writep的功能填入0;另外就是一旦启动写操作,就必须正确完成,期间不允许有其它操作函数,否则写入的数据可能会丢失。

文件写操作顺序:
  • pf_lseek(ofs)在启动定稿操作之前,必须将读/写指针移动到扇区边界,否则它将在第一次定稿操作时向下舍入到扇区边界;

  • pf_write(buff, btw, &bw)启动写入操作,将第一批数据写入文件;

  • pf_write(buff, btw, &bw)写入下一批数据,在进行写入操作时,不能使用任何其它文件函数;

  • pf_write(0, 0, &bw)完成当前写入操作,如果读/写指针不在扇区边界上,则扇区中的其余字节将被填充为零。

十、程序空间编译比较


没有添加Petit FAT时:



添加Petit FAT示例后:



十一、附件


  • 《Physical Layer Simplified Specification Version 9.10》手册(5.85MB)
  • SD_Information信息读取示例程序(6.12MB)
  • Petit FAT读写示例程序(6.17MB)


以上就是今天的分享,如果有需要查看原图、代码、附件的小伙伴,请点击底部“阅读原文”进行下载。


END

作者:xld0932
来源:21ic论坛


推荐阅读
GPIO翻转速度到底可以有多快?
这种项目,我不想做第二次了!!!
没想到最近爆火的DeepSeek,真能用来分析电路!

→点关注,不迷路←

嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论
  •   全球仿真软件头部企业核心能力与选型指南   仿真软件行业的领先企业因技术路径和行业聚焦不同形成多元格局,以下从技术能力、市场地位及行业适配性角度,解析全球范围内具有标杆意义的代表性企业:   华盛恒辉科技有限公司   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽
    华盛恒辉l58ll334744 2025-05-16 11:13 117浏览
  • 一、芯片概述:功能与应用场景WTR096录音语音芯片,是一款高性价比、低功耗的语音处理解决方案,专为需要灵活录音与播放功能的智能设备设计。其核心优势在于支持多段录音、内置存储扩展、外挂Flash适配,可广泛应用于智能玩具、家用电器(如语音闹钟、门铃)、工业设备提示器、安防告警系统等领域。二、核心参数与技术特性1. 多段录音能力:灵活应对场景需求WTR096支持最大10段独立录音,用户可通过指令或按键切换不同录音片段。例如:智能门锁可分别录制“欢迎回家”“密码错误”等多段提示音;工业设备可分段存储
    广州唯创电子 2025-05-16 09:04 107浏览
  •   全球仿真软件开发代表性企业解析(按领域分类)   仿真软件开发领域覆盖多行业与技术方向,以下按领域梳理国内外具有代表性的企业及其核心特点:   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在仿真软件,教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽车软件,ERP,系统二次开发,CRM等领域有很多成功案例。   
    华盛恒辉l58ll334744 2025-05-16 10:37 100浏览
  • 一、引言:录音语音芯片的广泛应用录音语音芯片凭借高集成度、低功耗和灵活的扩展性,广泛应用于智能家居、工业设备、玩具、安防系统等领域。此类芯片支持录音、存储及播放功能,但在实际应用中,用户可能遇到录音后播放无声音的问题。本文将从技术原理、常见故障及解决方案展开解析。二、录音语音芯片的工作原理语音芯片的核心流程分为三个阶段:录音与编码:通过麦克风采集音频信号,芯片将其转化为数字信号并压缩存储。存储介质:部分型号支持内置存储,而复杂场景(如长时录音)需外挂Flash芯片扩展容量。播放控制:通过UART
    广州唯创电子 2025-05-16 08:41 107浏览
  • 2025 年 5 月 16 日讯,英伟达首席执行官黄仁勋的专机于北京时间 13:29 抵达中国台北松山机场,其此行引起了广泛关注。黄仁勋此番赴台,主要是为了参加即将于 5 月 20 日至 23 日举行的台北电脑展(Computex 2025),他将在 19 日上午进行主题演讲。此次演讲备受期待,市场猜测其可能会宣布英伟达与富士康、广达等中国台湾地区 AI 服务器公司达成的更多合作,进一步推动 AI 技术的发展和应用。此外,据市场消息,黄仁勋将于 17 日邀请供应链伙伴聚餐,鸿海董事长刘扬伟、纬创
    鲜橙财经 2025-05-16 14:00 132浏览
  • 在现代影像技术的快速发展下,动态范围(Dynamic Range)成为评估影像质量的重要指针之一。无论是无人机在高空拍摄的画面、工作用相机在不同光线条件下的成像表现、还是随身记录仪在运动过程中的稳定录像,「动态范围」的数值高低直接影响画面的清晰度与细节还原能力。然而,拍摄环境中的极端条件,如耀眼的阳光、深沉的阴影或快速变化的光线,都对产品的影像拍摄提出了严峻的挑战。技术难题:不同环境下对影像设备的质量影响本次分享一家网络监控摄影机(Network Cameras) 制造厂商,经常收到客诉提到:在
    百佳泰测试实验室 2025-05-16 14:43 120浏览
  • 公司简介:Ping Identity 是谁?Ping Identity 是全球领先的身份与访问管理(IAM)解决方案提供商,致力于帮助企业安全管理用户身份、控制应用和资源的访问权。作为行业先驱,Ping Identity 的客户遍布全球,其产品广泛应用于金融、医疗、政府等对安全要求极高的行业。然而,作为一家技术驱动型企业,他们同样面临来自软件供应链的安全挑战,特别是在广泛采用开源组件的背景下,如何实现自动化安全管理、降低法律风险并保障客户数据的安全,成为企业可持续发展的关键。挑战:如何保障应用安
    艾体宝IT 2025-05-16 09:35 114浏览
  •   仿真软件开发领域代表性企业概览   在仿真软件开发领域,众多企业以技术特色与行业深耕为核心竞争力,形成差异化布局。以下梳理国内外代表性企业及其核心业务方向:   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽车软件,ERP,系统二次开发,CRM等领域有很多成功案例。
    华盛恒辉l58ll334744 2025-05-16 10:51 105浏览
  • 公司简介:致力于解析非结构化数据的 AI 企业Cinnamon AI 是一家全球性的人工智能技术公司,核心产品聚焦于处理非结构化数据,如电子邮件、语音记录和视频内容。他们提供的 AI 平台,能够帮助客户从海量数据中提取有价值的信息,提升业务效率。项目挑战:多云部署下的 CI/CD 持续优化难题在实际开发中,Cinnamon AI 面临以下几大挑战:需同时支持 AWS、Google Cloud Platform 和 Azure 等多个云平台,以及内部部署版本;希望持续缩短开发周期,提高产品更新频率
    艾体宝IT 2025-05-16 09:31 99浏览
  •   全球仿真软件头部企业核心实力与行业地位解析   仿真软件行业的竞争格局随技术迭代和市场需求动态演变,以下从技术优势、行业应用等维度,梳理全球范围内具备标杆意义的企业:   1. 华盛恒辉科技有限公司   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽车软件,ERP
    华盛恒辉l58ll334744 2025-05-16 11:37 113浏览
  • FM(RDS) 与 DAB 是车用音响领域常见的两种广播接收方式。随着科技的进步,DAB 作为数字音频传输的代表,逐渐受到更多关注。FM(RDS)属于传统的调频广播,透过调变载波的频率来传送声音讯号。RDS(Radio Data System) 则是FM广播的一种辅助技术,可以在FM讯号中夹带额外的文字信息,例如电台名称、节目类型、交通讯息等。DAB(Digital Audio Broadcasting)数字音频传输(DAB)是透过数字化处理广播音频讯号的传输,音质较 FM 清晰,此外,除了一般
    百佳泰测试实验室 2025-05-16 11:06 123浏览
  •   北京仿真软件开发代表性企业解析   北京作为国内仿真技术创新高地,集聚多家深耕细分领域的领先企业,其技术布局与行业适配性各具特色。以下为核心企业的优势解析及选型参考:   一、华盛恒辉科技有限公司   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽车软件,ERP,
    华盛恒辉l58ll334744 2025-05-16 16:00 123浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦