产品板子上想改下程序,改之前好好的.
将lwip换到最新版后,在FatFs操作时崩溃. 是调用 f_stat()引起的,在FatFs里面崩了。莫名奇妙…
想单独作个试验,移植最新版的FatFs R0.14来验证一下。
FatFs旧版也正常,就是我改完程序引起的问题,估计是内存申请失败引起的问题。
在开发板上单独验证了SPI方式SD卡驱动库配合FatFs的f_x函数的使用。看看调用f_stat()是否会崩溃。
f_stat没崩,那我将FatFs R0.14移植到产品板子,如果还有问题,再调试.
不过发现要注意的几个小知识点.
char sz_read_buf[0x100] = {'\0'};
void test_fatfs_read_file()
{
FRESULT file_rc = FR_OK;
FIL fp;
FSIZE_t file_size = 0;
FSIZE_t file_size_left = 0;
UINT br = 0;
// const char* psz_msg = "hello fatfs";
// mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
// f_open(
file_rc = f_stat(psz_file_on_dir, &fno);
printf("%s : %s = f_stat(%s)\n", ((FR_OK == file_rc) ? "ok" : "err"), FRESULT2psz(file_rc), psz_file_on_dir);
if (FR_OK == file_rc) {
// file not exist, create it
file_rc = f_open(&fp, psz_file_on_dir, FA_READ); // 参数3不能或上 FA_OPEN_APPEND, 否则seek到尾部去了, 读到的内容长度是0
printf("%s : %s = f_open(%s)\n", ((FR_OK == file_rc) ? "ok" : "err"), FRESULT2psz(file_rc), psz_file_on_dir);
if (FR_OK == file_rc) {
file_size = f_size(&fp);
printf("[%s] file size = [%d]\n", psz_file_on_dir, file_size);
file_size_left = file_size;
do {
printf("file_size_left = %d\n", file_size_left);
if (file_size_left <= 0) {
break;
}
memset(sz_read_buf, 0, sizeof(sz_read_buf));
file_rc = f_read(&fp, sz_read_buf, sizeof(sz_read_buf) - 1, &br);
printf("%s : %s = f_read(&fp), bw = %d\n", ((FR_OK == file_rc) ? "ok" : "err"), FRESULT2psz(file_rc), br);
if ((FR_OK != file_rc) || (0 == br)) {
break;
}
printf("read : %s\n", sz_read_buf);
file_size_left -= br;
} while (1);
file_size = f_size(&fp);
printf("[%s] file size = [%d]\n", psz_file_on_dir, file_size);
file_rc = f_close(&fp);
printf("%s : %s = f_close(&fp)\n", ((FR_OK == file_rc) ? "ok" : "err"), FRESULT2psz(file_rc));
}
}
}
FF_USE_LFN 是在 \fatfs\ffconf.h 定义的配置值. 管长文件名的操作。
#define FF_USE_LFN 3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
FF_USE_LFN默认的配置是0, 这在实际应用中肯定不行. http_server素材啥的, 都是同事在windows下调试好的, 不可能是8.3文件名格式。FF_USE_LFN按照说明, 可以取0~3. 因为我测试,是串行的代码。线尝试将FF_USE_LFN改成1, 但是发现写文件后,关闭文件句柄的时候,挂掉了。看起来是栈溢出。
挂掉的调用链
打开文件(f_open)
写一些内容(f_write() 很短(e.g. hello fatfs), 不超过30字节)
// 以下调用进了FatFs
f_close() 这是主动调用, 下面的函数调用都在FatFs中被动调用。
f_sync()
sync_fs()
mem_set(fs->win, 0, sizeof fs->win); // 在这个函数中执行都正常,但是无法返回到调用点,看起来栈溢出了.
/* Fill memory block */
static void mem_set (void* dst, int val, UINT cnt)
{
BYTE *d = (BYTE*)dst;
do {
*d++ = (BYTE)val;
} while (--cnt);
}
mem_set()看起来很干净,甚至连大块栈上空间都没用. 不可能是这个函数有问题。
mem_set()只在挂掉的这条调用链上才挂掉。下过断点,其他调用该链过了这里几次,都是正常的。
说明我测试这条调用链比较长,用的栈空间比较多吧。
下位机的栈是这样,没多大一点地方。
FF_USE_LFN改成2是不行的,这也是在栈上调用。没有去试,在栈上凯辟空间不靠谱。
尝试将FF_USE_LFN改成3,这是在堆上分配空间。
将FF_USE_LFN改成3后,需要自己实现ff_memalloc()/ff_memfree().
FatFs官方代码中直接调用的malloc()/free()
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif
又跑了几遍程序。发现测试程序出现了我打印的错误ITM信息。
是内存分配失败。同样的内存块(512bytes), 申请/释放成对调用,只需要8次,就会分配失败…
// 使用malloc()/free()的运行结果记录, 从下面的运行结果来看, 要自己实现内存池才靠谱.
// 下面的运行结果,可以看出都是一块内存成对的malloc()/free(), 应该是内存碎片引起的分配失败
// >> main
// >> test_fatfs
// ok : f_mount(sd:), rc = 0
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 1
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 1
// err : FR_NO_FILE = f_stat(sd:test1_file_not_exist.txt)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 2
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 2
// err : FR_EXIST = f_mkdir(sd:log)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 3
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 3
// ok : FR_OK = f_stat(sd:log)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 4
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 4
// err : FR_NO_FILE = f_stat(sd:log/my_file_not_exist.txt)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 5
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 5
// ok : FR_OK = f_stat(sd:log/my_log.txt)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 6
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 6
// ok : FR_OK = f_unlink(sd:log/my_log.txt)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 7
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 7
// err : FR_NO_FILE = f_stat(sd:log/my_log.txt)
// ok : 0x20002348 = fatfs_malloc(512), gi_cnt_for_fatfs_malloc = 8
// ok : fatfs_free(0x20002348), gi_cnt_for_fatfs_free = 8
// ok : FR_OK = f_open(sd:log/my_log.txt)
// ok : FR_OK = f_close(&fp)
// err : malloc(512), gi_cnt_for_fatfs_malloc = 9
// err : FR_NOT_ENOUGH_CORE = f_stat(sd:log/my_log.txt)
以前看ucos的文档,人家说在下位机中调用库函数中的malloc()/free()很危险,果真不虚。
将以前自己封装的星翼的内存管理库整进来,在ff_memalloc()/ff_memfree()调用自己的内存管理函数。再跑几次,都正常。问题解决。
\fatfs\ffsystem.c
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
// return (void*)malloc(msize); /* Allocate a new memory block with POSIX API */
return fatfs_malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
// free(mblock); /* Free the memory block with POSIX API */
fatfs_free(mblock); /* Free the memory block with POSIX API */
}
\fatfs\diskio.c
static int gi_cnt_for_fatfs_malloc = 0;
static int gi_cnt_for_fatfs_free = 0;
void* fatfs_malloc( UINT size )
{
// @todo
char* psz_malloc = ram_malloc_in(size);
gi_cnt_for_fatfs_malloc++;
if (NULL == psz_malloc) {
printf("err : malloc(%d), gi_cnt_for_fatfs_malloc = %d\n", size, gi_cnt_for_fatfs_malloc);
} else {
printf("ok : 0x%p = fatfs_malloc(%d), gi_cnt_for_fatfs_malloc = %d\n", psz_malloc, size, gi_cnt_for_fatfs_malloc);
}
return psz_malloc;
}
void fatfs_free( void* ptr )
{
// @todo
if (NULL != ptr) {
gi_cnt_for_fatfs_free++;
ram_free_in(ptr);
printf("ok : fatfs_free(0x%p), gi_cnt_for_fatfs_free = %d\n", ptr, gi_cnt_for_fatfs_free);
ptr = NULL;
}
}
ST_STM32F407_StdPeriph_Templates_FatFs_2020_0319_1726.zip
环境 : STM32F407 + SPI方式SD卡驱动库 + SPI2上的SD卡外接插座 + FatFs R0.14 + 对星翼内存管理库的二次封装库
功能 : 对SPI方式的SD卡内的16GB TF卡(Fat32文件格式),进行读写测试。将工程中能用到的FatFs的f_x函数都测试了一下,f_mount()挂载磁盘/卸载磁盘, f_stat()查看文件是否存在, f_mkdir()建立文件夹, f_open()打开文件, f_close()关闭文件, f_unlink()删除文件, f_write()写文件, f_read()读文件. 文件操作都正常。
SD卡外接模块到开发板自己焊的,焊的怪丑的:P
连接线用的网线, 皮实.
看来SPI连线长一些,也不影响SPI时序。应该等长线(差不多)就行。