FTPmicro Tutorial – FAT16.c


Per l'utilizzo della memoria SD, la scelta del Filesystem è inevitabilmente ricaduta su uno di tipo FAT; in questo modo è possibile
caricare i file direttamente da pc.
Nel file fat16.c è implementato il supporto a FAT16, realizzando la stessa interfaccia di MPFS, in modo tale da essere compatibile con esso (per ora in sola lettura).
Ciò significa che i metodi di questo file hanno lo stesso nome, parametri e comportamento di quelli presenti nel file MPFS.c.

Per meglio comprendere il funzionamento del codice, vediamo com'è organizzata una generica memoria di massa.
Inannzitutto, la memoria è divisa in settori solitamente costituiti da 512byte; più settori insieme formano
un cluster, la cui dimensione viene stabilita durante la creazione della partizione, e scritta in un apposito campo del Filesystem.
Il cluster è l'unità di base della FAT16, ad esempio un file di dimensione nulla, occupa comunque un intero cluster.
Il primo settore è denominato MBR (Master Boot Record), e contiene principalmente codice eseguibile per il lancio del sistema operativo.
Il nostro interesse, però, ricade su una piccola porzione di questo settore, ovvero la Partition Table, nella quale troviamo informazioni sulle partizioni.
In particolare, da un campo di tale tabella si può ricavare la locazione d'inizio della partizione, e quindi del Filesystem.

Offset Descrizione Dimensione
0x000 Codice eseguibile 446 Byte
0x1BE Informazioni sulla Prima Partizione 16 Byte
0x1CE Informazioni sulla Seconda Partizione 16 Byte
0x1DE Informazioni sulla Terza Partizione 16 Byte
0x1EE Informazioni sulla Quarta Partizione 16 Byte
0x1FE Firma Boot Sector: sempre 0x55AA 2 Byte

Tali informazioni sulle partizioni sono così formate:

Offset Descrizione Dimensione
0x00 Stato della partizione (attivo o inattivo) 1 Byte
0x01 Inizio partizione: Head 1 Byte
0x02 Inizio partizione: Cylinder/Sector 2 Byte
0x04 Tipo della partizione 1 Byte
0x05 Fine partizione: Head 1 Byte
0x06 Fine partizione: Cylinder/Sector 2 Byte
0x08 Inizio della partizione in settori 4 Byte
0x0C Numero di settori nella partizione 4 Byte

Quindi il campo che vogliamo leggere è il byte 0x1C6 del primo settore.

A questo punto possiamo leggere il primo settore della FAT16 (detto Boot Record), nel quale troviamo
numerose informazioni che devono essere salvate in alcune varibili.

Offset Descrizione Dimensione
0x000 Jump Code e NOP 3 Byte
0x003 Nome OEM 8 Byte
0x00B Byte Per Settore 2 Byte
0x00D Settori Per Cluster 1 Byte
0x00E Settori Riservati 2 Byte
0x010 Numero di Aree FAT 1 Byte
0x011 Numero massimo di elementi nella Root Directory 2 Byte
0x013 Numero di settori in partizioni < 32MB 2 Byte
0x015 Tipo di supporto (HardDisk, ecc.) 1 Byte
0x016 Dimensioni in settori della FAT Area 2 Byte
0x018 Settori per Traccia 2 Byte
0x01A Numero di Head 2 Byte
0x01C Numero di settori nascosti 4 Byte
0x020 Dimensione in settori della partizione 4 Byte
... ... ...
0x1FE Firma Boot Sector: sempre 0x55AA 2 Byte

Abbiamo già visto il MBR e il FBR, mentre per quando riguarda le altre zone:

  • FAT (File Allocation Table): nel Filesystem FAT16, come in molti altri, i cluster che formano un file
    non sono necessariamente contigui, ma sono spesso frammentati (cioè sparsi dove capita nel disco); per questo motivo esiste
    l'area denominata FAT, la quale contiene liste concatenate che permettono di rimettere in ordine i cluster di un singolo file.
  • FAT2: è semplicemente una copia di sicurezza della FAT.
  • Root Directory: contiene l'elenco dei file contenuti nella directory radice del disco.
  • Data Area: qui è dove si trovano i file.

Nel Filesystem MPFS i file sono contigui, quindi per semplificare il codice, nello stack Microchip un file è indentificato
dal suo indirizzo nella memoria (ma viene chiamato handle). Nel caso della FAT16, però, tale informazione non è
sufficiente, o meglio, richiederebbe di effettuare diversi calcoli ogni volta che si deve eseguire una lettura dalla memoria;
per tale motivo, è presente una tabella (denominata handleTable) contenente alcune informazioni. La define MAX_OPENED_FILES
determina la dimensione massima della tabella.

typedef struct {
	BYTE  attrib;
	WORD  cluster;		// cluster in DataArea
	BYTE  sector;   	// sector in cluster
	WORD  byte;		// byte in sector;
	DWORD filesize;
} handleEntry;

handleEntry handleTable[MAX_OPENED_FILES];

Giunti qui, possiamo vedere l'inizializzazione del driver FAT16:

/* partition data */
DWORD partitionStart;	
BYTE  clusterSize;		// sectors per cluster
DWORD fatStart;
DWORD rootStart;
DWORD dataArea;
BYTE  rootSize;

BOOL FATInit() {
	char* buf_pt = &sd_buffer[0];
	char i;

	sdPresent = SDInit();

	if (!sdPresent)
		return FALSE;

	SDReadSector(0);
	partitionStart = *(DWORD*)(buf_pt+0x1C6);

	SDReadSector(partitionStart);
	clusterSize = buf_pt[0x0D];	
	fatStart = partitionStart + *(WORD*)(buf_pt+0x0E);
	rootStart = fatStart + (*(WORD*)(buf_pt+0x16)) * buf_pt[0x10];
	rootSize = *(WORD*)(buf_pt+0x11) * sizeof(dirEntry) / SECTOR_SIZE;
	dataArea = rootStart + rootSize;

	for (i = 0; i < MAX_OPENED_FILES; i++)
		handleTable[i].attrib = FREE_HANDLE;

	mpfsOpenCount = 0;

	return TRUE;
}

Oltre ad inizializzare la memoria SD e la tabella degli Handle, vengono calcolati alcuni indirizzi caratteristici del Filesystem:

Variabile Descrizione Valore
clusterSize Numero di settori in un cluster  
fatStart Inizio della FAT1 inzio della partizione + settori riservati
rootStart Inizio della Root Directory Indirizzo della FAT + (numero di FAT per dimensione di una FAT)
rootSize Dimensione della Root Directory Numero di elementi nella directory per dimensione di un elemento
dataArea Inizio dell'Area Dati Inizio della Root Directory + la sua dimensione

Questi indirizzi e dimensioni sono espressi in settori di 512byte.

Una directory è costituita da elementi della stessa dimensione (32 byte)
che, per quanto concerne i file con nomi corti (8+3), contengono questi campi:

Offset Descrizione Dimensione
0x00 Nome 8 Byte
0x08 Estensione 3 Byte
0x0B Attributi 1 Byte
0x0C Riservato 1 Byte
0x0D Data e ora della creazione 7 Byte
0x14 Attributi Estesi 2 Byte
0x16 Ora 2 Byte
0x18 Data 2 Byte
0x20 Primo cluster del File 2 Byte
0x1C Dimensione del File 4 Byte

In particolare i campi Nome ed Estensione sono sempre scritti in maiscuolo, ed i caratteri non presenti
sono riempiti con il carattere spazio (0x20 in esadecimale). Ad esempio il file Pippo.txt sarà scritto nella forma

PIPPO   TXT     50 49 50 50 4F 20 20 20 54 58 54

Il metodo MPFSOpen cerca un file nella memoria, e se lo trova crea un handle e lo restituisce al chiamante:

MPFS MPFSOpen(BYTE* name){
	BYTE scount, ecount;
	dirEntry* entry;
	char* buf_pt = &sd_buffer[0];
	char i;
	char name_ext[11];

	if (!sdPresent) return MPFS_INVALID;

	for (i=0; i<11; i++)
		name_ext[i] = 0x20;
	
	for (i=0; i<8 && *name!='.' && *name; i++)
		name_ext[i] = toupper(*name++);

	if (*name == '.') {
		name++;
		for (i=8; i<11 && *name; i++)
			name_ext[i] = toupper(*name++);
	}

	scount = 0;
	while (scount < rootSize) {
		SDReadSector(rootStart + scount);

		ecount = 16;
		entry = (dirEntry*)buf_pt;
		while (ecount--) {
			if (entry->attrib == 0x0F) goto next;
// Long File Name
			if (entry->name_ext[0] == 0x00 || entry->name_ext[0] == 0xE5) goto next;
// Empty Entry
			if (memcmp((void*)name_ext, (void*)entry->name_ext, 11) == 0) { 	// found!
				for (i=0; i < MAX_OPENED_FILES && handleTable[i].attrib!=FREE_HANDLE; i++);
				if (i == MAX_OPENED_FILES) return MPFS_NOT_AVAILABLE;
				handleTable[i].attrib = USED_HANDLE;
				handleTable[i].cluster = entry->cluster;
				handleTable[i].filesize = entry->size;	
				handleTable[i].sector = 0;   	
				handleTable[i].byte = 0;
				mpfsOpenCount++;
				return i;
			}
		next:
			entry++;
		}

		scount++;
	}

	return MPFS_INVALID;
}

Nella prima parte, il nome del file viene scritto nella forma 8+3 vista in precedenza. In questo modo sarà
possibile confrontarlo con quelli presenti nella Root Directory. A questo punto vengono lette tutte le entry (elementi)
della Directory, partendo dal primo settore ed eventualmente leggendo i settori successivi.
Quando il confronto tra il nome del file cercato e quello letto dalla memoria ha esito positivo, viene cercato un handle libero
nella tabella, quindi vengono salvati alcuni dati:

  • filesize: la dimensione del file in byte.
  • cluster: il primo cluster del file nella Data Area.
  • sector: inizializzato a zero perchè tiene traccia di quale settore stiamo leggendo all'interno dello stesso cluster
  • byte: analogamente a sector, tiene traccia dell'ultimo byte letto all'interno del settore.

Con MPFSGetBegin il driver si prepara a leggere il file aperto con MPFSOpen, caricando nel buffer il settore
del file puntato dalle varibili salvate nella tabella:

BOOL MPFSGetBegin(MPFS handle){
	if (handle >= MAX_OPENED_FILES || handleTable[handle].attrib != USED_HANDLE)
		return FALSE;		
// invalid handle

 	if (handleTable[handle].filesize == 0)
		return TRUE;

	_currentHandle = handle;
	SDReadSector(dataArea + 
(handleTable[handle].cluster-2)*clusterSize + handleTable[handle].sector);

	return TRUE;
}

Da notare che i cluster iniziano da 2 e non da zero, inoltre il numero di cluster non è un indirizzo assoluto rispetto all'intera memoria
ma è relativo alla Data Area, quindi all'indirizzo da caricare va sommato l'indirizzo della Data Area.

La funzione MPFSGet legge e restituisce un byte del file alla volta. Se tale byte si trova caricato nel buffer
della memoria SD, è sufficiente leggerlo da lì, altrimenti bisogna calcolare il prossimo settore da caricare. Quest'ultimo
se si trova all'interno dello stesso cluster sarà semplicemente il successivo, altrimenti è necessario leggere la FAT per individuare il prossimo cluster.

BYTE MPFSGet() {
	char* buf_pt = &sd_buffer[0];
	BYTE b = 0;
	WORD tmp;

	if (_currentHandle == MPFS_INVALID)
		return 0;

	if (handleTable[_currentHandle].byte >= SECTOR_SIZE) {
		handleTable[_currentHandle].byte = 0;
		handleTable[_currentHandle].sector++;
		if (handleTable[_currentHandle].sector >= clusterSize) {
			tmp = handleTable[_currentHandle].cluster;
			SDReadSector(fatStart + tmp / (SECTOR_SIZE /2) );		
// read FAT Area
			handleTable[_currentHandle].cluster = *(WORD*)(buf_pt + (tmp % (SECTOR_SIZE/2)) * 2);
// next cluster in chain
			handleTable[_currentHandle].sector = 0;
		}
		SDReadSector(dataArea + (handleTable[_currentHandle].cluster-2)*clusterSize +
handleTable[_currentHandle].sector);
	}


	b = buf_pt[handleTable[_currentHandle].byte++];
	handleTable[_currentHandle].filesize--;

	return b;
}

Nella FAT, ad ogni parola di 16 bit corrisponde un cluster. Per fare un esempio, se stiamo leggendo il file che inizia al cluster 4 (cioè il 5°),
per conoscere il prossimo cluster del file, sarà sufficiente leggere la parola corrispondente nella FAT, ovvero la 5°. L'ultima parola di una catena
è 0xFFFF.
Essendo le parole di 16bit, in un settore della FAT sono presenti 256 elementi; perciò per sapere quale settore della FAT è necessario
caricare viene eseguito questo calcolo:

cluster_corrente / (SECTOR_SIZE /2)

A questo punto, possiamo conoscere il prossimo cluster leggendo la parola numero

(cluster_corrente % (SECTOR_SIZE/2)) * 2

all'interno di tale settore.

Altri metodi:

void MPFSClose() {
	mpfsOpenCount--;	// decrementa il numero di file aperti
	handleTable[_currentHandle].attrib = FREE_HANDLE;  // libera l'handle
}

BOOL MPFSIsEOF(void) {
	return (handleTable[_currentHandle].filesize == -1);  // se la dimensione è -1, il file è finito
}

Tutta la documentazione su FTPmicro è disponibile a questa pagina FTPmicro per Utenti Premium.
Potete realizzarlo da soli (fai-da-te) scaricando tutti i codici sorgenti (Schema elettrico realizzato con Orcad, lista parti, circuito stampato realizzato con Orcad, file gerber, codice sorgente assembler realizzato su piattaforma Microchip).

Il progetto montato e collaudato è disponibile sul nostro store --> FTPmicro

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend