FatFs with Kinetis

An SD (Secure Digital) Card interface is kind of standard for many applications today: it provides a lot of memory at a reasonable cost. Still, I need a software stack for it, up to the level of a file system. So far I was really happy with using FatFs: an open source FAT file system provided by Elm-Chan. I’m using the FatFs Processor Expert component already in multiple designs. What was missing: a port to the Freescale Kinetis ARM Cortex-M4 family of processors.

FatFs Architecture

FatFs is provided as set of open source files implementing the file system and block management:

FatFs Layers (Source: Elm-Chan)

FatFs Layers (Source: Elm-Chan)

To make things easy for an application (and programmer), I have Processor Expert components created which encapsulate the different blocks:

  1. FatFs module: the file system is implemented with the FAT_FileSystem component.
  2. Low level disk I/O: this performs read/write operations on the bus to the memory. For non-Kinetis this is implemented with the SD_Card component plus HW or SW SPI drivers; for Kinetis this is the FatFsMemSDHC component plus a SDHC_LDD driver.
  3. RTC: A Real Time Clock for time/date information, if files are written or changed on the disk. Here the GenericTimeDate component is used (see “There is a Time and Date for both Worlds”).

To verify my port, I have created an application with FatFs, FreeRTOS and a serial shell application.

Hardware

As hardware I use the TWR-K60N512 with the TWR-SER:

Tower System (TWR-K60N512, TWR-SER, TWR-ELE and P&E Multilink FX)

Tower System (TWR-K60N512, TWR-SER, TWR-ELEV and P&E Multilink FX)

The TWR-ELEV card provides power and a signal bus. The TWR-SER provides a serial connection to the host. As microcontroller card I use the TWR-K60N512, as it has a SD card slot on the backside of the card:

Back Side of Tower System with SD Card Slot

Back Side of Tower System with SD Card Slot

For debugging, CodeWarrior and the P&E Multilink Fx provide fast debugging and turnaround cycles.

Processor Expert Application

The application consist of multiple Processor Expert components:

Application with FatFs

Application with FatFs

Timeout Component

As many things can timeout working with SD cards, a special Timeout component is used. That component needs be ‘served’ by a timer interrupt, say every 10 ms (as specified in the Timeout component properties. This can be done e.g. with adding a TimerInt component and then call the AddTick() method from the interrupt event in Events.c:

void TI1_OnInterrupt(void)
{
  TMOUT1_AddTick();
}

💡 Alternatively a ‘shared’ interrupt event can be used, e.g. the TickTimer of an RTOS.

FAT_FileSystem Component

The FAT_FileSystem component implements the file system and offers typical file operations like reading and writing files:

FAT_FileSystem Component

FAT_FileSystem Component

In the properties many things of the file system are configured. One important thing is the ‘Memory’ property which implements the low-level disk I/O:

FAT_FileSystem Properties

FAT_FileSystem Properties

FatFsMemSDHC

For Kinetis, a new FatFsMemSDHC component implements the low-level disk I/O routines:

FatFsMemSDHC Component

FatFsMemSDHC Component

In the component properties, the speed, card detection and interfaces to other components are specified:

FatFsMemSDHC Properties

FatFsMemSDHC Properties

  • The component supports three Speed Mode: ‘slow‘ (max 400 kHz SPI clock) for card initialization, ‘normal‘ for normal operations after initialization (up to about 12 MHz SPI) and a ‘fast‘ speed for high-speed cards if supported by the card. The numbers are index values inthe the SDHC_LDD speed table (more about this below).
  • An optional Card Detect Pin: This references a GPIO_LDD component and field name. Additionally code for pin pullup select and pullup enable is provided.
  • Links to the SDHC and Timeout components complete the system interfaces.

The properties are mainly used in the driver Init() method:

%-BW_METHOD_BEGIN Init
%ifdef Init
%define! ParUserDataPtr
%define! RetVal
%include Common\FatFsMemSDHCInit.Inc
LDD_TDeviceData* %'ModuleName'%.%Init(LDD_TUserData *UserDataPtr)
{
  bool Error = FALSE;

  (void)UserDataPtr; /* not used */
  SD.SDHCPtr = 0;
%ifdef GPIO_LDD
  SD.GPIOPtr = 0;
%endif
  SD.Finished = FALSE;
  SD.Inserted = FALSE;
  SD.CardId = inherited.SDHC.NO_CARD;

%ifdef GPIO_LDD
  /* Enable pull-up on GPIO pin used for SD card detection */
  %CDPinSelectPullupCode
%CDPinEnablePullupCode
  SD.GPIOPtr = inherited.GPIO_LDD.Init(&SD);
%endif
  SD.SDHCPtr = inherited.SDHC.Init(&SD);
%ifdef GPIO_LDD
  SD.Inserted = (inherited.GPIO_LDD.GetFieldValue(SD.GPIOPtr, %CDFieldName) != 1);
%else
  SD.Inserted = FALSE;
%endif
  SD_Wait(&SD, &Error); /* Wait for card reset to finish */
  if (!Error && SD.Inserted) {
    if (inherited.SDHC.DetectCards(SD.SDHCPtr)!=ERR_OK) {
      Error = TRUE;
    }
    SD_Wait(&SD, &Error);
    if (!Error && SD.CardId != inherited.SDHC.NO_CARD) {
      /* card detected - selecting card... */
      Error = inherited.SDHC.SelectCard(SD.SDHCPtr, SD.CardId)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error) {
      /* card selected - requesting card info... */
      Error = inherited.SDHC.GetCardInfo(SD.SDHCPtr, &SD.CardInfo)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error) {
      /* switching gears for higher speed */
      Error = inherited.SDHC.SelectBusClock(SD.SDHCPtr, %'ModuleName'%.SPEED_INDEX_NORMAL)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error && SD.CardInfo.Caps.HighSpeed) {
      Error = inherited.SDHC.SelectBusClock(SD.SDHCPtr, %'ModuleName'%.SPEED_INDEX_FAST)!=ERR_OK;
      SD_Wait(&SD, &Error);
      /* running at high speed (high slew rate on all the SDHC pins should be set) */
    }
    if (!Error) {
      if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_8_BIT) {
        Error = inherited.SDHC.SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_8_BIT)!=ERR_OK;
      } else if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_4_BIT) {
        Error = inherited.SDHC.SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_4_BIT)!=ERR_OK;
      } else if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_1_BIT) {
        Error = inherited.SDHC.SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_1_BIT)!=ERR_OK;
      }
      SD_Wait(&SD, &Error);
      /* selected highest possible data width */
    }
  }
  return &SD;
}

%endif %- Init
%-BW_METHOD_END Init

After generating code, this produces the following C code:

/*
** ===================================================================
**     Method      :  FATM1_Init (component FatFsMemSDHC)
**
**     Description :
**         Initialization method
**     Parameters  :
**         NAME            - DESCRIPTION
**       * UserDataPtr     - Pointer to RTOS structure
**     Returns     :
**         ---             - Error code
** ===================================================================
*/
LDD_TDeviceData* FATM1_Init(LDD_TUserData *UserDataPtr)
{
  bool Error = FALSE;

  (void)UserDataPtr; /* not used */
  SD.SDHCPtr = 0;
  SD.GPIOPtr = 0;
  SD.Finished = FALSE;
  SD.Inserted = FALSE;
  SD.CardId = SDHC1_NO_CARD;

  /* Enable pull-up on GPIO pin used for SD card detection */
  PORT_PDD_SetPinPullSelect(PORTE_BASE_PTR, 28, PORT_PDD_PULL_UP);
  PORT_PDD_SetPinPullEnable(PORTE_BASE_PTR, 28, PORT_PDD_PULL_ENABLE);
  SD.GPIOPtr = GPIO2_Init(&SD);
  SD.SDHCPtr = SDHC1_Init(&SD);
  SD.Inserted = (GPIO2_GetFieldValue(SD.GPIOPtr, SD_CARD_DETECT) != 1);
  SD_Wait(&SD, &Error); /* Wait for card reset to finish */
  if (!Error && SD.Inserted) {
    if (SDHC1_DetectCards(SD.SDHCPtr)!=ERR_OK) {
      Error = TRUE;
    }
    SD_Wait(&SD, &Error);
    if (!Error && SD.CardId != SDHC1_NO_CARD) {
      /* card detected - selecting card... */
      Error = SDHC1_SelectCard(SD.SDHCPtr, SD.CardId)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error) {
      /* card selected - requesting card info... */
      Error = SDHC1_GetCardInfo(SD.SDHCPtr, &SD.CardInfo)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error) {
      /* switching gears for higher speed */
      Error = SDHC1_SelectBusClock(SD.SDHCPtr, FATM1_SPEED_INDEX_NORMAL)!=ERR_OK;
      SD_Wait(&SD, &Error);
    }
    if (!Error && SD.CardInfo.Caps.HighSpeed) {
      Error = SDHC1_SelectBusClock(SD.SDHCPtr, FATM1_SPEED_INDEX_FAST)!=ERR_OK;
      SD_Wait(&SD, &Error);
      /* running at high speed (high slew rate on all the SDHC pins should be set) */
    }
    if (!Error) {
      if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_8_BIT) {
        Error = SDHC1_SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_8_BIT)!=ERR_OK;
      } else if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_4_BIT) {
        Error = SDHC1_SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_4_BIT)!=ERR_OK;
      } else if (SD.CardInfo.Caps.DataWidths&LDD_SDHC_CARD_DATA_WIDTH_1_BIT) {
        Error = SDHC1_SetDataWidth(SD.SDHCPtr, LDD_SDHC_CARD_DATA_WIDTH_1_BIT)!=ERR_OK;
      }
      SD_Wait(&SD, &Error);
      /* selected highest possible data width */
    }
  }
  return &SD;
}

SDHC_LDD

CodeWarrior with Processor Expert provides the SDHC_LDD component which implements a device driver for SD cards. The FatFsMemSDHC component inherits its methods and events:

SDHC_LDD Component

SDHC_LDD Component

In the properties, the interface and pin settings are configured:

SDHC_LDD Properties

SDHC_LDD Properties

The Bus Clock lists several values (‘list of values’) with corresponding speed index from the FatFsMemSDHC component from above:

Speed Modes in SDHC_LDD

Speed Modes in SDHC_LDD

Depending on the card speed capabilities, the Init() method of the FatFsMemSDHC component will switch gears:

if (!Error) {
  /* switching gears for higher speed */
  Error = SDHC1_SelectBusClock(SD.SDHCPtr, FATM1_SPEED_INDEX_NORMAL)!=ERR_OK;
  SD_Wait(&SD, &Error);
}
if (!Error && SD.CardInfo.Caps.HighSpeed) {
  Error = SDHC1_SelectBusClock(SD.SDHCPtr, FATM1_SPEED_INDEX_FAST)!=ERR_OK;
  SD_Wait(&SD, &Error);
  /* running at high speed (high slew rate on all the SDHC pins should be set) */
}

FSShell Component

The FSShell component is part of the appliction and provides a shell interface with file commands like listing the directory or deleting files:

FSShell Component

FSShell Component

In the properties it links to the FatFs, RTOS, Timer and Terminal/SCI connection:

FSShell Properties

FSShell Properties

Shell Application

To test the SD card support along with a benchmark, the following application code has been written:

/*
* Shell.c
*
*      Author: Erich Styger
*/
#include "PE_LDD.h"
#include "FRTOS1.h"
#include "Shell.h"
#include "FSSH1.h"
#include <string.h>

#define PL_HAS_SD_CARD  1 /* if we have SD card support */

#if PL_HAS_SD_CARD
/*! \brief Simple benchmark function: first we are going to write a file, then we will copy it */
static void benchmark(const FSSH1_StdIOType *io) {
  static FIL fp, fpDest;
  uint16_t i;
  UINT bw;
  uint8_t read_buf[10];
  TIMEREC time, startTime;
  int32_t start_mseconds, mseconds;

  /* write benchmark */
  FSSH1_SendStr((const unsigned char*)"Benchmark: open file, write 10k times 10 bytes (100'000 bytes), close file:\r\n", io->stdOut);
  FSSH1_SendStr((const unsigned char*)"Deleting any existing files...\r\n", io->stdOut);
  (void)FSSH1_DeleteFile((const unsigned char*)"./bench.txt", io);
  (void)FSSH1_DeleteFile((const unsigned char*)"./copy.txt", io);

  FSSH1_SendStr((const unsigned char*)"Creating benchmark file...\r\n", io->stdOut);
  (void)TmDt1_GetTime(&startTime);
  if (FAT1_open(&fp, "./bench.txt", FA_CREATE_ALWAYS|FA_WRITE)!=FR_OK) {
    FSSH1_SendStr((const unsigned char*)"*** Failed opening benchmark file!\r\n", io->stdErr);
    return;
  }
  for(i=0;i<10000;i++) {
    if (FAT1_write(&fp, "benchmark ", sizeof("benchmark ")-1, &bw)!=FR_OK) { FSSH1_SendStr((const unsigned char*)"*** Failed writing file!\r\n", io->stdErr);
      (void)FAT1_close(&fp);
      return;
    }
  }
  (void)FAT1_close(&fp);
  (void)TmDt1_GetTime(&time);
  start_mseconds = startTime.Hour*60*60*1000 + startTime.Min*60*1000 + startTime.Sec*1000 + startTime.Sec100*10;
  mseconds = time.Hour*60*60*1000 + time.Min*60*1000 + time.Sec*1000 + time.Sec100*10 - start_mseconds;
  FSSH1_SendNum32s(mseconds, io->stdOut);
  FSSH1_SendStr((const unsigned char*)" mseconds needed for command.\r\n", io->stdOut);

  /* read benchmark */
  FSSH1_SendStr((const unsigned char*)"Reading benchmark file...\r\n", io->stdOut);
  (void)TmDt1_GetTime(&startTime);
  if (FAT1_open(&fp, "./bench.txt", FA_READ)!=FR_OK) {
    FSSH1_SendStr((const unsigned char*)"*** Failed opening benchmark file!\r\n", io->stdErr);
    return;
  }
  for(i=0;i<10000;i++) {
    if (FAT1_read(&fp, &read_buf[0], sizeof(read_buf), &bw)!=FR_OK) {
      FSSH1_SendStr((const unsigned char*)"*** Failed reading file!\r\n", io->stdErr);
      (void)FAT1_close(&fp);
      return;
    }
  }
  (void)FAT1_close(&fp);
  (void)TmDt1_GetTime(&time);
  start_mseconds = startTime.Hour*60*60*1000 + startTime.Min*60*1000 + startTime.Sec*1000 + startTime.Sec100*10;
  mseconds = time.Hour*60*60*1000 + time.Min*60*1000 + time.Sec*1000 + time.Sec100*10 - start_mseconds;
  FSSH1_SendNum32s(mseconds, io->stdOut);
  FSSH1_SendStr((const unsigned char*)" mseconds needed for command.\r\n", io->stdOut);

  /* copy benchmark */
  FSSH1_SendStr((const unsigned char*)"Benchmark: copy file (100'000 bytes):\r\n", io->stdOut);
  FSSH1_SendStr((const unsigned char*)"Going to copy file...\r\n", io->stdOut);
  (void)TmDt1_GetTime(&startTime);
  (void)FSSH1_CopyFile((const unsigned char*)"./bench.txt", (const unsigned char*)"./copy.txt", io);
  (void)TmDt1_GetTime(&time);
  start_mseconds = startTime.Hour*60*60*1000 + startTime.Min*60*1000 + startTime.Sec*1000 + startTime.Sec100*10;
  mseconds = time.Hour*60*60*1000 + time.Min*60*1000 + time.Sec*1000 + time.Sec100*10 - start_mseconds;
  FSSH1_SendNum32s(mseconds, io->stdOut);
  FSSH1_SendStr((const unsigned char*)" mseconds needed for command.\r\n", io->stdOut);
  FSSH1_SendStr((const unsigned char*)"done!\r\n", io->stdOut);
}
#endif /* PL_HAS_SD_CARD */

/*!
* \brief Parses a command
* \param cmd Command string to be parsed
* \param handled Sets this variable to TRUE if command was handled
* \param io I/O stream to be used for input/output
* \return Error code, ERR_OK if everything was fine
*/
static uint8_t ParseCommand(const unsigned char *cmd, bool *handled, const FSSH1_StdIOType *io) {
  /* handling our own commands */
#if PL_HAS_SD_CARD
  if (UTIL1_strcmp((char*)cmd, FSSH1_CMD_HELP)==0) {
    FSSH1_SendHelpStr((const unsigned char*)"run benchmark", (const unsigned char*)"Run FatFS benchmark\r\n", io->stdOut);
    *handled = TRUE;
  } else if (UTIL1_strcmp((char*)cmd, "run benchmark")==0) {
    benchmark(io);
    *handled = TRUE;
  }
#endif
  return ERR_OK;
}

static portTASK_FUNCTION(ShellTask, pvParameters) {
  unsigned char cmd_buf[32];
#if PL_HAS_SD_CARD
  bool cardMounted = FALSE;
  static FAT1_FATFS fileSystemObject;
#endif

(void)pvParameters;
  FSSH1_Init();
  (void)FSSH1_ParseCommand((const unsigned char*)FSSH1_CMD_HELP, FSSH1_GetStdio(), ParseCommand);
  for(;;) {
#if PL_HAS_SD_CARD
    (void)FSSH1_CheckCardPresence(&cardMounted, 0 /* volume */, &fileSystemObject, FSSH1_GetStdio());
#endif
    (void)FSSH1_ReadAndParseCmd(cmd_buf, sizeof(cmd_buf), FSSH1_GetStdio(), ParseCommand /* local cmd parser */);
    FRTOS1_vTaskDelay(50/portTICK_RATE_MS);
    LED2_Neg();
  };
}

void SHELL_Init(void) {
if (FRTOS1_xTaskCreate(ShellTask, (signed portCHAR *)"Shell", configMINIMAL_STACK_SIZE+350, NULL, tskIDLE_PRIORITY+1, NULL) != pdPASS) {
  for(;;){} /* error */
}

Running the Application

Putting everything together will run multiple tasks on the board, one of it running the a console shell:

Example Application Session

Example Application Session

Source and Example Code

The Processor Expert components discussed is available from here. The example project sources are available here or with this direct link.

Happy FatFs’ing 🙂

Advertisements

192 thoughts on “FatFs with Kinetis

  1. Hi Erich,

    I think that the problem was I passed to the sd writing function more than 512bytes. After this correction, I have made about 20 tests and only an image has been corrupted. I decided to increase the writing size of data to compensate the writing low speed. Anyway to write about 2,5M Byte of images on SD card it’s necessary to wait about 33 minutes. Please note that I have to reduce the SD clock because, writing 512 bytes at standard speed every time, some images were corrupted.

    Many thanks.

    Best Regards,

    Stefano

    Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s