Aplicação do sistema de arquivos FatFS em MCU

O sistema de arquivos FatFS é um sistema de arquivos bem conhecido no campo dos microcontroladores. Devido à sua leveza e compatibilidade, ele é preferido pelos desenvolvedores de MCU. Ao implementar tarefas como leitura e gravação de arquivos em discos U e cartões SD, muitas vezes precisamos de um sistema de arquivos para dar suporte ao nosso trabalho. Especialmente em algumas aplicações de MCU, a adição de um sistema de arquivos pode melhorar significativamente a facilidade de interação do sistema. Neste artigo, apresentamos a transplantação e a aplicação do sistema de arquivos FatFS no STM32F4.

Característica do FatFS

  1. Sistema de arquivos FAT/exFAT compatível com DOS/Windows.
  2. Independente da plataforma e fácil de transplantar.
  3. O código do programa e o espaço de trabalho ocupam muito pouco espaço.
  4. Suporta as seguintes opções de configuração:
    • Nomes de arquivos longos em ANSI/OEM ou Unicode.
    • Sistema de arquivos exFAT, LBA de 64 bits e GPT para armazenar grandes quantidades de dados.
    • Segurança de thread do RTOS.
    • Vários volumes (unidades físicas e partições).
    • Tamanho de setor variável.
    • Várias páginas de código, incluindo DBCS.
    • Somente leitura, API opcional, buffers de E/S, etc.

Transplante FatFS em MCU

Preparação

Precisamos fazer alguns preparativos necessários antes de iniciar a transplantação do FatFS. Primeiro, você precisa preparar a plataforma de hardware correspondente. Estamos usando a plataforma operacional STM32F407VET6 aqui. O trabalho de portabilidade das bibliotecas relacionadas ao hardware USB também foi concluído.

Em segundo lugar, também precisamos preparar o código-fonte relevante do FatFS. Aqui, usamos a versão R0.14b mais recente, que pode ser baixada do site:

http://elm-chan.org/fsw/ff/00index_e.html

Após a descompactação do código-fonte baixado, há duas pastas: document e source. A pasta document contém a documentação relevante, que é a mesma do conteúdo do site. Você pode visualizar esses documentos para trabalhar durante a transplantação. A pasta Source contém arquivos relacionados ao código-fonte, incluindo principalmente:

FatFS Source Code
FatFS Source Code

Entre a série de arquivos mostrados na imagem acima, o arquivo 00readme.txt contém uma introdução a cada arquivo. Visualizamos seu conteúdo da seguinte forma:

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 esses arquivos, ff.c e ff.h são arquivos principais. ffunicode.c é a codificação de caracteres, e a codificação será selecionada de acordo com a configuração do arquivo de configuração. O arquivo ffsystem.c é determinado de acordo com suas próprias necessidades. Portanto, os arquivos relacionados à plataforma de aplicativo específica e que precisam ser implementados por nós são o arquivo de configuração ffconf.h e os arquivos de operação de disco diskio.h e diskio.c. Esses arquivos também são o foco da nossa transplantação.

Implementar Transplante

Concluímos o trabalho de preparação para a transplantação e agora vamos implementar a transplantação do aplicativo para discos USB de grande capacidade. Como dissemos anteriormente, os arquivos que precisam ser processados para a transplantação são o arquivo de configuração ffconf.h e os arquivos de operação do disco diskio.h e diskio.c.

Sobre o arquivo de configuração ffconf.h, ele na verdade tem uma instância de si mesmo. Só precisamos modificar a configuração conforme necessário. Os parâmetros de configuração que precisamos modificar aqui incluem:

O parâmetro de configuração do método de codificação suportado FF_CODE_PAGE está relacionado à questão da codificação de arquivos. Nós o configuramos para suportar chinês simplificado.

O número de unidades lógicas é configurado com o parâmetro FF_VOLUMES. O FatFS pode ser aplicado a várias unidades ao mesmo tempo, portanto, precisamos configurar o número de unidades de acordo com a situação real.

Parâmetro de configuração de carimbo de data/hora FF_FS_NORTC: na maioria das vezes, não precisamos registrar carimbos de data/hora, então o desativamos aqui.

O restante são funções relacionadas à implementação de operações de E/S de disco. O documento de ajuda do FatFS nos informa que há dois tipos de funções que precisam ser implementadas: uma é a função relacionada ao controle do dispositivo de disco, principalmente funções para obter o status do dispositivo, inicializar funções do dispositivo e ler. Funções de dados, funções de gravação de dados e funções relacionadas ao controle do dispositivo; a segunda categoria é a função de operação do relógio em tempo real, usada principalmente para obter a função de hora atual. Portanto, implementar essas 6 funções é a principal tarefa da transplantação.

Obter função de status do dispositivo

Função de detecção do estado do disco disk_status. Utilizada para detetar o estado do disco e será chamada no ficheiro ff.c. O protótipo da sua função é o seguinte:

				
					ˆ DSTATUS disk_status(BYTE drV);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar a função de aquisição do estado do disco da seguinte forma:

				
					/*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 função do dispositivo

Função de inicialização da mídia de armazenamento disk_initialize. É usada para inicializar o dispositivo de disco e será chamada no arquivo ff.c. Seu protótipo de função é o seguinte:

				
					ˆ DSTATUS disk_initialize(BYTE drv);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar a função de inicialização da unidade de disco, mas na verdade não precisamos dela aqui, porque a inicialização já foi concluída na biblioteca USB HOST, então basta retornar a função correta diretamente.

				
					/*Initialization function for USBH*/
static DSTATUS USBH_initialize(BYTE lun)
{
  //Initialization has been completed in the USB HOST library
return RES_OK;
}
				
			

Ler dados

Função de leitura de setor disk_read. É usada para ler dados do disco. É escrita de acordo com a E/S específica do disco e será chamada no arquivo ff.c. Seu protótipo de função é o seguinte:

				
					- DRESULT disk_read(BYTE drv, BYTE*buff, DWORD sector, BYTE.count);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar a função de leitura de dados do disco da seguinte forma:

				
					/*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;
}
				
			

Gravar dados

Escreva a função do setor disk_write. Ela é usada para gravar dados no disco. É escrita de acordo com a E/S específica do disco e será chamada no arquivo ff.c. Seu protótipo de função é o seguinte:

				
					- DRESULT disk_write(BYTE drv, const BYTE*buff, DWORD sector, BYTE count);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar a função de gravação de dados no disco da seguinte forma:

				
					/*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;
}
				
			

Funções relacionadas com o dispositivo de controlo

Função de controle de mídia de armazenamento disk_ioctl. Você pode escrever o código funcional necessário nesta função, como obter o tamanho da mídia de armazenamento, detectar se a mídia de armazenamento está ligada ou não e o número de setores da mídia de armazenamento, etc. Se for um aplicativo simples, não há necessidade de escrevê-lo. Seu protótipo de função é o seguinte:

				
					- DRESULT disk_ioctl(BYTE drv, BYTE ctrl, VoiI*buff);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar as funções relacionadas ao controle do dispositivo de disco da seguinte forma:

				
					/*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;
}
				
			

Obter a hora atual

Função de relógio em tempo real get_fattime. Usada para obter a hora atual e retornar um inteiro sem sinal de 32 bits. As informações do relógio estão contidas nesses 32 bits. Se você não usar um carimbo de data/hora, poderá retornar diretamente um número, como 0. Seu protótipo de função é o seguinte:

				
					ˆ DWORD get_fattime(Void);
				
			

De acordo com a definição do protótipo e os requisitos do nosso dispositivo de armazenamento em massa USB, podemos implementar a função de aquisição do estado do disco da seguinte forma:

				
					/*Read clock function*/
DWORD get_fattime(void)
{

return 0;

}
				
			

Conclua a escrita dos 6 programas acima e o trabalho de transplante estará basicamente concluído. Você pode perceber que o nome da função que implementamos parece ser diferente da função protótipo. Isso se deve principalmente à facilitação das operações quando vários dispositivos de armazenamento existem ao mesmo tempo. Podemos simplesmente chamar a função que implementamos na função de destino.

Teste de aplicativos

Concluímos a transplantação do FatFS, agora vamos verificar se a transplantação está correta. Para isso, vamos escrever um aplicativo para gravar dados em um arquivo em uma unidade flash USB e ler os dados do arquivo.

				
					/* 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);
}
				
			

O arquivo que criamos inicialmente no disco USB é chamado "STM32.TXT". No código-fonte acima, após criarmos o arquivo, nós o modificamos para abrir e sair. Escreva "This is STM32 working with FatFs!" (Este é o STM32 funcionando com FatFs!) no arquivo STM32.TXT criado, conforme mostrado na figura abaixo:

write file buffer
write file buffer

Em seguida, tentamos anexar conteúdo a um arquivo já existente. Ainda é o arquivo STM32.TXT, conforme mostrado abaixo:

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

Neste ponto, concluímos a transplantação e os testes do sistema de arquivos FatFS. Pelos resultados dos testes, a transplantação está correta, pelo menos não foram encontrados problemas em aplicações simples.

Conclusão

Neste artigo, transplantamos o sistema de arquivos FatFS para STM32F4 e realizamos um teste simples de leitura e gravação. A julgar pelos resultados do teste, não há nenhum problema no processo de transplante do FatFS.

Quando fizemos a portabilidade, levamos em consideração a facilidade de operação quando várias unidades estão disponíveis ao mesmo tempo. A função de operação de E/S de disco que definimos precisa ser implementada com base no hardware real e, em seguida, a função de E/S de disco que escrevemos é chamada na função de retorno de chamada especificada pelo sistema.

Compartilhar em:

Rolar para cima

Instant Quote