The TFS in Action

In a recent posting, an abstraction for the M25P80 Serial Memory Device was examined. In a later posting, the application programming interface for the Trivial File System (TFS) was explained. This article will delve into some examples of the TFS in action, showing how, even a file system as simple as TFS can be used successfully in an embedded system. A future posting will detail the internals of the TFS.

The first step in using the TFS is to configure the file system for use. this is accomplished with a few simple definitions:

//**********************************************************
// Platform Specific Control - the M25P80 SPI Flash Device.
//**********************************************************
#define USE_M25P80_SPI_FLASH            1
#define M25P80_THREAD_SAFE              0

#if USE_M25P80_SPI_FLASH
#define USE_TRIVIAL_FILE_SYSTEM         1
#define TFS_THREAD_SAFE                 1
#define TFS_HANDLES                     3
#endif

The first define enable support for the M25P80. The second bypasses thread safe mutex semaphores. This is possible since the TFS is the only user of the M25P90. Next, the TFS is enabled followed by thread safe semaphores at the file system level. Finally, three file handles are allocated, so up to three files can be opened/acted upon at one time. This is similar to the file handles setting in old MS-DOS™.

The first bit of code that is needed is to open the file system form use. An example is shown below:

switch (emTFSOpenFS())
{
    case emOK:
    case emWRN_WRITE_PROTECT:
        break;

    case emERR_UNFORMATTED:
    case emERR_UNKNOWN_VERSION:
        // Warn the user about this slow operation.
        // ... some code deleted ...
        emTFSFormatMedia(16384);
        break;

    case emERR_NO_RESPONSE:
    default:
        // This should never happen.
        // Tell the user of the disaster
        // ... some code deleted ... 
        while (1) {}  // Spin loop
        break;
}

This code is a bit paranoid; the M25P80 is soldered onto the development board so emERR_NO_RESPONSE should be impossible, still the code tests and reacts to this calamity anyway. Overall the code is clear, efficient and simple. Next is an example of touch screen parameters being retrieved. Note that if the file cannot be read, this simple code assumes that the file was not found and calls the screen calibration routine to supply the needed parameters.

h = emTFSOpenFile((PU8)"TSP");

if (h >= 0)
{
    if (emTFSReadFile(h, 
                      0, 
                      sizeof(emTOUCHPARMS), 
                      (PU8)&touchParms) == emOK)
        emSetTouchLimits(&touchParms);

    emTFSCloseFile(h);
}
else
{
    setupTouchScreen();
}

Finally, an example of touch screen parameters being saved out to non-volatile storage.

// Write out the touch screen calibration data.
if ((h = emTFSCreateFile((PU8)"TSP", sizeof(emTOUCHPARMS))) >= 0)
{
    emTFSWriteFile(h, 0, sizeof(emTOUCHPARMS), (PU8)&tp);
    emTFSCloseFile(h);
}

Note that when the file is created, we need to tell the TFS about the size of the data that will be stored. In embedded applications this is often simply the “sizezof” the data structure that holds the required information.

In conclusion, the TFS simplifies many of the tasks encountered in embedded systems  without the high overhead of more capable, full-featured, file systems. As always,  your comments and suggestions are welcomed and invited.

Peter Camilleri (aka Squidly Jones)

A Brief Time Out

In an embedded system, there are many occasions when a delay in execution is required. For most of these, I prefer to make use of the task sleep APIs of most RTOS packages. There are times however when no RTOS is present, or the time interval is too short for an RTOS. In those cases, a dedicated delay library comes in handy.

The quick and dirty approach is just to spin loop, like this:

#define DELAY_1MS 16000/5 // for 16MIPS
void DelayMs(WORD time)
{
   unsigned delay;
   while(time--)
      for(delay=0; delay<DELAY_1MS; delay++);
}

This code leaves a lot to be desired. For starters, it’s sensitive to compiler optimization, caching, pre-fetching or other system speed ups that can throw off the time or even eliminate the delay. Another defect is that is is hard to slip in other duties without throwing off the delay accuracy quite severely.

Better solutions usually revolve around the use of a timer peripheral. The PIC32 includes the MIPS 4K core timer. This 32 bit timer runs at the peripheral bus speed (typically 40MHz) and its value can be read. The are many other capabilities too, but for this delay library, reading the counter value will suffice.

To start our timer, we need to record the current value of the core timer. This is the starting point or “now” in our delay:

#define emStartDelay() ReadCoreTimer()

Not much here, just a macro around a read to the core timer. This returns a 32 bit value that is the starting point of the delay. Now as time goes by, the core timer will increment. The amount of time that has passed is simply: Current Timer Value – Start Timer Value. This holds true, even if the core timer should overflow. Now, if this elapsed time is greater than the desired delay, the timer has expired.

if ((ReadCoreTimer() - start) >= duration)
{
   // Stuff here
}

Note that a timer can be started and its end detected all without modifying the steady counting of the core timer, or using up any limited resources. Thus a large number of software timers can be created without any difficulty. This simple bit of code is the heart of the following delay library interface:

#define emStartDelay() ReadCoreTimer()  // Get a starting point.
B8 emIsFinishedRaw(U32 start, U32 duration);  // Finished yet?
void emFinishRaw(U32 start, U32 duration);  // Finish the delay!
void emDelayRaw(U32 duration); // Start and finish a delay!

The astute reader will have noted that most of these functions end with the word “raw”; why is that? Simply these functions deal with time only in terms of core timer ticks. For my API, I want to deal in abstract, standard time units. I chose nanoseconds because they are a standard time unit and second, they hint at the sorts of time scales I’m aiming for. To help with the transition from nanoseconds to ticks, a few macros help out:

#define NSPT_NUM (1)  // NSPT_NUM / NSPT_DEN equals the number
#define NSPT_DEN (25) // of PB tics per nanosecond.

These two integers form a rational number that represents tics per nanosecond. No floating point required or desired! At 40MHz, the core timer step period is 25ns, so each nanosecond is 1/25 of a tic. Nanoseconds are converted to tics in the next macro:

// Convert nanoseconds to core timer tics.
#if NSPT_NUM == (1)
#define CVT_NS_2_TICS(ns) ((ns)/NSPT_DEN)
#else
#define CVT_NS_2_TICS(ns) ((NSPT_NUM *(ns))/NSPT_DEN)
#endif

Note the slight optimization for the (common) case where the numerator is 1. This leads to the actual library interface:

#define emIsFinishedNS(start, ns) \
    (emIsFinishedRaw(start, CVT_NS_2_TICS(ns)))
#define emFinishNS(start, ns)     \
    (emFinishRaw(start, CVT_NS_2_TICS(ns)))
#define emDelayNS(ns)             \
    (emDelayRaw(CVT_NS_2_TICS(ns)))

Which is a flexible delay library using standardized units. Of course, Other units, like microseconds or milliseconds could be used and creating those routines is left as an exercise for the reader.

Now, let’s see this code in action. Example 1, a simple delay is needed in the code:

emDelay(200); // Delay 200ns

Example 2, a sequence of code must not complete before a minimum time:

start = emStartDelay();
// Other code goes here . . .
emFinishNS(1000); // Make sure at least 1000ns has elapsed.
// Something crucial and time sensitive here

Example 3, a state machine must remain in a state for a certain length of time:

switch (state)
{
    // other states removed
    case s_0010:
        timer = emStartDelay;
        state = s_0011;
        break;
    case s_0011:
        if (emIsFinishedNS(timer, 2000))
            state = s_0012;
        break;
    // other states removed
}

It’s assumed that the switch is executed frequently so that after 2 microseconds, state s_0011 will transition to state s_0012.

So there it is: a flexible delay library with just a core timer and a few scant lines of code. As always, comments and suggestions are welcomed.

Peter Camilleri (aka Squidly Jones)

Squidly’s Progress

Faster than a speeding tax refund!; More powerful than an HO Gauge Locomotive!; Able to reach the top floor of tall buildings by using the required number of elevators!

Look up at the ceiling! It’s a nerd! It’s so lame! It’s SUPER SQUIDLY JONES!

All kidding aside, I am glad to to say that I have finally earned the apparently not-hard-to-earn title of SUPER MEMBER on the Microchip Support forums. These forums are a great source of information on all things Microchip. I subscribe to the forums and answer questions when ever I can to help out others and keep my brain flexible. On those occasions when I get stuck, the forums are a great source of help and inspiration. Take a look at the Microchip Forums

In the meantime, if there are questions, concerns or suggestions on your mind, please feel free to submit a reply or comment.

Peter Camilleri (aka Squidly Jones)

The Trivial File System

This posting is the third in a series that began with a look at Non-volatile Options and then moved on to an examination of the M25P80 serial memory device. With this posting we look at the Trivial File System or TFS created to exploit the M25P80 as a storage medium in an embedded development system (The mikromedia for PIC32).

The TFS is a very rudimentary file system for storing parameters, settings and infrequently changed data for an embedded system. The TFS is not designed to compete or be compatible with any “real” big computer file systems. It is not designed to hold “user” data that may need to be updated frequently. Rather, the TFS is best thought of as a combination of EEPROM for parameters and an extension to flash program memory for data.

In examining the TFS, three questions would seem to be of the greatest relevance:

  1. What does the TFS do?
  2. How is the TFS used?
  3. How does the TFS work?

The TFS lives up to its name by doing as little as possible and providing only basic services with ten functions. These functions tell the story of what the TFS is all about:

emRESULT emTFSOpenFS(void)
void emTFSCloseFS(void)

These simple functions open and close the file system. The open function can return some interesting values:

emOK – the file system is opened and available.
emWRN_WRITE_PROTECT – a warning that the device is write protected.
emERR_NO_RESPONSE – can’t initialize the device.
emERR_UNFORMATTED – unformatted media.
emERR_UNKNOWN_VERSION – the file system version is unknown.

 emRESULT emTFSFormatMedia(U32 dirSize)

The format function erases all data on the M25P80 and prepares the media to receive data. There are two reasons to call the format function. 1) The open function has returned emERR_UNFORMATTED or emERR_UNKNOWN_VERSION or 2) there is a need to reclaim file space in the media. The format function is the only function that calls the erase routines of the flash memory, and because of this, it can be extremely slow, up to 20 seconds!

Note that the format function takes one argument, the directory size in bytes. Memory space for the directory must be pre-allocated and cannot be expanded. Each file  directory entry consumes 4 bytes plus one byte for each character in the name. Every time a file is replaced with a new version, a new directory entry is required. It is important that the directory area be large enough for all the files it needs to hold and yet not so large that it eats into the space needed to store the file data.

emRESULT emTFSOpenFile(PC8 name)

This function is used to open an existing file with the given name. The file name is a null terminated string of one to 15 characters and is case sensitive. Except for null, there are no reserved characters. This function returns:

A handle value if successful.
emERR_FILE_NOT_FOUND – the file could not be found.
emERR_OUT_OF_HANDLES – the supply of handles is exhausted.

emRESULT emTFSCreateFile(PC8 name, U32 size)

This function is used to create new files and replace existing ones. The name is a null terminated string, the size is the amount of file space reserved for the file. The total space used must be known when the file is created. If a file of the same name exists, it is marked as pending erase. The new file is created as write in progress. Possible return values include:

A handle value if successful.
emERR_OUT_OF_HANDLES – the supply of handles is exhausted.
emERR_INVALID_FILE_NAME – the name is not valid
emERR_OUT_OF_HANDLES - the supply of handles is exhausted.
emERR_OUT_OF_SPACE – there is not enough space for the file.
emERR_WRITE_PROTECT – the media is write protected.

emRESULT emTFSCloseFile(emTFSHANDLE hnd)

This function closes a file, freeing up the handle used to access it. If the file was newly created, its status is changed from write in progress to valid. If an older file was being replaced, its status is changed from pending erase to erased. Possible return values include:

emOK – no errors, all OK
emERR_INVALID_HANDLE – the handle was not of an opened file.

emRESULT emTFSDeleteFile(PC8 name)

This simple function deletes the file of the given name. Note however that this function does NOT free up space on the media. Only a format command can do that. The delete command merely makes the file inaccessible. Possible return values are:

emOK – all OK!
emERR_WRITE_PROTECT – the media is write protected.
emERR_FILE_NOT_FOUND – no file of that name found to delete.

emRESULT emTFSReadFile(emTFSHANDLE hnd, U32 adr, U16 len, PU8 buf)

This function is used to read data from a file where hnd is the handle of the file, adr is the offset in bytes from the start of the file (0 is the start of the file), len is the number of bytes to be read, and buf is a pointer to a buffer to hold the data. Possible return values include:

emOK – all OK
emERR_INVALID_HANDLE - the handle was not of an opened file.
emERR_INVALID_POSN – the read was not within the scope of the file.

emRESULT emTFSWriteFile(emTFSHANDLE hnd, U32 adr, U16 len, PU8 buf)

This function is used to write data to a file, sort of. The actual operation performed is to bitwise AND the new data with the data already on the media. Fresh media  has the value 0xFF so this emulates a write operation the first time it is performed. Subsequent writes to the same section of media only AND the old and new values. To get a clean slate, a new file must be created (see above).

The parameters are hnd, the handle of the file, adr is the offset in bytes from the start of the file (0 is the start of the file), len is the number of bytes to be written, and buf is a pointer to a buffer to that holds the data to be written. Possible return values are:

emOK – all OK
emERR_INVALID_HANDLE - the handle was not of an opened file.
emERR_WRITE_PROTECT - the media is write protected.
emERR_INVALID_POSN - the write was not within the scope of the file.

 U32 emTFSFileSize(emTFSHANDLE hnd)

The function returns the size of the file attached to the handle in bytes or zero if there is an error.

There. A whole file system in only ten modest functions. In future postings, the usage and internals of the TFS shall be examined. Until then, please take the time to comment or make suggestions.

Peter Camilleri (aka  Squidly Jones)