Great News: Files Can Be Saved Inside ESP32!
by Fernando Koyanagi in Circuits > Microcontrollers
1518 Views, 3 Favorites, 0 Comments
Great News: Files Can Be Saved Inside ESP32!
![Ótima notícia: Agora você pode salvar tudo dentro do ESP32!](/proxy/?url=https://content.instructables.com/FQ3/WCX6/JR0YKTHX/FQ3WCX6JR0YKTHX.jpg&filename=Ótima notícia: Agora você pode salvar tudo dentro do ESP32!)
![1.1.png](/proxy/?url=https://content.instructables.com/F78/K6MD/JR0YKTK4/F78K6MDJR0YKTK4.png&filename=1.1.png)
![1.png](/proxy/?url=https://content.instructables.com/FJ7/3KEM/JR0YKTK5/FJ73KEMJR0YKTK5.png&filename=1.png)
Today we’ll discuss about SPIFFS (SPI Flash File System). These essentially are file systems. Inside the Flash memory, the program is recorded in one piece and the other is free for its own program to save and access files. In this case, Flash is divided by a Lib. This is helpful because it prevents complete dependability on an SD Card, meaning less hardware. And yes, 1.3 megabytes is enough to save a lot. I’ll also discuss an application with fixed records on file using SPIFFS of ESP32, and present the assembly and source code used.
Demonstration
![demo.jpg](/proxy/?url=https://content.instructables.com/FAV/AFJ1/JR0YKTKC/FAVAFJ1JR0YKTKC.jpg&filename=demo.jpg)
Resources Used
![7.png](/proxy/?url=https://content.instructables.com/FPD/JNBN/JR0YKTOG/FPDJNBNJR0YKTOG.png&filename=7.png)
· ESP32 WROOM Dev Board
· Display SPI TFT 1.8 ''
· DHT22
· 4k7 ohm resistor
· Button
· 10k ohm resistor
· Jumpers
Assembly
![8.png](/proxy/?url=https://content.instructables.com/FCM/RHH1/JR0YKTPT/FCMRHH1JR0YKTPT.png&filename=8.png)
![9.png](/proxy/?url=https://content.instructables.com/F16/VXM5/JR0YKTQ2/F16VXM5JR0YKTQ2.png&filename=9.png)
Code - Installing Libraries
Install the following libraries on your Arduino IDE:
Adafruit GFX
https://github.com/adafruit/Adafruit-GFX-Library
Adafruit ST7735
https://github.com/adafruit/Adafruit-ST7735-Library
SimpleDHT
Code - Diagram
![12.png](/proxy/?url=https://content.instructables.com/F00/ILJF/JR0YKTVD/F00ILJFJR0YKTVD.png&filename=12.png)
![13.png](/proxy/?url=https://content.instructables.com/FYP/GSG9/JR0YKTW6/FYPGSG9JR0YKTW6.png&filename=13.png)
ESP32 Code - Declarations and Variables
#include <Arduino.h> // lib Arduino (opcional)
#include <Adafruit_GFX.h> // Biblioteca de gráficos #include <Adafruit_ST7735.h> // Biblioteca do hardware ST7735 #include <SPI.h> // Biblioteca para comunicação SPI #include <Fonts/FreeSerif9pt7b> // Fonte Serif que é usada no display #include <SimpleDHT.h> // lib do DHT #include "FS_File_Record.h" // Nossa lib personalizada do SPIFFS // Pinos do display #define TFT_DC 12 // A0 #define TFT_CS 13 // CS #define TFT_MOSI 14 // SDA #define TFT_CLK 27 // SCK #define TFT_RST 0 // RESET #define TFT_MISO 0 // MISO // Pino do botão de exclusão de arquivo const int buttonPin = 34; // Tamanho dos registros de temperatura e umidade (13 caracteres), exemplo: // 100.00;100.00 // temp;umid const int sizeOfRecord = 13; //100.00 // Variáveis usadas em 'formatValue' const int integralPartSize = 3, decimalPartSize = 2; // Objeto da nossa lib que recebe o nome do arquivo e tamanho fixo de registro FS_File_Record ObjFS("/dht22.bin", sizeOfRecord); // Pino do DHT22 const int pinDHT22 = 32; // Objeto do dht22 SimpleDHT22 dht22(pinDHT22); // Objeto do display Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST); // Altura da fonte, usada na função resetDisplay int fontHeight = 7; // Variáveis usada para contagem/comparação de tempo (millis) na função timeout long millisRefShowSpace; bool flagShowSpace = false; // String que recebe as mensagens de erro String errorMsg; // Variável que guarda o último registro obtido String lastRecord = "";
Setup
![18.png](/proxy/?url=https://content.instructables.com/FOE/MCUH/JR0YKU6W/FOEMCUHJR0YKU6W.png&filename=18.png)
void setup()
{ // Inicializamos o display tft.initR(INITR_BLACKTAB); // Iniciamos a Serial com velocidade de 115200 Serial.begin(115200); // Seta botão como entrada (INPUT) pinMode(buttonPin, INPUT); // Exibe na serial "Starting..." para debug Serial.print("Starting..."); // Se não foi possível iniciar o File System, exibimos erro e reiniciamos o ESP if(!ObjFS.init()) { Serial.println("File system error"); delay(1000); ESP.restart(); } // Exibimos mensagem Serial.println("File system ok"); // Se o arquivo não existe, criamos o arquivo if(!ObjFS.fileExists()) { Serial.println("New file"); ObjFS.newFile(); // Cria o arquivo } // Iniciamos uma task que irá ler o botão de exclusão xTaskCreatePinnedToCore( buttonEvent, //Função que será executada "buttonEvent", //Nome da tarefa 10000, //Tamanho da pilha &tft, //Parâmetro da tarefa (no caso não usamos) 2, //Prioridade da tarefa NULL, //Caso queira manter uma referência para a tarefa que vai ser criada (no caso não precisamos) 0); //Número do core que será executada a tarefa (usamos o core 0 para o loop ficar livre com o core 1) resetDisplay(); // EXEMPLO DE BUSCA (FIND) // Obs: O primeiro registro se posiciona na pos 0 (zero) // String reg = ObjFS.findRecord(10); // showDisplay(reg); // Exibimos o arquivo showFile(); }
ResetDisplay
// Limpa o display e posiciona o cursor no início
void resetDisplay() { // Verificamos se o display não está ocupado por alguma das tasks if(!flagDisplayisBusy) { flagDisplayisBusy = true; tft.setFont(&FreeSerif9pt7b); tft.fillScreen(ST77XX_BLACK); tft.setTextColor(ST7735_WHITE); tft.setCursor(0,fontHeight+5); tft.setTextSize(1); flagDisplayisBusy = false; } }
ShowFile
// Exibimos no display o arquivo, pausando a exibição a cada 6 registros
void showFile() { int count = 0; String linha = ""; // Exibe na serial e no display o início do arquivo Serial.println("# Begin of file #"); showDisplay("# Begin of file #"); errorMsg = ""; // Posiciona o ponteiro do arquivo no início ObjFS.rewind(); // Exibe todos os registros até o fim while(ObjFS.readFileNextRecord(&linha, &errorMsg) && linha != "") { Serial.println(linha); count++; // A cada 6 registros, pausamos e resetamos o display if(count % 6 == 0) { //Exibe "..." sinalizando que ainda não é o fim do arquivo showDisplay("..."); // Aguarda 1.5s para poder visualizar os valores delay(1500); // Limpa display resetDisplay(); } showDisplay(linha); } // Se existir mensagem de erro exibe na serial e no display if(errorMsg != "") { Serial.println(errorMsg); showDisplay(errorMsg); } // Exibe na serial e no display o fim do arquivo Serial.println("# End of file #"); showDisplay("# End of file #"); delay(1500); }
Loop
![22.png](/proxy/?url=https://content.instructables.com/FV9/ERR0/JR0YKUBJ/FV9ERR0JR0YKUBJ.png&filename=22.png)
void loop()
{ // Se não houver memória disponível, exibe e reinicia o ESP if(!ObjFS.availableSpace()) { Serial.println("Memory is full!"); showDisplay("Memory is full!"); delay(10000); return; } // Lê temperatura e umidade do DHT String values = readDHTValues(); // Escrevemos no arquivo e exibimos erro ou sucesso na serial para debug if(values != "" && !ObjFS.writeFile(values, &errorMsg)) Serial.println(errorMsg); else Serial.println("Write ok");
Loop (continued) and ReadDHTValues
![23.png](/proxy/?url=https://content.instructables.com/FSL/O09B/JR0YKUDW/FSLO09BJR0YKUDW.png&filename=23.png)
// Atribui para a variável global a última amostra
lastRecord = values; // Exibe a última amostra no display showLastRecord(); // Exibimos o espaço total, usado e disponível no display, de tempo em tempo showAvailableSpace(); // Aguarda 2500ms delay(2500); }
// Função que lê e formata os dados de temperatura e umidade
String readDHTValues() { float temperature = 0; float humidity = 0; int err = SimpleDHTErrSuccess; // Lê sensor if ((err = dht22.read2(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) { Serial.print("Read DHT22 failed, err="); Serial.println(err); delay(1000); return "-------------"; //importante: deve possuir o mesmo sizeOfRecord, ou seja, 13 caracteres } // Retorna valores formatados, separados por ponto-virgula return formatValue(temperature)+";"+formatValue(humidity); }
ShowLastRecord and ShowAvailableSpace
![24.png](/proxy/?url=https://content.instructables.com/FOM/32SC/JR0YKUGM/FOM32SCJR0YKUGM.png&filename=24.png)
// Exibe última amostra de temperatura e umidade obtida
void showLastRecord() { resetDisplay(); showDisplay("Last record:"); showDisplay(lastRecord); }
// Exibe o espaço total, usado e disponível no display
void showAvailableSpace() { long prevMillis; if(!eventButton && timeout(10000, &millisRefShowSpace, &flagShowSpace)) { Serial.println("Space: "+String(ObjFS.getTotalSpace())+" Bytes"); Serial.println("Used: "+String(ObjFS.getUsedSpace())+" Bytes"); resetDisplay(); showDisplay("Total space:"); showDisplay(String(ObjFS.getTotalSpace())+" Bytes"); tft.setTextColor(ST7735_YELLOW); showDisplay("Used space:"); showDisplay(String(ObjFS.getUsedSpace())+" Bytes"); tft.setTextColor(ST77XX_GREEN); showDisplay("Free space:"); showDisplay(String(ObjFS.getTotalSpace()-ObjFS.getUsedSpace())+" Bytes"); // Registramos 4 vezes enquanto a mensagem aparece no display for(int i=0; i<4; i++) { // Lê temperatura e umidade do DHT String values = readDHTValues(); // Escrevemos no arquivo e exibimos erro ou sucesso na serial para debug if(values != "" &&!ObjFS.writeFile(values, &errorMsg)) Serial.println(errorMsg); else Serial.println("Write ok"); prevMillis = millis(); while(prevMillis+2500 > millis() && !eventButton); if(eventButton) { eventButton = false; // aguarda exibição "FILE DELETED" delay(500); break; } } //fim do for } //fim do if(timeout) } //fim da função
ButtonEvent
![26.png](/proxy/?url=https://content.instructables.com/FX4/7MY8/JR0YKUI6/FX47MY8JR0YKUI6.png&filename=26.png)
![27.png](/proxy/?url=https://content.instructables.com/FSU/YZQ5/JR0YKUIV/FSUYZQ5JR0YKUIV.png&filename=27.png)
// Função executada pela task de evento do botão
void buttonEvent(void* display) { TickType_t taskDelay = 10 / portTICK_PERIOD_MS; // IMPORTANTE: A tarefa não pode terminar, deve ficar presa em um loop infinito while(true) { // Se o botão foi pressionado e a flag estiver "false" if(digitalRead(buttonPin) == HIGH && !eventButton) { // Sinalizamos que o botão foi pressionado eventButton = true; // Tenta excluir o arquivo if(ObjFS.destroyFile()) { Serial.println("File deleted"); // Enquanto o display estiver ocupado, aguardamos while(flagDisplayisBusy) vTaskDelay(taskDelay); // Sinalizamos que o display está ocupado flagDisplayisBusy = true; // Exibimos no display showFileDeleted(true); // Sinalizamos que o display está desocupado flagDisplayisBusy = false; lastRecord = ""; }
else
{ Serial.println("Failed to delete file"); while(flagDisplayisBusy) vTaskDelay(taskDelay); // Sinalizamos que o display está ocupado flagDisplayisBusy = true; // Exibimos no display showFileDeleted(false); // Sinalizamos que o display está desocupado flagDisplayisBusy = false; } } else if(digitalRead(buttonPin) == LOW && eventButton) // Se o botão foi solto e a flag estiver "true" { // Sinalizamos que o botão foi solto eventButton = false; } // Executamos um delay de 10ms, os delays executado nas xTasks são diferentes vTaskDelay(taskDelay); //IMPORTANTE: SEMPRE DEIXAR UM DELAY PARA ALIMENTAR WATCHDOG } }
ShowFileDeleted
![28.png](/proxy/?url=https://content.instructables.com/FR2/1FR5/JR0YKUO4/FR21FR5JR0YKUO4.png&filename=28.png)
// Posiciona cursor no centro e exibe a mensagem "FILE DELETED" em amarelo
void showFileDeleted(bool sucess) { // Posição y aonde o texto será exibido int y = (tft.height()/2)-(fontHeight*2); // Limpamos o display (aqui não usamos a função resetDisplay porque a flag displayisBusy neste momento está como true e o resetDisplay não será executado) // Portanto limpamos o display executando os comandos separadamente tft.setFont(&FreeSerif9pt7b); tft.fillScreen(ST77XX_BLACK); tft.setTextColor(ST7735_WHITE); tft.setTextSize(1); // Define cor do texto amarela tft.setTextColor(ST7735_YELLOW); // Posiciona no centro de eixo y tft.setCursor(0, y); // Se foi possível excluir if(sucess) { // Exibe mensagem "FILE DELETED" tft.println(" FILE"); tft.println(" DELETED!"); } else // Se não foi possível excluir { // Exibe mensagem "CANNOT DELETE" tft.println(" CANNOT"); tft.println(" DELETE"); } // Define cor do texto branca tft.setTextColor(ST7735_WHITE); }
Header FS_File_Record: List of Functions
![29.png](/proxy/?url=https://content.instructables.com/F27/KIHS/JR0YKUQ0/F27KIHSJR0YKUQ0.png&filename=29.png)
Download the .cpp and .h files from the attached FS_File_Record library next to the project!
class FS_File_Record
{ // Todas as funções desta lib são publicas, mais detalhes em FS_File_Record.cpp public: FS_File_Record(String, int); FS_File_Record(String); bool init(); bool readFileLastRecord(String *, String *); bool destroyFile(); String findRecord(int); bool rewind(); bool writeFile(String, String *); bool seekFile(int); bool readFileNextRecord(String *, String *); String getFileName(); void setFileName(String); int getSizeRecord(); void setSizeRecord(int); void newFile(); bool fileExists(); bool availableSpace(); int getTotalSpace(); int getUsedSpace(); };