The purpose of this application is to allow easy manipulation of the QSPI file system from a PC (**NOTE**: Application doesn't work with update to mbed OS 5 since USB Device support is currently not available for LPC4088)

Dependencies:   DMBasicGUI DMSupport

Note

This application doesn't work with the latest updates to mbed OS 5 since USB Device support is currently not available for LPC4088

The purpose of this application is to allow easy manipulation of the QSPI file system from a PC.

The application makes the LPC4088 Display Module appear as a USB Memory Stick when connected to a PC. The PC will see the current content of the QSPI file system plus an image file of the file system that can be downloaded and (at a later time) be used to restore the file system to it's current state.

To use this application:

  1. Download the lpc4088_displaymodule_fs_aid application using drag-n-drop and then reset the board
  2. Optionally start a terminal program to read the status messages from the application
  3. Connect a USB cable to the mini USB slot on the back of the LPC4088 Display Module, and then to the PC
  4. The PC will install drivers if needed and then the USB Memory Stick will be available as a new drive
  5. Modify the file system to suit your needs
  6. With the USB cable still connected, press the USER/ISP/SW1 button on the LPC4088 Display Module
  7. The application will now:
    1. disconnect the USB Memory Stick
    2. write all changes to the QSPI flash memory
    3. create a new image file of the updated QSPI file system and store it in the .current/ folder
    4. connect the USB Memory Stick again
  8. Continue from step 5. until satisfied

Note 1: If the QSPI doesn't have a file system on it or to replace the current one with a new, possibly of a different size, file system just add a file with the name format_qspi_X_mb (where X should be replace with the wanted file system size in Mb). For a 10 Mb file system the file name should be format_qspi_10_mb.

Note 2: The file system that is exposed is a copy (in SDRAM) of the QSPI file system. The reason for this is that the USBMSD class requires a FAT file system.

Note 3: The image files created in step 7.3 above will be a *.fsX file (where the 'X' is the size of the file system in MB so *.fs1 for a 1MByte file system). The *.fsX file extensions are recognized by the HDK and can be used to drag-n-drop to the MBED drive in the same way as the *.bin files are. A *.fsX file will not overwrite the program stored in internal flash.

main.cpp

Committer:
alindvall
Date:
2016-05-16
Revision:
1:b04139d88c59
Parent:
0:06e35dd73c95

File content as of revision 1:b04139d88c59:

/******************************************************************************
 * Includes
 *****************************************************************************/

#include "mbed.h"
#include "mbed_interface.h"
#include "rtos.h"

#include "DMBoard.h"
#include "RAMFileSystem.h"
#include "USBMSD_RAMFS.h"

#include "crc.h"

#include "lpc_swim.h"
#include "lpc_swim_font.h"
#include "lpc_winfreesystem14x16.h"

/******************************************************************************
 * Typedefs and defines
 *****************************************************************************/

typedef bool (*syncFunc)(const char* name, bool isDir);

/* Size of RAM file system */
#define RAM_FS_SIZE (20*1024*1024)


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

//DigitalOut myled1(LED1);
//DigitalOut myled2(LED2);
//DigitalIn  button(p23);

//DigitalOut myled(LED1);
static char lsbuff[NAME_MAX+1];

static char image_file_name[128] = { '\0' };

/******************************************************************************
 * Local functions
 *****************************************************************************/


static void ledShowProgress()
{
  static int state = 0;
  state = (state + 1) % 2;

  DMBoard* board = &DMBoard::instance();
  board->setLED(DMBoard::Led1, state==0);
  board->setLED(DMBoard::Led2, state!=0);
}

static void handleError(const char* msg)
{
  DMBoard* board = &DMBoard::instance();
  board->logger()->printf(msg);
  while(true) {
    board->setLED(DMBoard::Led1, false);
    board->setLED(DMBoard::Led2, false);
    wait(0.3);
    board->setLED(DMBoard::Led1, true);
    board->setLED(DMBoard::Led2, true);
    wait(0.3);
  }
}

static void waitForButtonPress()
{
  DMBoard* board = &DMBoard::instance();
  printf("Press button to sync file systems\n");

  board->setLED(DMBoard::Led1, false);
  board->setLED(DMBoard::Led2, false);
  while(!board->buttonPressed()) {
    wait(0.1);
  }
  board->setLED(DMBoard::Led1, true);
  printf("Button pressed, now release it\n");
  while(board->buttonPressed()) {
    wait(0.1);
  }
}

static void addNotFormattedFile()
{
    FILE *fp = fopen("/ram/qspi_not_formatted.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "The QSPI file system has not be formatted. To format create a\n");
        fprintf(fp, "file with the name \"format_qspi_X_mb\" on the drive and then\n");
        fprintf(fp, "press the USER/ISP/SW1 button to format\n");
        fprintf(fp, "The X in the file name should be replaced with the desired\n");
        fprintf(fp, "file system size in Mb (1 to 16). For a 10 Mb file sytem the file\n");
        fprintf(fp, "name would be format_qspi_10_mb.\n");
        fclose(fp);
    }
}

static bool recursiveProcessFS(char* buff, const char* name, syncFunc func, bool adding)
{
  uint32_t len = strlen(buff);
  if (len > 0) {
    if (buff[len - 1] != '/') {
      buff[len++] = '/';
      buff[len] = '\0';
    }
  }
  strcat(buff, name);
  len += strlen(name);

  DIR *d = opendir(buff);
  bool result = true; // success
  if (d != NULL) {
    if (adding) {
      // when processing in adding mode folders must be created before it's content
      result = func(buff, true);
    }
    struct dirent *p;
    while (result && ((p = readdir(d)) != NULL)) {
      result = recursiveProcessFS(buff, p->d_name, func, adding);
      buff[len] = '\0';
    }
    closedir(d);
    if (result && !adding) {
      // when processing in removing mode folders must be deleted after it's content
      result = func(buff, true);
    }
  } else {
    // a file
    result = func(buff, false);
  }
  return result;
}

static uint32_t fileLen(FILE* f)
{
  uint32_t pos = ftell(f);
  fseek(f, 0, SEEK_END);
  uint32_t size = ftell(f);
  fseek(f, pos, SEEK_SET);
  return size;
}

static bool copy(FILE* fSrc, FILE* fDst)
{
  char buff[512];
  uint32_t left = fileLen(fSrc);
  uint32_t num;
  uint32_t chunk;

  fseek(fSrc, 0, SEEK_SET);
  do {
    chunk = (left < 512) ? left : 512;
    num = fread(buff, 1, chunk, fSrc);
    if (num > 0) {
      uint32_t tmp = fwrite(buff, 1, num, fDst);
      if (tmp != num) {
        // failed to write
        return false;
      }
      left -= num;
      ledShowProgress();
    }
  } while(num > 0 && left > 0);

  // copied entire file
  return true;
}

static bool identicalFiles(const char* srcName, const char* dstName)
{
  FILE* fSrc = NULL;
  FILE* fDst = NULL;
  bool identical = false;
  do
  {
    fSrc = fopen(srcName, "r");
    if (fSrc == NULL) {
      break;
    }
    fDst = fopen(dstName, "r");
    if (fDst == NULL) {
      break;
    }
    if (fileLen(fSrc) != fileLen(fDst)) {
      break;
    }
    if (crc_File(fSrc) != crc_File(fDst)) {
      break;
    }

    // All tests passed so the files are identical
    identical = true;

  } while(false);

  if (fSrc != NULL) {
    fclose(fSrc);
  }
  if (fDst != NULL) {
    fclose(fDst);
  }

  return identical;
}

static bool addExisting(const char* srcName, bool isDir, const char* dstName)
{
  bool result = true; // success
  if (isDir) {
    DIR *d = opendir(dstName);
    if (d == NULL) {
      if (dstName[1] != 'r') { printf("%s, new dir, adding\n", dstName); }
      if (mkdir(dstName, 0) != 0) {
        printf("Failed to create folder %s\n", dstName);
        result = false; // dir did not exist and could not be created
      }
    } else {
      closedir(d);
    }
  } else if (!identicalFiles(srcName, dstName)) {
    // Compare the files to avoid replacing with same
    FILE* fSrc = fopen(srcName, "r");
    if (fSrc != NULL) {
      FILE* fDst = fopen(dstName, "w"); // open and truncate
      if (fDst != NULL) {
        if (dstName[1] != 'r') { printf("%s, changed, updating\n", dstName); }
        result = copy(fSrc, fDst);
        if (!result) {
          printf("Failed to copy %s to %s\n", srcName, dstName);
        }
        fclose(fDst);
      } else {
        printf("Failed to create file %s\n", dstName);
        result = false; // unable to create file
      }
      fclose(fSrc);
    } else {
      printf("Failed to copen source file file %s\n", srcName);
      result = false; // unable to open source
    }
  } else {
    if (dstName[1] != 'r') { printf("%s identical, skipping\n", dstName); }
  }
  return result;
}

static bool addExistingToQspi(const char* name, bool isDir)
{
  // create the target file name by replacing /ram/ with /qspi/
  char buff[256];
  buff[0] = '\0';
  strcat(buff, "/qspi/");
  strcat(buff, name+5);

  // Don't add the file created by createImageFile()
  if (strcmp(name, image_file_name) == 0) {
    return true;
  }

  return addExisting(name, isDir, buff);
}

static bool addExistingToRAM(const char* name, bool isDir)
{
  // create the target file name by replacing /qspi/ with /ram/
  char buff[256];
  buff[0] = '\0';
  strcat(buff, "/ram/");
  strcat(buff, name+6);
  return addExisting(name, isDir, buff);
}

static bool removeIfMissing(const char* toLookFor, const char* toRemove, bool isDir)
{
  int result = 0; //success
  if (isDir) {
    DIR *d = opendir(toLookFor);
    if (d == NULL) {
      // dir doesn't exist => delete it
      if (toRemove[1] != 'r') { printf("%s, missing, deleting dir\n", toRemove); }
      result = remove(toRemove);
      ledShowProgress();
    } else {
      // dir exist => don't delete
      closedir(d);
    }
  } else {
    FILE* f = fopen(toLookFor, "r");
    if (f == NULL) {
      // file doesn't exist => delete it
      if (toRemove[1] != 'r') { printf("%s, missing, deleting file\n", toRemove); }
      result = remove(toRemove);
      ledShowProgress();
    } else {
      // file exist => don't delete
      fclose(f);
    }
  }
  return (result == 0);
}

static bool removeMissingFromQspi(const char* name, bool isDir)
{
  // create the target file name by replacing /qspi/ with /ram/
  char buff[256];
  buff[0] = '\0';
  strcat(buff, "/ram/");
  strcat(buff, name+6);
  removeIfMissing(buff, name, isDir);
  return true;
}

static bool removeMissingFromRAM(const char* name, bool isDir)
{
  // create the target file name by replacing /ram/ with /qspi/
  char buff[256];
  buff[0] = '\0';
  strcat(buff, "/qspi/");
  strcat(buff, name+5);

  // Don't remove the file created by createImageFile()
  if (strcmp(name, image_file_name) == 0) {
    return true;
  }

  removeIfMissing(buff, name, isDir);
  return true;
}

static void syncDir(const char* to, const char* from)
{
  printf("Starting to sync %s on top of %s (This may take time. LED1 & 2 blink for each file)\n", from, to);

  char* buff = (char*)malloc(512);
  if (buff != NULL)
  {
    buff[0] = '\0';
    if (strcmp(to, "/qspi/") == 0) {
      if (!recursiveProcessFS(buff, to, removeMissingFromQspi, false)) {
        printf("Failed to remove files from %s that were missing on %s\n", to, from);
      } else {
        buff[0] = '\0';
        if (!recursiveProcessFS(buff, from, addExistingToQspi, true)) {
          printf("Failed to add files to %s that existed on %s\n", to, from);
        }
      }
    } else {
      if (!recursiveProcessFS(buff, to, removeMissingFromRAM, false)) {
        printf("Failed to remove files from %s that were missing on %s\n", to, from);
      } else {
        buff[0] = '\0';
        if (!recursiveProcessFS(buff, from, addExistingToRAM, true)) {
          printf("Failed to add files to %s that existed on %s\n", to, from);
        }
      }
    }
    free(buff);
  }
  printf("Sync completed\n");
}


static void createImageFile(QSPIFileSystem* qspi)
{
  uint32_t startAddr;
  uint32_t endAddr;
  uint32_t size;

  printf("Creating image of existing (if any) QSPI file system\n");

  if (!qspi->getMemoryBoundaries(&startAddr, &endAddr))
  {
    handleError("QSPI FS not formatted or impossible to determine it's size\n");
  }

  // Align the start address to an even multiple of 1MB
  startAddr = startAddr & 0xfff00000;

  // Update the file to match the size of the file system
  size = endAddr - startAddr;
  if ((size < 0x00100000) || (size > 0x01000000) || ((size & 0xfffff) > 0))
  {
    sprintf(lsbuff, "QSPI FS size is not supported (%u bytes)\n", size);
    handleError(lsbuff);
  }
  sprintf(image_file_name, "/ram/.current/fs_image.fs%d", (size >> 20));

  // NOTE: The line below is very very !!!! important. For some weird reason the
  //       RAM file system must have at least one folder on it before USB is connected.
  //       If the RAM file system doesn't have any folders on it the result is that
  //       the content of the RAM file system will be the same after USB is disconnected
  //       as it was before connecting - regardless of what is added. Very strange indeed.
  mkdir("/ram/.current", 0);

  printf("QSPI FS max size is %d MB\n", (size >> 20));

  FILE *fp = fopen(image_file_name, "w");
  if (fp != NULL)
  {
    while (size > 0)
    {
      uint32_t written = fwrite((char*)(endAddr - size), 1, size, fp);
      size -= written;
      if (written == 0)
      {
        handleError("Failed to create QSPI image file\n");
      }
    }
    fclose(fp);
  }
}

static bool list(const char* name, bool isDir)
{
  if (isDir) {
    printf("d:         %s\n", name);
  } else {
    FILE* f = fopen(name, "r");
    if (f != NULL) {
      uint32_t len = fileLen(f);
      printf("f: %7u %s\n", len, name);
      fclose(f);
    } else {
      printf("f:     ??? %s\n", name);
    }
  }
  return true;
}

static void recursiveList(const char* dirname)
{
  printf("\nRecursive list of file and folders in %s\n", dirname);
  char* buff = (char*)malloc(512);
  if (buff != NULL)
  {
    buff[0] = '\0';
    recursiveProcessFS(buff, dirname, list, true);
    free(buff);
  }
}

static bool formatIfRequested()
{
  DMBoard* board = &DMBoard::instance();
  RtosLog* logger = board->logger();
  char marker[50];
  int size;
  uint32_t maxSize = SPIFI::instance().memorySize()>>20;

  for (size = 1; size <= maxSize; size++) {
    sprintf(marker, "/ram/format_qspi_%d_mb", size);
    FILE *fp = fopen(marker, "r");
    if (fp != NULL) {
      logger->printf("Found a marker file requesting to place a %d Mb file system on QSPI\n", size);
      logger->printf("This operation may take up to a couple of minutes!\n");
      QSPIFileSystem* qspi = board->getQspiFS();

      int err = qspi->format(size);
      if (err == 0) {
        logger->printf("Successfully added a %d Mb file system to QSPI!\n", size);
      } else {
        logger->printf("Failed to format QSPI!\n");
      }
      // formatting was requested
      return true;
    }
  }
  // no formatting requested
  return false;
}

static void showInfoScreen()
{
  static SWIM_WINDOW_T* win = NULL;
  static void* fb = NULL;

  Display* disp = DMBoard::instance().display();
  win = (SWIM_WINDOW_T*)malloc(sizeof(SWIM_WINDOW_T));
  fb = disp->allocateFramebuffer();

  swim_window_open(win,
                   disp->width(), disp->height(),         // full size
                   (COLOR_T*)fb,
                   0,0,disp->width()-1, disp->height()-1, // window position and size
                   0,                                     // border
                   BLUE, WHITE, BLACK);                   // colors: pen, backgr, forgr

  swim_set_font(win, (FONT_T*)&font_winfreesys14x16);

  // Show a message
  swim_put_text_centered_win(win, "In this version all instructions are printed on the console!", disp->height()/2 - 20);
  swim_put_text_centered_win(win, "Connect a terminal application using 115200, 8N1.", disp->height()/2 + 20);

  // Start display in default mode (16-bit)
  Display::DisplayError disperr = disp->powerUp(fb);
  if (disperr != Display::DisplayError_Ok) {
    DMBoard::instance().logger()->printf("Failed to initialize the display, got error %d\r\n", disperr);
    wait_ms(2000); // allow RtosLog to flush messages
    mbed_die();
  }
}

/******************************************************************************
 * Main function
 *****************************************************************************/
int main()
{
  DMBoard::BoardError err;
  DMBoard* board = &DMBoard::instance();
  RtosLog* log = board->logger();
  err = board->init();
  if (err != DMBoard::Ok) {
    log->printf("Failed to initialize the board, got error %d\r\n", err);
    wait_ms(2000); // allow RtosLog to flush messages
    mbed_die();
  }

  log->printf("\n\n---\nQSPI file syncer app\nBuilt: " __DATE__ " at " __TIME__ "\n\n");

  showInfoScreen();

  // allocate a chunk of memory in the external SDRAM to use as a RAM file system
  void* fsmem = malloc(RAM_FS_SIZE);
  if (fsmem == NULL) {
    log->printf("Failed to allocate memory for RAM file system\n");
    mbed_die();
  }

  // create a file system based on the allocated memory
  RAMFileSystem ramfs((uint32_t)fsmem, RAM_FS_SIZE, "ram");
  USBMSD_RAMFS usbmsd(&ramfs);

  while(true)
  {
    // add an empty file system on it
    ramfs.format();

    QSPIFileSystem* qspi = board->getQspiFS();
    bool qspiFormatted = qspi->isformatted();
    if (!qspiFormatted)
    {
      addNotFormattedFile();
    }
    else
    {
      createImageFile(qspi);

      // Copy QSPI FS to RAM FS
      syncDir("/ram/", "/qspi/");
    }

    printf("Insert the USB cable!\n");
    printf("Starting USB...\n");
    for (int i = 0; i < 10; i++)
    {
      if (usbmsd.connect())
      {
        printf("Connected!\n");
        break;
      }
      printf("Failed to connect USB, testing again...\n");
      printf("Insert (or remove and then insert) the USB cable!\n");
      wait(1);
    }

    waitForButtonPress();

    usbmsd.disconnect();
    printf("Disconnected!\n");

    // Look for (re)format instruction file
    if (formatIfRequested()) {
      continue;
    }

    // Copy RAM FS to QSPI FS
    recursiveList("/ram/");
    syncDir("/qspi/", "/ram/");
  }
}