本帖最后由 xld0932 于 2024-2-22 17:52 编辑
#申请原创# @21小跑堂
1.前因
大家熟悉的文件系统有哪些?我首先想到的就是FatFs了,不管是否带有RTOS,移植和参考例程都很多,上手也容易;当然还有其它RTOS自带的文件系统扩展件,比如RT-Thread的DFS、Segger的emFile、uC/OS的uC/FS、FreeRTOS的FreeRTOS-Plus-FAT、ThreadX的FileX等等;但要使用这些文件系统,大家基本上都会选用资源大些的MCU,因为MCU的SRAM和FLASH空间小了,根本就使用不了哈!
最近客户一个项目,功能很简单:周期性的采集传感器的数据,并进行记录;记录使用离线的方式,将数据存放在TF卡里;为什么不使用SPI FLASH进行数据存储呢?第一是因为数据量比较大,市面上最大的SPI FLASH都无法满足要求、第二是SPI FLASH中的数据无法直接读取出来,所以客户就选用TF卡了;但客户对硬件成本很敏感,希望极具性价比;看到这里小伙伴是不是有同感,这样的需求选个资源大些的MCU就搞定了……但成本贵哈!客户使用的外设功能就2个,一个是传感器、另一个就是TF卡,你说用一个48PIN的MCU,都感觉有些浪费资源。
最后在做好了功能验证的前提下,决定选用MM32G0001A1T这块芯片,它有TSSOP20、QFN20和SOP8这三种封装,为了量多有节省客户硬件板面积,我们使用QFN20的封装;此外MM32G0001系列MCU是搭载了高性能Arm Cortex-M0作为内核的32位微控制器,最高工作频率可达到48MHz,具有16KB FLASH程序存储空间和2KB SRAM系统内存空间,丰富的外设满足了客户的产品应用需求,传感器我们使用了1路I2C接口、TF卡我们使用了1路SPI接口。
很多小伙伴已经产生疑惑了,这么小资源的MCU,能带得动文件系统吗?更何况还有其它的应用功能呢?下面见分晓……
2.SD卡接口
我们从SD卡的官网上下载了《Physical Layer Simplified Specification Version 9.10》手册,在3.7章节中介绍和SD卡的引脚及接口定义。对SD卡的操作接口有两种模式,即SD模式和SPI模式;当使用SD模式来操作SD卡时,需要MCU带有SDIO接口外设,一般的MCU还真没有;当使用SPI模式来操作SD卡时,只需要MCU带有SPI接口外设,一般MCU还真带有SPI接口外设,就算没有,使用GPIO来模拟SPI接口通讯时序,也能够实现,但SDIO通过GPIO来模拟通讯时序,显然要比SPI来得复杂。
所以从这边我们看出了MM32G0001带有硬件SPI接口,完全可以实现对SD卡的操作;且硬件SPI接口实现更方便,通讯速率更快。
3.Petit FAT
Petit FAT是我们大家常用的FatFs文件系统的一个子集,设计它的初衷是应用在内存受限的8位微控制器上的,即使RAM大小小于扇区大小,Petit FAT也可以实现对文件系统的读写操作。Petit FAT具有极小的RAM开销,一个工作空间仅占用44个字节、非常小的代码空间,仅占用2KB~4KB,且代码功能可裁剪、支持FAT12/FAT16/FAT32多种文件系统格式,具体的功能及描述可以参考官网:Petit FAT File System Module (elm-chan.org)
所以在选用Petit FAT文件系统之后,MM32G0001对文件系统的实现成为了可能!谁也阻挡不了客户极具性价比的心,MM32G0001满足了客户的心愿!接下来我们就需要在MM32G0001上来验证Petit FAT读写TF卡功能了……
4.环境准备
因为前期功能验证,所以客户硬件还没有打板,所以我们需要使用MM32官方的开发板,只有前期验证充分,后面项目在落地的时候就会更顺些!
- Mini-G0001开发板
- TF卡模块、TF卡及杜邦线
- MM32-LINK MINI调试烧录工具
- USB转TTL串口工具
- MM32官网下载MM32G0001库函数与例程(V2版本):上海灵动微电子股份有限公司 (mindmotion.com.cn)
5.SD卡SPI模式
在《Physical Layer Simplified Specification Version 9.10》手册的第7章节,专门描述了SPI模式操作SD卡的技术要领。我们要掌握SPI模式下SD卡的初始化流程、SPI模式下SD卡支持的命令、命令发送、CRC校验等等。
5.1.SPI模式下的命令格式:
可以参考《Physical Layer Simplified Specification Version 9.10》手册的7.3.1章节
代码实现:
uint8_t SD_CalcCRC7(uint64_t Bits, uint8_t BitCount)
{
uint8_t i = 0, CRC7 = 0, XORB = 0;
for (i = 1; i <= BitCount; i++)
{
XORB = ((CRC7 >> 6) & 1) ^ ((Bits >> (BitCount - i)) & 1);
CRC7 = (CRC7 << 1) & 0x7F;
CRC7^= (XORB << 3) | XORB;
}
return (CRC7);
}
uint8_t SD_SendCommand(uint8_t Command, uint32_t Argument)
{
uint8_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有3个版本,每个版本对应的CSD各个字段的功能定义都不尽相同,如下图列举出的是实验TF卡的Version 2.0版本的CSD定义字段:
代码实现:
uint32_t SD_GetField(uint8_t *Source, uint8_t Length, uint8_t Width, uint8_t Start)
{
uint32_t Value = 0, Index = 0, Offset = 0, BitValue = 0;
for (uint8_t i = 0; i < Width; i++)
{
Index = (Start + i) / 8;
Offset = (Start + i) % 8;
BitValue = (Source[(Length - 1) - Index] >> Offset) & 1;
Value |= BitValue << i;
}
return Value;
}
void SDGetCSD(void)
{
uint8_t CSD[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信息的命令,则是可以省略不需要实现的。
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挂载设备时,进行初始化调用的。具体实现如下所示:
/**
* [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);
}
7.Petit FAT配置
在pffconf.h文件中,是关于Petit FAT的配置,其中有功能函数的使能开关、FAT支持的格式选择、以及PF_USE_LCC这个宏的配置;下表显示了通过配置选项删除了哪些功能以减少代码空间:
着重说一下PF_USE_LCC这个宏,这边是在调试的时候趟过的坑^^,其默认值为0;当SD卡中的文件名为“HELLO.txt”时,我在使用pf_open函数打开这个文件,会提示:FR_NO_FILE,原因是因为在SD卡的根目录中,所有的文件名和文件后缀名都是大写的,当使用"HELLO.txt"和“HELLO.TXT”进行文本比较时,肯定不会匹配通过,所以解决的办法有两个:一是将PF_USE_LCC的宏值修改为1,二是在pf_open打开文件时,将文件名和文件后缀名都改为大写。
8.Petit FAT示例
在完成移植和配置后,我们就可以对TF卡中的文件进行读写了,编写了一个读写文件的示例函数,如下所示:
FATFS fs; /* Work area (file system object) for the volume */
void Petit_FatFs_Sample(void)
{
BYTE buff[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来读写文件中的数据了。
9.注意事项
9.1.Petit FAT在使用时还是有一些限制的,它无法创建文件,只能打开现有已经存在的文件、无法追加数据和扩展文件大小、文件的时间戳不会更新、只读属性的文件也无法阻止写入操作
9.2.pf_lseek函数的使用,要在pf_open成功之后,其参数ofs需要是扇区的倍数值,才有效;
9.3.pf_write函数写入操作只能在扇区边上启动和停止,就是说一次写一个扇区(512字节),如果不满一个扇区数据,会通根据移植的disk_writep的功能填入0;另外就是一旦启动写操作,就必须正确完成,期间不允许有其它操作函数,否则写入的数据可能会丢失;
9.4.文件写操作顺序:- pf_lseek(ofs) 在启动定稿操作之前,必须将读/写指针移动到扇区边界,否则它将在第一次定稿操作时向下舍入到扇区边界
- pf_write(buff, btw, &bw) 启动写入操作,将第一批数据写入文件
- pf_write(buff, btw, &bw) 写入下一批数据,在进行写入操作时,不能使用任何其它文件函数
- pf_write(0, 0, &bw) 完成当前写入操作,如果读/写指针不在扇区边界上,则扇区中的其余字节将被填充为零
10.程序空间编译比较
没有添加Petit FAT时:
添加Petit FAT示例后:
11.附件
《Physical Layer Simplified Specification Version 9.10》手册:
Part1 Physical Layer Simplified Specification Version 9.10 December 1, 2023.PDF
(5.85 MB)
SD_Information信息读取示例程序:
LibSamples_MM32G0001_V2.0.2_SD_Information.zip
(6.12 MB)
Petit FAT读写示例程序:
LibSamples_MM32G0001_V2.0.2_Petit_FatFs.zip
(6.17 MB)
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
打赏榜单
21小跑堂 打赏了 200.00 元 2024-02-26 理由:恭喜通过原创审核!期待您更多的原创作品~(蓝v达人打赏已提升)
|
MM32G0001小资源MCU借助Petit FAT,玩转文件系统,读写SD储存卡。文章整体结构完善,内容充实,资料详细,优质原创!