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