Pessoal, hoje vamos falar do
SPIFFS (SPI Flash File System), ou seja, Sistemas de Arquivos. Dentro da
memória Flash, em um pedaço é gravado o programa e o outro pedaço fica livre
para esse próprio programa gravar e acessar arquivos. A Flash, neste caso, é
dividida por uma Lib. Isso tudo é muito bom porque nem sempre você quer usar um
SD Card, correto? Isso significa um hardware a menos, mas, sim, 1.3 megabyte, o
suficiente para gravar muita coisa. Vou, então, te apresentar uma aplicação com
registros fixos em arquivo utilizando o SPIFFS do ESP32 e apresentar montagem e
código fonte utilizado.
Demonstração
Recursos usados
- ESP32 WROOM Dev Board
- Display SPI TFT 1.8’’
- DHT22
- Resistor 4k7 ohm
- Botão
- Resistor 10k ohm
- Jumpers
Montagem
Código
Instalação de Bibliotecas
Instale na sua Arduino IDE as
bibliotecas abaixo:
Adafruit GFX
Adafruit ST7735
SimpleDHT
Código
Diagrama
Código ESP32
Declarações e Variáveis
#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.h> // 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
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
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 (continuação) e readDHTValues
// 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 e ShowAvailableSpace
// 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
// 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
// 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: Lista de funções
Baixe os arquivos .cpp e .h da
biblioteca FS_File_Record anexados junto ao projeto!
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(); };
11 Comentários
Boa Noite, tem algum limite de gravação nessa memória? Pergunto em relação a apagar e escrever.. se existe um limite físico na quantidade de gravação.
ResponderExcluirMuito legal, da certo no esp8266 ?
ResponderExcluirO registro dos dados pode ser acompanhado da data e horário? É interessante para uma análise mais profunda da informação. Obrigado, um excelente início de 2019 e que o ano novo seja de alegrias e saúde!
ResponderExcluirOlá Fernando, excelente matéria.
ResponderExcluircomo enxergar o conteúdo da pasta 'data' no SPIFFS do ESP32 para certificar de que estão todos lá? Há alguma forma de vê-los? Obrigado
Boa noite Fernando! Se quiser salvar mais de um arquivo, tem como!?
ResponderExcluirBoa noite Fernando! Se quiser salvar mais de um arquivo, tem como!?
ResponderExcluirBoa noite Professor, estou tentando fazer o download da biblioteca .INO, mas na hora de descompactar tenho a mensagem de que o arquivo está corrompido ou danificado.
ResponderExcluirConseguiu resolver? Estou com mesmo problema
ExcluirPega todos os arquivos que tem no zip, e coloca em uma pasta
ExcluirMuito legal Prof. Fernando. Esse código serve para os displays ILI9341? Obg
ResponderExcluirOnde está a biblioteca FS_File_Record ?
ResponderExcluir