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:
Neste post, será utilizado os seguintes materiais:
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.
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:
É 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.
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.
Avisos:
/****************************************************************************** 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() {}
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:
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:
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.
/****************************************************************************** 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() {}
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:
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:
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.
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:
Siga os passos abaixo para configurar a frequência da CPU da placa ESP8266:
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.
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.
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.
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:
Com os passos acima realizados, o plugin está pronto para ser utilizado.
Siga os passos abaixo para realizar o upload de arquivos para a memória Flash do ESP8266, utilizando o sistema de arquivos LittleFS:
abc 123
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()'.
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.
Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:
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:
Com os passos acima realizados, o plugin está pronto para ser utilizado.
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:
abc 123 A placa eh ESP32
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.
Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:
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:
Com os passos acima realizados, o plugin está pronto para ser utilizado.
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:
abc 123 A placa eh Raspberry Pi Pico
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.
Veja no vídeo abaixo como é a leitura do arquivo pelo monitor serial:
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:
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;
"r"
➜ Arquivo de texto aberto para leitura. O stream (fluxo) é posicionado no início do arquivo;"r+"
➜ Aberto para leitura e escrita. O stream (fluxo) é posicionado no início do arquivo;"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."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."a"
➜ Aberto para anexar (escrever no final do arquivo). O arquivo é criado se não existir. O stream (fluxo) é posicionado no final do arquivo."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
;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;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:
SeekSet
➜ a posição é definida em tantos bytes quanto definido em offset
bytes desde o início;SeekCur
➜ a posição atual é movida por tantos bytes quanto definido em offset
;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.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.
/****************************************************************************** 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.
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.
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:
/****************************************************************************** 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) } } }
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.
/****************************************************************************** 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 } }
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:
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.
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!
|
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!