FreeRTOS FAT SL – F_FS_THREAD_AWARE

Summary

If you have been following this series of articles about the FreeRTOS FAT SL filesystem, you might have read my post called “FreeRTOS FAT SL – Musings on my Implementation”.  In that post I asked myself “Why do I get the error message ‘undefined reference to xQueueCreateMutex'”.  Here is a screen shot from PSoC Creator

FreeRTOS FAT SL Error Message

When I traced through the problem I noticed that it comes from this block of code:

#if F_FS_THREAD_AWARE == 1
	{
		extern SemaphoreHandle_t fs_lock_semaphore;

		if( fs_lock_semaphore == NULL )
		{
			fs_lock_semaphore = xSemaphoreCreateMutex();
            
			if( fs_lock_semaphore == NULL )
			{
				return F_ERR_OS;
			}
		}
	}
#endif /* F_FS_THREAD_AWARE */

Which gave me the hint that it had something to do with F_FS_THREAD_AWARE being turned on.  When I got the error message I assumed that it had something to do with the tangled mess of include files that I had created.  This flag is set in config_fat_sl.h

/**************************************************************************
**
**  FAT SL user settings
**
**************************************************************************/
#define F_SECTOR_SIZE           512  /* Disk sector size. */
#define F_FS_THREAD_AWARE       1     /* Set to one if the file system will be access from more than one task. */
#define F_MAXPATH               16    /* Maximum length a file name (including its full path) can be. */
#define F_MAX_LOCK_WAIT_TICKS   20

So, to “solve” the problem I just turned it off, knowing that I would need to come back to it to figure out because for sure if you are using the FreeRTOS FAT SL filesystem in an RTOS, which I am, you had better have the thread awareness turned on.  Specifically what the flag does is create and use a mutex around the actual “disk” to prevent re-entrant code from hosing you.

This morning I sat down and figured it out.  The answer is that I am an idiot.  Almost all of the features of FreeRTOS that you might want to use need to be turned on in the FreeRTOSConfig.h file.  This is true of mutex.  Here is the section of the file with the issue… look at line 19

#define configUSE_PREEMPTION                    1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE                 0
#define configCPU_CLOCK_HZ                      ( ( unsigned long ) CYDEV_BCLK__SYSCLK__HZ )
#define configTICK_RATE_HZ                      1000
#define configMAX_PRIORITIES                    5
#define configMINIMAL_STACK_SIZE                128
#define configMAX_TASK_NAME_LEN                 16
#define configUSE_16_BIT_TICKS                  0
#define configIDLE_SHOULD_YIELD                 1
#define configUSE_TASK_NOTIFICATIONS            1
#define configUSE_MUTEXES                       0 // <=== here is the problem line
#define configUSE_RECURSIVE_MUTEXES             0
#define configUSE_COUNTING_SEMAPHORES           0
#define configUSE_ALTERNATIVE_API               0 /* Deprecated! */
#define configQUEUE_REGISTRY_SIZE               10
#define configUSE_QUEUE_SETS                    0
#define configUSE_TIME_SLICING                  0
#define configUSE_NEWLIB_REENTRANT              0
#define configENABLE_BACKWARD_COMPATIBILITY     0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5

When I turn on the configUSE_MUTEXS things compile just fine and I can get on with my life.

FreeRTOS FAT SL – Musings on my Implementation

Summary

I just finished writing the “last” article about building a FreeRTOS FAT SL filesystem into the Cypress FM24V10 FRAM using a CY8CKIT-044.  My implementation works pretty well…. but I am not really that happy with it.  As I sit here and write this article I am not totally sure what I should do next.

I suppose the first thing to do is talk about the things that I don’t like in what I did.

  1. Real Time Clock
  2. Command Line Interpreter
  3. DMA Media Driver
  4. Include files
  5. F_FS_THREAD_AWARE 0
  6. Template project
  7. Performance Metrics
  8. Wear Leveling
  9. Performance Metrics
  10. Discussion of FAT Filesystems

Real Time Clock

As part of the port, you are supposed to provide psp_rtc.c which has one function, psp_getcurrentimedate.  This function is used to get the time to use as a timestamp on files.  I left it default, which means every transaction is timestamped the same, probably not good.  Moreover, there is an RTC in the PSoC4200M that is on the CY8CKIT-044.  The board also has the watch crystal which drives the RTC populated so there is really no good reason not to turn it on.

FreeRTOS FAT SL PSoC RTC

However, when I wrote the original example the command line interpreter that I build only took one character at a time, so there was no good way to set the clock.   Which brings me to the next problem.

Command Line Interpreter (CLI)

When I originally build the example project my command interpreter just had an infinite loop that waited for a character from the keyboard, then did one command based on that character.  It looks like this:

   while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        
        while(UART_SpiUartGetRxBufferSize()) // if there is data then read and process
        {
            char c;
            
            c= UART_UartGetChar();
			switch(c)
			{
				case 'i': 
				break;
				
			
                case '?': // Print out the list of commands
                    
                break;
                    
                default:
                break;
			}
        }
        // Turn the interrupts back on
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY); 
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }

What I did was cheap and easy… but, FreeRTOS has a CLI built in, so I suppose that I should have used.

DMA Media Driver

When I read and write from the FRAM I put in code that is blocking.  Meaning that it essentially hangs the entire system until they return.  Not really good given that this is an RTOS.  This is what all of the I2C_ functions below do.

  status = I2C_I2CMasterSendStart( calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); //

There is no reason that I shouldn’t have used the DMA engine to read and write the FRAM which would have freed up the processor.

Include files

I absolutely hate the scheme that I used to name and use the includes in the FreeRTOS FAT SL port.  I should fix this for sure.

F_FS_THREAD_AWARE 0

When I originally tried to compile this project I marked this #define from “config_fat_sl.h” as “1”.  However when I do that, I end up with this error which I should for sure fix as the code is not reentrant and this #define protects you.

An RTOS bug in the FreeRTOS FAT SL Implementation

Template project

My good friend Mark Saunders pointed out PSoC Creator has a new feature which you can use to make template projects.. which I didnt know about until he told me.  Obviously this would be better than what I am doing.

Performance Metrics

I did not collect any performance metrics when I build this project.  How much RAM? Flash?  How long does it take to read and write files?  I don’t know.  Moreover, I put in debugging information into the media driver which was counter productive to good memory usage.  For instance in this snip from the readsector function I define a big ass buffer of 128 bytes on line 134, then I printout a message to the uart each time this function is called.

static int fram_readsector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    char buff[128]; // A scratch buffer for UART Printing
    (void)driver;
    uint16 address;
    uint32_t status;
    
    sprintf(buff,"Read sector %d\n",(int)sector);
    UART_UartPutString(buff);

Error Checking

There are a bunch of places where I could have put in much better error checking, and I didnt.  For instance in this section of the readsector function if the I2C_I2CMasterWriteByte function fails, it probably hangs the I2C bus until the chip is reset… this is bad.  Even when an error occurs, printing a message probably isn’t a good idea (line 146).

    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    
    I2C_I2CMasterSendRestart(calcI2CAddress(sector),I2C_I2C_READ_XFER_MODE);

Wear Leveling

Many nonvolatile memory chip will wear out if you write them too many time.  Even 100K cycles can easily happen on a key sector of the filesystem for instance sector 0.  One convient thing about the FRAM is that it doesnt wear out.  But, when I started this journey I was originally going to use the PSoC6 development kit which uses a NOR Flash.  The NOR Flash will for sure wear out.  To combat this problem, people have developed wear leveling schemes.  But I don’t address this issue at all with my media driver.

Discussion of FAT Filesystems

As I wrote about the FreeRTOS FAT SL Filesystem I was originally planning a tutorial on file systems.  But as I dug a little bit a whole bunch of issues came up which felt a little bit overwhelming to address.  The issues that were left unaddressed are:

  1. Copywrite of the FAT File System
  2. Efficiency of FAT File Systems
  3. The licensing of the FreeRTOS FAT SL
  4. Other FAT implementations

I suppose that at some point I should come back and look at those issues.

FreeRTOS FAT SL – PSoC Example Project (Part 2)

Summary – Examples using FreeRTOS FAT SL

In the previous several articles I have written about the FreeRTOS FAT SL Filesystem.  This included using the Cypress FM24V10 FRAM, creating an FRAM media driver and building an example project.  This article will show you the C-functions in “extestfs.c” that are used to actually test the filesystem.  Notice that I named all of the functions starting with “ex” which I adopted from the FreeRTOS FAT SL examples.  The functions in this article are all called by the command line interpreter that I built in the previous post.  They include

  • exInit – Initializing the Filesystem
  • exCreateFile – Creating a file in the FileSystem
  • exReadFile – Reading the data from a file
  • exFormat – Formatting the disk
  • exDriveInfo – Printing the drive information
  • exDirectory – Print an “ls” or “dir”

exInit

Before the FreeRTOS FAT SL Filesystem can do anything, you must initialize it.  All this function does is call the function that I created to turn on the file system in the media port.  One thing that is interesting is that it returns an error code that tells you if the file system is formatted or  not.   It figured this out by looking at the data in the first sector of the FRAM.

// This function initalize the filesystem
void exInit(void)
{
    unsigned char ucStatus;
    
	/* First create the volume. */
	ucStatus = f_initvolume( fram_initfunc );
    UART_UartPutString("Initialized sucessfully\n");
	/* It is expected that the volume is not formatted. */
	if( ucStatus == F_ERR_NOTFORMATTED )
	{
        UART_UartPutString("Filesystem Unformatted\n");
	}
    
    else
    {
        UART_UartPutString("Filesystem Formatted\n");
    }
}

exCreateFile

This functiom creates a file called “afile.txt” in the FreeRTOS FAT SL filesystem with the ASCII characters for 0-9.  I did something that was probably not helpful in that I made a loop (on line 43) that started a ‘0’ which is also known as 49 (go look at the ASCII table).

This function calls f_open which will create a new file when it is passed the argument “w”.  I also use the function f_putc to output characters to the file.

As I am looking at the code to write this article I realized that I didnt f_close the file (which I have now fixed).

#define FILENAME "afile.txt"
// exCreateFile - this function creates a file called "afile.txt" an
void exCreateFile(void)
{
    F_FILE *pxFile;
    UART_UartPutString("Attempting Create & Write File\n");
    pxFile = f_open(FILENAME, "w" );
  
    if(pxFile)
    {
        for(int i='0' ; i< '9' ; i++)
        {
            f_putc(i,pxFile);
        }
        f_close(pxFile);
    }
    else
    {
        UART_UartPutString("Failed File Create\n");
    }
}

exReadFile

The exReadFile functions reads all of the characters out of the “afile.txt” and prints them to the screen.  It uses the function f_getc which reads one character from the file.  Notice that I open the file in read mode by using the “r” option.

void exReadFile(void)
{
    UART_UartPutString("Attempting Read\n");
    F_FILE *pxFile;
    pxFile = f_open( FILENAME, "r" );
    if(pxFile)
    {
        while(!f_eof(pxFile))
        {
            UART_UartPutChar(f_getc(pxFile));
        }
        f_close(pxFile);
    }
    else
    {
        UART_UartPutString("File not found\n");
    }
}

exFormat

The exFormat function calles f_format with the FAT12 option.  It is impossible to use FAT16 and FAT32 which require a much larger media to use.  FAT12 was originally created for use on smallish floppy disk.

void exFormat(void)
{
    char buff[128];
	/* Format the created volume. */
    unsigned char ucStatus;
	ucStatus = f_format( F_FAT12_MEDIA );
	if( ucStatus == F_NO_ERROR )
	{
        UART_UartPutString("Formatted sucessfully\n");
    }
    else
    {
        sprintf(buff,"Error = %d\n",ucStatus);
        UART_UartPutString(buff);   
        UART_UartPutString("Format Error\n");
    }   
}

exDriveInfo

The exDriveInfo function calls the f_getfreespace function to find out how much space is free for use on the FRAM FreeRTOS FAT SL Filesystem.

void exDriveInfo( void )
{
    char buff[128];
    F_SPACE xSpace;
    unsigned char ucReturned;

    /* Get space information on current embedded FAT file system drive. */
    ucReturned = f_getfreespace( &xSpace );
    if( ucReturned == F_NO_ERROR )
    {
        /* xSpace.total holds the total drive size, xSpace.free holds the
        free space on the drive, xSpace.used holds the size of the used space
        on the drive, xSpace.bad holds the size of unusable space on the
        drive. */
        sprintf(buff,"Free Space = %lu\nUsed Space = %lu\nTotal = %lu\n",xSpace.free,xSpace.used,xSpace.total);
        UART_UartPutString(buff);
    }
    else
    {
        /* xSpace could not be completed.  ucReturned holds the error code. */
        UART_UartPutString("Free space failed\n");
    }
}

exDirectory

The exDirectory function calls the f_findfirst and f_findnext functions to iterate all of the files and directories on the top level of the file system.  The f_findfirst function uses a wildcard regular expression to match filenames. When it finds a file it prints information about that file.

void exDirectory( void )
{
    char buff[128];
    F_FIND xFindStruct;

    /* Print out information on every file in the subdirectory "subdir". */
    if( f_findfirst( "*.*", &xFindStruct ) == F_NO_ERROR )
    {
        do
        {
            sprintf(buff,"filename:%s ", xFindStruct.filename );
            UART_UartPutString(buff);

            if( ( xFindStruct.attr & F_ATTR_DIR ) != 0 )
            {
                UART_UartPutString ( "is a directory directory\n" );
            }
            else
            {
                sprintf ( buff, "is a file of size %lu\n", xFindStruct.filesize );
                UART_UartPutString(buff);
            }

        } while( f_findnext( &xFindStruct ) == F_NO_ERROR );
    }
}

 

FreeRTOS FAT SL – PSoC Example Project (Part 1)

Summary

In the previous articles I discussed using the FRAM, and making a FreeRTOS FAT SL media driver.  In this article I will discuss building an example project that uses that work to make a real FreeRTOS FAT SL system example.  I was originally planning on making just one article explaining the example, but my example code turned out to be a little bit sprawling, so I decided to break it into two pieces.  In part 1 (this article) I will

  1. Build the FreeRTOS Template
  2. Import the FreeRTOS FAT SL FileSystem & FRAM Media Driver
  3. Build a command line interpreter
  4. Nuke the data in the FRAM

In the next article I will show you the details of using the FreeRTOS FAT SL filesystem.

Build the FreeRTOS Template & Schematic

I start the project from my FreeRTOS template project.  You can read all about that here.  Then I add in the components required to make things work.  The schematic is simple:

  • A UART component to run the Command Line Interpreter
  • An I2C to control the FRAM

FreeRTOS FAT SL Example Schematic

The I2C is setup as a master at it highest speed setting.

FreeRTOS FAT SL I2C Configuration

On the CY8CKIT-044 the pins need to be configured as so:

FreeRTOS FAT SL - Pin Configuration

Import the FreeRTOS FAT SL FileSystem & FRAM Media Driver

The first step in importing the FreeRTOS FAT SL FileSystem and and media driver is to copy the FreeRTOS-Plus-FAT-SL directory into my project.  Once that is done, my folder structure looks like this:

FreeRTOS FAT SL Code Layout

The next step is to fix the include paths to include the config, api and psp include directories.  This can be done on the build settings menu.

FreeRTOS FAT SL Include Files

Finally I import the .h and .c files into my project so that it looks like this:

FreeRTOS FAT SL Project Directory Structure

Build a Command Line Interpreter (CLI)

I think that it is convenient to have a command line utility (one that connects to the UART port) to send commands to my program to test it.  To build the CLI, I create a task called uartTask which takes keys from the terminal and then processes them in a giant switch statement.  While I was building the FreeRTOS FAT SL system, I noticed that FreeRTOS also has a scheme for CLIs which I will try out in the future.

In the UART configuration I turn on the “RX FIFO not empty” interrupt (meaning that there is data in the fifo that needs to be processed)

FreeRTOS FAT SL UART Configuration

I connect the interrupt to the interrupt service routine on line 64.  In the ISR I use the FreeRTOS notification scheme to send notifications to the uartTask to wakeup and process data (line 50).

My CLI has two different things that it does:

  • It calls functions in the file extestfs.c that start with “ex” , meaning example, to format, initalize etc.
  • It keeps a “currentSector” variable that lets me print out the raw data in a sector.  The currentSector variable is incremented (with the keyboard +) and decremented (with the keyboard -) and set to 0 with the (keyboard 0)

Once all of the keys have been processed (the Rx FIFO buffer is empty – line 71), it turns back on the Rx FIFO interrupt (lines 150-151), then waits for another notification from the ISR (line 69)

TaskHandle_t uartTaskHandle;
void uartISR()
{
    
    BaseType_t xHigherPriorityTaskWoken;
    // disable the interrupt
    UART_SetRxInterruptMode(0);
    vTaskNotifyGiveFromISR(uartTaskHandle,&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    
}

// This is the main task which processes commands from the UART and prints the results
// on the screen.
void uartTask(void *arg)
{
    (void)arg;
    UART_Start();
    I2C_Start();
    clearScreen();
    UART_UartPutString("Start Filesystem Demo\n");
    UART_SetCustomInterruptHandler(uartISR);
    
    int currentSector=0;    
    while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        
        while(UART_SpiUartGetRxBufferSize()) // if there is data then read and process
        {
            char c;
            
            c= UART_UartGetChar();
			switch(c)
			{
				case 'i': // Initialize the RTOS
					exInit();
				break;
				case 'f': // Format the FRAM
					exFormat();
				break;
                    
				case 'q': // Return the drive information
					exDriveInfo();
				break;
			
                case 'w': // Create a file
				    exCreateFile();
				break;
					
				case 'r': // Read the file that was created (if it exists)
				    exReadFile();
				break;
					
				case '0': // Goto to sector 0 and print the data
				    currentSector = 0;
					exPrintSector(0,0,0);
				break;
				
			    case '+': // Print the "current" sector and go to the next
				    exPrintSector(0,0,currentSector);
				    currentSector += 1;
			    break;
					
			    case '-': // print the current sector and go to the previous
				    exPrintSector(0,0,currentSector);
				    currentSector -= 1;
				    if(currentSector<0)
					    currentSector =0;
			    break;
                    
    			case 'd': // Do a directory
	    			exDirectory();
		    	break;
                    
                case 'c':
                    clearScreen();
                break;
                    
                case 'b': // Erase the FRAM (write all 0's)
                    blankFRAM();
                break;
			
                case '?': // Print out the list of commands
                    UART_UartPutString("b - Blank FRAM - write all 0's\n");
                    UART_UartPutString("i - Initalize Filesystem\n");
                    UART_UartPutString("f - Format FRAM\n");
                    UART_UartPutString("q - Print FileSystem Information\n");
                    UART_UartPutString("w - Create a file called \"afile.bin\"\n");
                    UART_UartPutString("r - Read the file called \"afile.bin\" and print contents\n");
                    UART_UartPutString("0 - Goto sector 0 and print contents\n");
                    UART_UartPutString("+ - Goto next sector and print contents\n");
                    UART_UartPutString("- - Goto previous sector and print contents\n");
                    UART_UartPutString("d - Print Directory\n");
                    UART_UartPutString("c - Clear Screen\n");
                    UART_UartPutString("? - Print help\n");        
                    
                break;
                    
			    default:
				    UART_UartPutString("Unknown :");
				    UART_UartPutChar(c);
				    UART_UartPutChar('\n');
			    break;
			}
        }
        // Turn the interrupts back on
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY); 
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }
}

Nuke the FRAM Data

I thought that it would be a good idea to have a function to put 0’s in all of the locations in the FRAM, to prove that I could start with a clean slate.  This function does just that.  Specifically it write 0’s to the 64K locations in I2C address 0x50 (the first bank) and to I2C address 0x51 (the second bank)

// This function erases - write 0's into the FRAM... also known as NUKE the FRAM
void blankFRAM(void)
{
    int i;
    UART_UartPutString("Starting Erase 1st Block\n");
    I2C_I2CMasterSendStart(0x50,I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte(0);
    I2C_I2CMasterWriteByte(0);
    for(i=0;i<0xFFFF;i++) // Write 64K of 0's to erase first bank
    {
        I2C_I2CMasterWriteByte(0);
    }
    I2C_I2CMasterSendStop();
    UART_UartPutString("Starting Erase 2nd Block\n");
    I2C_I2CMasterSendStart(0x51,I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte(0);
    I2C_I2CMasterWriteByte(0);
    for(i=0;i<0xFFFF;i++) // write 64k of 0's to erase 2nd bank
    {
        I2C_I2CMasterWriteByte(0);
    }
    I2C_I2CMasterSendStop();
    
    UART_UartPutString("Erase Complete\n");

}

As always you can find this project on the IoT Expert GitHub site git@github.com:iotexpert/PSoC-FileSystem.git

FreeRTOS FAT SL FileSystem Porting to PSoC4M

CY8CKIT-044 for FreeRTOS FAT FL FileSystem

Summary of FreeRTOS FAT SL FileSystem Port

In the previous article I discussed the Cypress 24V10 FRAM which I am going to use to store nonvolatile data for the FreeRTOS FAT SL FileSystem.  The “SL” in the name stands for “super light” and was built by HCC Embedded for, get this, embedded applications.

In this article I am going to show you how to build a media driver to make the FreeRTOS FAT SL FileSystem work.  In the next article I will talk about how to actually use the FreeRTOS FAT SL FileSystem on the CY8CKIT-044 using the Cypress FMV24V10 FRAM.

The media driver is pretty simple,  you just need to provide the following functions:

And two structures

  • F_DRIVER – Function pointers to the media driver functions
  • F_PHY – Information about the FRAM

To make all of this work I will start by copying the “ram” driver that was provided as an example (ill call mine framdrv_f.c).  The file that I started with resides in FreeRTOS-Plus/Source/FreeRTOS-Plus-FAT-SL/media-drv/ram/ramdrv_f.c

F_DRIVER Structure

The F_DRIVER structure mostly contains function pointers to the driver functions (which you create).  All throughout the FreeRTOS FAT SL FileSystem code it will do things like :

status = mdrv->getstatus( mdrv );

which calls the function in the media driver structure named “getstatus”.

Here is the exact definition of the F_DRIVER STRUCTURE (found in FreeRTOS-Plus-FAT-SL/api/api_mdriver.h) :

typedef struct F_DRIVER  F_DRIVER;

typedef int           ( *F_WRITESECTOR )( F_DRIVER * driver, void * data, unsigned long sector );
typedef int           ( *F_READSECTOR )( F_DRIVER * driver, void * data, unsigned long sector );
typedef int           ( *F_GETPHY )( F_DRIVER * driver, F_PHY * phy );
typedef long          ( *F_GETSTATUS )( F_DRIVER * driver );
typedef void          ( *F_RELEASE )( F_DRIVER * driver );

typedef struct F_DRIVER
{
  unsigned long  user_data;     /* user defined data */
  void         * user_ptr;      /* user define pointer */

  /* driver functions */
  F_WRITESECTOR          writesector;
  F_READSECTOR           readsector;
  F_GETPHY               getphy;
  F_GETSTATUS            getstatus;
  F_RELEASE              release;
} _F_DRIVER;

typedef F_DRIVER *( *F_DRIVERINIT )( unsigned long driver_param );

The user_data and user_ptr do not appear to be used anywhere in the FreeRTOS FAT SL FileSystem code, so I am not sure what they had in mind for those variables.

The F_PHY Structure

The other structure that is needed (sort of) is F_PHY.  This structure contains parameters that were originally meant for disk drives and are no longer used.  For the FRAM I will divide up the 128k array into “sectors” of “512” bytes.  For some reason which I don’t understand the sector size must be fixed to 512 bytes.  The history of FAT filesystems is incredibly messy, and I started to dig through it so that I understood the number, but I am not sure that there are enough hours in my life.

typedef struct
{
  unsigned short  number_of_cylinders;
  unsigned short  sector_per_track;
  unsigned short  number_of_heads;
  unsigned long   number_of_sectors;
  unsigned char   media_descriptor;

  unsigned short  bytes_per_sector;
} F_PHY;

As I wrote this article I didnt remember setting up the F_PHY structure… but things still worked (ill address this later in the article).

F_DRVERINIT()

This function sets up the function pointers and marks the in_use variable.

/****************************************************************************
 *
 * fram_initfunc
 *
 * this init function has to be passed for highlevel to initiate the
 * driver functions
 *
 * INPUTS
 *
 * driver_param - driver parameter
 *
 * RETURNS
 *
 * driver structure pointer
 *
 ***************************************************************************/
F_DRIVER * fram_initfunc ( unsigned long driver_param )
{
  ( void ) driver_param;

    UART_UartPutString("Calling init\n");

  if( in_use )
    return NULL;

  (void)psp_memset( &t_driver, 0, sizeof( F_DRIVER ) );

  t_driver.readsector = fram_readsector;
  t_driver.writesector = fram_writesector;
  t_driver.getphy = fram_getphy;
  t_driver.release = fram_release;

  in_use = 1;

  return &t_driver;
} /* fram_initfunc */

F_GETPHY()

This function returns information about the FRAM including the size (in sectors) and the size of the sectors (in bytes)

/****************************************************************************
 *
 * ram_getphy
 *
 * determinate ramdrive physicals
 *
 * INPUTS
 *
 * driver - driver structure
 * phy - this structure has to be filled with physical information
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
static int fram_getphy ( F_DRIVER * driver, F_PHY * phy )
{
  /* Not used. */
  ( void ) driver;

  phy->number_of_sectors = maxsector;
  phy->bytes_per_sector = F_SECTOR_SIZE;

  return MDRIVER_RAM_NO_ERROR;
}

FRAM Helper Functions

In order to map sectors to the correct I2C address (remember the FRAM has two banks of memory in two different I2C addresses) and to the correct bank address, I created two function which map “sector” into address and I2C address.

static const unsigned long maxsector = FDRIVER_VOLUME0_SIZE / F_SECTOR_SIZE;
static const unsigned long halfsector = FDRIVER_VOLUME0_SIZE / F_SECTOR_SIZE / 2;

/****************************************************************************
 *
 * calcAddress
 *
 * This function takes a sector and returns the address in the bank for the
 * start of the secor
 *
 * INPUTS
 *
 * unsigned long sector - which logical sector in the FRAM
 * 
 * RETURNS
 *
 * The FRAM Address of the sector
 *
 ***************************************************************************/
static inline uint32_t calcAddress(unsigned long sector)
{
    if(sector < halfsector)
        return sector * F_SECTOR_SIZE;
    else
        return (sector-halfsector) * F_SECTOR_SIZE;
}

/****************************************************************************
 *
 * calcI2CAddress
 *
 * This function takes a sector from 0 --> maxsector and figures out which bank
 * the address exists.
 *
 * INPUTS
 *
 * unsigned long sector - which logical sector in the FRAM to write to
 * 
 * RETURNS
 *
 * The I2C Address of Bank 0 or Bank 1
 *
 ***************************************************************************/
static inline uint32_t calcI2CAddress(unsigned long sector)
{
    if(sector < halfsector)
        return 0x50; // I2C Bank 0 Address from the datasheet
    else
        return 0x51; // I2C Bank 0 Address - From the datasheet  
}

F_READSECTOR()

This function reads 512 bytes that are located in the sector into the RAM buffer pointed to by data.  Notice on lines 26 & 27 I put in debugging information.  In order to read you need to

  • Send a start (line 30)
  • Send the I2C address and the write bit
  • Set the address you want to read from (line 37-38)
  • Send a restart (line 41)
  • read the 512 bytes (lines 44-54)
  • On the last byte NAK (line 47-50)
/****************************************************************************
 *
 * fram_readsector
 *
 * This function reads 512 bytes into the SRAM at the pointer data
 *
 * INPUTS
 *
 * driver - driver structure
 * void *data - a pointer to the SRAM where the 512 bytes of data will be written
 * unsigned long sector - which logical sector in the FRAM to read from
 * 
 * RETURNS
 *
 * error code or MDRIVER_RAM_NO_ERROR if successful
 *
 ***************************************************************************/

static int fram_readsector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    char buff[128]; // A scratch buffer for UART Printing
    (void)driver;
    uint16 address;
    uint32_t status;
    
    sprintf(buff,"Read sector %d\n",(int)sector);
    UART_UartPutString(buff);
    
    address = calcAddress(sector);
    status = I2C_I2CMasterSendStart( calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    
    I2C_I2CMasterSendRestart(calcI2CAddress(sector),I2C_I2C_READ_XFER_MODE);
    
    uint8_t d;
    for(i=0;i<F_SECTOR_SIZE;i++)
    {
        
        if(i != F_SECTOR_SIZE - 1)
            d = I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA);
        else
            d = I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA);
         
        *((uint8_t *)data + i) = d;
        
    }
    
    I2C_I2CMasterSendStop();
    
  return MDRIVER_RAM_NO_ERROR;
}

F_WRITESECTOR()

The write sector is very similar to the read.  To write the data you need to

  • Send a start (line 204)
  • Write the address you want to write to (line 205-206)
  • Write the 512 bytes (lines 208-214)
  • Send a stop (line 215)
/****************************************************************************
 *
 * fram_writesector
 *
 * This function takes 512 bytes of user input and writes to the FRAM in the
 *
 * INPUTS
 *
 * driver - driver structure
 * void *data - a pointer to the SRAM where the 512 bytes of data exists
 * unsigned long sector - which logical sector in the FRAM to write to
 * 
 * RETURNS
 *
 * error code or MDRIVER_RAM_NO_ERROR if successful
 *
 ***************************************************************************/

static int fram_writesector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    (void)driver;
    char buff[128]; // A scratch buffer for UART Printing
    uint16 address;
    int i;
        
    sprintf(buff,"Wrote sector %d\n",(int)sector);
    UART_UartPutString(buff);
        
    address = calcAddress(sector);
    I2C_I2CMasterSendStart(calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    for(i=0;i<F_SECTOR_SIZE;i++)
    {
        uint8_t d = *((uint8_t *)data +i);
       
        I2C_I2CMasterWriteByte(d); 
        
    }
    I2C_I2CMasterSendStop();
    
  return MDRIVER_RAM_NO_ERROR;
}

F_GETSTATUS()

As I wrote the article I noticed in the documentation that I needed to provide the “F_GETSTATUS” function.  The reason that I had not noticed before was that it was not in the media driver provided in the distribution.  That being said, my implementation always returns a 0 indicating OK.

/****************************************************************************
 *
 * fram_getstatus
 *
 * This function must return the status of the drive... F_ST_MISSING, F_ST_CHANGED
 * or F_ST_WRPROECT or 0 (for OK)
 * INPUTS
 *
 * driver_param - driver parameter
 *
 *
 * For the FRAM I dont support any of these other status's
 ***************************************************************************/

static long fram_getstatus(F_DRIVER *driver)
{
    (void) driver;
    // F_ST_MISSING	The media is not present (it has been removed or was never inserted).
    //F_ST_CHANGED	Since F_GETSTATUS() was last called the media has either been removed and re-inserted, or a different media has been inserted.
    //F_ST_WRPROTECT	The media is write protected.
    
    return 0;
}

F_RELEASE()

This function simply set the in_use to 0;

/****************************************************************************
 *
 * fram_release
 *
 * Releases a drive
 *
 * INPUTS
 *
 * driver_param - driver parameter
 *
 ***************************************************************************/
static void fram_release ( F_DRIVER * driver )
{
  /* Not used. */
  ( void ) driver;

  /* Disk no longer in use. */
  in_use = 0;
}

In the next article I will show you the source code for an example project using the FreeRTOS FAT SL Filesystem.

FreeRTOS FAT SL FileSystem – Using the FM24V10 FRAM

Summary

Last week while working on a PSoC FreeRTOS Project, I looked at the CY8CKIT-044 Development kit that I was using and remembered that right under the Ambient Light Sensor there is a Cypress FM24V10 FRAM.  Way back, I started pushing our Development Kit team to put other Cypress parts onto the dev kits…. and because they are awesome, they aggressively started doing that.  One of those chips is the FM24V10 FRAM.  As I looked at the chip, I realized again that I had never really written anything into the FM24V10 FRAM on any of our dev kits, which seems silly after I pushed the dev kit team for them.  I realized that the reason I had never used the FM24V10 FRAM was I didn’t have a nifty way to write files.  But, while working on the other FreeRTOS projects, I noticed the FreeRTOS FAT SL Filesystem is built into FreeRTOS.  The next couple (3?) articles I will talk about making the FreeRTOS FAT SL Filesystem work on a PSoC4200M with a FM24V10 I2C FRAM.

CY8CKIT-044 with FM24V10 FRAM

The Cypress FM24V10 FRAM

The “F” in FRAM stands for ferroelectric.  What that means is each of the memory cells is a little magnet.  So what you say?  Well the “what” is that FRAMs don’t wear out (unlike Flash), you can write individual cells (unlike Flash), and it is super low power, it is plenty fast (I2C = 3.4MBS), and it retains data for a long long time (151 years).

That list sounds great.  Actually really great.  But, what is the but?  There are two answers to that question.  The first answer is there is a density penalty, the biggest I2C FRAM is 1Mb where you can buy a 1Gb NOR Flash or even bigger NAND Flash.  The next issue is price.  The digikey pricing for a 128Kb FRAM is $12.76 or 9.9c/kb and a 1GB NOR Flash is $14.97 or 0.0014c/kb.  That means an FRAM is 6724 x as expensive per bit.

How do I use it?  The FRAM is attached to the I2C pins on the CY8CKIT-044.  That means that I can talk to it with either the Bridge Control Panel, or with the PSoC.  To start with I will show you the Bridge Control Panel.  First, I probe the I2C bus, and I see that there are three I2C devices.

  • 0x0F = The I2C Accelerometer (which I wrote about here)
  • 0x50= The first 64K of FRAM
  • 0x51 = The second 64K of FRAM

The 128K FRAM is divided into two banks which are accessed on separate I2C addresses.  By splitting 128K into two blocks of 64K the chip designers optimized the addressing of the memory.  In order to access 64K you need 16-bits, which means to access 128K you need 17-bits.  This would have required sending a whole extra byte of address when you really only needed 1 of those 8 bits.  They avoid this problem by having two addresses.

The FM24V10 FRAM is easy to use as it implements an EzI2C (like) protocol.  EZI2C keeps an internal address pointer.  The pointer is automatically incremented each time you do an I2C transaction.  When you do a write transaction, the first two bytes you write are placed into the address pointer.  I say “EZI2C like” because the pointer is not retained between transactions.  Some examples:

If I want to write 0x56 into location 0x0123 I would issue

  • I2C Start
  • I2C Write to I2C address 0x50
  • Send 0x01, 0x02 (which write the address pointer)
  • Send 0x56
  • I2C Stop

I can do that sequence with the Bridge Control Panel command “W 50 01 23 56 P;”  The “W” stands for “send a start and send the 7-bit address 0x50 (left shifted 1) and send a ‘0’ for write”.  For some reason the engineers at Philips decided that the I2C protocol would have 7-bit addressing and then 1 bit to represent write or read.  When you talk about I2C there is always confusion about “8-bit address” versus “7-bit address”.  When they talk about 8-bit address they are talking about the 7-bit address shifted left 1 bit and then or-ed with the read or write bit.  Here is a picture from the bridge control panel:

And the same thing from the logic analyzer.

Once I have written 0x56 into address 0x0123, I can read it back by a “w 50 01 23 r 50 x p;” which sends:

  • I2C Start
  • I2C Write to I2C address 0x50
  • Send 0x01, 0x02 (which write the address pointer)
  • I2C Restart
  • Sends 0x50 (shifted left) or-ed with “read”
  • Reads the byte
  • I2C Stop

In the transaction output window below, you can see that the “x” is replaced with “56” meaning that it read the previously written value back.

The only other interesting thing about the protocol is that once you have sent and address, you can keep reading, or writing without sending the address again.  For instance to write 01, 02, 03, 04 to address 23, 23,25 and 26 you can send “W 50 00 23 01 02 03 04 p”, then you can read it back with “W 50 00 23 r 50 x x x x p;”

In the next Article I will start the process of using the Free RTOS File System to write and read from the I2C FRAM.