Program to benchmark the speed of the different file system options versus placing data directly into arrays.

Dependencies:   DMBasicGUI DMSupport

This program is used to measure the performance of the different file system options on the LPC4088 Display Module.

The performance wiki page and more specifically the software part describes this program and the output.

As the program doesn't use the display at all it can be used on both the 4.3" and 5" display modules.

main.cpp

Committer:
embeddedartists
Date:
2019-11-05
Revision:
6:47b4bed9fa13
Parent:
5:788710a95951

File content as of revision 6:47b4bed9fa13:

/*
 *  Copyright 2015 Embedded Artists AB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "mbed.h"
#include "DMBoard.h"
#include "MCIFileSystem.h"
#include "QSPIFileSystem.h"
#include "USBHostMSD.h"
#include "img_data.h"
#include "Image.h"

/******************************************************************************
 * Defines and typedefs
 *****************************************************************************/

typedef struct {
  bool           rw;  // use in the read/write test? Used in image tests regardless
  uint32_t       size;
  const char*    fname;
  const uint8_t* iflash_direct;
  const uint8_t* qspi_direct;
} bench_input_t;

#define NUM_BENCHMARKS  (sizeof(BENCHMARK_INPUT)/sizeof(BENCHMARK_INPUT[0]))

#define COPYBUF_SIZE  (10*1024*1024)

#define USBH_CONNECTION_EVENT  (1)

#define QSPIFS_SIZE_MB  (8)
#define QSPIFS_SIZE     (QSPIFS_SIZE_MB * 1024*1024)

/******************************************************************************
 * Local variables
 *****************************************************************************/

static bench_input_t BENCHMARK_INPUT[] = {
    // The _red_ images
    {
        .rw = true,
        .size = img_size_iflash_32x32_red_bmp,
        .fname = "32x32_red.bmp",
        .iflash_direct = img_iflash_32x32_red_bmp,
        .qspi_direct = img_qspi_32x32_red_bmp,
    },
    {
        .rw = false,
        .size = img_size_iflash_32x32_red_png,
        .fname = "32x32_red.png",
        .iflash_direct = img_iflash_32x32_red_png,
        .qspi_direct = img_qspi_32x32_red_png,
    },
    {
        .rw = false,
        .size = img_size_iflash_32x32_red_raw,
        .fname = "32x32_red.raw",
        .iflash_direct = img_iflash_32x32_red_raw,
        .qspi_direct = img_qspi_32x32_red_raw,
    },
    {
        .rw = true,
        .size = img_size_iflash_64x64_red_bmp,
        .fname = "64x64_red.bmp",
        .iflash_direct = img_iflash_64x64_red_bmp,
        .qspi_direct = img_qspi_64x64_red_bmp,
    },
    {
        .rw = false,
        .size = img_size_iflash_64x64_red_png,
        .fname = "64x64_red.png",
        .iflash_direct = img_iflash_64x64_red_png,
        .qspi_direct = img_qspi_64x64_red_png,
    },
    {
        .rw = false,
        .size = img_size_iflash_64x64_red_raw,
        .fname = "64x64_red.raw",
        .iflash_direct = img_iflash_64x64_red_raw,
        .qspi_direct = img_qspi_64x64_red_raw,
    },
    {
        .rw = true,
        .size = img_size_qspi_128x128_red_bmp,
        .fname = "128x128_red.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_red_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_128x128_red_png,
        .fname = "128x128_red.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_red_png,
    },
    {
        .rw = false,
        .size = img_size_qspi_128x128_red_raw,
        .fname = "128x128_red.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_red_raw,
    },
    {
        .rw = false,
        .size = img_size_qspi_480x272_red_bmp,
        .fname = "480x272_red.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_red_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_480x272_red_png,
        .fname = "480x272_red.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_red_png,
    },
    {
        .rw = true,
        .size = img_size_qspi_480x272_red_raw,
        .fname = "480x272_red.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_red_raw,
    },
    {
        .rw = true,
        .size = img_size_qspi_800x480_red_bmp,
        .fname = "800x480_red.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_red_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_800x480_red_png,
        .fname = "800x480_red.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_red_png,
    },
    {
        .rw = false,
        .size = img_size_qspi_800x480_red_raw,
        .fname = "800x480_red.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_red_raw,
    },

    // The _flowers_ images
    {
        .rw = false,
        .size = img_size_iflash_32x32_flowers_bmp,
        .fname = "32x32_flowers.bmp",
        .iflash_direct = img_iflash_32x32_flowers_bmp,
        .qspi_direct = img_qspi_32x32_flowers_bmp,
    },
    {
        .rw = false,
        .size = img_size_iflash_32x32_flowers_png,
        .fname = "32x32_flowers.png",
        .iflash_direct = img_iflash_32x32_flowers_png,
        .qspi_direct = img_qspi_32x32_flowers_png,
    },
    {
        .rw = false,
        .size = img_size_iflash_32x32_flowers_raw,
        .fname = "32x32_flowers.raw",
        .iflash_direct = img_iflash_32x32_flowers_raw,
        .qspi_direct = img_qspi_32x32_flowers_raw,
    },
    {
        .rw = false,
        .size = img_size_iflash_64x64_flowers_bmp,
        .fname = "64x64_flowers.bmp",
        .iflash_direct = img_iflash_64x64_flowers_bmp,
        .qspi_direct = img_qspi_64x64_flowers_bmp,
    },
    {
        .rw = false,
        .size = img_size_iflash_64x64_flowers_png,
        .fname = "64x64_flowers.png",
        .iflash_direct = img_iflash_64x64_flowers_png,
        .qspi_direct = img_qspi_64x64_flowers_png,
    },
    {
        .rw = false,
        .size = img_size_iflash_64x64_flowers_raw,
        .fname = "64x64_flowers.raw",
        .iflash_direct = img_iflash_64x64_flowers_raw,
        .qspi_direct = img_qspi_64x64_flowers_raw,
    },
    {
        .rw = false,
        .size = img_size_qspi_128x128_flowers_bmp,
        .fname = "128x128_flowers.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_flowers_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_128x128_flowers_png,
        .fname = "128x128_flowers.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_flowers_png,
    },
    {
        .rw = false,
        .size = img_size_qspi_128x128_flowers_raw,
        .fname = "128x128_flowers.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_128x128_flowers_raw,
    },
    {
        .rw = false,
        .size = img_size_qspi_480x272_flowers_bmp,
        .fname = "480x272_flowers.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_flowers_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_480x272_flowers_png,
        .fname = "480x272_flowers.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_flowers_png,
    },
    {
        .rw = false,
        .size = img_size_qspi_480x272_flowers_raw,
        .fname = "480x272_flowers.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_480x272_flowers_raw,
    },
    {
        .rw = false,
        .size = img_size_qspi_800x480_flowers_bmp,
        .fname = "800x480_flowers.bmp",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_flowers_bmp,
    },
    {
        .rw = false,
        .size = img_size_qspi_800x480_flowers_png,
        .fname = "800x480_flowers.png",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_flowers_png,
    },
    {
        .rw = false,
        .size = img_size_qspi_800x480_flowers_raw,
        .fname = "800x480_flowers.raw",
        .iflash_direct = NULL,
        .qspi_direct = img_qspi_800x480_flowers_raw,
    },    
};

static MCIFileSystem*  mcifs;
static FATFileSystem*  mciFatFs;
static QSPIFileSystem* qspifs;
static USBHostMSD*     usbmsd;

/******************************************************************************
 * Private Functions
 *****************************************************************************/


static bool fileExists(const char* fname) {
  FILE* f = fopen(fname, "r");
  if (f != NULL) {
    fclose(f);
    return true;
  }
  return false;
}

static bool haveAllFiles(const char* prefix) {
  char buff[512] = {0};
  strcpy(buff, prefix);
  int len = strlen(buff);

  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    strcpy(buff+len, BENCHMARK_INPUT[i].fname);
    if (!fileExists(buff)) {
      DMBoard::instance().logger()->printf("File %s is missing\n", buff);
      return false;
    }
  }
  return true;
}

static uint32_t writeInChunks(FILE* f, const uint8_t* src, uint32_t len, bool destIsMCI)
{
  if (destIsMCI) {
    uint32_t left = len;
    uint32_t written = 0;
    uint32_t chunk;
    while (left > 0) {
      // MCI File system only supports writing 512 bytes at a time
      chunk = ((left < 512) ? left : 512);
      uint32_t tmp = fwrite(src+written, 1, chunk, f);
      if (tmp != chunk) {
        // failed, return what we have written so far
        return written;
      }
      left -= chunk;
      written += chunk;
    }
    return written;
  } else {
    // All file systems except for MCI supports complete fwrites and will split it into
    // chunks (if needed) internally.
    return fwrite(src, 1, len, f);
  }
}

static bool createFiles(const char* prefix) {
  RtosLog* log = DMBoard::instance().logger();
  char buff[512] = {0};
  strcpy(buff, prefix);
  int len = strlen(buff);

  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    strcpy(buff+len, BENCHMARK_INPUT[i].fname);
    log->printf("  writing %u bytes to %s\n", BENCHMARK_INPUT[i].size, buff);
    FILE* f = fopen(buff, "w");
    if (f == NULL) {
      log->printf("Failed to create file %s - ABORTING\n", buff);
      return false;
    }
    uint32_t written;
    if (BENCHMARK_INPUT[i].iflash_direct == NULL) {
      written = writeInChunks(f, BENCHMARK_INPUT[i].qspi_direct, BENCHMARK_INPUT[i].size, (prefix[1]=='m'));
    } else {
      written = writeInChunks(f, BENCHMARK_INPUT[i].iflash_direct, BENCHMARK_INPUT[i].size, (prefix[1]=='m'));
    }
    if (written != BENCHMARK_INPUT[i].size) {
      log->printf("Failed to write %u (only wrote %u) bytes to %s - ABORTING\n", BENCHMARK_INPUT[i].size, written, buff);
      fclose(f);
      return false;
    }
    fclose(f);
  }
  return true;
}

static bool prepareMCI() {
  RtosLog* log = DMBoard::instance().logger();
  bool ok = false;
  
  mcifs = new MCIFileSystem(P4_16);
  mciFatFs = new FATFileSystem("mci");
  mciFatFs->mount(mcifs);
  
  if (mcifs->cardInserted()) {
    log->printf("uSD card detected\n");
      
    if (haveAllFiles("/mci/")) {
      log->printf("uSD file system prepared!\n");
      ok = true;
    } else {
      log->printf("One or more files missing, need to (re-)prepare the uSD file system\n");

      log->printf("Preparing uSD file system...\n");
      if (createFiles("/mci/")) {
        log->printf("uSD file system prepared!\n");
        ok = true;
      } else {
        log->printf("Failed to prepare uSD file system!\n");
      }
    }
  } else {
    log->printf("No uSD card detected. Insert one and reset\n");
  }
  
  return ok;
}

static bool prepareUSB() {
  RtosLog* log = DMBoard::instance().logger();
  bool ok = false;

  usbmsd = new USBHostMSD("usb");
  USBHost* host = USBHost::getHostInst();
  EventFlags connectionEvent;
  host->signalOnConnections(&connectionEvent, USBH_CONNECTION_EVENT);

  log->printf("waiting for connect/disconnect message from USBHost...\n");
  connectionEvent.wait_any(USBH_CONNECTION_EVENT);
    
  if (usbmsd->connect()) {
    log->printf("USB MemoryStick detected\n");

    if (haveAllFiles("/usb/")) {
      log->printf("USB MemoryStick file system prepared!\n");
      ok = true;
    } else {
      log->printf("One or more files missing, need to (re-)prepare the USB MemoryStick\n");
    
      log->printf("Preparing USB MemoryStick file system...\n");
      if (createFiles("/usb/")) {
        log->printf("USB MemoryStick file system prepared!\n");
        ok = true;
      } else {
        log->printf("Failed to prepare USB MemoryStick file system!\n");
      }
    }      
  } else {
    log->printf("No USB MemoryStick detected. Insert one and reset\n");
  }
  
  return ok;
}

static bool prepareQSPIFS() {
  RtosLog* log = DMBoard::instance().logger();
  bool ok = false;
  bool format = false;

  qspifs = new QSPIFileSystem("qspi");

  do {
    if (qspifs->isformatted()) {
      uint32_t start, end;
      log->printf("QSPI file system detected\n");
      qspifs->getMemoryBoundaries(&start, &end);
      if ((end-start) >= QSPIFS_SIZE) {
        if (haveAllFiles("/qspi/")) {
          log->printf("QSPI file system prepared!\n");
          ok = true;
          break;
        } else {
          log->printf("One or more files missing, need to (re-)prepare the QSPI file system\n");
          format = true;
        }      
      } else {
        log->printf("Found too small file system (only %dMB). Formatting...\n", (end-start)/(1024*1024));
        format = true;
      }
    } else {
      log->printf("No QSPI file system detected. Formatting...\n");
      format = true;
    }
    
    if (format) {
      if (qspifs->format(QSPIFS_SIZE_MB) == 0) {
        log->printf("Formatting successful\n");
      } else {
        log->printf("Failed to format QSPI file system!\n");
        break;
      }
    }
    
    log->printf("Preparing QSPI file system...\n");
    if (createFiles("/qspi/")) {
      log->printf("QSPI file system prepared!\n");
      ok = true;
    } else {
      log->printf("Failed to prepare QSPI file system!\n");
    }
  } while(false);
  
  return ok;
}

static bool prepare() {
  RtosLog* log = DMBoard::instance().logger();

  // make sure that the linker actually placed the data in the
  // correct flashes
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].iflash_direct != NULL) {
      uint32_t tmp = (uint32_t)BENCHMARK_INPUT[i].iflash_direct;
      if ((tmp & 0xff000000) != 0x00000000) {
        log->printf("IFLASH data for benchmark %d is at 0x%08x NOT in IFLASH!! Aborting\n", i, tmp);
        return false;
      }
      tmp = (uint32_t)BENCHMARK_INPUT[i].qspi_direct;
      if ((tmp & 0xff000000) != 0x28000000) {
        log->printf("QSPI data for benchmark %d is at 0x%08x NOT in QSPI!! Aborting\n", i, tmp);
        return false;
      }
    }
  }
  return prepareMCI() && prepareUSB() && prepareQSPIFS();
}

static void readFile(const char* fname, uint8_t* dest) {
  FILE* f = fopen(fname, "r");
  if (f != NULL) {
    int num = fread(dest, 1, 1024, f);
    while (num > 0) {
      dest+=num;
      num = fread(dest, 1, 1024, f);
    }
    fclose(f);
  }
}

static void runReadBenchmarks() {
  RtosLog* log = DMBoard::instance().logger();
  uint32_t times[NUM_BENCHMARKS][5] = {0};
  uint32_t tmp;
  char buff[512];
  Timer t;
  uint8_t* dest = (uint8_t*)malloc(COPYBUF_SIZE);
  if (dest == NULL) {
    log->printf("Failed to allocate 10MBytes as buffer\n");
    return;
  }
  
  t.start();
  
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (!BENCHMARK_INPUT[i].rw) {
      // don't include the files for the image decoding in 
      // the benchmark set
      continue;
    }

    // MCI
    sprintf(buff, "/mci/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    tmp = t.read_us();
    readFile(buff, dest);
    times[i][0] = t.read_us() - tmp;
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][0]);
      
    //USB
    sprintf(buff, "/usb/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    tmp = t.read_us();
    readFile(buff, dest);
    times[i][1] = t.read_us() - tmp;
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][1]);

    //QSPIFS
    sprintf(buff, "/qspi/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    tmp = t.read_us();
    readFile(buff, dest);
    times[i][2] = t.read_us() - tmp;
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][2]);

    //IFLASH
    sprintf(buff, "IFLASH /%s", BENCHMARK_INPUT[i].fname);
    if (BENCHMARK_INPUT[i].iflash_direct != NULL) {
      memset(dest, 0, COPYBUF_SIZE);
      tmp = t.read_us();
      memcpy(dest, BENCHMARK_INPUT[i].iflash_direct, BENCHMARK_INPUT[i].size);
      times[i][3] = t.read_us() - tmp;
      log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][3]);
    } else {
      log->printf("Benchmarking %-30s skipped\n", buff);
    }

    //QSPI
    sprintf(buff, "QSPI /%s", BENCHMARK_INPUT[i].fname);
    if (BENCHMARK_INPUT[i].qspi_direct != NULL) {
      memset(dest, 0, COPYBUF_SIZE);
      tmp = t.read_us();
      memcpy(dest, BENCHMARK_INPUT[i].qspi_direct, BENCHMARK_INPUT[i].size);
      times[i][4] = t.read_us() - tmp;
      log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][4]);
    } else {
      log->printf("Benchmarking %-30s skipped\n", buff);
    }
  }
  
  log->printf("\n\n----\nSummary:\n");
  
  log->printf("\n  File Information\n");
  log->printf("%20s %10s\n", "Filename", "Size");
  log->printf("%20s %10s\n", "--------", "----");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      log->printf("%20s %10d bytes\n", BENCHMARK_INPUT[i].fname, BENCHMARK_INPUT[i].size);
    }
  }

  log->printf("\n  Read times (in us)\n");
  log->printf("%20s %10s %10s %10s %10s %10s\n", "Filename", "uSD Card", "USB", "QSPI FS", "IFLASH[]", "QSPI[]");
  log->printf("%20s %10s %10s %10s %10s %10s\n", "--------", "--------", "---", "-------", "--------", "------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      char* p = (char*)dest;
      for (int x = 0; x < 5; x++) {
        if (times[i][x] == 0) {
          p += sprintf(p, "%10s ", "N/A");
        } else {
          p += sprintf(p, "%10d ", times[i][x]);
        }
      }
      log->printf("%20s %s\n", BENCHMARK_INPUT[i].fname, dest);
    }
  }

  log->printf("\n  Read speeds\n");
  log->printf("%20s  %-12s %-12s %-12s %-12s %-12s\n", "Filename", "uSD Card", "USB", "QSPI FS", "IFLASH[]", "QSPI[]");
  log->printf("%20s  %s %s %s %s %s\n", "--------", "------------", "------------", "------------", "------------", "------------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      char* p = (char*)dest;
      for (int x = 0; x < 5; x++) {
        if (times[i][x] == 0) {
          p += sprintf(p, "%12s ", "N/A ");
        } else {
          double t = times[i][x];
          double s = BENCHMARK_INPUT[i].size;
          double v = (s*1000000)/t;
          if (v < 10000) {
            p += sprintf(p, "%#7.2F b/s  ", v);
          } else if (v < 10000000) {
            p += sprintf(p, "%#7.2F Kb/s ", v/1024.0);
          } else {
            p += sprintf(p, "%#7.2F Mb/s ", v/(1024.0*1024.0));
          }
        }
      }
      log->printf("%20s  %s \n", BENCHMARK_INPUT[i].fname, dest);
    }
  }
  
  log->printf("\n\n---\n");
  
  free(dest);
}

static uint32_t writeFile(const char* fname, int benchId, Timer* t, uint8_t* buff) {
  uint32_t size = BENCHMARK_INPUT[benchId].size;

  // To make all tests equal all source data is copied to external SDRAM
  // and from there to the destination file. Only the time from SDRAM to
  // file is meassured.
  if (BENCHMARK_INPUT[benchId].iflash_direct == NULL) {
    memcpy(buff, BENCHMARK_INPUT[benchId].qspi_direct, size);
  } else {
    memcpy(buff, BENCHMARK_INPUT[benchId].iflash_direct, size);
  }

  uint32_t time = t->read_us();  
  int written = 0;
  FILE* f = fopen(fname, "w");
  if (f != NULL) {
    written = writeInChunks(f, buff, size, (fname[1]=='m'));
    fclose(f);
  }
  if (written == size) {
    return t->read_us() - time;      
  } else {
    DMBoard::instance().logger()->printf("Failed to write %s (only wrote %u of %u bytes). Aborting\n", fname, written, size);
    return 0;
  }
}

static void runWriteBenchmarks() {
  RtosLog* log = DMBoard::instance().logger();
  uint32_t times[NUM_BENCHMARKS][3] = {0};
  char buff[512];
  Timer t;
  
  log->printf("Preparing to run WRITE tests...\n");

  // To make all tests equal all source data is copied to external SDRAM
  // and from there to the destination file. Only the time from SDRAM to
  // file is meassured.
  uint8_t* dest = (uint8_t*)malloc(COPYBUF_SIZE);
  if (dest == NULL) {
    log->printf("Failed to allocate 10MBytes as buffer\n");
    return;
  }
  
  // Clear the entire QSPI file system
  if (qspifs->format(QSPIFS_SIZE_MB) == 0) {
    log->printf("Formatting successful\n");
  } else {
    log->printf("Failed to format QSPI file system!\n");
    return;
  }
  
  // For uSD and USB formatting is a bad idea as the memory
  // might contain other important file. Just delete the files
  // we are using instead.
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    // MCI
    sprintf(buff, "/mci/%s", BENCHMARK_INPUT[i].fname);
    remove(buff);
      
    //USB
    sprintf(buff, "/usb/%s", BENCHMARK_INPUT[i].fname);
    remove(buff);
  }
  
  t.start();
  
  // Do the benchmarking
  for (int i = 0; i < NUM_BENCHMARKS; i++) {      
    if (!BENCHMARK_INPUT[i].rw) {
      // don't include the files for the image decoding in 
      // the benchmark set
      continue;
    }

    // MCI
    sprintf(buff, "/mci/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    times[i][0] = writeFile(buff, i, &t, dest);
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][0]);
      
    //USB
    sprintf(buff, "/usb/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    times[i][1] = writeFile(buff, i, &t, dest);
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][1]);

    //QSPIFS
    sprintf(buff, "/qspi/%s", BENCHMARK_INPUT[i].fname);
    memset(dest, 0, COPYBUF_SIZE);
    times[i][2] = writeFile(buff, i, &t, dest);
    log->printf("Benchmarking %-30s took %8uus\n", buff, times[i][2]);
  }
  
  log->printf("\n\n----\nSummary:\n");
  
  log->printf("\n  File Information\n");
  log->printf("%20s %10s\n", "Filename", "Size");
  log->printf("%20s %10s\n", "--------", "----");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      log->printf("%20s %10d bytes\n", BENCHMARK_INPUT[i].fname, BENCHMARK_INPUT[i].size);
    }
  }

  log->printf("\n  Write times (in us)\n");
  log->printf("%20s %10s %10s %10s\n", "Filename", "uSD Card", "USB", "QSPI FS");
  log->printf("%20s %10s %10s %10s\n", "--------", "--------", "---", "-------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      char* p = (char*)dest;
      for (int x = 0; x < 3; x++) {
        if (times[i][x] == 0) {
          p += sprintf(p, "%10s ", "N/A");
        } else {
          p += sprintf(p, "%10d ", times[i][x]);
        }
      }
      log->printf("%20s %s\n", BENCHMARK_INPUT[i].fname, dest);
    }
  }

  log->printf("\n  Write speeds\n");
  log->printf("%20s  %-12s %-12s %-12s\n", "Filename", "uSD Card", "USB", "QSPI FS");
  log->printf("%20s  %s %s %s\n", "--------", "------------", "------------", "------------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    if (BENCHMARK_INPUT[i].rw) {
      char* p = (char*)dest;
      for (int x = 0; x < 3; x++) {
        if (times[i][x] == 0) {
          p += sprintf(p, "%12s ", "N/A ");
        } else {
          double t = times[i][x];
          double s = BENCHMARK_INPUT[i].size;
          double v = (s*1000000)/t;
          if (v < 10000) {
            p += sprintf(p, "%#7.2F b/s  ", v);
          } else if (v < 10000000) {
            p += sprintf(p, "%#7.2F Kb/s ", v/1024.0);
          } else {
            p += sprintf(p, "%#7.2F Mb/s ", v/(1024.0*1024.0));
          }
        }
      }
      log->printf("%20s  %s \n", BENCHMARK_INPUT[i].fname, dest);
    }
  }
  
  log->printf("\n\n---\n");

  free(dest);
}

static void runImageBenchmarks() {
  RtosLog* log = DMBoard::instance().logger();
  uint32_t times[NUM_BENCHMARKS][5] = {0};
  uint32_t tmp;
  char buff[512];
  int result;
  Image::ImageData_t imgData;
  Timer t;
  
  t.start();
  
  for (int i = 0; i < NUM_BENCHMARKS; i++) {

    // MCI
    sprintf(buff, "/mci/%s", BENCHMARK_INPUT[i].fname);
    tmp = t.read_us();
    result = Image::decode(buff, Image::RES_16BIT, &imgData);
    times[i][0] = t.read_us() - tmp;
    if (result == 0) {
      log->printf("Decoding %-30s took %8uus\n", buff, times[i][0]);
      if (imgData.pointerToFree != NULL) {
        free(imgData.pointerToFree);
        imgData.pointerToFree = NULL;
      }
    } else {
      log->printf("Decoding %-30s failed\n", buff);
      times[i][0] = 0;
    }
      
    //USB
    sprintf(buff, "/usb/%s", BENCHMARK_INPUT[i].fname);
    tmp = t.read_us();
    result = Image::decode(buff, Image::RES_16BIT, &imgData);
    times[i][1] = t.read_us() - tmp;
    if (result == 0) {
      log->printf("Decoding %-30s took %8uus\n", buff, times[i][1]);
      if (imgData.pointerToFree != NULL) {
        free(imgData.pointerToFree);
        imgData.pointerToFree = NULL;
      }
    } else {
      log->printf("Decoding %-30s failed\n", buff);
      times[i][1] = 0;
    }

    //QSPIFS
    sprintf(buff, "/qspi/%s", BENCHMARK_INPUT[i].fname);
    tmp = t.read_us();
    result = Image::decode(buff, Image::RES_16BIT, &imgData);
    times[i][2] = t.read_us() - tmp;
    if (result == 0) {
      log->printf("Decoding %-30s took %8uus\n", buff, times[i][2]);
      if (imgData.pointerToFree != NULL) {
        free(imgData.pointerToFree);
        imgData.pointerToFree = NULL;
      }
    } else {
      log->printf("Decoding %-30s failed\n", buff);
      times[i][2] = 0;
    }

    //IFLASH
    sprintf(buff, "IFLASH /%s", BENCHMARK_INPUT[i].fname);
    if (BENCHMARK_INPUT[i].iflash_direct != NULL) {
      tmp = t.read_us();
      result = Image::decode(BENCHMARK_INPUT[i].iflash_direct, BENCHMARK_INPUT[i].size, Image::RES_16BIT, &imgData);
      times[i][3] = t.read_us() - tmp;
      if (result == 0) {
        log->printf("Decoding %-30s took %8uus\n", buff, times[i][3]);
        if (imgData.pointerToFree != NULL) {
          free(imgData.pointerToFree);
          imgData.pointerToFree = NULL;
        }
      } else {
        log->printf("Decoding %-30s failed\n", buff);
        times[i][3] = 0;
      }
    } else {
      log->printf("Decoding %-30s skipped\n", buff);
    }

    //QSPI
    sprintf(buff, "QSPI /%s", BENCHMARK_INPUT[i].fname);
    if (BENCHMARK_INPUT[i].qspi_direct != NULL) {
      tmp = t.read_us();
      result = Image::decode(BENCHMARK_INPUT[i].qspi_direct, BENCHMARK_INPUT[i].size, Image::RES_16BIT, &imgData);
      times[i][4] = t.read_us() - tmp;
      if (result == 0) {
        log->printf("Decoding %-30s took %8uus\n", buff, times[i][4]);
        if (imgData.pointerToFree != NULL) {
          free(imgData.pointerToFree);
          imgData.pointerToFree = NULL;
        }
      } else {
        log->printf("Decoding %-30s failed\n", buff);
        times[i][4] = 0;
      }
    } else {
      log->printf("Decoding %-30s skipped\n", buff);
    }
  }
  
  log->printf("\n\n----\nSummary:\n");
  
  log->printf("\n  File Information\n");
  log->printf("%20s %10s\n", "Filename", "Size");
  log->printf("%20s %10s\n", "--------", "----");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    log->printf("%20s %10d bytes\n", BENCHMARK_INPUT[i].fname, BENCHMARK_INPUT[i].size);
  }

  log->printf("\n  Decode times (in us)\n");
  log->printf("%20s %10s %10s %10s %10s %10s\n", "Filename", "uSD Card", "USB", "QSPI FS", "IFLASH[]", "QSPI[]");
  log->printf("%20s %10s %10s %10s %10s %10s\n", "--------", "--------", "---", "-------", "--------", "------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    char* p = (char*)buff;
    for (int x = 0; x < 5; x++) {
      if (times[i][x] == 0) {
        p += sprintf(p, "%10s ", "N/A");
      } else {
        p += sprintf(p, "%10d ", times[i][x]);
      }
    }
    log->printf("%20s %s\n", BENCHMARK_INPUT[i].fname, buff);
  }

  log->printf("\n  Decode speeds\n");
  log->printf("%20s  %-12s %-12s %-12s %-12s %-12s\n", "Filename", "uSD Card", "USB", "QSPI FS", "IFLASH[]", "QSPI[]");
  log->printf("%20s  %s %s %s %s %s\n", "--------", "------------", "------------", "------------", "------------", "------------");
  for (int i = 0; i < NUM_BENCHMARKS; i++) {
    char* p = (char*)buff;
    for (int x = 0; x < 5; x++) {
      if (times[i][x] == 0) {
        p += sprintf(p, "%12s ", "N/A ");
      } else {
        double t = times[i][x];
        double s = BENCHMARK_INPUT[i].size;
        double v = (s*1000000)/t;
        if (v < 10000) {
          p += sprintf(p, "%#7.2F b/s  ", v);
        } else if (v < 10000000) {
          p += sprintf(p, "%#7.2F Kb/s ", v/1024.0);
        } else {
          p += sprintf(p, "%#7.2F Mb/s ", v/(1024.0*1024.0));
        }
      }
    }
    log->printf("%20s  %s \n", BENCHMARK_INPUT[i].fname, buff);
  }
  
  log->printf("\n\n---\n");
}

/******************************************************************************
 * Main
 *****************************************************************************/

int main()
{
  DMBoard::BoardError err;
  DMBoard* board = &DMBoard::instance();
  RtosLog* log = board->logger();
  
  do {
    err = board->init();
    if (err != DMBoard::Ok) {
      log->printf("Failed to initialize the board, got error %d\r\n", err);
      break;
    }
    
    log->printf("\n\nBenchmarking. (Built " __DATE__ " at " __TIME__ ")\n\n");
    
    log->printf("Preparing file systems for benchmarking\n");
    if (!prepare()) {
      log->printf("Failed to prepare for benchmarking\r\n");
      break;
    }
    
    runReadBenchmarks();
    runImageBenchmarks();
    
    ThisThread::sleep_for(1000);
    log->printf("Press the USER button to run WRITE tests!\n");
    while(!board->buttonPressed()) {
      ThisThread::sleep_for(20);
    }
    while(board->buttonPressed()) {
      ThisThread::sleep_for(20);
    }
    
    runWriteBenchmarks();
    
  } while(false);

  if (err != DMBoard::Ok) {
    log->printf("\nTERMINATING\n");
  }  

  while(true) {
    ThisThread::sleep_for(1000);
  }   
}