Il file system FatFS è un file system ben noto nel campo dei microcontrollori. Grazie alla sua leggerezza e compatibilità, è apprezzato dagli sviluppatori MCU. Quando si implementano attività come la lettura e la scrittura di file su chiavette USB e schede SD, spesso è necessario un file system per supportare il nostro lavoro. In particolare, in alcune applicazioni MCU, l’aggiunta di un file system può migliorare significativamente la facilità d’uso dell’interazione con il sistema. In questo articolo, introduciamo il porting e l’applicazione del file system FatFS su STM32F4.
Caratteristica di FatFS
- File system FAT/exFAT compatibile con DOS/Windows.
- Indipendente dalla piattaforma e facile da portare.
- Il codice del programma e lo spazio di lavoro occupano pochissimo spazio.
- Supporta le seguenti varie opzioni di configurazione:
- Nomi file lunghi in ANSI/OEM o Unicode.
- File system exFAT, LBA a 64 bit e GPT per archiviare grandi quantità di dati.
- Sicurezza del thread di RTOS.
- Più volumi (unità fisiche e partizioni).
- Dimensione del settore variabile.
- Più pagine di codice, inclusi DBCS.
- API solo lettura, opzionale, buffer I/O, ecc.
Porting di FatFS su MCU
Preparazione
Dobbiamo fare alcuni preparativi necessari prima di iniziare il porting di FatFS. Innanzitutto, è necessario preparare la piattaforma hardware corrispondente. Stiamo utilizzando la piattaforma operativa STM32F407VET6 qui. Il lavoro di porting delle librerie correlate all’hardware USB è stato completato.
In secondo luogo, dobbiamo anche preparare il codice sorgente rilevante di FatFS. Qui utilizziamo l’ultima versione R0.14b, che può essere scaricata dal sito web:
http://elm-chan.org/fsw/ff/00index_e.html
Dopo aver decompresso il codice sorgente scaricato, ci sono due cartelle: document e source. La cartella document contiene la documentazione pertinente, che è la stessa del contenuto sul sito web. Puoi visualizzare questi documenti per lavorare durante il porting. La cartella Source contiene file di codice sorgente correlati, principalmente inclusi:

Tra la serie di file mostrati nell’immagine sopra, il file 00readme.txt ha un’introduzione a ciascun file. Visualizziamo il suo contenuto come segue:
| File Name | Description |
|---|---|
| 00readme.txt | Readme file |
| 00history.txt | Version history file |
| ff.c | FatFs module |
| ffconf.h | Configuration file for the FatFs module |
| ff.h | Header file for the FatFs application module |
| diskio.h | Header file for the FatFs and disk I/O module |
| diskio.c | An instance that attaches disk I/O functions to FatFS |
| ffunicode.c | Unicode encoding utility functions |
| ffsystem.c | Optional operating system-related file instance |
Tra questi file, ff.c e ff.h sono file principali. ffunicode.c è la codifica dei caratteri e la codifica verrà selezionata in base alla configurazione del file di configurazione. Il file ffsystem.c è determinato in base alle proprie esigenze. Pertanto, i file correlati alla specifica piattaforma applicativa e che devono essere implementati da noi sono il file di configurazione ffconf.h e i file di operazione del disco diskio.h e diskio.c. Questi file sono anche il fulcro del nostro porting.
Implementa il porting
Abbiamo completato i lavori di preparazione per il trapianto e ora implementeremo il trapianto dell’applicazione per dischi USB di grande capacità. Come abbiamo detto prima, i file che devono essere elaborati per il trapianto sono il file di configurazione ffconf.h e i file di operazione su disco diskio.h e diskio.c.
Riguardo al file di configurazione ffconf.h, in realtà ha un’istanza di se stesso. Dobbiamo solo modificare la configurazione in base alle necessità. I parametri di configurazione che dobbiamo modificare qui includono:
Il parametro di configurazione del metodo di codifica supportato FF_CODE_PAGE è correlato al problema della codifica dei file. Lo configuriamo per supportare il cinese semplificato.
Il numero di unità logiche è configurato con il parametro FF_VOLUMES. FatFS può essere applicato a più unità contemporaneamente, quindi dobbiamo configurare il numero di unità in base alla situazione reale.
Il parametro di configurazione del timestamp FF_FS_NORTC, la maggior parte delle volte non abbiamo bisogno di registrare i timestamp, quindi lo disattiviamo qui.
Il resto sono funzioni correlate per implementare le operazioni di I/O su disco. La documentazione di aiuto di FatFS ci dice che ci sono due tipi di funzioni che devono essere implementate: una è funzioni relative al controllo del dispositivo disco, principalmente funzioni per ottenere lo stato del dispositivo, funzioni di inizializzazione del dispositivo e lettura. Funzioni dati, funzioni di scrittura dati e funzioni di controllo del dispositivo correlate; la seconda categoria è la funzione di operazione dell’orologio in tempo reale, che viene utilizzata principalmente per ottenere la funzione di tempo corrente. Pertanto, implementare queste 6 funzioni è il compito principale del trapianto.
Ottieni la funzione di stato del dispositivo
Funzione di rilevamento dello stato del disco disk_status. Viene utilizzato per rilevare lo stato del disco e verrà chiamato nel file ff.c. Il suo prototipo di funzione è il seguente:
ˆ DSTATUS disk_status(BYTE drV);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare la funzione di acquisizione dello stato del disco come segue:
/*Status acquisition function for USBH*/
static DSTATUS USBH_status(BYTE lun)
{
DRESULT res = RES_ERROR;
if(USBH_MSC_UnitIsReady(&hUsbHostFS, lun))
{
res = RES_OK;
}
else
{
res = RES_ERROR;
}
return res;
}
Inizializza la funzione del dispositivo
Funzione di inizializzazione del supporto di archiviazione disk_initialize. Viene utilizzato per inizializzare il dispositivo disco e verrà chiamato nel file ff.c. Il suo prototipo di funzione è il seguente:
ˆ DSTATUS disk_initialize(BYTE drv);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare la funzione di inizializzazione del disco, ma in realtà non ne abbiamo bisogno qui, perché l’inizializzazione è stata completata nella libreria USB HOST, quindi restituisci semplicemente la funzione corretta direttamente.
/*Initialization function for USBH*/
static DSTATUS USBH_initialize(BYTE lun)
{
//Initialization has been completed in the USB HOST library
return RES_OK;
}
Leggi i dati
Funzione di lettura del settore disk_read. Viene utilizzato per leggere i dati del disco. È scritto in base all’I/O del disco specifico e verrà chiamato nel file ff.c. Il suo prototipo di funzione è il seguente:
- DRESULT disk_read(BYTE drv, BYTE*buff, DWORD sector, BYTE.count);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare la funzione di lettura dei dati del disco come segue:
/*Read sector function for USBH*/
static DRESULT USBH_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Read(&hUsbHostFS, lun, sector, buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info);
switch(info.sense.asc)
{
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog ("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
Scrivi i dati
Funzione di scrittura del settore disk_write. Viene utilizzato per scrivere i dati del disco. È scritto in base all’I/O del disco specifico e verrà chiamato nel file ff.c. Il suo prototipo di funzione è il seguente:
- DRESULT disk_write(BYTE drv, const BYTE*buff, DWORD sector, BYTE count);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare la funzione di scrittura dei dati del disco come segue:
/*Write sector function for USBH*/
static DRESULT USBH_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Write(&hUsbHostFS, lun, sector, (BYTE *)buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info);
switch(info.sense.asc)
{
case SCSI_ASC_WRITE_PROTECTED:
USBH_ErrLog("USB Disk is Write protected!");
res = RES_WRPRT;
break;
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
Funzioni correlate al controllo del dispositivo
Funzione di controllo del supporto di archiviazione disk_ioctl. Puoi scrivere il codice funzionale necessario in questa funzione, come ottenere la dimensione del supporto di archiviazione, rilevare se il supporto di archiviazione è alimentato o meno e il numero di settori del supporto di archiviazione, ecc. Se si tratta di un’applicazione semplice, non è necessario scriverlo. Il suo prototipo di funzione è il seguente:
- DRESULT disk_ioctl(BYTE drv, BYTE ctrl, VoiI*buff);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare le funzioni correlate al controllo del dispositivo disco come segue:
/*USBH IO control function */
static DRESULT USBH_ioctl(BYTE lun, BYTE cmd, void *buff)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
switch(cmd)
{
/* Make sure that no pending write process */
case CTRL_SYNC:
res = RES_OK;
break;
/* Get number of sectors on the disk (DWORD) */
case GET_SECTOR_COUNT :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_nbr;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get R/W sector size (WORD) */
case GET_SECTOR_SIZE :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get erase block size in unit of sector (DWORD) */
case GET_BLOCK_SIZE :
if(USBH_MSC_GetLUNInfo(&hUsbHostFS, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size / USB_DEFAULT_BLOCK_SIZE;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
default:
res = RES_PARERR;
}
return res;
}
Ottieni l'ora corrente
La funzione dell’orologio in tempo reale get_fattime. Utilizzata per ottenere l’ora corrente e restituire un intero senza segno a 32 bit. Le informazioni sull’orologio sono contenute in questi 32 bit. Se non si utilizza un timestamp, è possibile restituire direttamente un numero, ad esempio 0. Il suo prototipo di funzione è il seguente:
ˆ DWORD get_fattime(Void);
In base alla sua definizione di prototipo e ai requisiti del nostro dispositivo di archiviazione di massa USB, possiamo implementare la funzione di acquisizione dello stato del disco come segue:
/*Read clock function*/
DWORD get_fattime(void)
{
return 0;
}
Completa la scrittura dei suddetti 6 programmi e il lavoro di porting è sostanzialmente completato. Potresti scoprire che il nome della funzione che abbiamo implementato sembra essere diverso dalla funzione prototipo. Questo è principalmente per facilitare le operazioni quando esistono più dispositivi di archiviazione contemporaneamente. Possiamo semplicemente chiamare la funzione che abbiamo implementato nella funzione di destinazione.
Test dell'applicazione
Abbiamo completato il porting di FatFS, ora verifichiamo se il porting è corretto. A tal fine, scriviamo un’applicazione per scrivere dati su un file su una chiavetta USB e leggere i dati del file.
/* USB HOST MSC operation function, this part of the function is set according to needs */
static void MSC_Application(void)
{
FRESULT res; /* FatFs function return value */
uint32_t byteswritten, bytesread; /* Number of files read and written */
uint8_t wtext[] = "This is STM32 working with FatFs!"; /* Write file buffer */
uint8_t wtext2[] = "This is an example of FatFs reading and writing!"; /* Write file buffer */
uint8_t wtext3[] = "This is a test of appending data to a file!"; /* Write file buffer */
uint8_t rtext[100]; /* Read file buffer */
/* Register the file system object to the FatFs module */
if(f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
/* Open a file */
if(f_open(&USBHFile, "STM32.TXT", FA_OPEN_EXISTING | FA_WRITE) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
res=f_lseek(&USBHFile,f_size(&USBHFile)); //Point the pointer to the end of the file
//res=f_lseek(&USBHFile,100); //Point the pointer to the end of the file
/* Write data to file */
res = f_write(&USBHFile, wtext, sizeof(wtext), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
res = f_write(&USBHFile, wtext2, sizeof(wtext2), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
res = f_write(&USBHFile, wtext3, sizeof(wtext3), (void *)&byteswritten);
res = f_write(&USBHFile, "\r\n", sizeof("\r\n")-1, &byteswritten);
if((byteswritten == 0) || (res != FR_OK))
{
/* Error handling */
Error_Handler();
}
else
{
/* Close file */
f_close(&USBHFile);
/* Open file for reading */
if(f_open(&USBHFile, "STM32.TXT", FA_READ) != FR_OK)
{
/* Error handling */
Error_Handler();
}
else
{
/* Read data from file */
res = f_read(&USBHFile, rtext, sizeof(rtext), (void *)&bytesread);
if((bytesread == 0) || (res != FR_OK))
{
/* Error handling */
Error_Handler();
}
else
{
/* Close file */
f_close(&USBHFile);
/* Compare read and written data */
if((bytesread != byteswritten))
{
/* Error handling */
Error_Handler();
}
else
{
/* No errors */
}
}
}
}
}
}
FATFS_UnLinkDriver(USBHPath);
}
Il file che abbiamo creato per la prima volta sul disco USB si chiama “STM32.TXT”. Nel codice sorgente di cui sopra, dopo aver creato il file, lo modifichiamo per aprirlo ed esistere. Scriviamo “Questo è STM32 che funziona con FatFs!” nel file STM32.TXT creato, come mostrato nella figura seguente:

Successivamente, proviamo ad aggiungere contenuto a un file già esistente. È ancora il file STM32.TXT, come mostrato di seguito:

A questo punto, abbiamo completato il porting e il test del file system FatFS. Dai risultati del test, il porting è corretto, almeno non sono stati riscontrati problemi in applicazioni semplici.
Conclusione
In questo articolo, abbiamo portato il file system FatFS su STM32F4 e condotto un semplice test di lettura e scrittura. A giudicare dai risultati del test, non ci sono problemi nel processo di porting di FatFS.
Quando abbiamo eseguito il porting, abbiamo considerato la facilità di operazione quando sono disponibili più unità. La funzione di operazione di I/O del disco che definiamo deve essere implementata in base all’hardware reale, quindi la funzione di I/O del disco che abbiamo scritto viene chiamata nella funzione di callback specificata dal sistema.




