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
- Sistema de arquivos FAT/exFAT compatível com DOS/Windows.
- Independente da plataforma e fácil de transplantar.
- O código do programa e o espaço de trabalho ocupam muito pouco espaço.
- 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:

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 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 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:

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

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.




