Aplicação do sistema de arquivos FatFS em MCU

Índice

Application of FatFS file system on 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.

Sobre o autor

Picture of Aidan Taylor
Aidan Taylor

I am Aidan Taylor and I have over 10 years of experience in the field of PCB Reverse Engineering, PCB design and IC Unlock.

Compartilhar

Postagem recomendada

Precisa de ajuda?

Rolar para cima

Cotação instantânea

Instant Quote