Exemplo conceitual usando SPIFFS em Flash externa

O SPIFFS, ou SPI Flash File System, é um sistema de arquivos criado para memórias SPI NOR Flash em sistemas embarcados pequenos. A documentação oficial o descreve como um sistema de arquivos voltado a dispositivos SPI NOR Flash, com foco em alvos embarcados, pouca RAM, apagamento por blocos e suporte a wear leveling. (GitHub)
A ideia geral é parecida com LittleFS: o firmware fornece uma camada de acesso à Flash, e o sistema de arquivos organiza essa memória em arquivos. A diferença está no desenho interno. O SPIFFS é mais antigo, simples e bastante usado em bases legadas, mas não possui diretórios reais e, para projetos novos, costuma ser menos interessante que LittleFS quando a robustez contra falha de energia é prioridade.
No STM32N6, o SPIFFS faria sentido se o projeto usasse uma Flash externa conectada por SPI, QSPI ou OctoSPI, e se houvesse algum motivo específico para escolhê-lo: compatibilidade com firmware antigo, reaproveitamento de ferramentas, migração de projeto vindo de ESP8266/ESP32 antigo ou uma biblioteca já validada em produção.
Assim como no LittleFS, o SPIFFS precisa de funções de leitura, escrita e apagamento. A diferença é que a interface de portabilidade do SPIFFS normalmente usa uma estrutura spiffs_config, na qual informamos o endereço físico da área reservada, o tamanho total do sistema de arquivos, o tamanho dos blocos, o tamanho das páginas e as funções de acesso à Flash.
Um exemplo conceitual de configuração seria:
#include "spiffs.h"
#include <stdio.h>
#include <string.h>
/*
* Instância global do SPIFFS.
*/
static spiffs fs;
/*
* Buffers usados pelo SPIFFS.
* Os tamanhos devem ser ajustados conforme a configuração real.
*/
#define SPIFFS_WORK_BUFFER_SIZE (4096U)
#define SPIFFS_FD_BUFFER_SIZE (32U * 4U)
#define SPIFFS_CACHE_BUFFER_SIZE (4096U)
static uint8_t spiffs_work_buffer[SPIFFS_WORK_BUFFER_SIZE];
static uint8_t spiffs_fd_buffer[SPIFFS_FD_BUFFER_SIZE];
static uint8_t spiffs_cache_buffer[SPIFFS_CACHE_BUFFER_SIZE];
/*
* Região reservada na Flash externa.
* Estes valores são didáticos.
*/
#define SPIFFS_FLASH_BASE_ADDR 0x00000000UL
#define SPIFFS_FLASH_TOTAL_SIZE (4U * 1024U * 1024U)
#define SPIFFS_FLASH_BLOCK_SIZE 4096U
#define SPIFFS_FLASH_PAGE_SIZE 256U
No SPIFFS, é comum que o desenvolvedor configure uma região fixa da Flash para o sistema de arquivos. Em um produto real, essa região deve ser separada da área de bootloader, firmware principal, parâmetros críticos e qualquer outra partição usada pelo sistema.
Agora criamos as funções de acesso à Flash. O exemplo abaixo continua genérico, usando funções abstratas como ExternalFlash_Read, ExternalFlash_Program e ExternalFlash_EraseSector.
/*
* Função de leitura usada pelo SPIFFS.
*/
static s32_t stm32_spiffs_read(
u32_t address,
u32_t size,
u8_t *destination
)
{
if (ExternalFlash_Read(address, destination, size) != 0)
{
return SPIFFS_ERR_INTERNAL;
}
return SPIFFS_OK;
}
/*
* Função de escrita usada pelo SPIFFS.
*/
static s32_t stm32_spiffs_write(
u32_t address,
u32_t size,
u8_t *source
)
{
if (ExternalFlash_Program(address, source, size) != 0)
{
return SPIFFS_ERR_INTERNAL;
}
return SPIFFS_OK;
}
/*
* Função de apagamento usada pelo SPIFFS.
*/
static s32_t stm32_spiffs_erase(
u32_t address,
u32_t size
)
{
uint32_t currentAddress = address;
uint32_t endAddress = address + size;
while (currentAddress < endAddress)
{
if (ExternalFlash_EraseSector(currentAddress) != 0)
{
return SPIFFS_ERR_INTERNAL;
}
currentAddress += SPIFFS_FLASH_BLOCK_SIZE;
}
return SPIFFS_OK;
}
Aqui há um detalhe importante: em memórias Flash NOR, o apagamento acontece por setor ou bloco. Por isso, a função de erase percorre a região solicitada e apaga setor por setor. Em um driver real, também seria necessário aguardar o término das operações internas da memória e verificar erros de programação ou proteção de escrita.
A configuração e montagem do SPIFFS ficariam assim:
static int SPIFFS_Init(void)
{
spiffs_config config;
memset(&config, 0, sizeof(config));
config.phys_addr = SPIFFS_FLASH_BASE_ADDR;
config.phys_size = SPIFFS_FLASH_TOTAL_SIZE;
config.phys_erase_block = SPIFFS_FLASH_BLOCK_SIZE;
config.log_block_size = SPIFFS_FLASH_BLOCK_SIZE;
config.log_page_size = SPIFFS_FLASH_PAGE_SIZE;
config.hal_read_f = stm32_spiffs_read;
config.hal_write_f = stm32_spiffs_write;
config.hal_erase_f = stm32_spiffs_erase;
s32_t result = SPIFFS_mount(
&fs,
&config,
spiffs_work_buffer,
spiffs_fd_buffer,
sizeof(spiffs_fd_buffer),
spiffs_cache_buffer,
sizeof(spiffs_cache_buffer),
0
);
if (result != SPIFFS_OK)
{
/*
* Em desenvolvimento, pode-se formatar se a montagem falhar.
* Em produto final, isso deve ser feito com muito cuidado.
*/
SPIFFS_format(&fs);
result = SPIFFS_mount(
&fs,
&config,
spiffs_work_buffer,
spiffs_fd_buffer,
sizeof(spiffs_fd_buffer),
spiffs_cache_buffer,
sizeof(spiffs_cache_buffer),
0
);
}
return result;
}
A lógica é parecida com LittleFS: tenta montar; se falhar, pode formatar; depois tenta montar novamente. Porém, em firmware de produto, formatar automaticamente pode ser perigoso. O ideal é validar se o erro realmente indica sistema de arquivos inexistente ou corrompido, e não apenas uma falha temporária de leitura, alimentação ou inicialização da Flash.
Agora podemos criar um arquivo simples:
static int SPIFFS_WriteConfig(void)
{
const char *content =
"{\n"
" \"device\": \"STM32N6\",\n"
" \"storage\": \"SPIFFS\",\n"
" \"mode\": \"legacy_flash_fs\"\n"
"}\n";
spiffs_file file = SPIFFS_open(
&fs,
"config.json",
SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_RDWR,
0
);
if (file < 0)
{
return SPIFFS_errno(&fs);
}
s32_t written = SPIFFS_write(
&fs,
file,
(void *)content,
strlen(content)
);
if (written < 0)
{
int error = SPIFFS_errno(&fs);
SPIFFS_close(&fs, file);
return error;
}
SPIFFS_close(&fs, file);
return SPIFFS_OK;
}
E a leitura:
static int SPIFFS_ReadConfig(void)
{
char buffer[256];
spiffs_file file = SPIFFS_open(
&fs,
"config.json",
SPIFFS_RDONLY,
0
);
if (file < 0)
{
return SPIFFS_errno(&fs);
}
s32_t bytesRead = SPIFFS_read(
&fs,
file,
buffer,
sizeof(buffer) - 1
);
if (bytesRead < 0)
{
int error = SPIFFS_errno(&fs);
SPIFFS_close(&fs, file);
return error;
}
buffer[bytesRead] = '\0';
printf("Configuração lida do SPIFFS:\r\n%s\r\n", buffer);
SPIFFS_close(&fs, file);
return SPIFFS_OK;
}
O uso dentro de uma tarefa FreeRTOS seria direto:
#include "cmsis_os.h"
void SPIFFSTask(void *argument)
{
if (SPIFFS_Init() == SPIFFS_OK)
{
SPIFFS_WriteConfig();
SPIFFS_ReadConfig();
}
for (;;)
{
osDelay(1000);
}
}
Este exemplo mostra que SPIFFS pode ser integrado ao STM32N6 sem depender de um sistema operacional específico. O FreeRTOS apenas executa a tarefa. O sistema de arquivos em si depende principalmente da camada de acesso à Flash.
A principal limitação prática do SPIFFS, quando comparado ao LittleFS, é que ele não trabalha com diretórios reais. Um nome como "logs/system.txt" pode ser tratado apenas como uma string de nome de arquivo, dependendo da configuração e da camada superior. Já o LittleFS oferece uma abstração mais próxima de um sistema de arquivos moderno, com diretórios de fato. Além disso, LittleFS é explicitamente documentado com foco em resiliência contra falhas de energia, wear leveling dinâmico e uso limitado de RAM/ROM. (GitHub)
Para um projeto novo com STM32N6 e Flash externa, minha recomendação técnica continua sendo LittleFS. Eu reservaria SPIFFS para cenários de legado, reaproveitamento de código ou compatibilidade com ferramentas já existentes. Para SD Card, a recomendação permanece sendo FATFS ou FreeRTOS+FAT, porque o cartão SD se comporta como dispositivo de blocos e precisa de compatibilidade com computadores; o FatFs, por exemplo, documenta funções clássicas como f_open, f_read, f_write e f_close para manipulação de arquivos em volumes FAT. (Elm Chan)
Em resumo, a escolha pode ser organizada assim:
STM32N6 + SD Card
Use FATFS ou FreeRTOS+FAT.
STM32N6 + Flash externa em projeto novo
Prefira LittleFS.
STM32N6 + Flash externa em projeto legado
SPIFFS pode ser mantido, se já estiver validado.