Comparativo prático: FATFS, LittleFS e SPIFFS no STM32N6
Depois de ver os exemplos com FATFS no SD Card, LittleFS em Flash externa e SPIFFS em Flash externa, podemos comparar os três de forma mais prática. A pergunta principal não deve ser apenas “qual é melhor?”, mas sim: qual sistema de arquivos combina melhor com a mídia física e com o tipo de dado que meu firmware precisa armazenar?
No STM32N6, que é um microcontrolador de alto desempenho para aplicações embarcadas modernas, podemos ter mais de uma mídia de armazenamento no mesmo projeto. Um projeto pode usar SD Card para arquivos grandes e removíveis, enquanto usa Flash externa com LittleFS para configurações internas. Essa divisão é muito mais profissional do que tentar forçar um único sistema de arquivos para todos os usos.
O FATFS é a escolha mais natural quando usamos SD Card. Ele trabalha bem sobre dispositivos de bloco, permite criar arquivos compatíveis com computadores e facilita a exportação de dados. Se o objetivo é salvar arquivos .csv, .txt, .json, .bin, imagens, logs extensos ou dados de sensores para análise posterior em um PC, FATFS é a opção mais prática.
O LittleFS é mais adequado quando usamos Flash NOR interna ou externa controlada diretamente pelo firmware. Ele foi pensado para sistemas embarcados que precisam lidar com apagamento por blocos, ciclos limitados de escrita e risco de perda de energia. Para configurações, metadados, parâmetros persistentes, certificados, pequenos logs e estado interno do produto, LittleFS é uma excelente escolha.
O SPIFFS também foi criado para Flash SPI, mas hoje tende a ser mais interessante em sistemas legados. Ele ainda pode funcionar bem em projetos existentes, principalmente quando o firmware já foi validado com ele. Porém, para novos projetos, LittleFS costuma ser a escolha mais robusta e organizada, especialmente por oferecer diretórios reais e melhor estratégia de consistência.
A comparação pode ser resumida assim:
| Critério | FATFS | LittleFS | SPIFFS |
|---|---|---|---|
| Melhor mídia | SD Card, eMMC, mídia de bloco | Flash NOR interna/externa | Flash NOR SPI |
| Compatível com PC | Sim | Não diretamente | Não diretamente |
| Bom para logs grandes | Sim | Parcialmente | Parcialmente |
| Bom para configurações internas | Sim, mas pode ser exagerado | Sim | Sim |
| Suporte a diretórios | Sim | Sim | Não possui diretórios reais |
| Tolerância a queda de energia | Depende do uso e da mídia | Muito boa | Mais limitada |
| Wear leveling | Depende do SD/eMMC | Sim | Sim |
| Uso recomendado no STM32N6 com SD Card | Sim | Não é o ideal | Não é o ideal |
| Uso recomendado no STM32N6 com Flash externa | Possível, mas menos natural | Sim | Possível em legado |

Do ponto de vista de arquitetura de software embarcado, essa separação também ajuda a manter o firmware mais limpo. O acesso ao hardware pode ser encapsulado por camadas, seguindo uma abordagem semelhante aos padrões de acesso a hardware e gerenciamento de recursos em sistemas embarcados. Bruce Powel Douglass destaca que sistemas embarcados normalmente precisam lidar com restrições severas de memória, desempenho, confiabilidade e recursos, o que reforça a importância de escolher bem as abstrações de acesso ao hardware e aos recursos compartilhados.
Em um sistema com FreeRTOS, a escolha do sistema de arquivos também afeta o desenho das tarefas. Com FATFS no SD Card, é comum criar uma tarefa de armazenamento, receber mensagens por fila e gravar no cartão de forma controlada. Com LittleFS ou SPIFFS, a estratégia pode ser parecida, mas normalmente voltada a arquivos menores e operações mais pontuais. O importante é evitar que várias tarefas acessem a mesma mídia ao mesmo tempo sem proteção.
Na prática, eu adotaria a seguinte regra de decisão:
Preciso remover o cartão e ler no computador?
Use SD Card com FATFS.
Preciso guardar configuração interna robusta?
Use Flash externa com LittleFS.
Tenho firmware antigo já baseado em SPIFFS?
Mantenha SPIFFS se estiver estável.
Estou começando um projeto novo?
Use FATFS para SD Card e LittleFS para Flash.
Essa decisão evita um erro comum: tentar comparar LittleFS e SPIFFS como se fossem substitutos diretos do FATFS em SD Card. Eles não nasceram para o mesmo problema. FATFS organiza uma mídia de bloco compatível com PC; LittleFS e SPIFFS organizam Flash bruta controlada pelo microcontrolador.
Boas práticas de projeto ao gravar arquivos em SD Card e Flash
Ao usar sistemas de arquivos em microcontroladores, o maior erro é imaginar que gravar um arquivo seja uma operação simples, instantânea e sempre segura. Em um computador comum, o sistema operacional, o driver, o cache, a controladora do disco e o próprio hardware escondem boa parte da complexidade. Em um microcontrolador, essa responsabilidade fica muito mais próxima do firmware.
No caso de um STM32N6 com SD Card, a primeira boa prática é tratar o cartão como um recurso relativamente lento e sujeito a falhas. Um cartão pode demorar para responder, pode ser removido, pode estar mal formatado, pode ter setores problemáticos ou pode perder energia durante uma gravação. Por isso, a aplicação não deve depender de uma escrita imediata para continuar executando tarefas críticas. A gravação em arquivo deve ser isolada em uma tarefa própria, como vimos na seção anterior, ou protegida por um mecanismo claro de exclusão mútua.
A segunda boa prática é evitar abrir e fechar arquivos excessivamente. Abrir, escrever, sincronizar e fechar a cada pequena mensagem é simples, mas pode ser ineficiente. Por outro lado, manter o arquivo aberto por muito tempo sem sincronizar aumenta o risco de perder dados recentes em caso de reset. O projeto precisa encontrar um equilíbrio. Para logs comuns, uma estratégia razoável é gravar várias linhas e chamar f_sync() periodicamente, por quantidade de registros ou por intervalo de tempo.
Um exemplo simples de sincronização periódica com FATFS seria:
#define LOG_SYNC_INTERVAL_LINES 10U
static uint32_t logSyncCounter = 0;
static void Storage_WriteLine(FIL *file, const char *line)
{
UINT bytesWritten = 0;
FRESULT result;
result = f_write(
file,
line,
strlen(line),
&bytesWritten
);
if (result == FR_OK && bytesWritten == strlen(line))
{
logSyncCounter++;
if (logSyncCounter >= LOG_SYNC_INTERVAL_LINES)
{
f_sync(file);
logSyncCounter = 0;
}
}
}
Esse código reduz o número de sincronizações físicas no cartão. A consequência é que, se a energia cair antes do próximo f_sync(), algumas linhas recentes podem ser perdidas. Essa é uma decisão de engenharia: desempenho e vida útil de um lado, persistência imediata do outro.
A terceira boa prática é separar arquivos por função. Em vez de gravar tudo em um único arquivo enorme, pode ser melhor separar logs de sensores, eventos de erro, configurações e resultados de processamento. Isso facilita a análise posterior, reduz o risco de perder tudo em um único ponto de corrupção e deixa o firmware mais organizado.
/config/system.json
/logs/events.csv
/logs/sensors.csv
/logs/errors.csv
/data/sample_0001.bin
/data/sample_0002.bin
Em FATFS com SD Card, essa organização é natural porque há suporte a diretórios reais. Em LittleFS também é possível organizar dessa forma. Em SPIFFS, é preciso tomar cuidado, pois os “caminhos” podem ser apenas nomes de arquivo com barras, sem diretórios reais.
A quarta boa prática é usar formatos adequados ao tipo de dado. Para logs humanos, CSV e texto simples são excelentes, porque podem ser abertos em qualquer editor. Para dados de sensores em alta taxa, arquivos binários são mais eficientes. Para configuração, JSON é legível e flexível, mas pode consumir mais espaço e processamento do que um formato binário simples. A escolha do formato afeta desempenho, desgaste, depuração e facilidade de manutenção.
Uma configuração em JSON pode ser útil assim:
{
"device": "STM32N6",
"sample_rate_hz": 1000,
"log_enabled": true,
"storage": "sdcard"
}
Já dados amostrados em alta frequência podem ser mais bem representados por uma estrutura binária:
typedef struct
{
uint32_t timestamp_ms;
int16_t accel_x;
int16_t accel_y;
int16_t accel_z;
} SensorSample_t;
E gravados com:
static FRESULT Storage_WriteSample(FIL *file, const SensorSample_t *sample)
{
UINT bytesWritten = 0;
return f_write(
file,
sample,
sizeof(SensorSample_t),
&bytesWritten
);
}

A quinta boa prática é proteger o sistema contra corrupção causada por queda de energia. Em SD Card com FATFS, operações como criação, extensão e atualização de arquivos podem alterar metadados do sistema de arquivos. Se a energia cair nesse momento, existe risco de perda parcial de dados ou corrupção. O uso de f_sync() ajuda, mas não resolve tudo. Em equipamentos mais críticos, vale implementar detecção de queda de tensão, capacitor de reserva, sinal de “power fail” e uma rotina de fechamento emergencial.
Em Flash externa com LittleFS, a robustez contra falhas repentinas tende a ser melhor, porque o sistema foi projetado considerando esse tipo de cenário. Mesmo assim, isso não elimina a necessidade de um bom projeto elétrico e de uma boa política de escrita. Nenhum sistema de arquivos transforma uma alimentação instável em uma plataforma confiável por milagre.
A sexta boa prática é não gravar configurações críticas diretamente por cima do arquivo antigo sem uma estratégia de validação. Uma técnica simples é usar dois arquivos: um arquivo ativo e um arquivo temporário. Primeiro grava-se o temporário, sincroniza-se, valida-se o conteúdo e depois renomeia-se para o nome final.
config.json arquivo atual válido
config.tmp novo arquivo em gravação
O fluxo seria:
1. Gravar config.tmp
2. Sincronizar config.tmp
3. Fechar config.tmp
4. Validar conteúdo
5. Renomear config.tmp para config.json
Em FATFS, esse tipo de técnica reduz a chance de terminar com uma configuração parcialmente escrita. Em LittleFS, operações desse tipo combinam bem com a filosofia de consistência do sistema. Em SPIFFS, a técnica também pode ser usada, mas é necessário respeitar suas limitações.
A sétima boa prática é validar o retorno de todas as funções. Em exemplos didáticos, às vezes ignoramos alguns erros para simplificar. Em produto real, isso não é aceitável. Funções como f_mount, f_open, f_write, f_read, f_sync e f_close retornam códigos que precisam ser analisados. O firmware deve saber diferenciar cartão ausente, falha de escrita, disco cheio, arquivo inexistente e erro interno.
Um padrão mais seguro seria:
static bool Storage_CheckResult(FRESULT result, const char *operation)
{
if (result != FR_OK)
{
printf("[STORAGE] Falha em %s. Código FATFS: %d\r\n",
operation,
(int)result);
return false;
}
return true;
}
E o uso:
FRESULT result = f_open(&file, "events.csv", FA_OPEN_APPEND | FA_WRITE);
if (!Storage_CheckResult(result, "f_open(events.csv)"))
{
return;
}
A oitava boa prática é planejar o espaço disponível. Logs crescem. Arquivos temporários acumulam. Dados de sensores podem ocupar muito mais espaço do que o previsto. O firmware deve consultar espaço livre, limitar tamanho de arquivo, rotacionar logs e apagar dados antigos quando necessário. Em sistemas industriais, é comum implementar rotação por tamanho ou por quantidade de arquivos.
events_0001.csv
events_0002.csv
events_0003.csv
Quando o número máximo de arquivos é atingido, o firmware pode apagar o mais antigo ou parar de registrar, dependendo da aplicação.
A nona boa prática é evitar alocação dinâmica durante operações críticas de arquivo. Em microcontroladores, especialmente com FreeRTOS, o uso descontrolado de malloc() pode fragmentar heap e tornar o sistema imprevisível. Prefira buffers estáticos, filas com tamanho fixo e estruturas previsíveis. Essa decisão melhora a confiabilidade e facilita a análise de memória.
A décima boa prática é criar uma camada de abstração para armazenamento. Em vez de espalhar chamadas diretas a FATFS, LittleFS ou SPIFFS por todo o firmware, crie uma API própria, por exemplo:
bool Storage_Init(void);
bool Storage_WriteLog(const char *message);
bool Storage_ReadConfig(char *buffer, size_t bufferSize);
bool Storage_WriteConfig(const char *buffer);
Com isso, a aplicação não precisa saber se os dados estão no SD Card, em LittleFS ou em SPIFFS. Esse tipo de organização também permite trocar a implementação no futuro sem reescrever todas as tarefas.
A arquitetura final fica mais limpa:
Aplicação
│
▼
Storage API
│
├── FATFS em SD Card
├── LittleFS em Flash externa
└── SPIFFS em Flash externa legada
Essa abordagem também se aproxima de uma boa prática comum em software embarcado: isolar detalhes de hardware e middleware atrás de interfaces bem definidas. Isso melhora portabilidade, teste e manutenção.
Em resumo, gravar arquivos em microcontroladores exige mais disciplina do que apenas chamar uma função de escrita. É preciso considerar mídia física, falha de energia, concorrência, desgaste, tamanho dos arquivos, formato dos dados, tratamento de erro e arquitetura do firmware. No STM32N6, temos desempenho suficiente para construir uma camada de armazenamento bem organizada; o desafio é não desperdiçar essa capacidade com uma arquitetura frágil.