Notes

You want to do something new with IoT, RFID ?. Drop me a message :).

Sunday, September 4, 2011

FreeRTOS and Implementation of FAT file system FatFs on SD card

Le Trung Thang 2011

After building successful the application “Building an Application for AT91SAM7S withFreeRTOS RTOS and IAR”, in this article we will combine FatFs, a FAT file system module, with FreeRTOS to make an application with functions more diverse than
 FatFs is a generic FAT file system module for small embedded systems that can be ported to any underlying hardware. It has been architected with separate layers for hardware accesses. FatFS is compatible with Windows FAT12/16/32 file system (Figure 1).
Fig 1. Application model with RTOS
1. Configuration Project

As any libraries, to use FatFs you need include its source file to IAR. FatFs supplies main source files in directory FatFs/src and some option files in directory FatFs/src/option. We add these two directories to IAR as figure 2.

Fig 2. Add source file to IAR


2. Include files

There are three header files (.h) in FatFs for using.
- diskio.h: This file stores prototype for I/O driver interface of FatFs. We should create a file diskio.c  which is implementation of this header file. Details of implementation, refer below: Implement I/O driver for FatFS.
- ff.h: This stores all declarations of standard functions for accessing SD card, as f_open (open a file), f_read (read file from physical medium),  f_close (close a file),…
- ffconf.h:  this is file for configuration of  FatFs. Refer http://elm-chan.org/fsw/ff/00index_e.html for details.

3. Implement I/O driver for FatFS
You must provide the following functions to FatFs to give control for read and write to the SD card.
-    Disk_initialization: Required for initialization of the SD card.
-    Disk_status: Required to get the status of the SD card for read and write.
-    Disk_read: Required to read the data from the SD card.
-    Disk_write: Required to write the data to the SD card.
-    Disk_ioctl: Control device dependent features. 


Since the FatFs module is completely separated from the disk I/O layer, it requires the following functions to access the SD card. The low level disk I/O module is not a part of the FatFs module and hence the above APIs are provided to access the SD card on AT91SAM7S Evaluation board. All above APIs were declared in file diskio.h , we will implement these APIs in file diskio.c.
SD card supports some protocols to communicate with host system. Here, we use the SPI protocol for communication between SD card with the host system (the evaluation board). Refer to AT91SAM7s datasheet to read more detail about SPI protocol on AT91SAM7S256 MCU. In addition, Atmel supplied a library for SPI peripheral. We will use this library to implement I/O driver for FatFs.
The following sections provide more details on API implementation.

DSTATUS disk_initialize (
    BYTE drv                /* Physical drive number (0..) */
)
{

    // Initialize the SD card driver
    if (SD_Init(&sdDrv, (SdDriver *)&sdSpiDrv)) {

        printf("-E- SD/MMC card initialization failed\n\r");
        DiskStatus = STA_NOINIT;
        return DiskStatus;
    }
    else {

        volatile unsigned int sizeInMB, sizeOfBlock;
        if (SD_TOTAL_SIZE(&sdDrv) == 0xFFFFFFFF) {
            sizeInMB = SD_TOTAL_BLOCK(&sdDrv) / (1024 * 2);
            sizeOfBlock = 512;
        }
        else {
            sizeInMB = SD_TOTAL_SIZE(&sdDrv) / (1024 * 1024);
            sizeOfBlock = SD_TOTAL_SIZE(&sdDrv) / SD_TOTAL_BLOCK(&sdDrv);
        }
        printf("-I- SD/MMC card initialization successful\n\r");
        printf("-I- Size: %u MB, %u * %d\n\r",
               sizeInMB,
               SD_TOTAL_BLOCK(&sdDrv), sizeOfBlock);

        SD_DisplayRegisterCSD(&sdDrv);
    }

    DiskStatus = STA_READY;
    return DiskStatus;
}

/*-----------------------------------------------------------------------*/
/* Return Disk Status                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
    BYTE drv        /* Physical drive number (0..) */
)
{
    DSTATUS stat = DiskStatus;

    return stat;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
    BYTE drv,        /* Physical drive number (0..) */
    BYTE *buff,        /* Data buffer to store read data */
    DWORD sector,    /* Sector number (LBA) */
    BYTE count        /* Sector count (1..255) */
)
{
    unsigned char result;
    DRESULT res = RES_ERROR;

    // Read the data from the Card
    memset(buff, 0xFF, SD_BLOCK_SIZE * count);
    result = SD_ReadBlock(&sdDrv, sector, count, buff);

    if(result == RES_OK) res = RES_OK;
    if (DiskStatus == STA_NOINIT ) res = RES_NOTRDY ;

    return res;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if _READONLY == 0

DRESULT disk_write (
    BYTE drv,            /* Physical drive number (0..) */
    const BYTE *buff,    /* Data to be written */
    DWORD sector,        /* Sector number (LBA) */
    BYTE count            /* Sector count (1..255) */
)
{
    DRESULT res = RES_ERROR;
    unsigned int result;

     //Write data to the Card
     //memset(buff, 0x00, SD_BLOCK_SIZE * count);
     result = SD_WriteBlock(&sdDrv, sector, count, buff);

     if(result == RES_OK) res = RES_OK;
     if (DiskStatus == STA_NOINIT ) res = RES_NOTRDY ;

     return res;
}
#endif /* _READONLY */

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
// Command    Description
//
//CTRL_SYNC    Make sure that the disk drive has finished pending write process.
// When the disk I/O module has a write back cache, flush the dirty sector immediately.
// In read-only configuration, this command is not needed.
//
//GET_SECTOR_COUNT    Returns total sectors on the drive into the DWORD variable pointed by Buffer.
// This command is used in only f_mkfs function.
//
//GET_BLOCK_SIZE    Returns erase block size of the memory array in unit
// of sector into the DWORD variable pointed by Buffer.
// When the erase block size is unknown or magnetic disk device, return 1.
// This command is used in only f_mkfs function.
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
    BYTE drv,        /* Physical drive number (0..) */
    BYTE ctrl,        /* Control code */
    void *buff        /* Buffer to send/receive control data */
)
{
    DRESULT res=RES_ERROR;

        switch (ctrl) {

            case GET_BLOCK_SIZE:
                *(DWORD*)buff = 1;
                res = RES_OK;
                break;

            case GET_SECTOR_COUNT :   /* Get number of sectors on the disk (DWORD) */

                *(DWORD*)buff = sdDrv.blockNr ;
                res = RES_OK;
                break;

            case GET_SECTOR_SIZE :   /* Get sectors on the disk (WORD) */
                *(WORD*)buff = SD_BLOCK_SIZE;
                res = RES_OK;
                break;

            case CTRL_SYNC :   /* Make sure that data has been written */
                res = SDSPI_IsBusy(&sdSpiDrv);
                //res = RES_OK;
                break;

            default:
                res = RES_PARERR;
        }

   return res;
}
/*-----------------------------------------------------------------------*/
// get time
/*-----------------------------------------------------------------------*/
DWORD get_fattime (void){

  return   ((DWORD)(2011 - 1980) << 25) /* Fixed to Jan. 1, 2011 */
   | ((DWORD)1 << 21)
   | ((DWORD)1 << 16)
   | ((DWORD)0 << 11)
   | ((DWORD)0 << 5)
   | ((DWORD)0 >> 1);
}

4. Writing the code for testing

We will add a new task in the previous project. This task, with name vRWFileTask, will write a text file to SD card and then read its content again to check, it also reads all contents of Root directory in SD card. To prevent SD card is damaged, task vRWFileTask only run once. After that, it deletes itself.
the method to access to SD card is same with the method to access to a file in the standard C program. That is to call in sequence: open file (f_open) -> read/write file (f_read/f_write) -> close file (f_close).  
Sometime, open file operation was failed, so we will retry to open file one more time.
Program was tested success with Micro SD card 1GB and 2 GB.

//=============================================================================
// Read/Write file from/to SD card
//=============================================================================
portTASK_FUNCTION( vRWFileTask, pvParameters )
{
    static FRESULT rc;   /* Result code */
    static FATFS fatfs;  /* File system object */
    static FIL fil;   /* File object */
    static DIR dir;   /* Directory object */
    static FILINFO fno;  /* File information object */

    static UINT bw, br, numread;
    static BYTE buff[64];
    BYTE Message[] = "HELLO from www.letrungthang.blogspot.com" ; // message's content
    TCHAR *FilePath = "MESSAGE.TXT" ; // file path

    f_mount(0, &fatfs);  /* Register volume work area (never fails) */

    // File testing
    printf("Open File to write...\n\r");

    //------------------------------------
    //------------------------------------
    rc = f_open(&fil, FilePath, FA_WRITE | FA_OPEN_ALWAYS); /* Create a file on the drive 0 */
    if(rc)  rc = f_open(&fil, FilePath, FA_WRITE | FA_OPEN_ALWAYS);// try again once

    if (!rc) {
      printf("Open File to write successful...\n\r");
    }else  {
      printf("Open File to write Failure...error = %u\n\r",rc);
    }
    printf("-Open File fil.fs = %d\n\r",fil.fs);
    printf("-Open File fil.id = %d\n\r",fil.id);
    printf("-Open File fil.dsect = %d\n\r",fil.dsect);
    printf("-Open File fil.clust = %d\n\r",fil.clust);
    printf("-Open File fil.fsize = %d\n\r",fil.fsize);

    printf("\nWrite a text data to the Card.\n\r");

    rc = f_write(&fil, Message, sizeof(Message)-1, &bw); // write file

    if (!rc) {
      printf("write success. %u bytes written.\n\r", bw);
    }else  {
      printf("write file Failure...error = %u\n\r",rc);
    }


    printf("\nClose the file writeen.\n\r");
    rc = f_close(&fil); // close file

    if (!rc) {
      printf("File writeen closed success.\n\r");
    }else  {
      printf("File close writeen was failure...error = %u\n\r",rc);
    }

    //------------------------------------
    //------------------------------------
    printf("\n\rOpen File to read...\n\r");
    rc = f_open(&fil, FilePath, FA_READ | FA_OPEN_EXISTING); /* Create a file on the drive 0 */
    if(rc)  rc = f_open(&fil, FilePath, FA_READ | FA_OPEN_EXISTING);// try again once

    printf("-Open File fil.fs = %d\n\r",fil.fs);
    printf("-Open File fil.id = %d\n\r",fil.id);
    printf("-Open File fil.dsect = %d\n\r",fil.dsect);
    printf("-Open File fil.clust = %d\n\r",fil.clust);
    printf("-Open File fil.fsize = %d\n\r",fil.fsize);

    if (!rc) {
      printf("Open File to read successful...\n\r");
    }else  {
      printf("Open File to read Failure...error = %u\n\r",rc);
    }

    printf("\nRead the file content from the Card:\n\r");
    for (;;) {
      rc = f_read(&fil, buff, sizeof(buff), &br);     /* Read a chunk of file */
      if (rc || !br) break;         /* Error or end of file */

      for (int i = 0; i < br; i++)                /* Type the data */
        putchar(buff[i]);

      numread += br ;
    }

    if (!rc) {
       printf("\n\r%u bytes read.\n\r", numread);
    }else  {
       printf("read file Failure...error = %u\n\r",rc);
     }

    printf("\nClose the file.\n\r");
    rc = f_close(&fil); // close file

    if (!rc) {
      printf("File closed.\n\r");
    }else  {
      printf("File close Failure...error = %u\n\r",rc);
    }
    //--------------------------------------
    //------------------------------------
    // Directory testing
    printf("\nOpen root directory.\n\r");
    rc = f_opendir(&dir, "");

    if (!rc) {
      printf("Open Dir OK.\n\r");
    }else  {
      printf("Open Dir Failure...error = %u\n\r",rc);
    }

    printf("\nDirectory listing...\n\r");
    for (;;) {
      rc = f_readdir(&dir, &fno); /* Read a directory item */
      if (rc || !fno.fname[0]) break; /* Error or end of dir */

      if (fno.fattrib & AM_DIR)
        printf("   [dir]  %s\n\r", fno.fname); // is Directory
      else
        printf("%8lu  %s\n\r", fno.fsize, fno.fname); // is file
    }

    if (!rc) {
      printf("Listing Dir complete.\n\r");
    }else  {
      printf("Listing Dir Failure...error = %u\n\r",rc);
    }

    printf("\nTest completed.\n\r");
    f_mount(0, NULL);/* Unregister work area prior to discard it */

    //------------------------------------
   // while(1); // use this code line if don't want using vTaskDelete
    vTaskDelete( NULL); // this task deletes itself

} 

2 GB Micro SD card

Attach Micro SD card to Board

Debug information


Debug information (cont.)

Content of Root directory
 Source code can download here.
= = = =***= = = =***= = = =***= = = =
FreeRTOS is Copyright (C) Real Time Engineers Ltd.
IAR Embedded Workbench for ARM is Copyright (C) IAR Systems AB.
FatFs is Copyright (C) http://elm-chan.org/fsw/ff/00index_e.html

11 comments:

vetal_alien said...

I'm trying to build your project, and have some error:

Fatal Error[Pe035]: #error directive: Board does not support the specified chip.

What I need to configure?
Thanks.

vetal_alien said...

Now I'm trying to start the project without RTOS, and now the problem is that sd card can't initialise with sdspi.c and sdmmc_spi.c, it sticks in SDSPI_Write function, in line

while(SDSPI_IsBusy(pSdSpi) == 1);

So what I have to do ?

P.S. Card initialises successfully with library mmc.h and it's functions, but it doesn't help with SDSPI_Write.

I have a AT91SAM7S128 chip and Olimex board.

Lê Trung Thắng said...

Did you use which tool to build ?. I use IAR Embedded Workbench for ARM 6.30 and build with result success.

Guide: Go to folder: \Atmel\at91sam7s-ek\FreeRTOS-getting-started
then run file: FreeRTOS-getting-started.eww.
Choose Rebuild All to rebuild project. The project only supports in the IAR tool.
I also need check which MCU you are using in your board: AT91SAM7S256 or another ?
If it is different with AT91SAM7S256, then you need modify again the linker file.

vetal_alien said...

I'm using IAR 6.1. MCU type is AT91SAM7S128.

If I'm trying to build "FreeRTOS-getting-started" I get a list of errors http://img267.imageshack.us/img267/508/48869424.png

Lê Trung Thắng said...

Try upgrade to IAR 6.30 and build again with this: http://www.mediafire.com/?ca0szag3uy9nnyc

Copy it to D dir (no C dir) and unzip. Please don't modify anything.

If you build OK, then you can change MCU to AT91SAM7S128, however, you then need modify again linker file. How to modify linker file: http://letrungthang.blogspot.com/2009/11/arm.html

If you are using the Evaluation edition, you might not build the project.

Evaluation edition
The Evaluation edition is a time-limited evaluation version that you can use for 30 days. Limitations:

- Expires 30 days after generation of the evaluation key.
- MISRA C is not available.
- The runtime library source code is not available.
- The object files generated by the compiler are in an encrypted form and will only be readable by the IAR ILINK linker. The linker will also read AEABI compliant ELF/DWARF code for linking of legacy and third party code.
- Compiler assembly list output is disabled.

vetal_alien said...

Thanks for files. I'll added some lines to "additional include directories" (it were errors trying to find ff.h, stdio.h and crc7.h).

After that the project succesfully was build. I changed MCU type (without editing linker file) and project still worked.

And it working perfectly!!
There was a problem with 32 Mb sd card, but it's ok with 4 Gb sd.

I'll also try to start project without OS. I need to convert pictures 320x240 on sd card to pixels and to show them on a simple display.
Maybe you can say me some advices or take some useful links for that task?

I'm really appreciate your help. Thanks.

krrrish said...

I want to interface read files from sd card using lpc214x..
need some guidelines.....
thank you....

Lê Trung Thắng said...

krrrish: Due to lpc214x has peripheral different with AT91SAM7S, So you need to re-implement I/O driver files.

Vincent said...

Hi Thang,

I am unable to download the source, pls email to me key2hsl@yahoo.com

Thanks,
Vincent

vetal_alien said...

It's ok with source file. Download it by link above.

vetal_alien said...

Hello again.
Now I have a problem with reading a file for second time. I'm trying to call this function without changes, and have an old problem:
SDSPI_IsBusy function returns to me '1'.
What can be the cause for this?