Secure Digital SD Card Source Code Driver Project
Section 08. How The Driver Works

08. How The Driver Works

a) The Driver Defines

Note: this How The Driver Works section of the manual is for information only. You do not need to read and understand this large and in depth section to use the driver! However you may want to if you wish to gain an understanding of how each of the driver components works.

Pin Defines

FFS_CE

MMC / SD card Chip select pin (output)

FFC_DI

DO pin of MMC / SD card, DI pin of processor (used by the driver to check if pin is being pulled low by the card) (input)

The MMC or SD card detect pin is assigned using several defines to make it easy to use a direct microcontroller / processor pin or an external input buffer IC:-

FFS_CD_PIN_REGISTER

The register that should be read when reading the card detect pin state (e.g. the port register, or a ram register that gets read from a buffer IC).

FFS_CD_PIN_BIT

The bit of the register that is card detect pin (must be one of 0×80, 0×40, 0×20, 0×10, 0×08, 0×04, 0×02 or 0×01).

FFS_CD_PIN_FUNCTION

Optional function to call to read the FFS_CD_PIN_REGISTER. Just comment this out if its not required (i.e. if your not using an external buffer IC).

FFS_CD_PIN_NC

Optional define which should be included if the card socket card detect pin is normally closed (breaks when a card inserted), or should be commented out if pin is normally open. A 0V common pin is assumed for this with the card detect pin pulled up by a resistor. If using a +v common with a pull down resistor then reverse the logic of this define.

SPI Bus Defines

FFS_SPI_BUF_FULL

A bit definition that is >0 when the SPI receive buffer contains a received byte, also signifying that transmit is complete.

FFS_SPI_TX_BYTE(data)

A macro to write a byte and start transmission over the SPI bus.

FFS_SPI_RX_BYTE_BUFFER

Register that the last received SPI bus byte may be read from.

512 Byte Buffer Define

FFS_DRIVER_GEN_512_BYTE_BUFFER

The microcontroller / processor ram buffer that is used to buffer a complete sector of MMC or SD card data. A define is used as some compilers may have special requirements to create a large data buffer. The driver only accesses the buffer using pointers, in case your compiler requires this. This buffer may also be shared with other functions in your application if you call the ffs_fflush() function for each open file and set ffs_buffer_contains_lba = 0xFFFFFFFF first.

Watchdog Timer Define

CLEAR_WATCHDOG_TIMER

Use this if you have a watchdog timer that needs to be reset for operations that can take a long time. Just comment this out if its not required.

User Options

FFS_FOPEN_MAX

The maximum number of files that may be opened simultaneously (1 – 254). 22 bytes of memory are required per file.

b) The Driver Functions

Standard Type And Function Names

For ease of interoperability this driver uses modified version of the standard ANSI-C function names and FILE data types. To avoid conflicting with your compilers stdio.h definitions you can comment out this section and use the modified ffs_ (flash filing system) names in your code. If you want to use the ANSI-C standard names then un-comment this section:-

	#define	fopen		ffs_fopen
	#define	fseek		ffs_fseek
	#define	ftell		ffs_ftell
	#define	fgetpos		ffs_fgetpos
	#define	fsetpos		ffs_fsetpos
	#define	rewind		ffs_rewind
	#define	fputc		ffs_fputc
	#define	fgetc		ffs_fgetc
	#define	fputs		ffs_fputs
	#define	fgets		ffs_fgets
	#define	fwrite		ffs_fwrite
	#define	fread		ffs_fread
	#define	fflush		ffs_fflush
	#define	fclose		ffs_fclose
	#define	remove		ffs_remove
	#define	rename		ffs_rename
	#define	clearerr	ffs_clearerr
	#define	feof		ffs_feof
	#define	ferror		ffs_ferror
	#define	putc		ffs_putc
	#define	getc		ffs_getc

	#define	EOF		FFS_EOF
	#define	SEEK_SET	FFS_SEEK_SET
	#define	SEEK_CUR	FFS_SEEK_CUR
	#define	SEEK_END	FFS_SEEK_END

Open File

FFS_FILE* ffs_fopen (const char *filename, const char *access_mode)

This function opens a file for read and or write access.

For ease of use this driver does not differentiate between text and binary mode. You may open a file in either mode (or neither) and all file operations will be exactly the same (basically is if the file was opened in binary mode. LF characters will not be converted to a pair CRLF characters and vice versa. This makes using functions like fseek much simpler and avoids operating system difference issues. (If you are not aware there is no difference between a binary file and a text file – the difference is in how the operating system chooses to handle text files)

filename

Only 8 character DOS compatible root directory filenames are allowed. Format is F.E where F may be between 1 and 8 characters and E may be between 1 and 3 characters, null terminated, non-case sensitive. The ‘*’ and ‘?’ wildcard characters may be used.

access_mode

“r” Open a file for reading. The file must exist.
“r+” Open a file for reading and writing. The file must exist.
“w” Create an empty file for writing. If a file with the same name already exists its content is erased.
“w+” Create an empty file for writing and reading. If a file with the same name already exists its content is erased before it is opened.
“a” Append to a file. Write operations append data at the end of the file. The file is created if it doesn’t exist.
“a+” Open a file for reading and appending. All writing operations are done at the end of the file protecting the previous content from being overwritten. You can reposition (fseek) the pointer to anywhere in the file for reading, but writing operations will move back to the end of file. The file is created if it doesn’t exist.

Return value.

If the file has been successfully opened the function will return a pointer to the file. Otherwise a null pointer is returned (0×00).

Move File Byte Pointer

int ffs_fseek (FFS_FILE *file_pointer, long offset, int origin)

This function allows you to change the byte location in the file which the next read or write access will address. The function is quite complex as it looks to see if the new location is in the same cluster as the current location to avoid having to read all of the FAT table entries for the file from the file start where possible, which results in a large speed improvement.

file_pointer

Pointer to the open file to use.

origin

The initial position from where the offset is applied
FFS_SEEK_SET (0) Beginning of file
FFS_SEEK_CUR (1) Current position of the file pointer
FFS_SEEK_END (2) End of file

offset

Signed offset from the position set by origin

returns

0 if successful, 1 otherwise

int ffs_fsetpos (FFS_FILE *file_pointer, long *position)

This function is an alternative to ffs_seek. The value used is intended to be file system specific and obtained using the ffs_getpos function. However as the type is recommended to be a long and this doesn’t provide enough space to store everything needed for the low level file position this function calls the ffs_fseek function.

Get The Current Position In The File

long ffs_ftell (FFS_FILE *file_pointer)

This function returns the current position within the file (the next byte that will be read or written).

int ffs_fgetpos (FFS_FILE *file_pointer, long *position)

This function is an alternative to ffs_tell. The value returned is intended to be file system specific and only to be used with fsetpos. However as the position type is recommended to be a long and this doesn’t provide enough space to store everything needed for the low level file position this function calls the ffs_tell function.

Returns

0 if successful, 1 otherwise

Set File Byte Pointer To Start Of File

void ffs_rewind (FFS_FILE *file_pointer)

The file byte pointer is set to the first byte of the file and the file access error flag is cleared if it has been set.

file_pointer

Pointer to the open file to use.

Write Byte To File

int ffs_fputc (int data, FFS_FILE *file_pointer)

or

ffs_putc(int data, FFS_FILE *file_pointer)

file_pointer

Pointer to the open file to use.

data

The data byte to write which is converted to a byte before writing (the int type is specified by ANSI-C)

Returns

If there are no errors the written character is returned. If an error occurs FFS_EOF is returned.

Read Byte From File

int ffs_fgetc (FFS_FILE *file_pointer)

or

int ffs_getc (FFS_FILE *file_pointer)

file_pointer

Pointer to the open file to use.

Returns

The byte read is returned as an int value (int type is specified by ANSI-C). If the End Of File has been reached or there has been an error reading FFS_EOF is returned.

Write String To File

int ffs_fputs (const char *string, FFS_FILE *file_pointer)

or

int ffs_fputs_char (char *string, FFS_FILE *file_pointer)

This function writes a string to the file until a null termination is reached. The null termination is not written to the file. If a new line character (\n) is required it should be included at the end of the string

The alternative ffs_fputs_char function is not part of the ANSI-C standard but may be needed writing a string from ram with compilers that won’t deal with converting the ram string to a constant string.

Returns

Non-negative value if successful. If an error occurs FFS_EOF is returned.

Read String From File

char* ffs_fgets (char *string, int length, FFS_FILE *file_pointer)

This function reads characters from file and stores them into the specified buffer until a newline (\n) or EOF character is read or (length – 1) characters have been read. A newline character (\n) is not discarded. A null termination is added to the string

Returns

Pointer to the buffer if successful. A null pointer (0×00) if there is an error of the end-of-file is reached (use ffs_ferror or ffs_feof to check what happened).

Write Data Block To File

int ffs_fwrite (const void *buffer, int size, int count, FFS_FILE *file_pointer)

Writes count number of items, each one with a size of size bytes, from the specified buffer.

No translation occurs for files opened in text mode. The total number of bytes to be written is (size x count).

Returns

The number of full items (not bytes) successfully written. This may be less than the requested number if an error occurred.

Read Data Block From File

int ffs_fread (void *buffer, int size, int count, FFS_FILE *file_pointer)

Reads count number of items each one with a size of size bytes from the file to the specified buffer.

Total amount of bytes read is (size x count).

Returns

The number of items (not bytes) read is returned. If this number differs from the requested amount (count) an error has occurred or the End Of File has been reached (use ffs_ferror or ffs_feof to check what happened).

(For a very fast method of reading complete sectors at a time see the ‘Using The Driver In A Project’ section in this manual).

Store Any Unwritten Data To The Card

int ffs_fflush (FFS_FILE *file_pointer)

Write any data that is currently held in microcontroller / processor ram that is waiting to be written to the card. Update the file filesize value if it has changed.

This function does not need to be called by your application, but may be called if your application opens a file for a long period of time to avoid data loss if your device suddenly looses power.

Returns

0 if successful, 1 otherwise

Close File

int ffs_fclose (FFS_FILE *file_pointer)

Closes an open file, saving any unsaved data to the card and updating the file filesize value if it has changed.

Returns

0 if successful, 1 otherwise

Delete File

int ffs_remove (const char *filename)

This function is optimised to avoid unnecessary read and writes of the FAT table to greatly improve its speed.

Returns

0 if the file is successfully deleted, 1 if there was an error (the file doesn’t exist or can’t be deleted as its currently open.

Change File Size

int ffs_change_file_size (const char *filename, DWORD new_file_size)

This function allows you to increase or decrease a files size and is included to allow faster writing in certain situations. When writing a new file every time a sector is completed the driver must read the FAT table to find the next available sector, write to both FAT tables to mark the next sector as now used and then continue with writing the file. When needing to write a large amount of live data quickly this repeated process has a significant effect on write speeds and data buffering requirements. By using this function an application has the possibility to create an oversized file prior to the write starting and then overwriting the file with the data to be stored. As the file is already big enough all the driver has to do as each sector is completed is read the FAT table to find the location of the next sector, removing the need to scan and write to both FAT tables. Once the writing of the file is complete, if the total size of the data is smaller than the file size this function can be used again to reduce the file size.

Returns

0 if the file size was successfully changed, 1 if there was an error (the file doesn’t exist or can’t be changed as its currently open.

Rename File

int ffs_rename (const char *old_filename, const char *new_filename)

Returns

0 if the file is successfully renamed, 1 if there was and error (the file doesn’t exist or can’t be renamed as its currently open)

Clear Error & End Of File Flags

void ffs_clearerr (FFS_FILE *file_pointer)

Has End Of File Been Reached

int ffs_feof (FFS_FILE *file_pointer)

Has An Error Occurred During File Access

int ffs_ferror (FFS_FILE *file_pointer)

Is A Card Inserted And Available

BYTE ffs_is_card_available (void)

Do Background Tasks

void ffs_process (void)

This function needs to be called regularly from your applications main loop to detect a new card being inserted so that it can be initialised ready for access.

c) The Driver Sub Functions

These functions are used by the driver but should not be used by your application.

Find File

DWORD ffs_find_file (const char *filename, DWORD *file_size, BYTE *attribute_byte, DWORD *directory_entry_sector, BYTE *directory_entry_within_sector, BYTE *read_file_name, BYTE *read_file_extension)

This function searches for a specified filename. If wildcard characters are used then the first file that matches with the standard and wildcard characters will be found.

filename Only 8 character DOS compatible root directory filenames are allowed. Format is F.E where F may be between 1 and 8 characters and E may be between 1 and 3 characters, null terminated. The ‘*’ and ‘?’ wildcard characters are allowed.

*file_size

Pointer where the file size (bytes) will be written to.

*attribute_byte

Pointer where the attribute byte will be written to.

*directory_entry_sector

Pointer where the sector number that contains the files directory entry will be written to.

*directory_entry_within_sector

Pointer where the file directory entry number within the sector that contains the file will be written to.

*read_file_name

Pointer to a 8 character buffer where the filename read from the directory entry will be written to (this may be needed if using this function with wildcard characters)

*read_file_extension

Pointer to a 3 character buffer where the filename extension read from the directory entry will be written to (this may be needed if using this function with wildcard characters)

Returns

The file start cluster number (0xFFFFFFFF = file not found)

Convert File Name To Dos Filename

BYTE ffs_convert_filename_to_dos (const char *source_filename, BYTE *dos_filename, BYTE *dos_extension)

Used by functions to convert the application supplied filename to a driver specific DOS type filename. The source_filename is a case insensitive string with between 1 and 8 filename characters, a period (full stop) character, between 1 and 3 extension characters and a terminating null.

Returns

1 if the filename contained any wildcard characters, 0 if not (this allow calling functions to detect invalid names if they are creating a new file)

Read Next Directory Entry

BYTE ffs_read_next_directory_entry (BYTE *file_name, BYTE *file_extension, BYTE *attribute_byte, DWORD *file_size, DWORD *cluster_number, BYTE start_from_beginning, DWORD *directory_entry_sector, BYTE *directory_entry_within_sector)

*file_name

Pointer where the 8 character array filename will be written to.

*file_extension

Pointer where the 3 character array filename extension will be written to.

*attribute_byte

Pointer where the file attribute byte will be written to.

*file_size

Pointer where the file size will be written to.

*cluster_number

Pointer where the start cluster for the file will be written to.

start_from_beginning

Set to cause routine to start from 1st directory entry (this must be set if the drivers data buffer has been modified since the last call)

*directory_entry_sector

Pointer where the sector number that contains the files directory entry will be written to.

*directory_entry_within_sector

Pointer where the file directory entry number within the sector that contains the file will be written to.

Returns

1 if a file entry was found, 0 if not (marks the end of the directory

Overwrite The Last Directory File Name

void ffs_overwrite_last_directory_entry (BYTE *file_name, BYTE *file_extension, BYTE *attribute_byte, DWORD *file_size, DWORD *cluster_number)

*file_name

Pointer to an 8 character filename (must be DOS compatible – uppercase and any trailing unused characters set to 0×20)

*file_extension

Pointer to 3 character filename extension (must be DOS compatible – uppercase and any trailing unused characters set to 0×20)

*attribute_byte

Pointer to the file attribute byte

*file_size

Pointer to the file size

*cluster_number

Pointer to the start cluster number for the file

Get The Start Cluster Number For A File

DWORD get_file_start_cluster(FFS_FILE *file_pointer)

Returns

The cluster number of the start of the file. Further cluster numbers are read from the FAT table.

Create A New File

BYTE ffs_create_new_file (const char *file_name, DWORD *write_file_start_cluster, DWORD *directory_entry_sector, BYTE *directory_entry_within_sector)

*file_name Pointer to an 8 character filename

*write_file_start_cluster

The cluster number that contains the start of the file.

*directory_entry_sector

Pointer where the sector number that contains the files directory entry will be written to.

*directory_entry_within_sector

Pointer where the file directory entry number within the sector that contains the file will be written to.

Returns

1 if successful, 0 if not

Find Next Free Cluster In FAT Table

DWORD ffs_get_next_free_cluster (void)

Find the next available free cluster from the FAT table. The last found free cluster number is stored to help speed up successive calls to this function.

Returns

The cluster number, or 0xFFFFFFFF if no free cluster found (card is full)

Get Next Cluster Value From FAT Table

DWORD ffs_get_next_cluster_no (DWORD current_cluster)

This function looks up the current_cluster number in the FAT table and returns the FAT table entry which will be the next cluster number or the end of file marker.

Modify Cluster Value In FAT Table

void ffs_modify_cluster_entry_in_fat (DWORD cluster_to_modify, DWORD cluster_entry_new_value)

The cluster_to_modify FAT table entry is overwritten with cluster_entry_new_value.

Read Sector To Buffer

void ffs_read_sector_to_buffer (DWORD sector_lba)

Reads a sector of data (usually 512 bytes) to the microcontroller / processor ram buffer.

sector_lba

The ‘Logical Block Address’ / sector number to read.

Write Sector From Buffer

void ffs_write_sector_from_buffer (DWORD sector_lba)

Write a sector of data (usually 512 bytes) from the microcontroller / processor ram buffer.

sector_lba

The ‘Logical Block Address’ / sector number to read.

Is Card Present

BYTE ffs_is_card_present (void)

Returns

1 if present, 0 if not

Write Byte To Card

BYTE ffs_write_byte (BYTE data)

Read Word From Card

WORD ffs_read_word (void)

Read Byte From Card

BYTE ffs_read_byte (void)