谁说小资源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,你想知道的都在这里。
评论 (0)
  • 二位半 5线数码管的驱动方法这个2位半的7段数码管只用5个管脚驱动。如果用常规的7段+共阳/阴则需要用10个管脚。如果把每个段看成独立的灯。5个管脚来点亮,任选其中一个作为COM端时,另外4条线可以单独各控制一个灯。所以实际上最多能驱动5*4 = 20个段。但是这里会有一个小问题。如果想点亮B1,可以让第3条线(P3)置高,P4 置低,其它阳极连P3的灯对应阴极P2 P1都应置高,此时会发现C1也会点亮。实际操作时,可以把COM端线P3设置为PP输出,其它线为OD输出。就可以单独控制了。实际的驱
    southcreek 2025-05-07 15:06 685浏览
  • 后摄像头是长这个样子,如下图。5孔(D-,D+,5V,12V,GND),说的是连接线的个数,如下图。4LED,+12V驱动4颗LED灯珠,给摄像头补光用的,如下图。打开后盖,发现里面有透明白胶(防水)和白色硬胶(固定),用合适的工具,清理其中的胶状物。BOT层,AN3860,Panasonic Semiconductor (松下电器)制造的,Cylinder Motor Driver IC for Video Camera,如下图。TOP层,感光芯片和广角聚焦镜头组合,如下图。感光芯片,看着是玻
    liweicheng 2025-05-07 23:55 579浏览
  • 在印度与巴基斯坦的军事对峙情境下,歼10C的出色表现如同一颗投入平静湖面的巨石,激起层层涟漪,深刻印证了“质量大于数量”这一铁律。军事领域,技术优势就是决定胜负的关键钥匙。歼10C凭借先进的航电系统、强大的武器挂载能力以及卓越的机动性能,在战场上大放异彩。它能够精准捕捉目标,迅速发动攻击,以一敌多却毫不逊色。与之形成鲜明对比的是,单纯依靠数量堆砌的军事力量,在面对先进技术装备时,往往显得力不从心。这一现象绝非局限于军事范畴,在当今社会的各个领域,“质量大于数量”都已成为不可逆转的趋势。在科技行业
    curton 2025-05-11 19:09 59浏览
  • 硅二极管温度传感器是一种基于硅半导体材料特性的测温装置,其核心原理是利用硅二极管的电学参数(如正向压降或电阻)随温度变化的特性实现温度检测。以下是其工作原理、技术特点及典型应用:一、工作原理1、‌PN结温度特性‌硅二极管由PN结构成,当温度变化时,其正向电压 VF与温度呈线性负相关关系。例如,温度每升高1℃,VF约下降2 mV。2、‌电压—温度关系‌通过jing确测量正向电压的微小变化,可推算出环境温度值。部分型号(如SI410)在宽温域内(如1.4 K至475 K)仍能保持高线性度。
    锦正茂科技 2025-05-09 13:52 339浏览
  • UNISOC Miracle Gaming奇迹手游引擎亮点:• 高帧稳帧:支持《王者荣耀》等主流手游90帧高画质模式,连续丢帧率最高降低85%;• 丝滑操控:游戏冷启动速度提升50%,《和平精英》开镜开枪操作延迟降低80%;• 极速网络:专属游戏网络引擎,使《王者荣耀》平均延迟降低80%;• 智感语音:与腾讯GVoice联合,弱网环境仍能保持清晰通话;• 超高画质:游戏画质增强、超级HDR画质、游戏超分技术,优化游戏视效。全球手游市场规模日益壮大,游戏玩家对极致体验的追求愈发苛刻。紫光展锐全新U
    紫光展锐 2025-05-07 17:07 412浏览
  • 飞凌嵌入式作为龙芯合作伙伴,隆重推出FET-2K0300i-S全国产自主可控工业级核心板!FET-2K0300i-S核心板基于龙芯2K0300i工业级处理器开发设计,集成1个64位LA264处理器,主频1GHz,提供高效的计算能力;支持硬件ECC;2K0300i还具备丰富的连接接口USB、SDIO、UART、SPI、CAN-FD、Ethernet、ADC等一应俱全,龙芯2K0300i支持四路CAN-FD接口,具备良好的可靠性、实时性和灵活性,可满足用户多路CAN需求。除性价比超高的国产处理器外,
    飞凌嵌入式 2025-05-07 11:54 118浏览
  • 文/Leon编辑/cc孙聪颖‍《中国家族企业传承研究报告》显示,超四成“企二代” 明确表达接班意愿,展现出对家族企业延续发展的主动担当。中国研究数据服务平台(CNRDS)提供的精准数据进一步佐证:截至 2022 年,已有至少 280 家上市家族企业完成权杖交接,其中八成新任掌门人为创始人之子,凸显家族企业代际传承中 “子承父业” 的主流模式。然而,对于“企二代” 而言,接棒掌舵绝非易事。在瞬息万变的商业环境中,他们既要在白热化的市场竞争中开拓创新、引领企业突破发展瓶颈,又需应对来自父辈管理层的经
    华尔街科技眼 2025-05-06 18:17 64浏览
  • Matter协议是一个由Amazon Alexa、Apple HomeKit、Google Home和Samsung SmartThings等全球科技巨头与CSA联盟共同制定的开放性标准,它就像一份“共生契约”,能让原本相互独立的家居生态在应用层上握手共存,同时它并非另起炉灶,而是以IP(互联网协议)为基础框架,将不同通信协议下的家居设备统一到同一套“语义规则”之下。作为应用层上的互通标准,Matter协议正在重新定义智能家居行业的运行逻辑,它不仅能向下屏蔽家居设备制造商的生态和系统,让设备、平
    华普微HOPERF 2025-05-08 11:40 459浏览
  • 随着智能驾驶时代到来,汽车正转变为移动计算平台。车载AI技术对存储器提出新挑战:既要高性能,又需低功耗和车规级可靠性。贞光科技代理的紫光国芯车规级LPDDR4存储器,以其卓越性能成为国产芯片产业链中的关键一环,为智能汽车提供坚实的"记忆力"支持。作为官方授权代理商,贞光科技通过专业技术团队和完善供应链,让这款国产存储器更好地服务国内汽车厂商。本文将探讨车载AI算力需求现状及贞光科技如何通过紫光国芯LPDDR4产品满足市场需求。 车载AI算力需求激增的背景与挑战智能驾驶推动算力需求爆发式
    贞光科技 2025-05-07 16:54 242浏览
  • 文/郭楚妤编辑/cc孙聪颖‍相较于一众措辞谨慎、毫无掌舵者个人风格的上市公司财报,利亚德的财报显得尤为另类。利亚德光电集团成立于1995年,是一家以LED显示、液晶显示产品设计、生产、销售及服务为主业的高新技术企业。自2016年年报起,无论业绩优劣,董事长李军每年都会在财报末尾附上一首七言打油诗,抒发其对公司当年业绩的感悟。从“三年翻番顺大势”“智能显示我第一”“披荆斩棘幸从容”等词句中,不难窥见李军的雄心壮志。2012年,利亚德(300296.SZ)在深交所创业板上市。成立以来,该公司在细分领
    华尔街科技眼 2025-05-07 19:25 520浏览
  • 这款无线入耳式蓝牙耳机是长这个样子的,如下图。侧面特写,如下图。充电接口来个特写,用的是卡座卡在PCB板子上的,上下夹紧PCB的正负极,如下图。撬开耳机喇叭盖子,如下图。精致的喇叭(HY),如下图。喇叭是由电学产生声学的,具体结构如下图。电池包(AFS 451012  21 12),用黄色耐高温胶带进行包裹(安规需求),加强隔离绝缘的,如下图。451012是电池包的型号,聚合物锂电池+3.7V 35mAh,详细如下图。电路板是怎么拿出来的呢,剪断喇叭和电池包的连接线,底部抽出PCB板子
    liweicheng 2025-05-06 22:58 711浏览
  • 温度传感器的工作原理依据其类型可分为以下几种主要形式:一、热电阻温度传感器利用金属或半导体材料的电阻值随温度变化的特性实现测温:l ‌金属热电阻‌(如铂电阻 Pt100、Pt1000):高温下电阻值呈线性增长,稳定性高,适用于工业精密测温。l ‌热敏电阻‌(NTC/PTC):NTC 热敏电阻阻值随温度升高而下降,PTC 则相反;灵敏度高但线性范围较窄,常用于电子设备温控。二、热电偶传感器基于‌塞贝克效应‌(Seebeck effect):两种不同
    锦正茂科技 2025-05-09 13:31 314浏览
  • 在过去的很长一段时间里,外卖市场呈现出美团和饿了么双寡头垄断的局面。美团凭借先发优势、强大的地推团队以及精细化的运营策略,在市场份额上长期占据领先地位。数据显示,截至2024年上半年,美团外卖以68.2%的市场份额领跑外卖行业,成为当之无愧的行业老大。其业务广泛覆盖,从一线城市的繁华商圈到二三线城市的大街小巷,几乎无处不在,为无数消费者提供便捷的外卖服务。饿了么作为阿里本地生活服务的重要一环,依托阿里强大的资金和技术支持,也在市场中站稳脚跟,以25.4%的份额位居第二。尽管市场份额上与美团有一定
    用户1742991715177 2025-05-06 19:43 134浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦