IoT

LittleFS: Alto Desempenho para RP Pico, ESP32 e ESP8266

Eletrogate 12 de setembro de 2023

Introdução

Aprenda neste post como usar o sistema de arquivos LittleFS, que possui mais desempenho do que o sistema SPIFFS, nas placas Raspberry Pi Pico, ESP32 e ESP8266.

Será mostrado ainda neste post:

  • Diferença da SPIFFS com a LittleFS;
  • Teste de comparação de tempo de leitura de arquivos grandes na SPIFFS e na LittleFS;
  • Como fazer o upload de arquivos do computador para a LittleFS da placa ESP8266 utilizando o plugin de arquivos na IDE Arduino;
  • Como fazer o upload de arquivos do computador para a LittleFS da placa ESP32 utilizando o plugin de arquivos na IDE Arduino;
  • Como fazer o upload de arquivos do computador para a LittleFS da placa Raspberry Pi Pico (com core Arduino-Pico) utilizando o plugin de arquivos na IDE Arduino;
  • Principais métodos das bibliotecas LittleFS, Dir e File (bibliotecas necessárias para manipulação de arquivos na LittleFS);
  • Exemplos de uso da LittleFS nas placas ESP8266, ESP32 e Raspberry Pi Pico.

Materiais Necessários para o Projeto com LittleFS: Alto Desempenho para RP Pico, ESP32 e ESP8266

Neste post, será utilizado os seguintes materiais:


Sobre o sistema de arquivos LITTLEFS

LittleFS é um sistema de arquivos leve e otimizado, desenvolvido especificamente para dispositivos embarcados e de Internet das Coisas (IoT), com suporte a diretórios reais. Ele é projetado para ser eficiente em termos de espaço de armazenamento, consumo de energia e velocidade de acesso, tornando-o uma escolha popular para placas como ESP32, ESP8266 e Raspberry Pi Pico.

O LittleFS destaca-se por sua eficiência no uso de memória, sendo projetado para operar com quantidades reduzidas. Sua utilização de RAM é estritamente controlada, o que significa que o consumo não varia conforme o sistema de arquivos cresce. Além disso, o sistema de arquivos não permite recursão ilimitada e a alocação de memória dinâmica é limitada a buffers configuráveis que podem ser fornecidos de forma estática.

Além de sua eficiência no uso de memória, o LittleFS também foi projetado com foco em memórias flash, oferecendo nivelamento de desgaste em blocos dinâmicos. Além disso, ele possui a capacidade de detectar e contornar blocos defeituosos, garantindo maior confiabilidade e durabilidade no armazenamento de dados.

O sistema de arquivos LittleFS oferece suporte a nomes de arquivo de até 31 caracteres (incluindo seu caminho, caractere barra [‘/’], pontos de extensão de arquivo[‘.’]) + o caractere de terminação de string [‘\0’]), e tantos subdiretórios quanto o espaço permitir. Esse limite pode ser facilmente alcançado quando criamos nomes de arquivos longos ou temos subdiretórios muito aninhados (pasta em pasta). Se o limite for ultrapassado, o compilador não gerará nenhum erro, mas poderá ocorrer erros em tempo de execução, como não poder encontrar o arquivo buscado.

Caso o nome de arquivo não tenha o caractere "/", presume-se que os nomes de arquivo estejam no diretório raiz.

A abertura de arquivos em subdiretórios requer a especificação do caminho completo para o arquivo (ou seja, incluindo a barra inicial. Ex.: "/sub/dir/arquivo.txt"). Os subdiretórios são criados automaticamente quando se tenta criar um arquivo em um subdiretório e, quando o último arquivo em um subdiretório é removido, o próprio subdiretório é excluído automaticamente.


Diferença entre SPIFFS e LittleFS

O SPIFFS é o sistema de arquivos foi desenvolvido para aplicativos com restrição de espaço e RAM que utilizam muitos arquivos pequenos e se preocupam com o nivelamento de desgaste estático e dinâmico e não precisam de suporte de diretório real.

Mas LittleFS foi criado, e como se ele se concentra em maior desempenho e suporte a diretórios, o SPIFFS se tornou obsoleto para uso em novos projetos.

A seguir conta algumas das diferenças entre os sistemas de arquivos SPIFFS e LittleFS:

  • Suporte a diretórios
    • SPIFFS: ele apenas armazena uma lista “plana” de arquivos. Isso significa que todos os arquivos são armazenados no mesmo nível hierárquico, sem a capacidade de organizar ou agrupar os arquivos em subdiretórios. Os nomes dos arquivos no SPIFFS são tratados como strings completas, sem a estrutura de diretórios para separá-los logicamente.
    • LittlesFS: oferece suporte a diretórios reais, permitindo a criação de uma hierarquia de subdiretórios para organizar e estruturar os arquivos armazenados. Isso facilita a organização lógica e o gerenciamento de arquivos em projetos que exigem uma estrutura de diretórios mais complexa.
  • Desempenho na gravação de arquivos
    • SPIFFS: tem um desempenho de gravação de arquivos mais lento. Isso ocorre porque o SPIFFS não possui recursos avançados de otimização, o que o torna menos eficiente e mais lento ao gravar arquivos em comparação com o LittlesFS.
    • LittlesFS: é projetado para oferecer um desempenho aprimorado na gravação de arquivos em comparação com o SPIFFS. Ele implementa várias otimizações que ajudam a melhorar a velocidade e a eficiência das operações de gravação.
  • Confiabilidade
    • SPIFFS: apresenta algumas limitações. Por padrão, o SPIFFS não possui um mecanismo robusto de recuperação de falhas. Isso significa que, em caso de interrupção repentina de energia, podem ocorrer problemas como corrupção de dados ou perda de arquivos. O SPIFFS não possui uma verificação de integridade embutida para garantir a consistência dos dados após uma queda de energia. Portanto, é importante ter cuidado ao lidar com interrupções de energia ao usar o SPIFFS e tomar medidas adicionais para garantir a integridade dos dados.
    • LittlesFS: foi projetado com foco na confiabilidade e integridade dos dados, mesmo em casos de queda de energia. Ele incorpora recursos avançados de recuperação de falhas para minimizar a possibilidade de corrupção de dados. O LittlesFS utiliza uma funcionalidade que registra todas as operações de gravação antes de serem efetivamente aplicadas no sistema de arquivos, o que permite que seja restaurado o último estado anterior do sistema de arquivos em caso de falha de energia ou reinicialização inesperada. Com isso, o LittlesFS oferece maior confiabilidade e proteção contra perda de dados durante quedas de energia.
  • Nivelamento de desgaste
    • SPIFFS: foi projetado com o uso do sistema de arquivos na memória FLASH em mente, fornecendo nivelamento de desgaste ajudando a evitar o desgaste excessivo em áreas específicas da memória.
    • LittlesFS: assim como o SPIFFS, também foi projetado com o uso do sistema de arquivos na memória FLASH em mente. Ele fornece nivelamento de desgaste em blocos dinâmicos. Além disso, o LittlesFS pode detectar blocos defeituosos e contorná-los.
  • Alocação de arquivo
    • SPIFFS: o tamanho mínimo de alocação de arquivo é de 256 bytes. Isso significa que mesmo arquivos muito pequenos ocuparão pelo menos 256 bytes de espaço de armazenamento.
    • LittlesFS: possui um tamanho mínimo de alocação de arquivo maior de 4 kilobytes. Essa alocação mínima maior pode levar a um possível desperdício de espaço, especialmente ao trabalhar com muitos arquivos pequenos.

Nota

É importante observar que SPIFFS e LittleFS NÃO podem ser misturados e combinados em um mesmo projeto. Ambos são sistemas de arquivos concebidos para serem usados de forma exclusiva em diferentes aplicações e, portanto, não são compatíveis entre si.


Teste de Comparação de Tempo de Escrita/Leitura de Arquivos no SPIFFS e LittleFS - SpeedTest

Para realizar o teste de comparação de velocidade entre SPIFFS e LittleFS foi desenvolvido dois sketches. Um para testar a velocidade de escrita e leitura do sistema de arquivos SPIFFS e outro para o LittleFS.

Veja abaixo os sketches de teste dos sistemas de arquivos.

Sketch de teste do SPIFFS

Avisos:

  • O Sketch de teste apaga todos os dados existentes no sistema de arquivos antes de realizar os testes, portanto, é importante ter cuidado ao executar o código em um ambiente de produção ou em um dispositivo com dados importantes, pois tudo será apagado durante o processo de teste;
  • O Sketch de teste do sistema de arquivos SPIFFS é compatível somente com as placas ESP32 e ESP8266. O sketch NÃO é compatível com a placa Raspberry Pi Pico, pois esta placa não suporta mais SPIFFS.
/******************************************************************************
   Teste de comparação de tempo de leitura de arquivos no LittleFS e SPIFFS
                           Sketch de teste do SPIFFS

                       Criado em 10 de Julho de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/
      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

// AVISO: O sistema de arquivos será formatado no início do teste!

// Inclusão de Biblitecas
#include "FS.h"

// Inclui a biblioteca SPIFFS.h somente se a placa atual for a ESP32
#ifdef ESP32
#include "SPIFFS.h"
#endif

#define TESTSIZEKB 256  // O tamanho de um arquivo para testar (em kilobytes)

// Formata a velocidade em bytes por segundo
String rate(unsigned long start, unsigned long stop, unsigned long bytes) {
  String mensagemDeRetorno;         // Declaração da variável para armazenar a mensagem de retorno
  if (stop == start) {              // Se a contagem inicial e final forem iguais, ...
    mensagemDeRetorno = "Inf b/s";  // define "Inf b/s" na mensagem de retorno indicando que a velocidade é infinita, ou seja, não há limite ou restrição na taxa de transferência de dados

  } else {                                           // Senão, realiza o cálculo da taxa de velocidade em bytes/segundo
    unsigned long delta = stop - start;              // Calcula a diferença entre a contagem final e inicial
    float r = 1000.0 * (float)bytes / (float)delta;  // Calcula a taxa de velocidade em bytes/segundo
    if (r >= 1000000.0) {
      // Se a taxa for maior ou igual a 1.000.000,0, formata a velocidade em MB/s
      mensagemDeRetorno = String(r / 1000000.0, 2) + " MB/s";
    } else if (r >= 1000.0) {
      mensagemDeRetorno = String(r / 1000.0, 2) + " KB/s";  // Se a taxa for maior ou igual a 1000,0, formata a velocidade em KB/s
    } else {
      mensagemDeRetorno = String((int)r) + " bytes/s";  // Caso contrário, formata a velocidade em bytes/s
    }
  }
  return mensagemDeRetorno;  // Retorna o buffer contendo a taxa de velocidade formatada
}

void realizarSpeedTest() {

  // Início do teste de velocidade de escrita/leitura com sistema de arquivos
  Serial.println("Começando Teste de Velocidade de escrita/leitura com SPIFFS");

  // Mensagem indicando o processo de iniciar o sistema de arquivos
  Serial.println("Iniciando sistema de arquivos...");


#ifdef ESP8266                                              // Verifica se o código está sendo compilado para um dispositivo ESP8266 ou não
  if (!SPIFFS.begin()) {                                    // Inicia o sistema de arquivos SPIFFS
    Serial.println("Não foi possível iniciar, abortando");  // Caso a inicialização falhe, é mostrada uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }
#else   // se o código estiver sendo compilado para qualquer dispositivo que não seja ESP8266, como o ESP32
  if (!SPIFFS.begin(true)) {                                // Inicia o sistema de arquivos SPIFFS, formatando-o se algum erro ocorrer
    Serial.println("Não foi possível iniciar, abortando");  // Caso a inicialização falhe, é mostrada uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }
#endif  // marca o final do bloco condicional #ifdef e indica o término da verificação condicional


  // Mensagem indicando o processo de formatação do sistema de arquivos
  Serial.println("Fazendo formatação...");

  if (!SPIFFS.format()) {                                    // Formata o sistema de arquivos
    Serial.println("Não foi possível formatar, abortando");  // caso não seja possível formatar o sistema de arquivos, mostra uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }

  // Declara um array de 256 posições, sendo 1 byte para cada posição
  uint8_t data[256];

  // Preenche o array de dados com valores de 0 a 255
  for (int i = 0; i < 256; i++) {
    data[i] = (uint8_t)i;
  }

  // Imprime uma linha em branco
  Serial.println();

  // Imprime uma mensagem indicando que o arquivo será escrito no sistema de arquivos
  Serial.print("Escrevendo arquivo com tamanho de ");
  Serial.print(TESTSIZEKB);  // imprime o tamanho do arquivo em kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte o tamanho do arquivo de kilobytes para bytes (a cada 1024 bytes, se tem 1 kilobyte)
  Serial.println(" bytes) ...(pode demorar um pouco) ");

  // Inicia a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de escrita
  unsigned long start = millis();

  // Cria o arquivo para gravação, passando parâmetros, sendo o primeiro o nome do arquivo com sua extensão e o segundo sendo o modo de abertura do arquivo (w: modo de escrita)
  File f = SPIFFS.open("/arquivoDeTeste.bin", "w");

  // Se o arquivo não puder ser aberto, imprime uma mensagem de erro e aborta o teste
  if (!f) {
    Serial.println("Não é possível abrir o arquivo para gravação, abortando");
    return;
  }

  // Escreve os dados no arquivo
  for (int i = 0; i < TESTSIZEKB; i++) {  // laço de repetição 'for' que será executado 256 vezes
    for (int j = 0; j < 4; j++) {         // laço de repetição 'for' que será executado 4 vezes
      f.write(data, 256);                 // escreve no arquivo o conteúdo do array 'data' que possui 256 bytes

      // RESULTADO: ao escrever no arquivo 256 bytes por 4 vezes em que estas 4 vezes são executadas 256 vezes, é escrito ao total 262144 bytes (256 kilobytes):
      // 256 bytes * 4 vezes * 256 vezes =
      // 256 * 4 * 256 =
      // 262144 bytes = 256 kilobytes (262144 /256 = 256 kilobytes)
    }
  }

  // Fecha o arquivo
  f.close();

  // Interrompe a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de escrita
  unsigned long stop = millis();

  // Imprime uma mensagem indicando o tempo gasto para escrever o arquivo
  Serial.print("==> Tempo para gravar ");
  Serial.print(TESTSIZEKB);  // 256 kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte kilobytes em bytes (cada kilobyte tem 1024 bytes)
  Serial.print(" bytes) = ");
  Serial.print(stop - start);  // subtrai o tempo de início do tempo de parada, calculando assim o tempo gasto
  Serial.println(" milissegundos");

  // Imprime uma mensagem indicando a velocidade de escrita
  Serial.print("==> Velocidade de escrita: ");
  Serial.println(rate(start, stop, TESTSIZEKB * 1024));  // A velocidade de escrita é impressa no monitor serial, onde é calculada usando a função rate, em que é passado como parâmetros:
  //                                                          - o tempo de parada (stop),
  //                                                          - o tempo de início (start) e
  //                                                          - a quantidade de dados transitado em bytes.
  //                                                        O tamanho do arquivo é multiplicado por 1024 para obter o tamanho em bytes.
  Serial.println();

  // Imprime uma mensagem indicando que o arquivo será lido sequencialmente
  Serial.print("Lendo arquivo sequencialmente com tamanho de ");
  Serial.print(TESTSIZEKB);  // imprime o tamanho do arquivo em kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte o tamanho do arquivo de kilobytes para bytes (a cada 1024 bytes, se tem 1 kilobyte)
  Serial.println(" bytes) ...");

  // Inicia a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de leitura
  start = millis();

  // Abre o arquivo para leitura, passando parâmetros, sendo o primeiro o nome do arquivo com sua extensão e o segundo sendo o modo de abertura do arquivo (r: modo de leitura)
  f = SPIFFS.open("/arquivoDeTeste.bin", "r");

  // Se o arquivo não puder ser aberto, imprime uma mensagem de erro e aborta o teste
  if (!f) {
    Serial.println("Não é possível abrir o arquivo para leitura, abortando");
    return;
  }

  // Lê o arquivo sequencialmente
  for (int i = 0; i < TESTSIZEKB; i++) {  // laço de repetição 'for' que será executado 256 vezes
    for (int j = 0; j < 4; j++) {         // laço de repetição 'for' que será executado 4 vezes
      f.read(data, 256);                  // lê 256 bytes do conteúdo do arquivo e passa para o array 'data'
    }

    // RESULTADO: ao ler o arquivo com cada leitura tendo 256 bytes, e isto ocorrendo por 4 vezes em que estas 4 vezes são executadas 256 vezes, é lido ao total 262144 bytes (256 kilobytes):
    // 256 bytes * 4 vezes * 256 vezes =
    // 256 * 4 * 256 =
    // 262144 bytes = 256 kilobytes (262144 /256 = 256 kilobytes)
  }

  // Fecha o arquivo
  f.close();

  // Interrompe a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de leitura
  stop = millis();

  // Imprime uma mensagem indicando o tempo gasto para ler o arquivo sequencialmente
  Serial.print("==> Tempo para ler sequencialmente ");
  Serial.print(TESTSIZEKB);  // 256 kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte kilobytes em bytes (cada kilobyte tem 1024 bytes)
  Serial.print(" bytes) = ");
  Serial.print(stop - start);  // subtrai o tempo de início do tempo de parada, calculando assim o tempo gasto
  Serial.println(" milissegundos");

  // Imprime uma mensagem indicando a velocidade de leitura
  Serial.print("==> Velocidade de leitura: ");
  Serial.println(rate(start, stop, TESTSIZEKB * 1024));  // A velocidade de leitura é impressa no monitor serial, onde é calculada usando a função rate, em que é passado como parâmetros:
  //                                                          - o tempo de parada (stop),
  //                                                          - o tempo de início (start) e
  //                                                          - a quantidade de dados transitado em bytes.
  //                                                        O tamanho do arquivo é multiplicado por 1024 para obter o tamanho em bytes.
  Serial.println();

  // Imprime uma mensagem indicando o fim do teste
  Serial.println("Fim do Teste de Velocidade de escrita/leitura");
}

void setup() {
  // Inicializa a porta serial com 115200 bits por segundo de baud rate
  Serial.begin(115200);

  // Aguarda até que o monitor serial esteja pronto para receber dados, em que o usuário deve digitar e enviar qualquer dado pelo monitor serial
  while (Serial.available() == false) {}

  // Realiza o teste de velocidade de leitura e escrita no sistema de arquivos
  realizarSpeedTest();

  // Entra em um loop infinito
  while (1) {
    delay(10);
  }
}

void loop() {}

Explicação do Sketch

A primeira parte do código inclui as bibliotecas necessárias para trabalhar com sistemas de arquivos e configura uma constante TESTSIZEKB que define o tamanho do arquivo de teste em kilobytes.

A função rate() é criada para calcular e formatar a taxa de velocidade em bytes por segundo com base nos parâmetros fornecidos: tempo inicial, tempo final e quantidade de bytes transferidos. Ela retorna uma String formatada com a velocidade em Bytes/s, Kilobytes/s ou Megabytes/s.

A função realizarSpeedTest() é o núcleo do teste de velocidade. Ela inicia o sistema de arquivos SPIFFS e o formata. Em seguida, marca o tempo de ínicio (na variável start) na variável cria um arquivo chamado "arquivoDeTeste.bin" e grava 256KB de dados nele, divididos em quatro gravações de 256 bytes cada em que este ciclo de quatro gravações é executado 256 vezes. Veja abaixo o cálculo de quanto dado foi escrito:

  • 256 bytes × 4 vezes × 256 vezes =
  • 262144 bytes =
  • 256 kilobytes (262144 /256 = 256 kilobytes).

Em seguida, é marcado o tempo de parada (na variável stop) e  a velocidade de escrita é calculada (utilizando a função rate(), passando como parâmetro as vaiáveis start , stop e a quantidade de bytes transferidos) e é exibida no monitor serial.

Após a gravação, o código lê os 256KB de dados do arquivo em quatro gravações de 256 bytes cada em que este ciclo de quatro gravações é executado 256 vezes. Veja abaixo o cálculo de quanto dado foi lido pelo programa:

  • 256 bytes × 4 vezes × 256 vezes =
  • 262144 bytes =
  • 256 kilobytes (262144 /256 = 256 kilobytes).

O tempo gasto na leitura é medido, e a velocidade de leitura é calculada e exibida.

A função setup() configura a porta serial com uma taxa de transmissão de 115200 bps e aguarda até que o monitor serial esteja pronto para receber dados (ou seja, aguarda até que o usuário envie qualquer dado pelo monitor serial). Em seguida, chama a função realizarSpeedTest() para executar o teste de velocidade. Por fim, ainda dentro da função setup(), é executado um loop infinito através da estrutura de iteração while passando como parâmetro um valor booleano verdadeiro.

A função loop() é vazia, não tendo nenhum código dentro dela.

Sketch de teste do LittleFS

/******************************************************************************
   Teste de comparação de tempo de leitura de arquivos no LittleFS e SPIFFS
                        Sketch de teste do LittleFS

                       Criado em 10 de Julho de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/
      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

// AVISO: O sistema de arquivos será formatado no início do teste!

// Inclusão de Biblitecas
#include <FS.h>
#include <LittleFS.h>

#define TESTSIZEKB 256  // O tamanho de um arquivo para testar (em kilobytes)

// Formata a velocidade em bytes por segundo
String rate(unsigned long start, unsigned long stop, unsigned long bytes) {
  String mensagemDeRetorno;         // Declaração da variável para armazenar a mensagem de retorno
  if (stop == start) {              // Se a contagem inicial e final forem iguais, ...
    mensagemDeRetorno = "Inf b/s";  // define "Inf b/s" na mensagem de retorno indicando que a velocidade é infinita, ou seja, não há limite ou restrição na taxa de transferência de dados

  } else {                                           // Senão, realiza o cálculo da taxa de velocidade em bytes/segundo
    unsigned long delta = stop - start;              // Calcula a diferença entre a contagem final e inicial
    float r = 1000.0 * (float)bytes / (float)delta;  // Calcula a taxa de velocidade em bytes/segundo
    if (r >= 1000000.0) {
      // Se a taxa for maior ou igual a 1.000.000.0, formata a velocidade em MB/s
      mensagemDeRetorno = String(r / 1000000.0, 2) + " MB/s";
    } else if (r >= 1000.0) {
      mensagemDeRetorno = String(r / 1000.0, 2) + " KB/s";  // Se a taxa for maior ou igual a 1000.0, formata a velocidade em KB/s
    } else {
      mensagemDeRetorno = String((int)r) + " bytes/s";  // Caso contrário, formata a velocidade em bytes/s
    }
  }
  return mensagemDeRetorno;  // Retorna o buffer contendo a taxa de velocidade formatada
}


void realizarSpeedTest() {
  // Início do teste de velocidade de escrita/leitura com LittleFS
  Serial.println("Começando Teste de Velocidade de escrita/leitura com LittleFS");

  // Mensagem indicando o processo de iniciar o sistema de arquivos LittleFS
  Serial.println("Iniciando LittleFS...");

#ifdef ESP32 || ESP8266                                     // Verifica se o código está sendo compilado para um dispositivo ESP8266 ou ESP32
  if (!LittleFS.begin(true)) {                              // Inicia o sistema de arquivos LittleFS, formatando-o se algum erro ocorrer
    Serial.println("Não foi possível iniciar, abortando");  // Caso a inicialização falhe, é mostrada uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }
#else   // se o código estiver sendo compilado para qualquer dispositivo que não seja ESP8266 ou ESP32, como a placa Raspberry Pi Pico
  if (!LittleFS.begin()) {                                  // Inicia o sistema de arquivos LittleFS
    Serial.println("Não foi possível iniciar, abortando");  // Caso a inicialização falhe, é mostrada uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }
#endif  // marca o final do bloco condicional #ifdef e indica o término da verificação condicional

  // Mensagem indicando o processo de formatação do sistema de arquivos LittleFS
  Serial.println("Fazendo formatação...");

  if (!LittleFS.format()) {                                  // Formata o sistema de arquivos LittleFS
    Serial.println("Não foi possível formatar, abortando");  // caso não seja possível formatar o sistema de arquivos, mostra uma mensagem de erro no monitor serial e o programa é encerrado
    return;
  }

  // Declara um array de 256 posições, sendo 1 byte para cada posição
  uint8_t data[256];

  // Preenche o array de dados com valores de 0 a 255
  for (int i = 0; i < 256; i++) {
    data[i] = (uint8_t)i;
  }

  // Imprime uma linha em branco
  Serial.println();

  // Imprime uma mensagem indicando que o arquivo será escrito no sistema de arquivos
  Serial.print("Escrevendo arquivo com tamanho de ");
  Serial.print(TESTSIZEKB);  // imprime o tamanho do arquivo em kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte o tamanho do arquivo de kilobytes para bytes (a cada 1024 bytes, se tem 1 kilobyte)
  Serial.println(" bytes) ...(pode demorar um pouco) ");

  // Inicia a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de escrita
  unsigned long start = millis();

  // Cria o arquivo para gravação, passando parâmetros, sendo o primeiro o nome do arquivo com sua extensão e o segundo sendo o modo de abertura do arquivo (w: modo de escrita)
  File f = LittleFS.open("/arquivoDeTeste.bin", "w");

  // Se o arquivo não puder ser aberto, imprime uma mensagem de erro e aborta o teste
  if (!f) {
    Serial.println("Não é possível abrir o arquivo para gravação, abortando");
    return;
  }

  // Escreve os dados no arquivo
  for (int i = 0; i < TESTSIZEKB; i++) {  // laço de repetição 'for' que será executado 256 vezes
    for (int j = 0; j < 4; j++) {         // laço de repetição 'for' que será executado 4 vezes
      f.write(data, 256);                 // escreve no arquivo o conteúdo do array 'data' que possui 256 bytes

      // RESULTADO: ao escrever no arquivo 256 bytes por 4 vezes em que estas 4 vezes são executadas 256 vezes, é escrito ao total 262144 bytes (256 kilobytes):
      // 256 bytes * 4 vezes * 256 vezes =
      // 256 * 4 * 256 =
      // 262144 bytes = 256 kilobytes (262144 /256 = 256 kilobytes)
    }
  }

  // Fecha o arquivo
  f.close();

  // Interrompe a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de escrita
  unsigned long stop = millis();

  // Imprime uma mensagem indicando o tempo gasto para escrever o arquivo
  Serial.print("==> Tempo para gravar ");
  Serial.print(TESTSIZEKB);  // 256 kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte kilobytes em bytes (cada kilobyte tem 1024 bytes)
  Serial.print(" bytes) = ");
  Serial.print(stop - start);  // subtrai o tempo de início do tempo de parada, calculando assim o tempo gasto
  Serial.println(" milissegundos");

  // Imprime uma mensagem indicando a velocidade de escrita
  Serial.print("==> Velocidade de escrita: ");
  Serial.println(rate(start, stop, TESTSIZEKB * 1024));  // A velocidade de escrita é impressa no monitor serial, onde é calculada usando a função rate, em que é passado como parâmetros:
  //                                                          - o tempo de parada (stop),
  //                                                          - o tempo de início (start) e
  //                                                          - a quantidade de dados transitado em bytes.
  //                                                        O tamanho do arquivo é multiplicado por 1024 para obter o tamanho em bytes.
  Serial.println();

  // Imprime uma mensagem indicando que o arquivo será lido sequencialmente
  Serial.print("Lendo arquivo sequencialmente com tamanho de ");
  Serial.print(TESTSIZEKB);  // imprime o tamanho do arquivo em kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte o tamanho do arquivo de kilobytes para bytes (a cada 1024 bytes, se tem 1 kilobyte)
  Serial.println(" bytes) ...");

  // Inicia a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de leitura
  start = millis();

  // Abre o arquivo para leitura, passando parâmetros, sendo o primeiro o nome do arquivo com sua extensão e o segundo sendo o modo de abertura do arquivo (r: modo de leitura)
  f = LittleFS.open("/arquivoDeTeste.bin", "r");

  // Se o arquivo não puder ser aberto, imprime uma mensagem de erro e aborta o teste
  if (!f) {
    Serial.println("Não é possível abrir o arquivo para leitura, abortando");
    return;
  }

  // Lê o arquivo sequencialmente
  for (int i = 0; i < TESTSIZEKB; i++) {  // laço de repetição 'for' que será executado 256 vezes
    for (int j = 0; j < 4; j++) {         // laço de repetição 'for' que será executado 4 vezes
      f.read(data, 256);                  // lê 256 bytes do conteúdo do arquivo e passa para o array 'data'
    }

    // RESULTADO: ao ler o arquivo com cada leitura tendo 256 bytes, e isto ocorrendo por 4 vezes em que estas 4 vezes são executadas 256 vezes, é lido ao total 262144 bytes (256 kilobytes):
    // 256 bytes * 4 vezes * 256 vezes =
    // 256 * 4 * 256 =
    // 262144 bytes = 256 kilobytes (262144 /256 = 256 kilobytes)
  }

  // Fecha o arquivo
  f.close();

  // Interrompe a contagem de tempo e registra o tempo atual, para posterior cálculo de velocidade de leitura
  stop = millis();

  // Imprime uma mensagem indicando o tempo gasto para ler o arquivo sequencialmente
  Serial.print("==> Tempo para ler sequencialmente ");
  Serial.print(TESTSIZEKB);  // 256 kilobytes
  Serial.print(" kilobytes (");
  Serial.print(TESTSIZEKB * 1024);  // converte kilobytes em bytes (cada kilobyte tem 1024 bytes)
  Serial.print(" bytes) = ");
  Serial.print(stop - start);  // subtrai o tempo de início do tempo de parada, calculando assim o tempo gasto
  Serial.println(" milissegundos");

  // Imprime uma mensagem indicando a velocidade de leitura
  Serial.print("==> Velocidade de leitura: ");
  Serial.println(rate(start, stop, TESTSIZEKB * 1024));  // A velocidade de leitura é impressa no monitor serial, onde é calculada usando a função rate, em que é passado como parâmetros:
  //                                                          - o tempo de parada (stop),
  //                                                          - o tempo de início (start) e
  //                                                          - a quantidade de dados transitado em bytes.
  //                                                        O tamanho do arquivo é multiplicado por 1024 para obter o tamanho em bytes.
  Serial.println();

  // Imprime uma mensagem indicando o fim do teste
  Serial.println("Fim do Teste de Velocidade de escrita/leitura");
}

void setup() {
  // Inicializa a porta serial com 115200 bits por segundo de baud rate
  Serial.begin(115200);

  // Aguarda até que o monitor serial esteja pronto para receber dados, em que o usuário deve digitar e enviar qualquer dado pelo monitor serial
  while (Serial.available() == false) {}

  // Realiza o teste de velocidade de leitura e escrita no sistema de arquivos
  realizarSpeedTest();

  // Entra em um loop infinito
  while (1) {
    delay(10);
  }
}

void loop() {}

Explicação do Sketch

O código utiliza as bibliotecas <FS.h> e <LittleFS.h> para trabalhar com os sistemas de arquivos. A constante TESTSIZEKB define o tamanho do arquivo de teste em 256 kilobytes.

A função rate() é criada para calcular e formatar a taxa de velocidade em bytes por segundo com base nos parâmetros fornecidos: tempo inicial, tempo final e quantidade de bytes transferidos. Ela retorna uma mensagem formatada com a velocidade em bytes/s, KB/s ou MB/s.

A função realizarSpeedTest() é o núcleo do teste de velocidade. Ela inicia o sistema de arquivos LittleFS e formata-o. Em seguida, cria um arquivo chamado "arquivoDeTeste.bin" e grava 256KB de dados nele, divididos em quatro gravações de 256 bytes cada em que este ciclo de quatro gravações é executado 256 vezes. Veja abaixo o cálculo de quanto dado foi gravado pelo programa:

  • 256 bytes × 4 vezes × 256 vezes =
  • 262144 bytes =
  • 256 kilobytes (262144 /256 = 256 kilobytes).

O tempo gasto para a gravação é medido e a velocidade de escrita é exibida no monitor serial.

Após a gravação, o código lê os 256KB de dados do arquivo em quatro leituras de 256 bytes cada, em que este ciclo de quatro gravações é executado 256 vezes. Veja abaixo o cálculo de quanto dado foi gravado pelo programa:

  • 256 bytes × 4 vezes × 256 vezes =
  • 262144 bytes =
  • 256 kilobytes (262144 /256 = 256 kilobytes).

O tempo gasto na leitura é medido, e a velocidade de leitura é exibida. Tanto para a gravação quanto para a leitura, a função rate() é usada para calcular e formatar a velocidade de transferência.

A função setup() configura a porta serial com uma taxa de transmissão de 115200 bps e aguarda até que o monitor serial esteja pronto para receber dados (ou seja, aguarda até que o usuário envie qualquer dado pelo monitor serial). Em seguida, chama a função realizarSpeedTest() para executar o teste de velocidade. Por fim, ainda dentro da função setup(), é executado um loop infinito através da estrutura de iteração while passando como parâmetro um valor booleano verdadeiro.

A função loop() é vazia, não tendo nenhum código dentro dela.

Configurando as frequências de CPU e Flash

Para exemplificar como alterar as frequências de CPU e Flash, será utilizada a placa ESP8266, que no nosso caso, o modelo é LOLIN WEMOS D1 mini. Como a placa LOLIN WEMOS D1 mini não possui, na IDE Arduino, uma opção de alterar a frequência da memória Flash, será selecionada, na Arduino IDE, a placa Generic ESP8266 Module.

Siga os passos abaixo para configurar a frequência da memória Flash da placa ESP8266:

  1. Na Arduino IDE, navegue até o menu Ferramentas;
  2. Vá até a opção Flash Frequency;
  3. Selecione a frequência da memória Flash desejada.

Siga os passos abaixo para configurar a frequência da CPU da placa ESP8266:

  1. Na Arduino IDE, navegue até o menu Ferramentas;
  2. Vá até a opção CPU Frequency;
  3. Selecione a frequência da CPU desejada.

Demonstração de um Teste

Dentre os 10 testes à serem realizados, abaixo está a demonstração de como foi obtido o resultado do teste na placa ESP8266 com frequência de Flash (overclock) de 80 MHz e frequência de CPU (overclock) de 160 MHz e sistema de arquivos SPIFFS.

 

Resultado dos Testes

Abaixo, na tabela, estão os resultados dos testes, em que foram utilizadas as placas Raspberry Pi Pico, ESP8266 e Esp32. Cada placa foi feita uma configuração de frequência tanto de CPU quanto de memória Flash, o que afeta a velocidade escrita/leitura nos sistemas de arquivos. Estas configurações foram alteradas no menu Ferramentas da Arduino IDE. Observe que um mesmo teste, se executado duas vezes, pode ter uma pequena variação em seu resultado. Veja:

Placas e Configurações SPIFFS LittleFS
Leitura Escrita Leitura Escrita
Raspberry Pi Pico (Frequência de CPU padrão de 133 MHz) * * 5.96 MB/s 89.93 KB/s
Raspberry Pi Pico (Frequência de CPU overclock de 250 MHz) * * 11.40 MB/s 100.17 KB/s
ESP8266 (Frequência de Flash padrão de 40 MHz + Frequência de CPU padrão de 80 MHz) 1.52 MB/s 39.33 KB/s 2.36 MB/s 51.98 KB/s
ESP8266 (Frequência de Flash padrão de 40 MHz + Frequência de CPU overclock de 160 MHz) 2.30 MB/s 42.83 KB/s 3.32 MB/s 54.32 KB/s
ESP8266 (Frequência de Flash overclock de 80 MHz + Frequência de CPU padrão de 80 MHz) 1.82 MB/s 55.65 KB/s 2.88 MB/s 62.08 KB/s
ESP8266 (Frequência de Flash overclock de 80 MHz + Frequência de CPU overclock de 160 MHz) 3.05 MB/s 63.61 KB/s 4.52 MB/s 65.90 KB/s
ESP32 (Frequência de Flash padrão de 80 MHz + Frequência de CPU padrão de 240 MHz) 1.31 MB/s 158.88 KB/s 2.38 MB/s 88.65 KB/s
ESP32 (Frequência de Flash underclock de 40 MHz + Frequência de CPU padrão de 240 MHz) 1.32 MB/s 158.78 KB/s 2.36 MB/s 79.32 KB/s
ESP32 (Frequência de Flash padrão de 80 MHz + Frequência de CPU underclock de 80 MHz) 463.15 KB/s 70.00 KB/s 848.36 KB/s 57.86 KB/s
ESP32 (Frequência de Flash underclock de 40 MHz + Frequência de CPU underclock de 80 MHz) 460.71 KB/s 69.06 KB/s 851.12 KB/s 58.01 KB/s

*Como a placa Raspberry Pi Pico não suporta o sistema de arquivo SPIFFS, não foi possível realizar os teste no SPIFFS desta placa.

Análise dos Resultados

Os dados da tabela acima foram convertidos (os que necessitavam) para Kilobytes por segundo e, então, foi criado os gráficos abaixo para análise visual dos dados. Para melhor visualização dos gráficos, clique em algum deles para abri-lo expandido em uma nova guia.

Veja no gráfico abaixo a análise da velocidade de escrita do sistema de arquivos SPIFFS em relação ao sistema de arquivos LittleFS.

Veja no gráfico abaixo a análise da velocidade de leitura do sistema de arquivos SPIFFS em relação ao sistema de arquivos LittleFS.

Veja no gráfico abaixo a análise da velocidade de escrita e de leitura do sistema de arquivos SPIFFS em relação ao sistema de arquivos LittleFS.


Upload de Arquivos do Computador para a LittleFS da Placa ESP8266

Processo de instalação do plugin

Para transferir arquivos do computador para o sistema de arquivos LittleFS em uma placa ESP8266 é necessário instalar um plugin na Arduino IDE que permita realizar upload de arquivos para o sistema de arquivos LittleFS da placa ESP8266. Para isso, siga o passo a passo abaixo:

  1. Visite o site https://github.com/earlephilhower/arduino-esp8266littlefs-plugin/releases:
  2. Faça o download do arquivo .ZIP do plugin:
  3. Com o arquivo .ZIP baixado, extraia-o (necessário ter uma ferramenta de extração de arquivos compactados, como o WinRAR) para alguma pasta (como a de downloads):
  4. Abra o diretório do Sketchbook da Arduino IDE 1.x (a Arduino IDE 2.x ainda não é compatível com plugins).
    1. Para descobrir onde fica o diretório do Sketchbook da Arduino IDE, abra as configurações da IDE acessando o atalho Ctrl + , (vírgula):
    2. No campo Local do Sketchbook está o local do diretório do Sketchbook:
  5. No diretório do Sketchbook da Arduino IDE 1.x, crie um diretório chamado tools se ele ainda não existir:
  6. Mova a pasta que foi extraída no passo 3 para dentro da pasta tools:
  7. Reinicie a Arduino IDE 1.x.

Com os passos acima realizados, o plugin está pronto para ser utilizado.

Processo de uso do plugin

Siga os passos abaixo para realizar o upload de arquivos para a memória Flash do ESP8266, utilizando o sistema de arquivos LittleFS:

  1. Vá para o diretório do sketch atual (navegue no menu da IDE: Sketch → Mostrar a página do Sketch [Ctrl + K]):
  2. Crie um diretório chamado data:
  3. Dentro do diretório data, coloque os arquivos e pastas que serão transferidos para o sistema de arquivos LittleFS da placa ESP8266:Neste caso, foi criado um arquivo de texto chamado teste com o seguinte conteúdo:
    abc
    123
  4. Certifique-se de ter selecionado a placa ESP8266 correta e uma porta serial correta para a placa conectada. Antes de continuar com a transferência de arquivos, feche o Monitor Serial da Arduino IDE.
  5. Navegue no menu da Arduino IDE Ferramentas → ESP8266 LittleFS Data Upload. Isso deve iniciar o upload dos arquivos na memória Flash do ESP8266 com o sistema de arquivos LittleFS. Quando terminar, a barra de status do IDE exibirá a mensagem LittleFS Image Uploaded.

Lendo o conteúdo do arquivo que foi transferido para o LittleFS

Para testar se o arquivo está no sistema de arquivos da placa LittleFS, carregue o seguinte sketch na placa em que foi transferido o arquivo:

/******************************************************************************
      Lendo o conteúdo do arquivo que foi transferido para o LittleFS

                       Criado em 24 de Julho de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/
      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

#include <FS.h> // Inclui a biblioteca para trabalhar com sistemas de arquivos no Arduino.
#include <LittleFS.h> // Inclui a biblioteca específica para usar o sistema de arquivos LittleFS.

void setup() {
  Serial.begin(115200); // Inicializa a comunicação serial com uma taxa de baud rate de 115200.

  if (!LittleFS.begin()) {  // Inicializa o sistema de arquivos LittleFS. Se falhar, exibe uma mensagem no monitor serial e retorna.
    Serial.println("Falha na montagem do sistema de arquivos LittleFS");
    return;
  }

  Serial.println("Lendo arquivo /teste.txt"); // Exibe no monitor serial a mensagem indicando que o arquivo /teste.txt será lido.

  File file = LittleFS.open("/teste.txt", "r"); // Abre o arquivo /teste.txt em modo de leitura ("r") e armazena o objeto de arquivo em 'file'.
  if (!file) {  // Verifica se o arquivo foi aberto com sucesso. Se não, exibe uma mensagem de erro e retorna.
    Serial.println("Falha ao abrir arquivo para leitura");
    return;
  }

  Serial.println("Conteúdo do arquivo: ");  // Exibe no monitor serial a mensagem indicando que o conteúdo do arquivo será mostrado.

  while (file.available()) {  // Enquanto houver dados disponíveis para leitura no arquivo...
    Serial.write(file.read());  // Lê um byte do arquivo e o envia para o monitor serial para impressão.
  }
  file.close(); // Fecha o arquivo após a leitura ter sido concluída.
}

void loop() {}// Vazio, pois não precisamos de um loop contínuo para este sketch.
// O código é executado apenas uma vez na função 'setup()'.

Explicação do Sketch

Primeiro é incluída as bibliotecas para funcionamento do programa: #include <FS.h> e #include <LittleFS.h>. Essas bibliotecas permitem ao programa trabalhar com o sistema de arquivos LittleFS.

A função setup() é executada uma vez quando o ESP8266 é ligado ou reiniciado. Nela, a comunicação serial é iniciada com uma taxa de baud rate de 115200, permitindo que a saída seja mostrada no monitor serial. Em seguida, o programa tenta inicializar o sistema de arquivos LittleFS com a função LittleFS.begin(). Se a inicialização falhar, uma mensagem relatando uma falha na montagem do sistema de arquivos LittleFS é enviada para o monitor serial, e o programa retorna, encerrando a execução.

Após a inicialização bem-sucedida do LittleFS, o programa tenta abrir o arquivo "teste.txt" em modo de leitura ("r") usando a função LittleFS.open(). Se o arquivo não puder ser aberto, por exemplo, se ele não existir, o programa imprime a mensagem "Falha ao abrir arquivo para leitura" no monitor serial e retorna, encerrando a execução.

Caso o arquivo "teste.txt" seja aberto com sucesso, o programa entra em um loop while para ler e imprimir o conteúdo do arquivo. A função file.available() é usada para verificar se ainda há dados para ler no arquivo. Dentro do loop, a função file.read() lê um byte por vez do arquivo, e a função Serial.write() é usada para imprimir esse byte no monitor serial. O loop continua até que todo o conteúdo do arquivo tenha sido lido. Após a leitura ter sido concluída, o arquivo é fechado usando a função file.close().

O loop void loop() {} está vazio, o que significa que após a execução da função setup(), o programa não executa mais nada e fica em um loop infinito sem fazer nada.

Demonstração de Funcionamento do Sketch

Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:


Upload de Arquivos do Computador para a LittleFS da Placa ESP32

Processo de instalação do plugin

Para realizar a transferência de arquivos do computador para o sistema de arquivos LittleFS em uma placa ESP32, é essencial proceder à instalação de um plugin na Arduino IDE. Esse plugin capacitará o usuário a fazer o upload dos arquivos diretamente para o sistema de arquivos LittleFS da placa ESP32. Abaixo, apresentamos um guia passo a passo para seguir durante o processo:

  1. Faça o download do arquivo do plugin acessando https://github.com/lorol/arduino-esp32littlefs-plugin/raw/master/src/bin/esp32littlefs.jar:
  2. Abra o diretório do Sketchbook da Arduino IDE 1.x (a Arduino IDE 2.x ainda não é compatível com plugins).
    1. Para descobrir onde fica o diretório do Sketchbook da Arduino IDE, abra as configurações da IDE acessando o atalho Ctrl + , (vírgula):
    2. No campo Local do Sketchbook está o local do diretório do Sketchbook:
  3. No diretório do Sketchbook da Arduino IDE 1.x, crie um diretório chamado tools se ele ainda não existir:
  4. Dentro da pasta tools, crie uma pasta chamada ESP32LittleFS:
  5. Dentro da pasta ESP32LittleFS, crie uma pasta chamada tool:
  6. Mova o arquivo esp32littlefs.jar (baixado no passo 1) para dentro da pasta tool:
  7. Reinicie a Arduino IDE 1.x.

Com os passos acima realizados, o plugin está pronto para ser utilizado.

Processo de uso do plugin

Para efetuar o envio de arquivos para a memória Flash do ESP32, com o sistema de arquivos LittleFS, basta acompanhar os procedimentos a seguir:

  1. Vá para o diretório do sketch atual (navegue no menu da IDE: Sketch → Mostrar a página do Sketch [Ctrl + K]):
  2. Crie um diretório chamado data:
  3. Dentro do diretório data, coloque os arquivos e pastas que serão transferidos para o sistema de arquivos LittleFS da placa ESP32:Neste caso, foi criado um arquivo de texto chamado teste com o seguinte conteúdo:
    abc
    123
    A placa eh ESP32
  4. Certifique-se de ter selecionado a placa ESP32 correta e uma porta serial correta para a placa conectada. Antes de continuar com a transferência de arquivos, feche o Monitor Serial da Arduino IDE.
  5. Navegue no menu da Arduino IDE Ferramentas → ESP32 LittleFS Data Upload. Isso deve iniciar o upload dos arquivos na memória Flash do ESP32 com o sistema de arquivos LittleFS. Quando terminar, a barra de status do IDE exibirá a mensagem LITTLEFS Image Uploaded.

Lendo o conteúdo do arquivo que foi transferido para o LittleFS

Para testar se o arquivo está no sistema de arquivos da placa LittleFS, será carregado o mesmo sketch de teste que foi feito no bloco anterior. Clique aqui para ver o sketch ou clique aqui para ver a explicação do sketch referido.

Demonstração de Funcionamento do Sketch

Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:


Upload de Arquivos do Computador para a LittleFS da Placa Raspberry Pi Pico

Processo de instalação do plugin

Para executar a transferência de dados do computador para o sistema de arquivos LittleFS em uma placa Raspberry Pi Pico, é fundamental realizar a instalação de um complemento na Arduino IDE. Esse complemento permitirá ao utilizador carregar os dados diretamente para o sistema de arquivos LittleFS da placa Raspberry Pi Pico. Abaixo, disponibilizamos um manual sequencial a ser seguido durante a operação:

  1. Visite o site https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases:
  2. Faça o download do arquivo .ZIP do plugin:
  3. Com o arquivo .ZIP baixado, extraia-o (necessário ter uma ferramenta de extração de arquivos compactados, como o WinRAR) para alguma pasta (como a de downloads):
  4. Abra o diretório do Sketchbook da Arduino IDE 1.x (a Arduino IDE 2.x ainda não é compatível com plugins).
    1. Para descobrir onde fica o diretório do Sketchbook da Arduino IDE, abra as configurações da IDE acessando o atalho Ctrl + , (vírgula):
    2. No campo Local do Sketchbook está o local do diretório do Sketchbook:
  5. No diretório do Sketchbook da Arduino IDE 1.x, crie um diretório chamado tools se ele ainda não existir:
  6. Mova a pasta que foi extraída no passo 3 para dentro da pasta tools:
  7. Reinicie a Arduino IDE 1.x.

Com os passos acima realizados, o plugin está pronto para ser utilizado.

Processo de uso do plugin

Siga os passos abaixo para realizar o upload de arquivos para a memória Flash do Raspberry Pi Pico, utilizando o sistema de arquivos LittleFS:

  1. Vá para o diretório do sketch atual (navegue no menu da IDE: Sketch → Mostrar a página do Sketch [Ctrl + K]):
  2. Crie um diretório chamado data:
  3. Dentro do diretório data, coloque os arquivos e pastas que serão transferidos para o sistema de arquivos LittleFS da placa Raspberry Pi Pico: Neste caso, foi criado um arquivo de texto chamado teste com o seguinte conteúdo:
    abc
    123
    A placa eh Raspberry Pi Pico
  4. Certifique-se de ter selecionado a placa Raspberry Pi Pico correta e uma porta serial correta para a placa conectada. Antes de continuar com a transferência de arquivos, feche o Monitor Serial da Arduino IDE.
  5. Defina um tamanho para a partição do sistema de arquivos LittleFS. Para isso, navegue no menu da Arduino IDE Ferramentas → Flash Size e defina um tamanho diferente de zero.
  6. Navegue no menu da Arduino IDE Ferramentas → Pico LittleFS Data Upload. Isso deve iniciar o upload dos arquivos na memória Flash do Raspberry Pi Pico com o sistema de arquivos LittleFS. Quando terminar, a barra de status do IDE exibirá a mensagem LittleFS Image Uploaded.

Lendo o conteúdo do arquivo que foi transferido para o LittleFS

Para testar se o arquivo está no sistema de arquivos da placa LittleFS, será carregado o mesmo sketch de teste que foi feito no bloco anterior. Clique aqui para ver o sketch ou clique aqui para ver a explicação do sketch referido.

Demonstração de Funcionamento do Sketch

Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:


Principais Métodos das Classes das Bibliotecas LittleFS.h e FS.h

Como visto, para uso do sistema de arquivos LittleFS, é utilizada as bibliotecas FS.h e LittleFS.h. Veja abaixo os principais métodos da biblioteca LittleFS:

  • Métodos da classe LittleFS da biblioteca LittleFS.h;
    • bool setConfig(const FSConfig &cfg): permite configurar os parâmetros de um sistema de arquivos antes da montagem. Todos os sistemas de arquivos têm seus próprios “___Config” (ou seja: SDFSConfig, LittleFSConfig ou SPIFFSConfig) com seu conjunto personalizado de opções. Todos os sistemas de arquivos permitem habilitar/desabilitar explicitamente a formatação quando as montagens falham. Se você não chamar o método setConfig antes de executar begin(), obterá o comportamento e a configuração padrão do sistema de arquivos. Por padrão, O SPIFFS formatará automaticamente o sistema de arquivos se não puder montá-lo, enquanto o SDFS não;
    • bool begin(): Este método monta o sistema de arquivos. Deve ser chamado antes que qualquer outra operação no LittleFS seja feita. Retorna true se o sistema de arquivos foi montado com sucesso, false caso contrário;
    • void end(): Este método desmonta o sistema de arquivos;
    • bool format(): Formata o sistema de arquivos. Pode ser chamado antes ou depois da chamada begin. Retorna true se a formatação foi bem-sucedida e false, caso contrário;
    • File open(const char* path, const char* mode): Abre um arquivo. path deve ser um caminho absoluto começando com uma barra (por exemplo, /dir/filename.txt). mode é uma string que especifica o modo de acesso. Pode ser “r”, “w”, “a”, “r+”, “w+”, “a+” (Veja mais abaixo sobre os modos de acesso). O significado desses modos é o mesmo da função fopen() da linguagem C++. O método retorna o objeto File. Para verificar se o arquivo foi aberto com sucesso, use o operador booleano da classe File;
      • Modo de Acesso "r" ➜ Arquivo de texto aberto para leitura. O stream (fluxo) é posicionado no início do arquivo;
      • Modo de Acesso "r+" ➜ Aberto para leitura e escrita. O stream (fluxo) é posicionado no início do arquivo;
      • Modo de Acesso "w" ➜ Cria um arquivo vazio. Se já existir um arquivo com o mesmo nome, seu conteúdo será descartado e o arquivo será tratado como um novo arquivo vazio. O stream (fluxo) é posicionado no início do arquivo.
      • Modo de Acesso "w+" ➜ Aberto para leitura e escrita. O arquivo é criado se ele não existir, caso contrário, seu conteúdo será descartado e o arquivo será tratado como um novo arquivo vazio. O stream (fluxo) é posicionado no início do arquivo.
      • Modo de Acesso "a" ➜ Aberto para anexar (escrever no final do arquivo). O arquivo é criado se não existir. O stream (fluxo) é posicionado no final do arquivo.
      • Modo de Acesso "a+" ➜ Aberto para leitura e anexação (escrita no final do arquivo). O arquivo é criado se ele não existir. O stream (fluxo) é posicionado no início do arquivo, caso seja feita leitura. Caso seja anexação, o stream (fluxo) é posicionado ao final do arquivo.
    • bool exists(const char* path): Retorna true se existir um arquivo com o caminho especificado (path), caso contrário retorna false ;
    • bool mkdir(const char* path): Retorna true se a criação do diretório foi bem-sucedida, caso contrário retorna false;
    • bool rmdir(const char* path): Retorna true se o diretório foi removido com sucesso, caso contrário retorna false;
    • Dir openDir(const char* path): Abre um diretório dado seu caminho absoluto. Retorna um objeto Dir;
    • bool remove(const char* path): Exclui o arquivo dado seu caminho absoluto. Retorna true se o arquivo foi excluído com sucesso, caso contrário retorna false;
    • bool rename(const char* pathFrom, const char* pathTo): Renomeia o arquivo de pathFrom para pathTo. Os caminhos devem ser absolutos. Retorna true se o arquivo foi renomeado com sucesso, caso contrário retorna false;
  • Métodos da classe Dir da biblioteca FS.h;
    • bool next(): Retorna true enquanto houver arquivos no diretório para iterar. Deve ser chamado antes de chamar as funções fileName(), fileSize()e openFile();
    • String fileName(): Retorna o nome do arquivo atual apontado pelo iterador;
    • size_t fileSize(): Retorna o tamanho do arquivo atual apontado pelo iterador;
    • time_t fileTime(): Retorna o tempo de gravação, com o tipo time_t, do arquivo atual apontado pelo iterador;
    • time_t fileCreationTime(): Retorna o tempo de criação, com o tipo time_t, do arquivo atual apontado pelo iterador;
    • bool isFile() const: Retorna true se o “arquivo” atual apontado pelo iterador for um arquivo, e, caso contrário, retorna false;
    • bool isDirectory() const: Retorna true se o “arquivo” atual apontado pelo iterador interno for um diretório, e, caso contrário, retorna false;
    • bool rewind(): Redefine o ponteiro interno para o início do diretório;
  • Métodos da classe File da biblioteca FS.h:
    • bool seek(uint32_t offset, SeekMode mode): Esta função se comporta como a função fseek() da linguagem C++. Retorna true se a posição foi definida com sucesso e false se falhado. Dependendo do valor de mode, ele move a posição atual em um arquivo da seguinte forma:
      • modo SeekSet ➜ a posição é definida em tantos bytes quanto definido em offset bytes desde o início;
      • modo SeekCur ➜ a posição atual é movida por tantos bytes quanto definido em offset;
      • modo SeekEnd ➜ a posição será definida em tantos bytes quanto definido em offset a partir do final do arquivo;
    • size_t position() const: Retorna a posição atual dentro do arquivo, em bytes;
    • size_t size() const: Retorna o tamanho do arquivo, em bytes;
    • const char* name() const: Retorna o nome “curto” do arquivo (sem caminho), como const char*. Converta-o em String para armazenamento na memória;
    • const char* fullName() const: Retorna o nome do arquivo de caminho completo como const char*;
    • time_t getLastWrite(): Retorna o horário da última gravação do arquivo e válido apenas para arquivos abertos no modo somente leitura. Se um arquivo for aberto para gravação, o tempo retornado pode ser indeterminado;
    • time_t getCreationTime(): Retorna a hora de criação do arquivo, se disponível;
    • bool isFile() const: Retorna true se este “arquivo” apontar para um arquivo real;
    • bool isDirectory() const: Retorna true se este “arquivo” apontar para um diretório;
    • void close(): Fecha o arquivo. Nenhuma outra operação deve ser executada no objeto File após a chamada desta função;
    • File openNextFile(): Abre o próximo arquivo no diretório apontado pelo objeto File;
    • void rewindDirectory(): Redefine o ponteiro de openNextFile para o topo do diretório.

Exemplos de uso da LittleFS nas Placas ESP8266, ESP32 e Raspberry Pi Pico

Exemplo para placa ESP32

Neste exemplo, sempre que a placa ESP32 é iniciada, será anexado em um arquivo no sistema de arquivos LittleFS uma nova linha contendo a data e hora da inicialização da placa. Será utilizado para obter os dados de horário e data, uma conexão com um servidor NTP.

Material utilizado

  • 1x ESP32.
  • Rede sem fio com conexão com a Internet.

Sketch

/******************************************************************************
                Exemplo de uso da LittleFS na placa ESP32

                      Criado em 06 de Agosto de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/

      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

#include <WiFi.h>         // Inclui a biblioteca WiFi
#include "time.h"         // Biblioteca para acesso ao servidor NTP
#include "credenciais.h"  // arquivo de cabeçalho que armazena as credenciais de acesso à rede WiFi
#include <LittleFS.h>     // Inclui a biblioteca específica para usar o sistema de arquivos LittleFS
#include <FS.h>           // Inclui a biblioteca para trabalhar com sistemas de arquivos no Arduino

const char* ntpServer1 = "time.google.com";      // Servidor NTP primário
const char* ntpServer2 = "a.ntp.br";             // Servidor NTP secundário
const char* ntpServer3 = "time.cloudflare.com";  // Servidor NTP terciário
long gmtOffset_sec = -3 * 60 * 60;               // Deslocamento do horário de Greenwich (GMT) em segundos (fuso horário -3 horas)
const int daylightOffset_sec = 0;                // Deslocamento de horário de verão em segundos (nenhum neste caso)

void setup() {
  Serial.begin(115200);  // Inicializa a comunicação serial

  if (!LittleFS.begin(true)) {  // Inicializa o sistema de arquivos LittleFS
    Serial.println("Falha ao inicializar o sistema de arquivos LittleFS");
    return;
  }

  Serial.println();
  Serial.println("-------------------------------------------------------");
  Serial.printf("Conectando ao WiFi na rede %s \n", ssid);
  WiFi.begin(ssid, senha);                            // Conecta à rede WiFi utilizando as credenciais definidas em "credenciais.h"
  if (WiFi.waitForConnectResult() == WL_CONNECTED) {  // Espera a conexão WiFi ser estabelecida
    // Exibe mensagem de conexão bem-sucedida
    Serial.println("Conectado!");
  } else {
    // Exibe mensagem de erro na conexão e reinicia o ESP32
    Serial.println("Erro na conexão WiFi. Reiniciando...");
    delay(2000);
    ESP.restart();
  }

  // Configura o gerenciamento de tempo com os servidores NTP definidos
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2, ntpServer3);


  struct tm timeinfo;  // Estrutura para armazenar as informações do horário


  Serial.println("Obtendo horário do servidor NTP...");
  while (!getLocalTime(&timeinfo)) {  // Solicita o horário ao servidor NTP
    // Exibe mensagem de falha na obtenção do horário e tenta novamente, somente em caso de falha na obtenção
    Serial.println("Falha ao obter horário NTP. Tentando novamente...");
    delay(2000);
  }
  Serial.println("Obtido!");  // Exibe mensagem de sucesso na obtenção do horário

  String tempoDeInicializacao;               // Declaração da variável que irá armazenar o horário de inicialização formatado
  tempoDeInicializacao += timeinfo.tm_hour;  // Adiciona a hora à string de registro
  tempoDeInicializacao += ":";
  tempoDeInicializacao += timeinfo.tm_min;  // Adiciona o minuto à string de registro
  tempoDeInicializacao += ":";
  tempoDeInicializacao += timeinfo.tm_sec;  // Adiciona o segundo à string de registro
  tempoDeInicializacao += " ";
  tempoDeInicializacao += timeinfo.tm_mday;  // Adiciona o dia do mês à string de registro
  tempoDeInicializacao += "/";
  tempoDeInicializacao += timeinfo.tm_mon + 1;  // Adiciona o mês à string de registro (adicionando 1 pois o valor é baseado em zero)
  tempoDeInicializacao += "/";
  tempoDeInicializacao += timeinfo.tm_year + 1900;  // Adiciona o ano à string de registro (adicionando 1900 pois o valor é baseado em 1900)

  File file = LittleFS.open("/registro.txt", "a");  // Abre o arquivo "registro.txt" em modo de anexação
  if (!file) {                                       // Verifica se ocorreu uma falha ao abrir o arquivo "registro.txt"
    // Exibe mensagem de falha na abertura do arquivo e impede do programa continuar
    Serial.println("Falha ao abrir o arquivo registro.txt. Programa impedido de continuar.");
    return;
  }

  // Adiciona conteúdo ao arquivo "registro.txt" e verifica se ocorreu uma falha ao fazer esta operação
  if (!file.print("Inicialização detectada em " + tempoDeInicializacao + "\n")) {
    // Exibe mensagem de falha na escrita do arquivo
    Serial.println("Falha ao adicionar conteúdo ao arquivo registro.txt. Programa impedido de continuar.");
  }

  Serial.println("Inicialização do ESP32 foi registrada\n");  // Exibe mensagem de sucesso no registro da inicialização do ESP32

  Serial.println("Relatório dos horários de inicialização do ESP32:");  // Exibe mensagem informando que será mostrado o relatório de
  //                                                                        horários de inicialização do ESP32.
  
  file.close(); // Fecha o arquivo
  
  file = LittleFS.open("/registro.txt", "r"); // Abre o arquivo "registro.txt" em modo de leitura

  while (file.available()) {    // Enquanto houver conteúdo disponível no arquivo, o loop será executado
    Serial.write(file.read());  // Lê e escreve o conteúdo do arquivo no monitor serial
  }

  file.close();  // Fecha o arquivo


  while (true) {}  // Loop infinito, pois não pe feito mais nada
}

void loop() {}  // Função loop vazia, pois todo o trabalho é realizado na função setup()
const char * ssid = "SSID"; // define o nome SSID de sua rede WiFi.
const char * senha = "SENHA"; // define a senha de sua rede WiFi.

Explicação do Sketck

Este é um exemplo de uso do sistema de arquivos LittleFS em uma placa ESP32. O código é composto por dois arquivos: "Exemplo_ESP32.ino" e "credenciais.h". O sketch utiliza as bibliotecas WiFi e time.h para conectar-se a uma rede WiFi e obter a hora atual de um servidor NTP. As credenciais de acesso à rede WiFi (SSID e senha) são armazenadas em um arquivo chamado "credenciais.h".

No início do código, são incluídas as bibliotecas necessárias para a comunicação com a rede WiFi, acesso ao servidor NTP e o uso do sistema de arquivos LittleFS. As informações dos servidores NTP e deslocamentos de horário são definidas em variáveis.

Na função setup(), a comunicação serial é iniciada para possibilitar a depuração e exibição de informações no monitor serial. Em seguida, o sistema de arquivos LittleFS é inicializado. O ESP32 tenta conectar-se à rede WiFi usando as credenciais fornecidas em "credenciais.h". Se a conexão WiFi for estabelecida, uma mensagem de sucesso é exibida; caso contrário, o ESP32 é reiniciado.

O acesso ao servidor NTP é configurado com os dados fornecidos nas variáveis no início do código. Como redundância, é definido três endereços diferentes de servidores NTP, em que caso algum deles falhe, ainda terá outro para obter o horário. A função getLocalTime() é utilizada para obter a hora atual do servidor NTP. O horário é formatado em uma string e registrado em um arquivo chamado "registro.txt" usando a biblioteca LittleFS.

Em seguida, o programa exibe no monitor serial o relatório dos horários de inicialização do ESP32, lendo e exibindo o conteúdo do arquivo "registro.txt".

Por fim, na função setup(), o programa entra em um loop infinito, pois não é feito mais nada no programa.

A função loop() está vazia, pois todo o trabalho é realizado na função setup().

O arquivo "credenciais.h" contém apenas duas constantes: ssid e senha, que armazenam as informações de acesso à rede WiFi. Lembre-se: altere os valores das respectivas variáveis para que o ESP32 possa se conectar em sua rede WiFi com internet.

Vídeo de Demonstração do Funcionamento do Exemplo

Exemplo para placa Raspberry Pi Pico

Material utilizado

Arquivos que serão enviados ao LittleFS da placa RP Pico

Para este exemplo, deve-se fazer upload de arquivos para a placa Raspberry Pi Pico utilizando o plugin de upload. O arquivo .ZIP contendo os arquivos para teste pode ser baixado clicando aqui. Após o download, extraia o arquivo .ZIP para dentro da pasta do sketch do exemplo e faça o upload dos arquivos com o uso do plugin de upload (para orientações de como proceder, consulte o passo a passo Upload de arquivos do computador para a LittleFS da placa Raspberry Pi Pico). O esquema de arquivos pode ser visto a seguir:

Sketch

/******************************************************************************
              Exemplo de uso da LittleFS na placa Raspberry Pi Pico

                      Criado em 06 de Agosto de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/

      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

#include <LittleFS.h>  // Inclui a biblioteca específica para usar o sistema de arquivos LittleFS.
#include <FS.h>        // Inclui a biblioteca para trabalhar com sistemas de arquivos no Arduino.

void listarDiretorio(String caminho);  // Protótipo da função listarDiretorio

void setup() {
  Serial.begin(115200);  // Inicializa a comunicação serial com baud rate 115200
  delay(5000);           // Aguarda 5 segundos

  if (!LittleFS.begin()) {  // Inicializa o sistema de arquivos LittleFS e verifica se houve falha
    // se houve falhas, exibe a mensagem de alerta ao usuário e impede do programa continuar
    Serial.println("Falha ao inicializar o sistema de arquivos LittleFS");
    return;
  }

  const char* raiz = "/";  // Define o caminho raiz como uma string contendo apenas o caractere '/'
  Serial.println(raiz);
  listarDiretorio(raiz);  // Chama a função para listar o diretório (incluindo todos arquivos e subdiretórios) a partir do caminho raiz (função recursiva)
}

void loop() {}  // Função loop vazia, pois todo o trabalho é realizado na função setup()

// Função recursiva para listar todos os arquivos e subdiretórios de um determinado caminho
void listarDiretorio(String caminho) {
  Dir obj = LittleFS.openDir(caminho);  // Abre o diretório especificado pelo caminho

  while (obj.next()) {  // Loop para percorrer os arquivos e subdiretórios do diretório

    if (obj.isFile()) {                    // Verifica se o objeto atual é um arquivo
      File file = obj.openFile("r");       // Abre o arquivo atual no modo leitura ("r")
      Serial.print('\t');                  // Imprime um caractere de tabulação no monitor serial
      Serial.printf("%s: ", file.name());  // Imprime o nome do arquivo atual no monitor serial
      while (file.available()) {           // Loop para ler e imprimir o conteúdo do arquivo
        Serial.write(file.read());         // Lê e imprime o próximo byte do arquivo no monitor serial
      }
      Serial.println();  // Imprime uma nova linha no monitor serial para separar os arquivos

    } else if (obj.isDirectory()) {     // Verifica se o objeto atual é um diretório
      File file = obj.openFile("r");    // Abre o diretório atual no modo leitura ("r")
      Serial.println(file.fullName());  // Imprime o caminho completo do diretório no monitor serial

      listarDiretorio(file.fullName());  // Chama recursivamente a função para listar o diretório atual (explorando subdiretórios)
    }
  }
}

Explicação do Sketch

O código é um exemplo de como listar todos os arquivos e subdiretórios presentes no sistema de arquivos LittleFS da placa Raspberry Pi Pico.

No início do código, são incluídas as bibliotecas necessárias para o uso do sistema de arquivos LittleFS.

Na função setup(), a comunicação serial é iniciada para possibilitar a depuração e exibição de informações no monitor serial. Em seguida, o sistema de arquivos LittleFS é inicializado.

A função listarDiretorio() é a parte central do programa, implementada de forma recursiva. Essa função recebe um caminho como parâmetro e, a partir desse caminho, abre o diretório correspondente. Em seguida, um loop é utilizado para percorrer os arquivos e subdiretórios dentro desse diretório.

Dentro do loop da função listarDiretorio(), são feitas as verificações necessárias para determinar se o objeto atual é um arquivo ou um diretório. Se o objeto é um arquivo, ele é aberto em modo leitura e o nome do arquivo é impresso no monitor serial, seguido do conteúdo do arquivo. Se o objeto é um diretório, seu caminho completo é impresso no monitor serial, e a função listarDiretorio() é chamada recursivamente para explorar os subdiretórios dentro desse diretório.

A função listarDiretorio() continua a exploração dos subdiretórios recursivamente até que todos os arquivos e subdiretórios sejam listados. O uso de recursão permite que o código explore a hierarquia de diretórios sem a necessidade de usar listas para armazenar os caminhos. O programa é projetado para funcionar apenas uma vez no setup(), pois o loop é vazio, não havendo necessidade de executar o código repetidamente.

Vídeo de Demonstração do Funcionamento do Exemplo

Exemplo para placa ESP8266

Material utilizado

Sketch

/******************************************************************************
                Exemplo de uso da LittleFS na placa ESP8266

                      Criado em 06 de Agosto de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/

      Confira mais detalhes deste sketch em https://blog.eletrogate.com/
******************************************************************************/

#include <LittleFS.h>  // Inclui a biblioteca específica para usar o sistema de arquivos LittleFS.
#include <FS.h>        // Inclui a biblioteca para trabalhar com sistemas de arquivos no Arduino.

int listarDiretorios(const char *path, int qtdDiretorios = 0);  // Protótipo da função para listar diretórios

void criarDiretorio(const char *path);                      // Protótipo da função para criar diretórios
void deletarDiretorio(const char *path);                    // Protótipo da função para deletar diretórios
void criarArquivo(const char *path);                        // Protótipo da função para criar arquivos
void apagarArquivo(const char *path);                       // Protótipo da função para apagar arquivos
void escreverArquivo(const char *path, const char *linha);  // Protótipo da função para escrever em arquivos
void lerArquivo(const char *path);                          // Protótipo da função para ler arquivos
void anexarArquivo(const char *path, const char *linha);    // Protótipo da função para anexar texto em arquivos
void renomearArquivo(const char *path);                     // Protótipo da função para renomear arquivos

void setup() {
  Serial.begin(115200);  // Inicializa a comunicação serial
  Serial.println();
  delay(500);  // Aguarda 500ms

  Serial.println("Iniciando LittleFS...");  // Imprime mensagem indicando início da inicialização do LittleFS

  if (!LittleFS.begin()) {  // Verifica se houve falha ao inicializar o sistema de arquivos LittleFS
    Serial.println("Falha ao inicializar o sistema de arquivos LittleFS. Programa impedido de continuar");
    return;  // Encerra o programa caso haja falha
  }
  Serial.println("Sistema de arquivos LittleFS iniciado!");  // Imprime mensagem indicando que o LittleFS foi iniciado

  Serial.println();
  Serial.println("Listagem do diretório '/': ");                            // Imprime mensagem indicando a listagem do diretório raiz
  Serial.print(listarDiretorios("/") == 0 ? " -> Diretório vazio\n" : "");  // Verifica se o diretório está vazio e imprime mensagem adequada
  delay(2000);                                                              // Aguarda 2 segundos (apenas para visualização melhorada no monitor serial)

  criarDiretorio("/teste");  // Cria o diretório '/teste'
  delay(2000);               // Aguarda 2 segundos

  Serial.println();
  Serial.println("Listagem do diretório '/': ");                            // Imprime mensagem indicando a listagem do diretório raiz
  Serial.print(listarDiretorios("/") == 0 ? " -> Diretório vazio\n" : "");  // Verifica se o diretório está vazio e imprime mensagem adequada
  delay(2000);                                                              // Aguarda 2 segundos

  criarArquivo("/teste/arquivo.txt");  // Cria o arquivo '/teste/arquivo.txt'
  delay(2000);                         // Aguarda 2 segundos

  criarArquivo("/teste/arquivo.txt");  // Cria o arquivo '/teste/arquivo.txt' (já existente, apenas para demonstração)
  delay(2000);                         // Aguarda 2 segundos

  Serial.println();
  Serial.println("Listagem do diretório '/': ");                            // Imprime mensagem indicando a listagem do diretório raiz
  Serial.print(listarDiretorios("/") == 0 ? " -> Diretório vazio\n" : "");  // Verifica se o diretório está vazio e imprime mensagem adequada
  delay(2000);                                                              // Aguarda 2 segundos

  escreverArquivo("/teste/arquivo.txt", "ABC123");  // Escreve "ABC123" no arquivo '/teste/arquivo.txt'
  delay(2000);                                      // Aguarda 2 segundos

  lerArquivo("/teste/arquivo.txt");  // Lê e imprime o conteúdo do arquivo '/teste/arquivo.txt'
  delay(2000);                       // Aguarda 2 segundos

  anexarArquivo("/teste/arquivo.txt", "\nX\nY\nZ\n789");  // Anexa no arquivo '/teste/arquivo.txt' o seguinte conteúdo:
  // X
  // Y
  // Z
  // 789
  delay(2000);  // Aguarda 2 segundos

  lerArquivo("/teste/arquivo.txt");  // Lê e imprime o conteúdo do arquivo '/teste/arquivo.txt'
  delay(2000);                       // Aguarda 2 segundos

  renomearArquivo("/teste/arquivo.txt", "/arquivoABC.txt");  // Renomeia o arquivo '/teste/arquivo.txt' para '/arquivoABC.txt'
  delay(2000);                                               // Aguarda 2 segundos

  Serial.println();
  Serial.println("Listagem do diretório '/': ");                            // Imprime mensagem indicando a listagem do diretório raiz
  Serial.print(listarDiretorios("/") == 0 ? " -> Diretório vazio\n" : "");  // Verifica se o diretório está vazio e imprime mensagem adequada
  delay(2000);                                                              // Aguarda 2 segundos

  apagarArquivo("/arquivoABC.txt");  // Apaga o arquivo '/arquivoABC.txt'
  delay(2000);

  deletarDiretorio("/teste");  // Deleta o diretório '/teste' e seu conteúdo
  delay(2000);                 // Aguarda 2 segundos

  Serial.println();
  Serial.println("Listagem do diretório '/': ");                            // Imprime mensagem indicando a listagem do diretório raiz
  Serial.print(listarDiretorios("/") == 0 ? " -> Diretório vazio\n" : "");  // Verifica se o diretório está vazio e imprime mensagem adequada
  delay(2000);                                                              // Aguarda 2 segundos

  while (true) {
    delay(1);
  }  // Loop infinito, pois não é feito mais nada
}

void loop() {}  // Função loop vazia, pois todo o trabalho é realizado na função setup()

int listarDiretorios(const char *path, int qtdDiretorios) {  // Função para listar diretórios
  Dir obj = LittleFS.openDir(path);                          // Abre o diretório especificado

  while (obj.next()) {  // Itera sobre os arquivos e subdiretórios no diretório

    File file = obj.openFile("r");            // Abre o arquivo para leitura
    Serial.printf("/%s\n", file.fullName());  // Imprime o caminho completo do arquivo

    if (obj.isDirectory()) {  // Verifica se o objeto é um diretório
      qtdDiretorios++;        // Incrementa o contador de diretórios

      listarDiretorios(file.fullName(), qtdDiretorios);  // Chama recursivamente a função para listar subdiretórios
    }
  }
  return qtdDiretorios;  // Retorna a quantidade total de diretórios
}
void criarDiretorio(const char *path) {  // Função para criar diretório
  Serial.println();
  Serial.printf("Criando diretório %s\n", path);             // Imprime mensagem indicando a criação do diretório
  if (!LittleFS.mkdir(path)) {                               // Verifica se houve falha ao criar o diretório
    Serial.printf("Falha ao criar diretório '%s'\n", path);  // Imprime mensagem de falha
  } else {
    Serial.printf("Diretório '%s' criado\n", path);  // Imprime mensagem de sucesso
  }
}

void deletarDiretorio(const char *path) {  // Função para deletar diretório
  Serial.println();
  Serial.printf("Apagando diretório %s\n", path);   // Imprime mensagem indicando a exclusão do diretório
  if (LittleFS.exists(path)) {                      // Verifica se o diretório existe
    LittleFS.rmdir(path);                           // Remove o diretório
    Serial.printf("Diretório %s apagado\n", path);  // Imprime mensagem indicando que o diretório foi apagado
  } else {
    Serial.println("O diretório não existe.");  // Imprime mensagem de que o diretório não existe
  }
}

void criarArquivo(const char *path) {  // Função para criar arquivo
  Serial.println();
  Serial.printf("Criando arquivo %s\n", path);  // Imprime mensagem indicando a criação do arquivo

  if (!LittleFS.exists(path)) {               // Verifica se o arquivo não existe
    File arquivo = LittleFS.open(path, "w");  // Cria o arquivo
    arquivo.close();                          // Fecha o arquivo
    Serial.println("Arquivo criado");         // Imprime mensagem de que o arquivo foi criado
  } else {
    Serial.println("O arquivo já existe.");  // Imprime mensagem de que o arquivo já existe
  }
}

void apagarArquivo(const char *path) {  // Função para apagar arquivo
  Serial.println();
  Serial.printf("Apagando arquivo %s\n", path);  // Imprime mensagem indicando a exclusão do arquivo
  if (LittleFS.exists(path)) {                   // Verifica se o arquivo existe
    LittleFS.remove(path);                       // Remove o arquivo
    Serial.println("Arquivo apagado");           // Imprime mensagem indicando que o arquivo foi apagado
  } else {
    Serial.println("O arquivo não existe.");  // Imprime mensagem de que o arquivo não existe
  }
}

void escreverArquivo(const char *path, const char *linha) {  // Função para escrever conteúdo em um arquivo
  Serial.println();
  Serial.printf("Escrevendo no arquivo %s\n", path);  // Imprime mensagem indicando a escrita no arquivo
  if (LittleFS.exists(path)) {                        // Verifica se o arquivo existe
    File arquivo = LittleFS.open(path, "w");          // Abre o arquivo no modo de escrita
    arquivo.print(linha);                             // Escreve a linha no arquivo
    arquivo.close();                                  // Fecha o arquivo
    Serial.println("Conteúdo escrito");               // Imprime mensagem indicando que o conteúdo foi escrito
  } else {
    Serial.println("O arquivo não existe.");  // Imprime mensagem de que o arquivo não existe
  }
}

void lerArquivo(const char *path) {  // Função para ler um arquivo
  Serial.println();
  Serial.printf("Lendo o arquivo %s\n", path);  // Imprime mensagem indicando a leitura do arquivo
  if (LittleFS.exists(path)) {                  // Verifica se o arquivo existe
    File arquivo = LittleFS.open(path, "r");    // Abre o arquivo no modo de leitura
    while (arquivo.available()) {               // Enquanto houver conteúdo disponível no arquivo
      Serial.write(arquivo.read());             // Lê e imprime o conteúdo do arquivo
    }

    arquivo.close();                    // Fecha o arquivo
    Serial.println("\nConteúdo lido");  // Imprime mensagem indicando que o conteúdo foi lido
  } else {
    Serial.println("O arquivo não existe.");  // Imprime mensagem de que o arquivo não existe
  }
}

void anexarArquivo(const char *path, const char *linha) {  // Função para anexar conteúdo em um arquivo
  Serial.println();
  Serial.printf("Anexando conteúdo no arquivo %s\n", path);  // Imprime mensagem indicando a anexação de conteúdo no arquivo
  if (LittleFS.exists(path)) {                               // Verifica se o arquivo existe
    File arquivo = LittleFS.open(path, "a");                 // Abre o arquivo no modo de anexar
    arquivo.print(linha);                                    // Anexa a linha ao arquivo
    arquivo.close();                                         // Fecha o arquivo
    Serial.println("Conteúdo anexado");                      // Imprime mensagem indicando que o conteúdo foi anexado
  } else {
    Serial.println("O arquivo não existe.");  // Imprime mensagem de que o arquivo não existe
  }
}

void renomearArquivo(const char *path, const char *novoNome) {  // Função para renomear um arquivo
  Serial.println();
  Serial.printf("Renomeando o arquivo %s para %s\n", path, novoNome);  // Imprime mensagem indicando a renomeação do arquivo
  if (LittleFS.exists(path)) {                                         // Verifica se o arquivo existe
    LittleFS.rename(path, novoNome);                                   // Renomeia o arquivo
    Serial.println("Arquivo renomeado");                               // Imprime mensagem indicando que o arquivo foi renomeado
  } else {
    Serial.println("Arquivo não existe");  // Imprime mensagem de que o arquivo não existe
  }
}

Explicação do Sketck

O código é um exemplo de como realizar as principais operações com os arquivos e diretórios no sistema de arquivos LittleFS. O código é composto por dois arquivos: "Exemplo_ESP8266.ino" e "funcoes.ino".

O arquivo "Exemplo_ESP8266.ino" contém as funções setup() e listarDiretorios(). A função setup() é executada uma vez na inicialização do dispositivo e faz as principais operações desenvolvidadas no arquivo "funcoes.ino".

Neste arquivo, são incluídas as bibliotecas "LittleFS.h" e "FS.h", necessárias para trabalhar com o sistema de arquivos. Em seguida, são declarados os protótipos das funções que serão definidas no arquivo "funcoes.ino". Essas funções são responsáveis por:

  • Criar um diretório;
  • Deletar um diretório;
  • Criar um arquivo;
  • Deletar um arquivo.
  • Escrever em um arquivo;
  • Ler um arquivo;
  • Anexar em um arquivo;
  • Renomear um arquivo.

Na função setup(), a comunicação serial é iniciada para possibilitar a depuração e exibição de informações no monitor serial. Em seguida, o sistema de arquivos LittleFS é inicializado.

Ainda dentro da função setup(), são realizadas diversas operações usando as funções definidas no arquivo "funcoes.ino".

O arquivo "funcoes.ino" contém a implementação das funções prototipadas no arquivo "Exemplo_ESP8266.ino". Cada função realiza uma operação específica, como criar diretório, deletar diretório, criar arquivo, apagar arquivo, escrever conteúdo em arquivo, ler conteúdo de arquivo, anexar texto em arquivo e renomear arquivo. As funções são bem documentadas com comentários explicativos para facilitar a compreensão de seu funcionamento.

Em resumo, este exemplo utiliza o sistema de arquivos LittleFS para realizar operações  em arquivos e diretórios na placa ESP8266. As funções são organizadas nos arquivos "Exemplo_ESP8266.ino" e "funcoes.ino" para manter o código mais claro e modular. O programa usa a comunicação serial para exibir informações sobre as operações realizadas e o estado do sistema de arquivos no monitor serial.

Vídeo de Demonstração do Funcionamento do Exemplo


Conclusão

Em conclusão, o sistema de arquivos LittleFS apresenta notáveis vantagens sobre o SPIFFS, tornando-se uma escolha mais vantajosa para aplicações que exigem um eficiente gerenciamento de dados em dispositivos embarcados. A principal vantagem do LittleFS é o desempenho aprimorado em operações de leitura e gravação, especialmente em cenários com grande quantidade e variedade de arquivos, tornando assim o LittleFS uma opção preferencial.

Além disso, a portabilidade e o suporte a várias plataformas tornam o LittleFS uma solução versátil e adequada para diversos dispositivos e sistemas embarcados.

Gostaríamos de saber se você curtiu este post! Por favor, avalie e deixe um comentário sobre o conteúdo. E não esqueça de nos seguir no Instagram e nos marcar quando fizer algum projeto nosso: @eletrogate.

Até a próxima!


Sobre o Autor


Michel Galvão

Graduando em Engenharia de Software pela UniCV de Maringá/PR. Tem experiência em Automação Residencial e Agrícola. É um Eletrogater Expert. Tem como Hobby Sistemas Embarcados e IoT. É desenvolvedor de software de Sistemas MicSG. Site: https://micsg.com.br/


Eletrogate

12 de setembro de 2023

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!