Neste post iremos aprender a criar, renomear, adicionar conteúdo, copiar, mover, ler e excluir arquivos na memória FLASH do microcontrolador ESP32, através de um sistema de arquivos denominado SPIFFS. Este sistema de arquivos permite obtermos os arquivos salvos mesmo após uma queda de energia.
Para este post, é necessário somente um material:
SPIFFS, acrônimo de SPI Flash File System (Sistema de Arquivo Flash de Interface Periférica Serial), é um sistema de armazenamento de arquivos baseado na memória FLASH. Este sistema de arquivos funciona tanto no ESP32 quanto no ESP8266.
A SPIFFS permite que, mesmo que um novo programa seja carregado para a FLASH do microcontrolador, os arquivos armazenados na SPIFFS continuem intactos.
O tamanho máximo disponível para armazenar arquivos na SPIFFS depende da placa ESP e da partição escolhida. Como, por exemplo, a placa ESP32 Dev Module, que possui 1.5 MB de memória SPIFFS disponível. Pode ser consultada a quantidade de memória SPIFFS através da própria IDE Arduino, mas antes deve-se selecionar a placa correta:
Após selecionada a placa, vá ao menu Ferrramentas -> Partition Scheme e selecione o esquema de partição que melhor lhe atenda:
Nesta placa ESP32, com este esquema de partição atual, a SPIFFS tem 1.5 MB de espaço, sobrando, para o programa (APP), 1.2 MB de espaço disponível. Mas, também é possível ter 1 MB de espaço para o programa e 3 MB de espaço para a SPIFFS (para placas ESP32 com 4 MB de FLASH total), tendo somente como desvantagem a não possibilidade de utilizar atualização OTA (Over-the-Air) para o módulo ESP32.
De acordo com a Espressif, fabricante do chip ESP32, a SPIFFS suporta nivelamento de desgaste, verificações de consistência do sistema de arquivos e muito mais.
A SPIFFS foi projetada para lidar com dispositivos com baixa RAM (como o caso de microcontroladores).
Apesar destes benefícios da SPIFFS, ela possui algumas limitações:
Pode-se ver mais informações da SPIFFS na sua documentação do GitHub.
Para fazer o upload de arquivos que estão no computador para a memória SPIFFS, deve-se instalar um plugin para a IDE Arduino. Este plugin, chamado “plug-in arduino-esp32fs”, permite empacotar a pasta de dados do esboço na imagem do sistema de arquivos SPIFFS e carregar a imagem na memória FLASH ESP32.
<home_dir>/Arduino/tools/ESP32FS/tool/esp32fs.jar
).Para visualizar os arquivos na SPIFFS e ver se deu tudo certo no processo de upload, carregue o seguinte sketch:
/****************************************************************************** SPIFFS: Armazenamento de Arquivos do ESP32 Sketch de Teste da SPIFFS Criado em 19 mar. 2022 por Michel Galvão Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits https://www.eletrogate.com/ ******************************************************************************/ // Inclusão da(s) biblioteca(s) #include "SPIFFS.h" #include "FS.h" // Protótipo das Funções bool listDir(); bool readFile(String path); void setup() { Serial.begin(115200); Serial.println(); SPIFFS.begin(); listDir(); Serial.println(); readFile("/arquivoTRANSFERIDO_1.txt"); Serial.println(); readFile("/arquivoTRANSFERIDO_2.txt"); } void loop() {} bool listDir() { File root = SPIFFS.open("/"); // Abre o "diretório" onde estão os arquivos na SPIFFS // e passa o retorno para // uma variável do tipo File. if (!root) // Se houver falha ao abrir o "diretório", ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir o diretório"); return false; } File file = root.openNextFile(); // Relata o próximo arquivo do "diretório" e // passa o retorno para a variável // do tipo File. int qtdFiles = 0; // variável que armazena a quantidade de arquivos que // há no diretório informado. while (file) { // Enquanto houver arquivos no "diretório" que não foram vistos, // executa o laço de repetição. Serial.print(" FILE : "); Serial.print(file.name()); // Imprime o nome do arquivo Serial.print("\tSIZE : "); Serial.println(file.size()); // Imprime o tamanho do arquivo qtdFiles++; // Incrementa a variável de quantidade de arquivos file = root.openNextFile(); // Relata o próximo arquivo do diretório e // passa o retorno para a variável // do tipo File. } if (qtdFiles == 0) // Se após a visualização de todos os arquivos do diretório // não houver algum arquivo, ... { // Avisa o usuário que não houve nenhum arquivo para ler e retorna false. Serial.print(" - Sem arquivos para ler. Crie novos arquivos pelo menu "); Serial.println("principal, opção 2."); return false; } return true; // retorna true se não houver nenhum erro } bool readFile(String path) { File file = SPIFFS.open(path); // Abre o caminho do arquivo da SPIFFS no // e passa o retorno para uma variável do // tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para leitura"); return false; } while (file.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de repetição. { Serial.write(file.read()); // Escreve na Serial os bytes obtidos da leitura // do arquivo. } Serial.println(); file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro }
No monitor serial, é mostrado os nomes dos arquivos e o conteúdo dos mesmos:
Primeiro, começamos incluindo as bibliotecas que são necessárias (ambas são nativas do pacote ESP32):
Em seguida, fazemos os protótipos das funções.
No
void setup()
, inicializamos a Serial e a SPIFFS e, através dos método listDir()
, listamos todos os arquivos armazenados na SPIFFS. Também imprimimos, na serial, o conteúdo dos dois arquivos que transferimos para a SPIFFS. Fazemos isto através do método readFile()
.
No void loop()
, não realizamos nenhuma operação.
O método readFile()
e listDir()
podem ser vistos na seção “Operações com Arquivos da SPIFFS” deste post.
Abaixo, seguem alguns algoritmos criados para operar arquivos da SPIFFS. Estes algoritmos estão totalmente comentados para auxiliar a compreensão da lógica deles:
/** Lista todos os arquivos da SPIFFS @return true se ocorreu tudo certo ou false se houve algum erro */ bool listDir() { File root = SPIFFS.open("/"); // Abre o "diretório" onde estão os arquivos na SPIFFS // e passa o retorno para // uma variável do tipo File. if (!root) // Se houver falha ao abrir o "diretório", ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir o diretório"); return false; } File file = root.openNextFile(); // Relata o próximo arquivo do "diretório" e // passa o retorno para a variável // do tipo File. int qtdFiles = 0; // variável que armazena a quantidade de arquivos que // há no diretório informado. while (file) { // Enquanto houver arquivos no "diretório" que não foram vistos, // executa o laço de repetição. Serial.print(" FILE : "); Serial.print(file.name()); // Imprime o nome do arquivo Serial.print("\tSIZE : "); Serial.println(file.size()); // Imprime o tamanho do arquivo qtdFiles++; // Incrementa a variável de quantidade de arquivos file = root.openNextFile(); // Relata o próximo arquivo do diretório e // passa o retorno para a variável // do tipo File. } if (qtdFiles == 0) // Se após a visualização de todos os arquivos do diretório // não houver algum arquivo, ... { // Avisa o usuário que não houve nenhum arquivo para ler e retorna false. Serial.print(" - Sem arquivos para ler. Crie novos arquivos pelo menu "); Serial.println("principal, opção 2."); return false; } return true; // retorna true se não houver nenhum erro }
/** Lê o conteúdo de um arquivo especificado da SPIFFS @param path : o caminho/nome do arquivo à ser listado @return true se ocorreu tudo certo ou false se houve algum erro */ bool readFile(String path) { File file = SPIFFS.open(path); // Abre o caminho do arquivo da SPIFFS no // e passa o retorno para uma variável do // tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para leitura"); return false; } while (file.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de repetição. { Serial.write(file.read()); // Escreve na Serial os bytes obtidos da leitura // do arquivo. } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro }
/** Escreve o conteúdo informado em um novo arquivo da SPIFFS @param path : o caminho/nome do novo arquivo à ser escrito @param message : o conteúdo do arquivo que será gravado @return true se ocorreu tudo certo ou false se houve algum erro */ bool writeFile(String path, String message) { Serial.print("Gravando o arquivo "); Serial.print(path); Serial.println(" : "); File file = SPIFFS.open(path, FILE_WRITE); // Abre o arquivo, no modo escrita, // onde será gravado o seu conteúdo // e passa o retorno para // uma variável do tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } if (file.print(message)) // Se a escrita do arquivo com seu conteúdo der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo escrito"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na gravação do arquivo"); return false; } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro }
/** Adiciona conteúdo informado em um arquivo existente da SPIFFS @param path : o caminho/nome do arquivo à ser escrito @param message : o conteúdo do arquivo que será anexado @return true se ocorreu tudo certo ou false se houve algum erro */ bool appendFile(String path, String message) { Serial.print("Anexando conteúdo ao arquivo "); Serial.print(path); Serial.println(" : "); File file = SPIFFS.open(path, FILE_APPEND); // Abre o arquivo, no modo anexar, // onde será adicionado conteúdo // e passa o retorno para // uma variável do tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir o arquivo para anexar"); return false; } if (file.print(message)) // Se a escrita do arquivo com seu conteúdo der certo, ... { // informa ao usuário que deu certo Serial.println(" - mensagem anexada"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - Falha ao anexar"); return false; } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro }
/** Renomeia um determinado arquivo existente da SPIFFS @param path1 : o caminho/nome do arquivo à ser renomeado @param path2 : o novo caminho/nome do arquivo @return true se ocorreu tudo certo ou false se houve algum erro */ bool renameFile(String path1, String path2) { Serial.print("Renomenando arquivo "); Serial.print(path1); Serial.print(" para "); Serial.print(path2); Serial.println(" : "); if (SPIFFS.rename(path1, path2)) // renomeia o arquivo passando o // antigo caminho/nome (path1) e o // novo caminho/nome (path2) // Se a renomeação der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo renomeado"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha ao renomear"); return false; } return true; // retorna true se não houver nenhum erro }
/** Exclui um determinado arquivo existente da SPIFFS @param path : o caminho/nome do arquivo à ser excluído @return true se ocorreu tudo certo ou false se houve algum erro */ bool deleteFile(String path) { Serial.print("Deletando arquivo "); Serial.print(path); Serial.println(" : "); if (SPIFFS.remove(path)) // exclui o arquivo passando o // caminho/nome (path) // Se a exclusão der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo excluído"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na exclusão"); return false; } return true; // retorna true se não houver nenhum erro }
/** Move um determinado arquivo existente da SPIFFS para outro local @param pathOrigem : o caminho/nome do arquivo de origem @param pathDestino : o caminho/nome do arquivo de destino @return true se ocorreu tudo certo ou false se houve algum erro */ bool moveFile(String pathOrigem, String pathDestino) { Serial.print("Movendo arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.println(" : "); File fileOrigem = SPIFFS.open(pathOrigem); // Abre o arquivo do local de origem // e passa o retorno para // uma variável do tipo File. if (!fileOrigem) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para cópia"); return false; } File fileDestino = SPIFFS.open(pathDestino, FILE_WRITE); // Abre o arquivo do // local de destino, // no modo escrita, // e passa o retorno para // uma variável do tipo // File. if (!fileDestino) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } while (fileOrigem.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de // repetição. { if (!fileDestino.write(fileOrigem.read())) // Se a escrita byte-a-byte do arquivo // lido, também byte-a-byte, resultar // em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha para mover o arquivo"); return false; } } //Fecha os arquivos de origem e de destino fileOrigem.close(); fileDestino.close(); if (!SPIFFS.remove(pathOrigem)) // exclui o arquivo de origem passando o // caminho/nome (path) // Se a exclusão resultar em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na exclusão"); return false; } return true; // retorna true se não houver nenhum erro }
/** Copia um determinado arquivo existente da SPIFFS para outro local @param pathOrigem : o caminho/nome do arquivo de origem @param pathDestino : o caminho/nome do arquivo de destino @return true se ocorreu tudo certo ou false se houve algum erro */ bool copyFile(String pathOrigem, String pathDestino) { Serial.print("Copiando arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.println(" : "); File fileOrigem = SPIFFS.open(pathOrigem); // Abre o arquivo do local de origem // e passa o retorno para // uma variável do tipo File. if (!fileOrigem) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para cópia"); return false; } File fileDestino = SPIFFS.open(pathDestino, FILE_WRITE); // Abre o arquivo do // local de destino, // no modo escrita, // e passa o retorno para // uma variável do tipo // File. if (!fileDestino) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } while (fileOrigem.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de // repetição. { if (!fileDestino.write(fileOrigem.read())) // Se a escrita byte-a-byte do arquivo // lido, também byte-a-byte, resultar // em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha para mover o arquivo"); return false; } } //Fecha os arquivos de origem e de destino fileOrigem.close(); fileDestino.close(); return true; // retorna true se não houver nenhum erro }
Nenhum hardware extra é necessário, além da placa ESP32, de acordo com o seguinte esquemático:
Na IDE Arduino, cole o seguinte código:
/****************************************************************************** SPIFFS: Armazenamento de Arquivos do ESP32 Sketch Principal Criado em 11 mar. 2022 por Michel Galvão Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits https://www.eletrogate.com/ ******************************************************************************/ // Inclusão da(s) biblioteca(s) #include "SPIFFS.h" #include "FS.h" // Protótipo das Funções String readStringSerial(); char readCharSerial(); bool listDir(); bool readFile(String path); bool writeFile(String path, String message); bool appendFile(String path, String message); bool renameFile(String path1, String path2); bool deleteFile(String path); bool moveFile(String pathOrigem, String pathDestino); bool copyFile(String pathOrigem, String pathDestino); bool formatSPIFFS(); void setup() { // Configurações Iniciais Serial.begin(115200); // Inicializa Serial delay(1000); // Splash Screen na Serial Serial.println("\n\n"); Serial.println(" -> SPIFFS: Armazenamento de Arquivos do ESP32 <- "); if (SPIFFS.begin(false)) // Inicializa a partição SPIFFS, sem formata-la { Serial.println("SPIFFS inicializado!"); } else { Serial.println("Falha na inicialização SPIFFS"); Serial.println("[Programa terminado]"); while (1); // Loop infinito } } void loop() { // Loop Infinito char opcao; // armazena a opção do menu selecionada pelo usuário // Mostra as opções do menu na Serial Serial.println("MENU"); Serial.println("Digite a opção desejada e clique em ENTER:"); Serial.println(" 1. Ler Arquivo"); Serial.println(" 2. Novo Arquivo"); Serial.println(" 3. Editar Arquivo (adicionar Conteúdo)"); Serial.println(" 4. Renomear Arquivo"); Serial.println(" 5. Excluir Arquivo "); Serial.println(" 6. Copiar Arquivo para..."); Serial.println(" 7. Mover Arquivo para..."); Serial.println(" 8. Formatar SPIFFS "); opcao = readCharSerial(); // lê a opção selecionada pelo usuáro na Serial e armazena Serial.print("opcao "); Serial.print(opcao); Serial.println(" selecionada"); switch (opcao) { // exibe determinada opção de acordo com a seleção do usuário case '1': // Ler Arquivo { Serial.print("-> Para sair digite SAIR, ou "); Serial.println("Diante dos seguintes arquivos, "); if (!listDir()) //lista os arquivos do "diretório" '/', se houver // alguma falha é cancelada a operação. { break; } Serial.print("-> digite o caminho e nome do arquivo à ser lido "); Serial.print("(Exemplo: /arquivo.txt): "); String path = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser lido. if (path == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path); Serial.println("Conteúdo do Arquivo: "); Serial.println("-------------------------------------------"); if (readFile(path)) { // Lê o conteúdo do arquivo e mostra na Serial. Serial.println("-------------------------------------------"); Serial.println(); } break; } case '2': // Novo Arquivo { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo à ser gravado "); Serial.print("(Exemplo: /arquivo.txt): "); String path = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser gravado. if (path == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path); Serial.println("Digite o conteúdo do arquivo à ser gravado: "); Serial.println("INSTRUÇÕES:"); Serial.print(" - Digite o conteúdo linha a linha e clique em ENTER após "); Serial.println("cada inserção de linha;"); Serial.println(" - Digite VER para visualizar o conteúdo já escrito;"); Serial.print(" - Digite SALVAR para terminar de inserir conteúdo e"); Serial.println("salva - lo; "); Serial.print(" - Digite LIMPAR para limpar todo conteúdo inserido e "); Serial.println("começar a inserção novamente; "); Serial.print(" - Digite SAIR para limpar todo conteúdo inserido e "); Serial.println("sair sem salvar."); String message; // variável que armazenará o texto à ser gravado na SPIFFS bool controleWhile = true; // controla se deve ou não permanecer no while while (controleWhile) { // estrutura de repetição que controla e // manipula a inserção de dados pelo usuário. String line = readStringSerial(); // lê da Serial a nova linha // para armazenar no arquivo. if (line == "SALVAR") { // se o comando recebido for SALVAR, ... Serial.println(); Serial.println("-> SALVANDO"); writeFile(path, message); // Escreve o arquivo, com os dados // inseridos pelo usuário, // no caminho estabelecido pelo usuário. controleWhile = false; // sai da estrutura de repetição } else if (line == "LIMPAR") { // se o comando recebido for LIMPAR, ... Serial.println(); Serial.println("-> LIMPANDO"); message = ""; // limpa todo o texto inserido pelo usuário } else if (line == "SAIR") { // se o comando recebido for SAIR, ... Serial.println("-> SAINDO"); controleWhile = false; // sai da estrutura de repetição // sem salvar nenhum arquivo. } else if (line == "VER") { // se o comando recebido for VER, ... Serial.println(); Serial.print(message); // mostra na Serial todo o texto já inserido } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.print("-> "); Serial.println(line); message += line; // adiciona a linha inserida pelo usuário // na váriavel de armazenamneto de todo // o texto do novo arquivo. message += "\r\n"; // adiciona os caracteres de retorno // de carro e de nova linha. } } break; } case '3': // Editar Arquivo (adicionar Conteúdo) { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo à ser editado "); Serial.print("(Exemplo: / arquivo.txt): "); String path = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser lido. if (path == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path); if (!SPIFFS.exists(path)) // verfica se um arquivo com o // caminho fornecido não existe. { Serial.println(" - falha ao abrir arquivo para edição."); break; } Serial.println("Digite o conteúdo do arquivo à ser adicionado: "); Serial.println("INSTRUÇÕES: "); Serial.print(" - Digite o conteúdo linha a linha e clique em ENTER após "); Serial.println("cada inserção de linha; "); Serial.print(" - Digite VER_ARQUIVO para visualizar o arquivo já "); Serial.println("escrito; "); Serial.println(" - Digite VER para visualizar o conteúdo já escrito; "); Serial.print(" - Digite SALVAR para terminar de inserir conteúdo "); Serial.println("e salva - lo; "); Serial.print(" - Digite LIMPAR para limpar todo conteúdo inserido e "); Serial.println("começar a inserção novamente; "); Serial.println(" - Digite SAIR para limpar todo conteúdo inserido e sair " "sem salvar."); String message; // variável que armazenará o texto à ser gravado na SPIFFS bool controleWhile = true; // controla se deve ou não permanecer no while while (controleWhile) // estrutura de repetição que controla e // manipula a inserção de dados pelo usuário. { String line = readStringSerial(); // lê da Serial a nova linha // para anexar ao arquivo. if (line == "SALVAR") { // se o comando recebido for SALVAR, ... Serial.println(); Serial.println("-> SALVANDO"); appendFile(path, message);//Anexa ao arquivo os dados // inseridos pelo usuário, // no caminho/arquivo estabelecido pelo usuário. controleWhile = false; // sai da estrutura de repetição } else if (line == "LIMPAR") { // se o comando recebido for LIMPAR, ... Serial.println(); Serial.println("-> LIMPANDO"); message = ""; // limpa todo o texto inserido pelo usuário } else if (line == "SAIR") { // se o comando recebido for SAIR, ... Serial.println("-> SAINDO"); controleWhile = false; // sai da estrutura de repetição // sem salvar nenhum arquivo. } else if (line == "VER_ARQUIVO") //se o comando recebido for VER_ARQUIVO, ... { Serial.println(); Serial.println("-------------------------------------------"); if (readFile(path)) { // mostra na Serial o texto do arquivo já salvo, // no caminho especifcado pelo usuário. Serial.println("-------------------------------------------"); Serial.println(); } } else if (line == "VER") { // se o comando recebido for VER, ... Serial.println(); Serial.print(message); // mostra na Serial todo o texto já inserido } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.print("-> "); Serial.println(line); message += line; // adiciona a linha inserida pelo usuário // na váriavel de armazenamneto de todo // o texto para adicionar no arquivo. message += "\r\n"; // adiciona os caracteres de retorno // de carro e de nova linha. } } break; } case '4': // Renomear Arquivo { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo à ser renomeado "); Serial.print("(Exemplo: / arquivo.txt): "); String path1 = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser renomeado. if (path1 == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path1); Serial.print("-> Para sair digite SAIR, ou "); Serial.print("Digite o caminho e nome do arquivo para renomeação "); Serial.print("(Exemplo: / arquivoRenomeado.txt): "); String path2 = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o novo nome do // caminho do arquivo à ser renomeado. if (path2 == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path2); Serial.print("-> Deseja realmente renomear o arquivo "); Serial.print(path1); Serial.print(" para "); Serial.print(path2); Serial.print(" ? Digite SIM ou NÃO : "); String resposta = readStringSerial(); // lê da Serial a confirmação se o // usuário quer realmente renomear // o arquivo. Serial.println(resposta); if (resposta == "SIM") { // se a resposta do usuário for SIM, ... renameFile(path1, path2); // Renomeia o arquivo de nome // antigo 'path1' para o novo nome 'path2'. } else if (resposta == "NÃO") { // se não, se a resposta do usuário for NÃO, ... Serial.println("O arquivo NÃO foi renomeado."); // Mostra ao usuário que // o arquivo não foi // renomeado. } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.println("Opção inválida. Tente novamente."); // Mostra que a Opção // é inválida. } break; } case '5': // Excluir Arquivo { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo à ser excluído "); Serial.print("(Exemplo: / arquivo.txt) : "); String path = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser excluído. if (path == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(path); Serial.print("Deseja realmente excluir o arquivo "); Serial.print(path); Serial.print(" ? Esta é uma operação não reversível e permanente. "); Serial.print("-> Digite SIM ou NÃO : "); String resposta = readStringSerial(); // lê da Serial a confirmação se o // usuário quer realmente renomear // o arquivo. Serial.println(resposta); if (resposta == "SIM") { // se a resposta do usuário for SIM, ... deleteFile(path); // exclui o arquivo do caminho selecionado pelo usuário } else if (resposta == "NÃO") { // se não, se a resposta do usuário for NÃO, ... Serial.println("O arquivo NÃO foi excluído."); // Mostra ao usuário que // o arquivo não foi // excluído. } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.println("Opção inválida. Tente novamente."); // Mostra que a Opção // é inválida. } break; } case '6': // Copiar Arquivo para... { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo de ORIGEM para cópia "); Serial.print("(Exemplo: / arquivo.txt) : "); String pathOrigem = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser copiado. if (pathOrigem == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(pathOrigem); Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo de DESTINO para cópia "); Serial.print("(Exemplo: / arquivo.txt) : "); String pathDestino = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o novo caminho // do arquivo à ser copiado. if (pathDestino == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(pathDestino); Serial.print("Deseja realmente copiar o arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.print(" ? "); Serial.print("-> Digite SIM ou NÃO : "); String resposta = readStringSerial(); // lê da Serial a confirmação se o // usuário quer realmente copiar // o arquivo. Serial.println(resposta); if (resposta == "SIM") { // se a resposta do usuário for SIM, ... copyFile(pathOrigem, pathDestino); // copia o arquivo do caminho // de origem para o de destino, // ambos selecionados pelo usuário. } else if (resposta == "NÃO") { // se não, se a resposta do usuário for NÃO, ... Serial.println("O arquivo NÃO foi copiado."); // Mostra ao usuário que // o arquivo não foi // copiado. } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.println("Opção inválida. Tente novamente."); // Mostra que a Opção // é inválida. } break; } case '7': // Mover Arquivo para... { Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo de ORIGEM para mover "); Serial.print("(Exemplo: / arquivo.txt) : "); String pathOrigem = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o caminho do arquivo // à ser movido. if (pathOrigem == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(pathOrigem); Serial.print("-> Para sair digite SAIR, ou "); Serial.print("-> Digite o caminho e nome do arquivo de DESTINO para mover "); Serial.print("(Exemplo: / arquivo.txt) : "); String pathDestino = readStringSerial(); // Espera e lê da Serial a entrada // do usuário para o novo caminho // do arquivo à ser movido. if (pathDestino == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(pathDestino); Serial.print("Deseja realmente mover o arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.print(" ? "); Serial.print("-> Digite SIM ou NÃO : "); String resposta = readStringSerial(); // lê da Serial a confirmação se o // usuário quer realmente mover // o arquivo. Serial.println(resposta); if (resposta == "SIM") { // se a resposta do usuário for SIM, ... moveFile(pathOrigem, pathDestino); // move o arquivo do caminho // de origem para o de destino, // ambos selecionados pelo usuário. } else if (resposta == "NÃO") { // se não, se a resposta do usuário for NÃO, ... Serial.println("O arquivo NÃO foi movido."); // Mostra ao usuário que // o arquivo não foi // movido. } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.println("Opção inválida. Tente novamente."); // Mostra que a Opção // é inválida. } break; } case '8': // Formatar SPIFFS { Serial.println("-> Para sair digite SAIR, ou siga as instruções seguintes."); Serial.println("ATENÇÃO"); Serial.print(" Esta opção permite a formatação da SPIFFS. Este "); Serial.println(" procedimento de formatação apagará tudo."); Serial.print(" Se você tiver algum arquivo importante em seu sistema "); Serial.println(" de arquivos, não siga esta opção "); Serial.println(" antes de salvá -lo em outro local."); Serial.print("-> Deseja realmente seguir com esta opção ? Digite SIM "); Serial.println("ou NÃO : "); String resposta = readStringSerial(); // lê da Serial a confirmação se o // usuário quer realmente formatar // a partição SPIFFS. if (resposta == "SAIR") // se a entrada do usuário for "SAIR", a operação é // cancelada. { Serial.println("\nSAINDO"); break; } Serial.println(resposta); if (resposta == "SIM") { // se a resposta do usuário for SIM, ... Serial.println("Formatando SPIFFS"); formatSPIFFS(); // formata a partição SPIFFS } else if (resposta == "NÃO") {//se não, se a resposta do usuário for NÃO, ... Serial.println("SPIFFS não será formatada"); // Mostra ao usuário que // a partição SPIFFS // não foi formatada. } else { // se nenhum comando pré-estabelecido for encontrado, ... Serial.println("Opção inválida. Tente novamente."); // Mostra que a Opção // é inválida. } break; } default: // Opção selecionada fora dos valores padrão Serial.println("Opção Inválida"); // Mostra que a Opção // é inválida. break; } delay(1000); Serial.println(); } /** Espera o usuário da Serial digitar e enviar algum texto. @return o texto digitado no tipo String. */ String readStringSerial() { String retorno; // String de retorno bool controleLeitura = true; // controle do while while (controleLeitura) { // espera o usuário digitar algo na Serial delay(10); if (Serial.available()) { // se houver algo na Serial para ler, ... retorno = Serial.readString(); // lê os caracteres do buffer serial em // uma String. controleLeitura = false; // define a variável de controle do while // para sair da repetição. } } return retorno; // retorna a String capturada } /** Espera o usuário da Serial digitar e enviar algum caractere. @return o caractere digitado no tipo char. */ char readCharSerial() { char retorno; // caractere de retorno bool controleLeitura = true; // controle do while while (controleLeitura) { // espera o usuário digitar algo na Serial delay(10); if (Serial.available()) { // se houver algo na Serial para ler, ... retorno = Serial.read(); // lê o caractere da serial controleLeitura = false; // define a variável de controle do while // para sair da repetição. } } return retorno; // retorna o caractere capturado } /** Lista todos os arquivos da SPIFFS @return true se ocorreu tudo certo ou false se houve algum erro */ bool listDir() { File root = SPIFFS.open("/"); // Abre o "diretório" onde estão os arquivos na SPIFFS // e passa o retorno para // uma variável do tipo File. if (!root) // Se houver falha ao abrir o "diretório", ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir o diretório"); return false; } File file = root.openNextFile(); // Relata o próximo arquivo do "diretório" e // passa o retorno para a variável // do tipo File. int qtdFiles = 0; // variável que armazena a quantidade de arquivos que // há no diretório informado. while (file) { // Enquanto houver arquivos no "diretório" que não foram vistos, // executa o laço de repetição. Serial.print(" FILE : "); Serial.print(file.name()); // Imprime o nome do arquivo Serial.print("\tSIZE : "); Serial.println(file.size()); // Imprime o tamanho do arquivo qtdFiles++; // Incrementa a variável de quantidade de arquivos file = root.openNextFile(); // Relata o próximo arquivo do diretório e // passa o retorno para a variável // do tipo File. } if (qtdFiles == 0) // Se após a visualização de todos os arquivos do diretório // não houver algum arquivo, ... { // Avisa o usuário que não houve nenhum arquivo para ler e retorna false. Serial.print(" - Sem arquivos para ler. Crie novos arquivos pelo menu "); Serial.println("principal, opção 2."); return false; } return true; // retorna true se não houver nenhum erro } /** Lê o conteúdo de um arquivo especificado da SPIFFS @param path : o caminho/nome do arquivo à ser listado @return true se ocorreu tudo certo ou false se houve algum erro */ bool readFile(String path) { File file = SPIFFS.open(path); // Abre o caminho do arquivo da SPIFFS no // e passa o retorno para uma variável do // tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para leitura"); return false; } while (file.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de repetição. { Serial.write(file.read()); // Escreve na Serial os bytes obtidos da leitura // do arquivo. } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro } /** Escreve o conteúdo informado em um novo arquivo da SPIFFS @param path : o caminho/nome do novo arquivo à ser escrito @param message : o conteúdo do arquivo que será gravado @return true se ocorreu tudo certo ou false se houve algum erro */ bool writeFile(String path, String message) { Serial.print("Gravando o arquivo "); Serial.print(path); Serial.println(" : "); File file = SPIFFS.open(path, FILE_WRITE); // Abre o arquivo, no modo escrita, // onde será gravado o seu conteúdo // e passa o retorno para // uma variável do tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } if (file.print(message)) // Se a escrita do arquivo com seu conteúdo der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo escrito"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na gravação do arquivo"); return false; } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro } /** Adiciona conteúdo informado em um arquivo existente da SPIFFS @param path : o caminho/nome do arquivo à ser escrito @param message : o conteúdo do arquivo que será anexado @return true se ocorreu tudo certo ou false se houve algum erro */ bool appendFile(String path, String message) { Serial.print("Anexando conteúdo ao arquivo "); Serial.print(path); Serial.println(" : "); File file = SPIFFS.open(path, FILE_APPEND); // Abre o arquivo, no modo anexar, // onde será adicionado conteúdo // e passa o retorno para // uma variável do tipo File. if (!file) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir o arquivo para anexar"); return false; } if (file.print(message)) // Se a escrita do arquivo com seu conteúdo der certo, ... { // informa ao usuário que deu certo Serial.println(" - mensagem anexada"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - Falha ao anexar"); return false; } file.close(); // Fecha o arquivo return true; // retorna true se não houver nenhum erro } /** Renomeia um determinado arquivo existente da SPIFFS @param path1 : o caminho/nome do arquivo à ser renomeado @param path2 : o novo caminho/nome do arquivo @return true se ocorreu tudo certo ou false se houve algum erro */ bool renameFile(String path1, String path2) { Serial.print("Renomenando arquivo "); Serial.print(path1); Serial.print(" para "); Serial.print(path2); Serial.println(" : "); if (SPIFFS.rename(path1, path2)) // renomeia o arquivo passando o // antigo caminho/nome (path1) e o // novo caminho/nome (path2) // Se a renomeação der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo renomeado"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha ao renomear"); return false; } return true; // retorna true se não houver nenhum erro } /** Exclui um determinado arquivo existente da SPIFFS @param path : o caminho/nome do arquivo à ser excluído @return true se ocorreu tudo certo ou false se houve algum erro */ bool deleteFile(String path) { Serial.print("Deletando arquivo "); Serial.print(path); Serial.println(" : "); if (SPIFFS.remove(path)) // exclui o arquivo passando o // caminho/nome (path) // Se a exclusão der certo, ... { // informa ao usuário que deu certo Serial.println(" - arquivo excluído"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na exclusão"); return false; } return true; // retorna true se não houver nenhum erro } /** Move um determinado arquivo existente da SPIFFS para outro local @param pathOrigem : o caminho/nome do arquivo de origem @param pathDestino : o caminho/nome do arquivo de destino @return true se ocorreu tudo certo ou false se houve algum erro */ bool moveFile(String pathOrigem, String pathDestino) { Serial.print("Movendo arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.println(" : "); File fileOrigem = SPIFFS.open(pathOrigem); // Abre o arquivo do local de origem // e passa o retorno para // uma variável do tipo File. if (!fileOrigem) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para cópia"); return false; } File fileDestino = SPIFFS.open(pathDestino, FILE_WRITE); // Abre o arquivo do // local de destino, // no modo escrita, // e passa o retorno para // uma variável do tipo // File. if (!fileDestino) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } while (fileOrigem.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de // repetição. { if (!fileDestino.write(fileOrigem.read())) // Se a escrita byte-a-byte do arquivo // lido, também byte-a-byte, resultar // em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha para mover o arquivo"); return false; } } //Fecha os arquivos de origem e de destino fileOrigem.close(); fileDestino.close(); if (!SPIFFS.remove(pathOrigem)) // exclui o arquivo de origem passando o // caminho/nome (path) // Se a exclusão resultar em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha na exclusão"); return false; } return true; // retorna true se não houver nenhum erro } /** Copia um determinado arquivo existente da SPIFFS para outro local @param pathOrigem : o caminho/nome do arquivo de origem @param pathDestino : o caminho/nome do arquivo de destino @return true se ocorreu tudo certo ou false se houve algum erro */ bool copyFile(String pathOrigem, String pathDestino) { Serial.print("Copiando arquivo "); Serial.print(pathOrigem); Serial.print(" para "); Serial.print(pathDestino); Serial.println(" : "); File fileOrigem = SPIFFS.open(pathOrigem); // Abre o arquivo do local de origem // e passa o retorno para // uma variável do tipo File. if (!fileOrigem) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para cópia"); return false; } File fileDestino = SPIFFS.open(pathDestino, FILE_WRITE); // Abre o arquivo do // local de destino, // no modo escrita, // e passa o retorno para // uma variável do tipo // File. if (!fileDestino) // Se houver falha ao abrir o caminho, ... { // informa ao usuário que houve falhas e sai da função retornando false. Serial.println(" - falha ao abrir arquivo para gravação"); return false; } while (fileOrigem.available()) // Enquanto houver algum byte disponível para // leitura do arquivo, executa o bloco de // repetição. { if (!fileDestino.write(fileOrigem.read())) // Se a escrita byte-a-byte do arquivo // lido, também byte-a-byte, resultar // em algum erro, ... { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - falha para mover o arquivo"); return false; } } //Fecha os arquivos de origem e de destino fileOrigem.close(); fileDestino.close(); return true; // retorna true se não houver nenhum erro } /** Formata a SPIFFS. É excluído todos os arquivos permanentemente. @return true se ocorreu tudo certo ou false se houve algum erro */ bool formatSPIFFS() { //Tela de animação Serial.print("Formatando a partição SPIFFS. Aguarde"); Serial.print("."); delay(200); Serial.print("."); delay(200); Serial.print("."); delay(200); Serial.print("."); delay(200); Serial.print("."); delay(200); Serial.println("."); delay(200); if (SPIFFS.format()) //Formata o sistema de arquivos SPIFFS. // Se não houver erros, ... { // informa ao usuário que deu certo. Serial.println(" - Formatação da partição SPIFFS concluída com sucesso!"); } else { // informa ao usuário que deu erros e sai da função retornando false. Serial.println(" - Formatação da partição SPIFFS não foi bem - sucedida"); return false; } return true; // retorna true se não houver nenhum erro }
ATENÇÃO: Para este exemplo funcionar, o Monitor Serial deve estar configurado para “”Nenhum final-de-linha”:
Primeiro, começamos incluindo as bibliotecas necessárias:
Em seguida, fazemos os protótipos das Funções.
Em void setup()
, inicializamos a Serial.
Também inicializamos a SPIFFS e imprimimos na Serial o resultado da inicialização da SPIFFS. Caso a SPIFFS tenha a inicialização falhada, entramos em uma estrutura de repetição infinita (while()
).
Em void loop()
, criamos uma variável para armazenar a opção do menu principal selecionada pelo usuário.
Em seguida, ainda em void loop()
, lemos a entrada do usuário para a opção que ele deseja ir.
Logo após, através de uma estrutura condicional switch()
, verificamos qual opção o usuário selecionou.
Caso a opção selecionada seja '1'
(Ler arquivo), listamos todos os arquivos do “diretório” /. Caso houver alguma falha na listagem de arquivos, saímos da operação.
Em seguida, lemos a entrada do usuário do caminho do arquivo a ser lido.
Caso o usuário tenha dado o comando SAIR
, cancelamos a operação.
Logo após, é lido o conteúdo do arquivo e mostrado na Serial. Após isso, saímos da condicional switch()
com o comando break
.
Nas outras opções (2 a 7), o algoritmo é semelhante, diferenciando apenas da chamada da função que seguirá de acordo com a ação pretendida e na leitura dos comandos efetuados pelo usuário.
Ainda em void loop()
, damos uma pausa de 1 segundo e finalizamos esta função.
A função readStringSerial()
, faz a leitura da entrada do usuário e retorna à chamada da função.
Nesta função, começamos criando as variáveis locais de retorno e a que controlará se ainda devemos esperar o usuário digitar algo na Serial.
Na estrutura de repetição while()
, damos uma pausa de 10 milissegundos.
Ainda na estrutura de repetição, esperamos haver algum byte à ser lido na Serial. Caso haja, fazemos a leitura da entrada do usuário através da função Serial.readString()
. Ao terminar a armazenagem da entrada do usuário, saímos da estrutura de repetição.
E então retornamos a entrada do usuário para a chamada da função.
A função readCharSerial()
, é semelhante à anterior, só que é lido um caractere em vez de uma String.-
A leitura do caractere desta função é obtida através da função Serial.read()
.
Veja abaixo no vídeo a demonstração do funcionamento:
Conhecendo, agora, a SPIFFS, pode-se desenvolver projetos que necessitem armazenagem de muitos tipos diferentes de dados, como dados de calibração, sistemas de arquivos, armazenamento de parâmetros, arquivos para um WebServer (HTML, CSS, JS, entre outros), etc.
Curtiu o post? Avalie e deixe um comentário! Siga-nos também no Instagram e nos marque quando fizer algum projeto nosso: @eletrogate. Até a próxima!
Conheça a Metodologia Eletrogate e ofereça aulas de robótica em sua escola!
|
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!