ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FluidNC 在 ESP32 上以 FF_FS_TINY 重新编译 FatFs:为每个 SD 文件节省约 4 KB 内部 DRAM

FluidNC 在 ESP32 上以 FF_FS_TINY 重新编译 FatFs:为每个 SD 文件节省约 4 KB 内部 DRAM 嵌入式固件硬件开发智能硬件【免费下载链接】FluidNCThe next generation of motion control firmware项目地址https://gitcode.com/gh_mirrors/fl/FluidNC点击查看免费下载本技术指南基于 FluidNC 仓库中的 fatfs_tiny 组件说明深入剖析 FluidNC 如何在经典esp32Arduino core 2.0.17 / ESP-IDF v4.4.7构建环境下通过薄包装thin wrapper方式将预编译libfatfs.a中的 FatFs 引擎以FF_FS_TINY 1重新编译从而将每个并发打开的 SD 文件所占的内部 DRAM 从约 4 KB 压缩到不足 128 字节。读完本文你将理解 FatFs 两种窗口缓冲模型的本质差异、FluidNC 用链接顺序覆盖预编译库成员的实现手法、该方案的适用范围与升级维护流程以及它为何只适用于经典esp32环境。背景为什么一个文件系统组件需要被重新编译FluidNC 的经典esp32构建环境使用 PlatformIO 的framework-arduinoespressif323.20017.241212对应 ESP-IDF v4.4.7。该框架自带预编译的静态库tools/sdk/esp32/lib/libfatfs.a其中包含 Chan 的 FatFs 引擎R0.13c与 VFS 胶水层。问题在于这个归档库在构建时固化了以下配置CONFIG_FATFS_PER_FILE_CACHE 1 - FF_FS_TINY 0 CONFIG_WL_SECTOR_SIZE 4096 FF_MAX_SS MAX(FF_SS_SDCARD, FF_SS_WL) MAX(512, 4096) 4096关键结论来自 fatfs_tiny/README.md 的 Why 一节当FF_FS_TINY 0时struct FIL内嵌一个BYTE buf[FF_MAX_SS]即每个并发打开的 SD 文件要吃掉约 4 KB 内部 DRAMFF_MAX_SS取 SD 扇区 512 字节与 WL 扇区 4096 字节的最大值。更糟的是这笔开销不只是打开文件时才发生而是在挂载阶段就全额预付esp_vfs_fat_register()在挂载时一次性分配max_files * sizeof(FIL)max_files个文件描述符对应的FIL数组vfs_fat_link()注意不是renamerename调用的是f_rename()不使用FIL还会在 fd 表之外额外堆分配两个瞬态FIL。这两处分配点在 vfs_fat_tiny.c 的注释 中被明确标注出来// esp_vfs_fat_register(): ff_memalloc(sizeof(vfs_fat_ctx_t) max_files * sizeof(FIL)) // vfs_fat_link(): ff_memalloc(sizeof(FIL)) x2这正是 FluidNC 的sd_mount()默认max_files不得不从 3 降到 2 的直接原因对应提交 62bf3f6a。在真实挂载代码 esp32/sdspi.cpp 中可以看到mount_to_vfs_fat()将max_files原样传入esp_vfs_fat_register()err esp_vfs_fat_register(base_path, drv, max_files, fs);也就是说max_files每增加 1挂载时就要为FIL数组多预留 4 KB 级别的内部 DRAM——在 ESP32 上内部 SRAM 本就紧张这是不可忽略的负担。FF_FS_TINY 两种模式的原理对比FF_FS_TINY是 FatFs 的一个编译期选项它改变的是文件数据窗口缓冲的归属对比项FF_FS_TINY 0预编译库默认FF_FS_TINY 1本组件缓冲位置每个FIL内嵌私有BYTE buf[FF_MAX_SS]每个挂载点共享一个FATFS.win[FF_MAX_SS]窗口单文件内存开销~4 KB4096 字节 × 每文件不足 128 字节sizeof(FIL)骤减缓冲分配时机挂载时随max_files * sizeof(FIL)一次性预付 链接时瞬态分配挂载时按FATFS结构体分配一次与文件数无关多文件交错 I/O 性能每个文件有独立缓存互不干扰共享窗口被反复重读可能增加 SD 总线流量源码级佐证可以在逐字拷贝的 ff.c 中直接看到。例如f_read()中FF_FS_TINY 0时文件自己的fp-buf充当缓存先disk_read()填充fp-buf再从fp-buf拷贝给调用者而FF_FS_TINY 1时则改为调用move_window(fs, fp-sect)将共享窗口定位到目标扇区再从fs-win中拷贝数据#if FF_FS_TINY if (move_window(fs, fp-sect) ! FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ mem_cpy(rbuff, fs-win fp-fptr % SS(fs), rcnt); /* Extract partial sector */ #else mem_cpy(rbuff, fp-buf fp-fptr % SS(fs), rcnt); /* Extract partial sector */ #endif同样在f_open()中非 tiny 模式还会执行mem_set(fp-buf, 0, sizeof fp-buf)来清零每文件缓冲tiny 模式下这段代码直接被编译掉。FatFs 的连续多扇区直读路径disk_read一次读取多个连续扇区在两种模式下都保留因此大块顺序读的性能差异主要体现在部分扇区读与跨文件/目录交错访问的场景。原文档对此的权衡总结非常直白FF_FS_TINY 1后当文件 I/O 跨文件交错进行或与目录遍历混合时共享窗口会被更频繁地重读导致更多 SD 总线流量。因此原文档强调这需要在真实硬件上做可靠性 soak 测试而不仅仅是干净地编译通过。实现方案薄包装 链接顺序覆盖CONFIG_FATFS_PER_FILE_CACHE是 ESP-IDF 里真实存在的 Kconfig 选项但经典esp32环境无法通过配置改变它——因为该构建链接的是冻结的预编译归档而不是把 fatfs 组件作为源码参与编译。FatFs 的ffconf.h会在编译期计算FF_FS_TINY (!CONFIG_FATFS_PER_FILE_CACHE)所以要得到 tiny 模式必须在编译单元内覆盖这个宏。目录结构与文件角色fatfs_tiny 组件共含 5 个文件分工清晰ff.c 与 vfs_fat.c——**逐字verbatim**来自espressif/esp-idf的v4.4.7tagcomponents/fatfs/src/ff.c FatFs R0.13ccomponents/fatfs/vfs/vfs_fat.c。不要编辑。它们的ff.h/ffconf.h已与framework-arduinoespressif323.20017.241212中携带的头文件逐字节验证一致因此重编译出的目标文件除FF_FS_TINY这一项外与归档成员 ABI 完全相同。ff_tiny.c 与 vfs_fat_tiny.c——薄包装先#undef/#define CONFIG_FATFS_PER_FILE_CACHE 0再#include对应逐字文件。只有这两个包装被编译见下文platformio.ini的build_src_filter逐字的.c文件不单独参与构建。包装文件的工作原理ff_tiny.c 的机制值得逐行拆解// sdkconfig.h 通过命令行强制先包含#pragma once所以下面的 #undef/#define 生效 #include sdkconfig.h #undef CONFIG_FATFS_PER_FILE_CACHE #define CONFIG_FATFS_PER_FILE_CACHE 0 #include ff.c // 守卫证明覆盖已生效 _Static_assert(FF_FS_TINY 1, FF_FS_TINY override did not take effect); _Static_assert(sizeof(FIL) 256, FIL still carries a per-file sector buffer);要点先#include sdkconfig.h它带#pragma once然后立刻翻转宏。ffconf.h在自己的顶部也会#include sdkconfig.h但那次再包含是无操作所以ffconf.h在计算FF_FS_TINY (!CONFIG_FATFS_PER_FILE_CACHE)时读到的是覆盖后的值。该翻译单元对外提供与预编译libfatfs.a中ff.c.obj成员相同的公开符号f_open、f_read等。由于它是直接链接输入链接器先绑定它再搜索归档库于是过时的归档成员永远不会被拉入。两个_Static_assert是编译期自检tiny 模式下sizeof(FIL)应远小于 128 字节而非 tiny 模式在FF_MAX_SS 4096时超过 4 KB。vfs_fat_tiny.c 必须施加完全相同的覆盖因为 VFS 胶水层正是按max_files * sizeof(FIL)分配上下文数组的地方——如果两个翻译单元对sizeof(FIL)认知不一致ctx files[]数组与 FatFs 引擎会对元素大小产生分歧导致内存布局错位。它同样以直接链接输入的身份覆盖vfs_fat.c.obj成员并提供esp_vfs_fat_register、esp_vfs_fat_unregister_path等公开符号。platformio.ini 中的接入方式在 platformio.ini 的[common_esp32]段约第 136–147 行可以看到接入点[common_esp32] extends common_esp32_base platform https://github.com/platformio/platform-espressif32.git platform_packages platformio/framework-arduinoespressif32^3.20017.241212 build_src_filter ${common_esp32_base.build_src_filter} ../stdfs ../esp32/esp32/*.c ../esp32/esp32/*.cpp -../esp32/esp32/rmt_engine_queued_v4.cpp ; Recompiled FatFs with FF_FS_TINY1 to reclaim ~4KB internal DRAM per open ; SD file; overrides the ff.c/vfs_fat.c members of the precompiled libfatfs.a ; via link order. See FluidNC/esp32/esp32/fatfs_tiny/README.md. ../esp32/esp32/fatfs_tiny/ff_tiny.c ../esp32/esp32/fatfs_tiny/vfs_fat_tiny.c即只把ff_tiny.c与vfs_fat_tiny.c两个包装加入源码过滤集逐字的ff.c/vfs_fat.c由包装文件以#include方式吞入。原文档明确指出这种直接链接输入先于归档库被绑定的手法与FluidNC/stdfs17完全一致。其余归档成员为何不受影响libfatfs.a中其余的归档成员diskio*、ffsystem、ffunicode、vfs_fat_sdmmc、vfs_fat_spiflash保持原样、不做重编译。原文档给出的理由是FIL结构体从不跨越这些模块的接口——diskio只与物理层打交道vfs_fat_sdmmc/vfs_fat_spiflash只负责把文件系统挂到具体介质上都不涉及FIL内部布局因此 ABI 差异不会扩散。适用范围与限制原文档的 Scope 一节把边界划得非常清楚仅适用于经典esp32环境[common_esp32]也就是链接 Arduino core 2.0.17 预编译libfatfs.a的那条构建链。_s3环境使用 pioarduino / ESP-IDF v5.5其中带的是FatFs R0.15和不同的FIL结构体。这份 R0.13c 源码绝不能接入那些构建否则会与不同版本、不同布局的 FatFs 发生 ABI 冲突。这也解释了为什么 fatfs_tiny 只重编译了 VFS 胶水与 FatFs 引擎本身而没有触碰磁盘层——版本差异与接口边界共同决定了最小重编译面的划分。升级维护指南原文档 Updating 一节给出的维护流程直接决定了这套方案的生命周期如果经典esp32环境的 Arduino core / IDF 版本发生变化例如框架升级到新的framework-arduinoespressif32包预编译libfatfs.a的构建配置可能随之改变此时应从匹配的espressif/esp-idftag 重新获取components/fatfs/src/ff.c与components/fatfs/vfs/vfs_fat.c两个文件替换逐字拷贝重新验证ff.h/ffconf.h与tools/sdk/esp32/include/fatfs/src/下的头文件是否仍然逐字节一致——这是重编译对象与归档成员 ABI 相同这一前提成立的基础。只要前提条件同版本、同配置、头文件字节一致满足重编译对象就与归档成员 ABI 一致唯一的差异就是FF_FS_TINY这一个设置这正是本方案能以最小改动换回最大内存收益的根源。小结fatfs_tiny 是 FluidNC 针对 ESP32 内部 DRAM 紧张这一现实约束做的精巧工程取舍用逐字源文件 宏覆盖薄包装 直接链接输入覆盖归档成员三件套在保持 FatFs R0.13c 行为与 ABI 兼容的前提下把每个并发 SD 文件的缓冲开销从约 4 KB 降到不足 128 字节从而让sd_mount()得以用更小的max_files完成同样的工作、释放更多可用内存。它的代价是共享窗口在交错 I/O 场景下更频繁的重读因此真实硬件上的可靠性验证是上线前不可或缺的一步。如果你需要在 FluidNC 上复现或评估这一改动可从 fatfs_tiny 组件、接入点 platformio.ini 与 SD 挂载实现 三条路径入手结合自己的 SD 工作负载实测内存收益与吞吐影响。赞分享嵌入式固件硬件开发智能硬件【免费下载链接】FluidNCThe next generation of motion control firmware项目地址https://gitcode.com/gh_mirrors/fl/FluidNC点击查看免费下载相关推荐最完整Nuitka增量编译指南只重新编译修改过的模块以节省时间最完整Nuitka增量编译指南只重新编译修改过的模块以节省时间 你还在等待整个Python项目每次修改后重新编译吗是否希望只重新编译变更的模块将开发周期缩编译器开发工具ArduinoJson内存优化实战在ESP8266/ESP32上节省70% RAM的技巧ArduinoJson内存优化实战在ESP8266/ESP32上节省70% RAM的技巧 嵌入式JSON处理的内存困境 ESP8266/ESP32等物联网设备物联网嵌入式FluidNC终极指南重新定义ESP32控制器上的CNC固件体验FluidNC终极指南重新定义ESP32控制器上的CNC固件体验 FluidNC是专为ESP32控制器优化的下一代CNC固件由Grbl_ESP32的创建者开嵌入式固件硬件开发智能硬件上一篇终极指南Yaak API客户端动态参数生成器的10个高效用法下一篇SwiftUIX手势冲突终极解决方案5个高效处理策略让你告别交互混乱创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表