谁说小资源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,你想知道的都在这里。
评论
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 501浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 447浏览
  • 在不断发展的电子元件领域,继电器——作为切换电路的关键设备,正在经历前所未有的技术变革。固态继电器(SSR)和机械继电器之间的争论由来已久。然而,从未来发展的角度来看,固态继电器正逐渐占据上风。本文将从耐用性、速度和能效三个方面,全面剖析固态继电器为何更具优势,并探讨其在行业中的应用与发展趋势。1. 耐用性:经久耐用的设计机械继电器:机械继电器依靠物理触点完成电路切换。然而,随着时间的推移,这些触点因电弧、氧化和材料老化而逐渐磨损,导致其使用寿命有限。因此,它们更适合低频或对切换耐久性要求不高的
    腾恩科技-彭工 2025-01-10 16:15 97浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 7浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 475浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 491浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 457浏览
  • 随着通信技术的迅速发展,现代通信设备需要更高效、可靠且紧凑的解决方案来应对日益复杂的系统。中国自主研发和制造的国产接口芯片,正逐渐成为通信设备(从5G基站到工业通信模块)中的重要基石。这些芯片凭借卓越性能、成本效益及灵活性,满足了现代通信基础设施的多样化需求。 1. 接口芯片在通信设备中的关键作用接口芯片作为数据交互的桥梁,是通信设备中不可或缺的核心组件。它们在设备内的各种子系统之间实现无缝数据传输,支持高速数据交换、协议转换和信号调节等功能。无论是5G基站中的数据处理,还是物联网网关
    克里雅半导体科技 2025-01-10 16:20 436浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 10浏览
  • 电动汽车(EV)正在改变交通运输,为传统内燃机提供更清洁、更高效的替代方案。这种转变的核心是电力电子和能源管理方面的创新,而光耦合器在其中发挥着关键作用。这些不起眼的组件可实现可靠的通信、增强安全性并优化电动汽车系统的性能,使其成为正在进行的革命中不可或缺的一部分。光耦合器,也称为光隔离器,是一种使用光传输电信号的设备。通过隔离高压和低压电路,光耦合器可确保安全性、减少干扰并保持信号完整性。这些特性对于电动汽车至关重要,因为精确控制和安全性至关重要。 光耦合器在电动汽车中的作用1.电池
    腾恩科技-彭工 2025-01-10 16:14 75浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 170浏览
  • 随着全球向绿色能源转型的加速,对高效、可靠和环保元件的需求从未如此强烈。在这种背景下,国产固态继电器(SSR)在实现太阳能逆变器、风力涡轮机和储能系统等关键技术方面发挥着关键作用。本文探讨了绿色能源系统背景下中国固态继电器行业的前景,并强调了2025年的前景。 1.对绿色能源解决方案日益增长的需求绿色能源系统依靠先进的电源管理技术来最大限度地提高效率并最大限度地减少损失。固态继电器以其耐用性、快速开关速度和抗机械磨损而闻名,正日益成为传统机电继电器的首选。可再生能源(尤其是太阳能和风能
    克里雅半导体科技 2025-01-10 16:18 322浏览
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 468浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 100浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦