banner

Ir para o Forum

Ótima notícia: Agora você pode salvar tudo dentro do ESP32



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();
};






FAÇA O DOWNLOAD DOS ARQUIVOS:



3 comentários:

  1. 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.

    ResponderExcluir
  2. O 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!

    ResponderExcluir

Tecnologia do Blogger.