{"id":6890,"date":"2025-11-15T10:16:16","date_gmt":"2025-11-15T09:16:16","guid":{"rendered":"https:\/\/www.pschatzmann.ch\/home\/?p=6890"},"modified":"2025-11-15T12:21:21","modified_gmt":"2025-11-15T11:21:21","slug":"walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide","status":"publish","type":"post","link":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/","title":{"rendered":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I was searching for the most efficient generic way to process the whole <strong>SD directory tree in Arduino<\/strong>: this can be helpful if you want to search for files or if you want to store the whole file system in a tree in RAM.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-standard-sd-api-way\">The Standard SD API Way<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When using the API provided by the SD library, we need to open each file\/directory to find it&#8217;s children. So to process all files in the directory tree we can use a recursive function call:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">#include <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">SPI.h<\/span>&gt;<\/span>\n#include <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">SD.h<\/span>&gt;<\/span>\n\n#define PIN_AUDIO_KIT_SD_CARD_CS 13\n#define PIN_AUDIO_KIT_SD_CARD_MISO 2\n#define PIN_AUDIO_KIT_SD_CARD_MOSI 15\n#define PIN_AUDIO_KIT_SD_CARD_CLK 14\n\nint count = 0;\n\nclass NullPrintCls : public Print {\n  size_t write(uint8_t c) override {\n    return 1;\n  }\n} NullPrint;\n\nvoid printDirectory(Print&amp; out, File dir, int level) {\n  while (true) {\n    File entry = dir.openNextFile();\n    if (!entry) {\n      \/\/ No more files\n      break;\n    }\n\n    \/\/ Print indentation\n    for (int i = 0; i <span class=\"hljs-tag\">&lt; <span class=\"hljs-attr\">level<\/span>; <span class=\"hljs-attr\">i<\/span>++) {\n      <span class=\"hljs-attr\">out.print<\/span>(\"  \");\n    }\n\n    \/\/ <span class=\"hljs-attr\">Print<\/span> <span class=\"hljs-attr\">name<\/span>\n    <span class=\"hljs-attr\">out.print<\/span>(<span class=\"hljs-attr\">entry.name<\/span>());\n    <span class=\"hljs-attr\">count<\/span>++;\n\n    <span class=\"hljs-attr\">if<\/span> (<span class=\"hljs-attr\">entry.isDirectory<\/span>()) {\n      <span class=\"hljs-attr\">out.println<\/span>(\"\/\");\n      <span class=\"hljs-attr\">printDirectory<\/span>(<span class=\"hljs-attr\">out<\/span>, <span class=\"hljs-attr\">entry<\/span>, <span class=\"hljs-attr\">level<\/span>);\n    } \n    <span class=\"hljs-attr\">entry.close<\/span>();\n  }\n}\n\n<span class=\"hljs-attr\">void<\/span> <span class=\"hljs-attr\">setup<\/span>() {\n  <span class=\"hljs-attr\">Serial.begin<\/span>(<span class=\"hljs-attr\">115200<\/span>);\n  <span class=\"hljs-attr\">delay<\/span>(<span class=\"hljs-attr\">2000<\/span>);\n\n  <span class=\"hljs-attr\">Serial.println<\/span>(\"<span class=\"hljs-attr\">Initializing<\/span> <span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card...<\/span>\");\n  <span class=\"hljs-attr\">SPI.begin<\/span>(<span class=\"hljs-attr\">PIN_AUDIO_KIT_SD_CARD_CLK<\/span>, <span class=\"hljs-attr\">PIN_AUDIO_KIT_SD_CARD_MISO<\/span>, <span class=\"hljs-attr\">PIN_AUDIO_KIT_SD_CARD_MOSI<\/span>, <span class=\"hljs-attr\">PIN_AUDIO_KIT_SD_CARD_CS<\/span>);\n\n  <span class=\"hljs-attr\">if<\/span> (!<span class=\"hljs-attr\">SD.begin<\/span>(<span class=\"hljs-attr\">PIN_AUDIO_KIT_SD_CARD_CS<\/span>)) {\n    <span class=\"hljs-attr\">Serial.println<\/span>(\"<span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card<\/span> <span class=\"hljs-attr\">initialization<\/span> <span class=\"hljs-attr\">failed<\/span>!\");\n    <span class=\"hljs-attr\">return<\/span>;\n  }\n  <span class=\"hljs-attr\">Serial.println<\/span>(\"<span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card<\/span> <span class=\"hljs-attr\">initialized.<\/span>\\<span class=\"hljs-attr\">n<\/span>\");\n\n  <span class=\"hljs-attr\">auto<\/span> <span class=\"hljs-attr\">start_ms<\/span> = <span class=\"hljs-string\">millis();<\/span>\n  <span class=\"hljs-attr\">File<\/span> <span class=\"hljs-attr\">root<\/span> = <span class=\"hljs-string\">SD.open(<\/span>\"\/\");\n  <span class=\"hljs-attr\">printDirectory<\/span>(<span class=\"hljs-attr\">NullPrint<\/span>, <span class=\"hljs-attr\">root<\/span>, <span class=\"hljs-attr\">0<\/span>);\n  <span class=\"hljs-attr\">root.close<\/span>();\n\n  <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">Runtime<\/span> <span class=\"hljs-attr\">in<\/span> <span class=\"hljs-attr\">ms:<\/span> \");\n  <span class=\"hljs-attr\">Serial.println<\/span>(<span class=\"hljs-attr\">millis<\/span>() <span class=\"hljs-attr\">-<\/span> <span class=\"hljs-attr\">start_ms<\/span>);\n  <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">Number<\/span> <span class=\"hljs-attr\">of<\/span> <span class=\"hljs-attr\">files:<\/span> \");\n  <span class=\"hljs-attr\">Serial.println<\/span>(<span class=\"hljs-attr\">count<\/span>);\n}\n\n<span class=\"hljs-attr\">void<\/span> <span class=\"hljs-attr\">loop<\/span>() {}\n<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">I am not interested in timing the output, so I use a custom NullPrint object which does nothing. If you want to print the result, just replace the NullPrint with Serial. Here is the result:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">09<span class=\"hljs-selector-pseudo\">:33<\/span><span class=\"hljs-selector-pseudo\">:16.645<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Runtime<\/span> <span class=\"hljs-selector-tag\">in<\/span> <span class=\"hljs-selector-tag\">ms<\/span>: 95170\n09<span class=\"hljs-selector-pseudo\">:33<\/span><span class=\"hljs-selector-pseudo\">:16.645<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Number<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-tag\">files<\/span>: 2208\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This result is, as expected, really bad since we are forced to open each file just to process it&#8217;s directory information. Please note that in some Arduino variants the stack is quite limited: so you might need to replace this simple recursive approach with your own stack logic which is using the heap.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can improve the processing time by using a more efficient API compatible library: e.g. on the ESP32 you can use SDMMC: however the overall handicap coming from the inefficient directory functions remains!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"using-the-fatfs-library\">Using the fatfs library<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In the next approach, I am using the <a href=\"https:\/\/github.com\/pschatzmann\/arduino-fatfs\">arduino-fatfs<\/a> library. It is providing a <strong>recursive_directory_iterator<\/strong> which is using the dedicated directory API provided by the fatfs library. So this should be much more efficient. This method provides you the file including the full path, so we need to do some additional logic to extract the file name w\/o path and do the indentation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the Arduino sketch:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">#include <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">SPI.h<\/span>&gt;<\/span>\n#include \"fatfs.h\"\n#include \"filesystem.h\"\n\n\/\/ pins for SD card\n#define MISO 2\n#define MOSI 15\n#define SCLK 14\n#define CS 13\n\nint count = 0;\n\nclass NullPrintCls : public Print {\n  size_t write(uint8_t c) override {\n    return 1;\n  }\n} NullPrint;\n\n\/\/ Helper function to count directory depth for indentation\nint getDepth(const std::string&amp; path) {\n  int depth = 0;\n  for (char c : path) {\n    if (c == '\/') depth++;\n  }\n  return depth;\n}\n\nvoid printDirectory(Print&amp; out) {\n  \/\/ Use recursive_directory_iterator - it automatically traverses subdirectories!\n  for (auto it = recursive_directory_iterator(\"\/\");\n       it != recursive_directory_iterator::end();\n       ++it) {\n    auto entry = *it;\n    count++;\n\n    \/\/ Calculate indentation based on path depth\n    int depth = getDepth(entry.path);\n    std::string indent;\n    for (int i = 0; i <span class=\"hljs-tag\">&lt; <span class=\"hljs-attr\">depth<\/span>; <span class=\"hljs-attr\">i<\/span>++) {\n      <span class=\"hljs-attr\">indent<\/span> += <span class=\"hljs-string\">\"  \"<\/span>;\n    }\n\n    \/\/ <span class=\"hljs-attr\">Extract<\/span> <span class=\"hljs-attr\">filename<\/span> <span class=\"hljs-attr\">from<\/span> <span class=\"hljs-attr\">path<\/span>\n    <span class=\"hljs-attr\">const<\/span> <span class=\"hljs-attr\">char<\/span>* <span class=\"hljs-attr\">name<\/span> = <span class=\"hljs-string\">entry.path.c_str();<\/span>\n    <span class=\"hljs-attr\">const<\/span> <span class=\"hljs-attr\">char<\/span>* <span class=\"hljs-attr\">lastSlash<\/span> = <span class=\"hljs-string\">strrchr(name,<\/span> '\/');\n    <span class=\"hljs-attr\">const<\/span> <span class=\"hljs-attr\">char<\/span>* <span class=\"hljs-attr\">filename<\/span> = <span class=\"hljs-string\">lastSlash<\/span> ? <span class=\"hljs-attr\">lastSlash<\/span> + <span class=\"hljs-attr\">1<\/span> <span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">name<\/span>;\n\n    <span class=\"hljs-attr\">out.print<\/span>(<span class=\"hljs-attr\">indent.c_str<\/span>());\n    <span class=\"hljs-attr\">out.println<\/span>(<span class=\"hljs-attr\">filename<\/span>);\n  }\n}\n\n<span class=\"hljs-attr\">void<\/span> <span class=\"hljs-attr\">setup<\/span>() {\n  <span class=\"hljs-attr\">Serial.begin<\/span>(<span class=\"hljs-attr\">115200<\/span>);\n  <span class=\"hljs-attr\">delay<\/span>(<span class=\"hljs-attr\">3000<\/span>);\n\n  \/\/ <span class=\"hljs-attr\">Initialize<\/span> <span class=\"hljs-attr\">hardware<\/span> <span class=\"hljs-attr\">SPI<\/span> <span class=\"hljs-attr\">for<\/span> <span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card<\/span>\n  <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">Initializing<\/span> <span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card...<\/span>\\<span class=\"hljs-attr\">n<\/span>\");\n  <span class=\"hljs-attr\">SPI.begin<\/span>(<span class=\"hljs-attr\">SCLK<\/span>, <span class=\"hljs-attr\">MISO<\/span>, <span class=\"hljs-attr\">MOSI<\/span>);\n  <span class=\"hljs-attr\">delay<\/span>(<span class=\"hljs-attr\">100<\/span>);\n\n  <span class=\"hljs-attr\">if<\/span> (!<span class=\"hljs-attr\">SD.begin<\/span>(<span class=\"hljs-attr\">CS<\/span>, <span class=\"hljs-attr\">SPI<\/span>)) {\n    <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">SD<\/span> <span class=\"hljs-attr\">card<\/span> <span class=\"hljs-attr\">initialization<\/span> <span class=\"hljs-attr\">failed<\/span>!\\<span class=\"hljs-attr\">n<\/span>\");\n    <span class=\"hljs-attr\">while<\/span> (<span class=\"hljs-attr\">true<\/span>)\n      ;\n  }\n\n  <span class=\"hljs-attr\">auto<\/span> <span class=\"hljs-attr\">start_ms<\/span> = <span class=\"hljs-string\">millis();<\/span>\n  <span class=\"hljs-attr\">count<\/span> = <span class=\"hljs-string\">0;<\/span>\n\n  <span class=\"hljs-attr\">printDirectory<\/span>(<span class=\"hljs-attr\">NullPrint<\/span>);\n\n  <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">Runtime<\/span> <span class=\"hljs-attr\">in<\/span> <span class=\"hljs-attr\">ms:<\/span> \");\n  <span class=\"hljs-attr\">Serial.println<\/span>(<span class=\"hljs-attr\">millis<\/span>() <span class=\"hljs-attr\">-<\/span> <span class=\"hljs-attr\">start_ms<\/span>);\n  <span class=\"hljs-attr\">Serial.print<\/span>(\"<span class=\"hljs-attr\">Number<\/span> <span class=\"hljs-attr\">of<\/span> <span class=\"hljs-attr\">files:<\/span> \");\n  <span class=\"hljs-attr\">Serial.println<\/span>(<span class=\"hljs-attr\">count<\/span>);\n}\n\n<span class=\"hljs-attr\">void<\/span> <span class=\"hljs-attr\">loop<\/span>() {}\n<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is, as expected, <strong>much faster<\/strong> and in real live we can get rid of the unneeded functionality to make it even more efficient. I kept the NullPrint logic, so that you can call the method with Serial to check the output.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">09<span class=\"hljs-selector-pseudo\">:28<\/span><span class=\"hljs-selector-pseudo\">:30.608<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Runtime<\/span> <span class=\"hljs-selector-tag\">in<\/span> <span class=\"hljs-selector-tag\">ms<\/span>: 7301\n09<span class=\"hljs-selector-pseudo\">:28<\/span><span class=\"hljs-selector-pseudo\">:30.609<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Number<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-tag\">files<\/span>: 2208\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"using-ls-with-the-sdfat-library\">Using ls() with the SdFat library<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/github.com\/greiman\/SdFat\">SdFat library<\/a> is usually much more efficient than the SD library. In addition it povides the <strong>ls() function<\/strong> that is incredible fast. The only challenge: the output goes to a Print object, so we need to write our own logic to capture the output and do some parsing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the complete sketch<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">#include &lt;SPI.h&gt;\n#include \"SdFat.h\"\n#include &lt;string&gt;\n\n\/\/ pins for audiokit\n#define MISO 2\n#define MOSI 15\n#define SCLK 14\n#define CS 13\n\nSdFs sd;\nint count = 0;\n\n\/\/ Dummy output\nclass NullPrintCls : public Print {\n  size_t write(uint8_t c) override {\n    return 1;\n  }\n} NullPrint;\n\n\/\/ File info\nstruct SdFatFileInfo {\n  std::string name;\n  bool is_directory;\n  int level;\n};\n\n\/\/ A simple parser that provides SdFatFileInfo via a callback\nclass SdFatParserCls : public Print {\npublic:\n  SdFatParserCls() {\n    name.reserve(80);\n  }\n\n  size_t write(uint8_t c) override {\n    switch (c) {\n      case '\\n':\n        parse();\n        break;\n      case '\\t':\n        break;\n      default:\n        name += c;\n        break;\n    }\n    return 1;\n  }\n\n  void setCallback(void (*cb)(SdFatFileInfo&amp;, void* ref), void* ref = nullptr) {\n    this-&gt;ref = ref;\n    this-&gt;cb = cb;\n  }\n\nprotected:\n  std::string name;\n  void (*cb)(SdFatFileInfo&amp;, void* ref);\n  void* ref;\n  SdFatFileInfo info;\n\n  int spaceCount() {\n    for (int j = 0; j &lt; name.size(); j++) {\n      if (name&#91;j] != ' ') return j;\n    }\n    return 0;\n  }\n\n  void parse() {\n    int spaces = spaceCount();\n    info.level = spaces \/ 2;\n    info.name = name.erase(0, spaces);\n    info.is_directory = info.name&#91;info.name.size() - 1] == '\/';\n    if (cb) cb(info, ref);\n    name.clear();\n  }\n} SdFatParser;\n\n\/\/ output the file\nvoid processInfo(SdFatFileInfo&amp; info, void* ref) {\n  Print *p_out = (Print*) ref;\n  count++;\n  \/\/ indent\n  for (int j = 0; j &lt; info.level; j++) {\n    p_out-&gt;print(\"  \");\n  }\n  \/\/ print name\n  p_out-&gt;println(info.name.c_str());\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  delay(3000);\n  Serial.println(\"starting...\");\n\n  \/\/ start SPI and setup pins\n  SPI.begin(SCLK, MISO, MOSI);\n\n  if (!sd.begin(CS, SD_SCK_MHZ(4))) {\n    Serial.println(\"sd.begin() failed\");\n  }\n\n  \/\/ setup callback\n  SdFatParser.setCallback(processInfo, &amp;NullPrint);\n  \n  auto start_ms = millis();\n  \n  \/\/ process ls\n  sd.ls(&amp;SdFatParser, LS_R);\n  \n  Serial.print(\"Runtime in ms: \");\n  Serial.println(millis() - start_ms);\n  Serial.print(\"Number of files: \");\n  Serial.println(count);\n}\n\nvoid loop() {}\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The LS_R flag is specifying that we want to do the processing recursively. Here as well, you can replace the NullPrint with Serial if you want to print the result. The only disadvantage with this functionality is, that you can&#8217;t abort the processing and you are forced to process the complete directory tree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This gives by far the <strong>best result<\/strong>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">11<span class=\"hljs-selector-pseudo\">:26<\/span><span class=\"hljs-selector-pseudo\">:45.407<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Runtime<\/span> <span class=\"hljs-selector-tag\">in<\/span> <span class=\"hljs-selector-tag\">ms<\/span>: 2396\n11<span class=\"hljs-selector-pseudo\">:26<\/span><span class=\"hljs-selector-pseudo\">:45.407<\/span> <span class=\"hljs-selector-tag\">-<\/span>&gt; <span class=\"hljs-selector-tag\">Number<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-tag\">files<\/span>: 2208\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"result-overview\">Result Overview<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I was running the test on an AI Thinker AudioKit which uses an ESP32 and provides a built in SD drive. Here is the final summary which is giving the times to <strong>process 2208 files and directories<\/strong>:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Alternative<\/th><th>Runtime ms<\/th><\/tr><\/thead><tbody><tr><td>SD library standard approach<\/td><td>95170<\/td><\/tr><tr><td>fatfs recursive_directory_iterator<\/td><td>7301<\/td><\/tr><tr><td>SdFat using ls()<\/td><td>2396<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So using the SdFat library for this task seems to be the recommended approach! <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was searching for the most efficient generic way to process the whole SD directory tree in Arduino: this can be helpful if you want to search for files or if you want to store the whole file system in a tree in RAM. The Standard SD API Way When [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":6891,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"footnotes":""},"categories":[20],"tags":[],"class_list":["post-6890","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-arduino"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.9 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann\" \/>\n<meta property=\"og:description\" content=\"I was searching for the most efficient generic way to process the whole SD directory tree in Arduino: this can be helpful if you want to search for files or if you want to store the whole file system in a tree in RAM. The Standard SD API Way When [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Phil Schatzmann\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-15T09:16:16+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-15T11:21:21+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png\" \/>\n\t<meta property=\"og:image:width\" content=\"225\" \/>\n\t<meta property=\"og:image:height\" content=\"225\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"pschatzmann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"pschatzmann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/\"},\"author\":{\"name\":\"pschatzmann\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"headline\":\"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide\",\"datePublished\":\"2025-11-15T09:16:16+00:00\",\"dateModified\":\"2025-11-15T11:21:21+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/\"},\"wordCount\":525,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/tree.png\",\"articleSection\":[\"Arduino\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/\",\"name\":\"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/tree.png\",\"datePublished\":\"2025-11-15T09:16:16+00:00\",\"dateModified\":\"2025-11-15T11:21:21+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/tree.png\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/tree.png\",\"width\":225,\"height\":225},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2025\\\/11\\\/15\\\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\",\"name\":\"Phil Schatzmann Consulting\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\",\"name\":\"pschatzmann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"width\":305,\"height\":305,\"caption\":\"pschatzmann\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/","og_locale":"en_US","og_type":"article","og_title":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann","og_description":"I was searching for the most efficient generic way to process the whole SD directory tree in Arduino: this can be helpful if you want to search for files or if you want to store the whole file system in a tree in RAM. The Standard SD API Way When [&hellip;]","og_url":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/","og_site_name":"Phil Schatzmann","article_published_time":"2025-11-15T09:16:16+00:00","article_modified_time":"2025-11-15T11:21:21+00:00","og_image":[{"width":225,"height":225,"url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png","type":"image\/png"}],"author":"pschatzmann","twitter_card":"summary_large_image","twitter_misc":{"Written by":"pschatzmann","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#article","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/"},"author":{"name":"pschatzmann","@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"headline":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide","datePublished":"2025-11-15T09:16:16+00:00","dateModified":"2025-11-15T11:21:21+00:00","mainEntityOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/"},"wordCount":525,"commentCount":0,"publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png","articleSection":["Arduino"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/","url":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/","name":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide - Phil Schatzmann","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#primaryimage"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png","datePublished":"2025-11-15T09:16:16+00:00","dateModified":"2025-11-15T11:21:21+00:00","breadcrumb":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#primaryimage","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2025\/11\/tree.png","width":225,"height":225},{"@type":"BreadcrumbList","@id":"https:\/\/www.pschatzmann.ch\/home\/2025\/11\/15\/walking-an-entire-arduino-sd-card-directory-tree-a-performance-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.pschatzmann.ch\/home\/"},{"@type":"ListItem","position":2,"name":"Walking an Entire Arduino SD Card Directory Tree: A Performance Guide"}]},{"@type":"WebSite","@id":"https:\/\/www.pschatzmann.ch\/home\/#website","url":"https:\/\/www.pschatzmann.ch\/home\/","name":"Phil Schatzmann Consulting","description":"","publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.pschatzmann.ch\/home\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1","name":"pschatzmann","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","width":305,"height":305,"caption":"pschatzmann"},"logo":{"@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png"}}]}},"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/6890","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/comments?post=6890"}],"version-history":[{"count":25,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/6890\/revisions"}],"predecessor-version":[{"id":6930,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/6890\/revisions\/6930"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/media\/6891"}],"wp:attachment":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/media?parent=6890"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/categories?post=6890"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/tags?post=6890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}