Aplicación del sistema de archivos FatFS en MCU

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

  1. Sistema de archivos FAT/exFAT compatible con DOS/Windows.
  2. Independiente de la plataforma y fácil de trasplantar.
  3. El código del programa y el espacio de trabajo ocupan muy poco espacio.
  4. 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:

FatFS Source Code
FatFS Source Code

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 NameDescription
00readme.txtReadme file
00history.txtVersion history file
ff.cFatFs module
ffconf.hConfiguration file for the FatFs module
ff.hHeader file for the FatFs application module
diskio.hHeader file for the FatFs and disk I/O module
diskio.cAn instance that attaches disk I/O functions to FatFS
ffunicode.cUnicode encoding utility functions
ffsystem.cOptional 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:

write file buffer
write file buffer

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

FatFS porting verification writing data to file
FatFS porting verification - writing data to file

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.

Compartir en:

Scroll al inicio

Cotización