El sistema de archivos FatFS es un sistema de archivos muy conocido en el campo de los microcontroladores. Debido a su ligereza y compatibilidad, es el preferido por los desarrolladores de MCU. Al implementar tareas como la lectura y escritura de archivos en discos U y tarjetas SD, a menudo necesitamos un sistema de archivos que nos ayude en nuestro trabajo. Especialmente en algunas aplicaciones de MCU, la incorporación de un sistema de archivos puede mejorar significativamente la facilidad de interacción con el sistema. En este artículo, presentamos la trasplante y la aplicación del sistema de archivos FatFS en STM32F4.
Características de FatFS
- Sistema de archivos FAT/exFAT compatible con DOS/Windows.
- Independiente de la plataforma y fácil de trasplantar.
- El código del programa y el espacio de trabajo ocupan muy poco espacio.
- Admite las siguientes opciones de configuración:
- Nombres de archivo largos en ANSI/OEM o Unicode.
- Sistema de archivos exFAT, LBA de 64 bits y GPT para almacenar grandes cantidades de datos.
- Seguridad de subprocesos de RTOS.
- Múltiples volúmenes (unidades físicas y particiones).
- Tamaño de sector variable.
- Múltiples páginas de códigos, incluyendo DBCS.
- Solo lectura, API opcional, búferes de E/S, etc.
Trasplante de FatFS en MCU
Preparación
Antes de comenzar con la transplicación de FatFS, debemos realizar algunos preparativos necesarios. En primer lugar, hay que preparar la plataforma de hardware correspondiente. En este caso, utilizamos la plataforma operativa STM32F407VET6. También se ha completado el trabajo de portabilidad de las bibliotecas relacionadas con el hardware USB.
En segundo lugar, también debemos preparar el código fuente relevante de FatFS. Aquí utilizamos la última versión R0.14b, que se puede descargar desde el sitio web:
http://elm-chan.org/fsw/ff/00index_e.html
Una vez descomprimido el código fuente descargado, hay dos carpetas: document y source. La carpeta document contiene la documentación relevante, que es la misma que la del sitio web. Puede consultar estos documentos para trabajar durante la traslación. La carpeta Source contiene archivos relacionados con el código fuente, entre los que se incluyen principalmente:

Entre la serie de archivos que se muestran en la imagen anterior, el archivo 00readme.txt contiene una introducción a cada uno de ellos. Su contenido es el siguiente:
| 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 |
Entre estos archivos, ff.c y ff.h son archivos principales. ffunicode.c es la codificación de caracteres, y la codificación se seleccionará según la configuración del archivo de configuración. El archivo ffsystem.c se determina según sus propias necesidades. Por lo tanto, los archivos relacionados con la plataforma de aplicación específica y que debemos implementar son el archivo de configuración ffconf.h y los archivos de operación de disco diskio.h y diskio.c. Estos archivos son también el centro de nuestra transplantación.
Implementar el trasplante
Hemos completado el trabajo de preparación para la migración y ahora vamos a implementar la migración de la aplicación para discos USB de gran capacidad. Como hemos dicho anteriormente, los archivos que hay que procesar para la migración son el archivo de configuración ffconf.h y los archivos de operación del disco diskio.h y diskio.c.
En cuanto al archivo de configuración ffconf.h, en realidad tiene una instancia de sí mismo. Solo tenemos que modificar la configuración según sea necesario. Los parámetros de configuración que debemos modificar aquí incluyen:
El parámetro de configuración del método de codificación compatible FF_CODE_PAGE está relacionado con la cuestión de la codificación de archivos. Lo configuramos para que sea compatible con el chino simplificado.
El número de unidades lógicas se configura con el parámetro FF_VOLUMES. FatFS se puede aplicar a varias unidades al mismo tiempo, por lo que debemos configurar el número de unidades según la situación real.
El parámetro de configuración de marca de tiempo FF_FS_NORTC. La mayoría de las veces no necesitamos registrar marcas de tiempo, por lo que lo desactivamos aquí.
El resto son funciones relacionadas con la implementación de operaciones de E/S de disco. El documento de ayuda de FatFS nos indica que hay dos tipos de funciones que deben implementarse: una es la relacionada con el control de dispositivos de disco, principalmente funciones para obtener el estado del dispositivo, inicializar funciones del dispositivo y leer. Funciones de datos, funciones de escritura de datos y funciones relacionadas con el control de dispositivos; la segunda categoría es la función de operación del reloj en tiempo real, que se utiliza principalmente para obtener la función de hora actual. Por lo tanto, la implementación de estas 6 funciones es la tarea principal del trasplante.
Obtener función de estado del dispositivo
Función de detección del estado del disco disk_status. Se utiliza para detectar el estado del disco y se invocará en el archivo ff.c. Su prototipo de función es el siguiente:
ˆ DSTATUS disk_status(BYTE drV);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar la función de adquisición del estado del disco de la siguiente manera:
/*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;
}
Inicializar la función del dispositivo
Función de inicialización del medio de almacenamiento disk_initialize. Se utiliza para inicializar el dispositivo de disco y se invocará en el archivo ff.c. Su prototipo de función es el siguiente:
ˆ DSTATUS disk_initialize(BYTE drv);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar la función de inicialización de la unidad de disco, pero en realidad no la necesitamos aquí, ya que la inicialización se ha completado en la biblioteca USB HOST, por lo que basta con devolver directamente la función correcta.
/*Initialization function for USBH*/
static DSTATUS USBH_initialize(BYTE lun)
{
//Initialization has been completed in the USB HOST library
return RES_OK;
}
Leer datos
Función de lectura de sector disk_read. Se utiliza para leer datos del disco. Se escribe según la E/S específica del disco y se invocará en el archivo ff.c. Su prototipo de función es el siguiente:
- DRESULT disk_read(BYTE drv, BYTE*buff, DWORD sector, BYTE.count);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar la función de lectura de datos del disco de la siguiente manera:
/*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;
}
Escribir datos
Escribir la función del sector disk_write. Se utiliza para escribir datos en el disco. Se escribe según la E/S específica del disco y se llamará en el archivo ff.c. Su prototipo de función es el siguiente:
- DRESULT disk_write(BYTE drv, const BYTE*buff, DWORD sector, BYTE count);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar la función de escritura de datos en disco de la siguiente manera:
/*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;
}
Funciones relacionadas con el dispositivo de control
Función de control de medios de almacenamiento disk_ioctl. En esta función puede escribir el código funcional que necesite, como obtener el tamaño del medio de almacenamiento, detectar si el medio de almacenamiento está encendido o no, y el número de sectores del medio de almacenamiento, etc. Si se trata de una aplicación sencilla, no es necesario escribirlo. Su prototipo de función es el siguiente:
- DRESULT disk_ioctl(BYTE drv, BYTE ctrl, VoiI*buff);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar las funciones relacionadas con el control del dispositivo de disco de la siguiente manera:
/*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;
}
Obtener la hora actual
Función de reloj en tiempo real get_fattime. Se utiliza para obtener la hora actual y devolver un entero sin signo de 32 bits. La información del reloj está contenida en estos 32 bits. Si no se utiliza una marca de tiempo, se puede devolver directamente un número, como 0. Su prototipo de función es el siguiente:
ˆ DWORD get_fattime(Void);
De acuerdo con su definición prototipo y los requisitos de nuestro dispositivo de almacenamiento masivo USB, podemos implementar la función de adquisición del estado del disco de la siguiente manera:
/*Read clock function*/
DWORD get_fattime(void)
{
return 0;
}
Complete la escritura de los 6 programas anteriores y el trabajo de trasplante estará básicamente terminado. Es posible que observe que el nombre de la función que hemos implementado parece ser diferente del de la función prototipo. Esto se debe principalmente a que facilita las operaciones cuando existen varios dispositivos de almacenamiento al mismo tiempo. Podemos simplemente llamar a la función que hemos implementado en la función de destino.
Pruebas de aplicaciones
Hemos completado el trasplante de FatFS, ahora vamos a verificar si el trasplante es correcto. Para ello, escribamos una aplicación para escribir datos en un archivo en una unidad flash USB y leer los datos del archivo.
/* 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);
}
El archivo que creamos inicialmente en el disco USB se llama «STM32.TXT». En el código fuente anterior, después de crear el archivo, lo modificamos para abrirlo y salir de él. Escriba «¡Esto es STM32 funcionando con FatFs!» en el archivo STM32.TXT creado, como se muestra en la siguiente figura:

A continuación, intentamos añadir contenido a un archivo ya existente. Sigue siendo el archivo STM32.TXT, como se muestra a continuación:

En este punto, hemos completado el trasplante y las pruebas del sistema de archivos FatFS. Según los resultados de las pruebas, el trasplante es correcto, al menos no se han encontrado problemas en aplicaciones sencillas.
Conclusión
En este artículo, hemos trasplantado el sistema de archivos FatFS a STM32F4 y hemos realizado una sencilla prueba de lectura y escritura. A juzgar por los resultados de la prueba, no hay ningún problema en el proceso de trasplante de FatFS.
Al realizar la migración, tuvimos en cuenta la facilidad de uso cuando hay varias unidades disponibles al mismo tiempo. La función de operación de E/S de disco que definimos debe implementarse en función del hardware real y, a continuación, la función de E/S de disco que escribimos se invoca en la función de devolución de llamada especificada por el sistema.




