Manual
do
Maker
.
com
Vou começar falando a real; é mais fácil e indolor utilizar os recursos da API do Arduino para ESP32 se quiser utilizar o sistema de arquivos para gravar seus próprios arquivos. Esse artigo só tem o propósito de informação, eu prefiro esse outro artigo a respeito.
É muito legal utilizar os recursos nativos do ESP-IDF, menos a manipulação de arquivos, que exige uma quantidade considerável de esforço. Nesse post, estou utilizando a UART, task, SPIFFS e o display OLED onboard do ESP32 da CurtoCircuito. Ainda não tem? Que dó. Mas você pode pegar o seu nesse link. Aproveite e dê uma olhada nos outros modelos, tem coisas muito bacanas relacionadas ao ESP32.
Não se assuste com a quantidade de includes. Conforme você for se acostumando, saberá de onde vem cada recurso e fará automaticamente seus includes. Pra esse artigo, tem esse tantinho:
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_spiffs.h"
#include "driver/uart.h"
#include "freertos/task.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Para inicializar o SPIFFS, você precisa primeiramente indicar um diretório na raiz e sempre apontar o caminho completo para esse diretório. No caso, estou utilizando "/spiffs". Esse diretório sempre deve ser precedido por "/".
Outra coisa muito comum no ESP-IDF é a utilização de estruturas com tipos próprios. Para o SPIFFS, uma manipulação básica dessa estrutura pode ser:
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true
};
Não é necessário especificar um label e utilizar a opção format_if_mount_failedé uma ótima escolha para o caso de uma inicialização direta pelo firmware. Se você criar o SPIFFS no computador para depois fazer o flashing, essa opção pode ser dispensada.
Depois, lá no setup() inicializamos o SPIFFS e validamos sua criação:
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
uart_write_bytes(EX_UART_NUM,(const char*)"Falha ao montar ou formatar o sistema de arquivos\n", 66);
} else if (ret == ESP_ERR_NOT_FOUND) {
uart_write_bytes(EX_UART_NUM,(const char*)"Nao pude encontrar a particao\n",41);
} else {
uart_write_bytes(EX_UART_NUM,(const char*)"Falha ao iniciar o SPIFFS\n", 38);
}
while (true);
}
Já descrevi os detalhes dessa configuração nesse outro artigo, não vou detalhar tudo de novo. Basicamente, montamos a estrutura:
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
Depois inicializamo-la no setup:
//serial
uart_param_config(EX_UART_NUM, &uart_config);
uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);
uart_write_bytes(EX_UART_NUM,(const char*)"iniciando...\n",13);
Uma task é como uma thread, que possui execução assíncrona. Já escrevi alguns artigos sobre diversas formas de utilizar as tasks, dê uma olhada no menu ESP32 para conferir.
Para criar essa task, primeiro precisamos declarar a função que será chamada. Fiz algo bem simplório:
static void fileHandler(void *pvParameters){
writeFile("teste.txt","Manual do Maker");
renameFile("teste.txt","dobitaobyte.txt");
readFile("teste.txt");
vTaskDelete(NULL);
}
Depois basta iniciar a task sempre que desejar. Eu só fiz uma chamada no setup():
xTaskCreatePinnedToCore(fileHandler,"fileHandler",10000,NULL,1,NULL,0);
Também escrevi alguns artigos a respeito. Mais uma vez, dê uma olhada no menu ESP32, ou se quiser algo um pouco mais elaborado, tem esse tema de natal que escrevi recentemente. Nele, utilizo um buzzer para tocar Jingle bell e fiz um floco de neve para a animação no display.
Para escrever, simplesmente use essa função:
void drawText(const void *text,byte posX, byte posY){
char *text2display = (char*) text;
//display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(posX,posY);
display.println((char*)text);
//display.setCursor(0,10);
display.display();
}
O teste ainda está cheio de bugs, não estou fazendo todos os tratamentos de erros possíveis, mas é uma prova de conceito funcional, quando eu tiver um projetinho divertido, vou melhorar isso e explicar o que poderia acontecer baseado nesse artigo. Como você pôde ver na declaração da task, estou escrevendo um arquivo, renomeando-o e posteriormente lendo-o. Dentro da função readFile aproveitei a linha da leitura do arquivo e exibi o resultado no display, que resultou na imagem de destaque do artigo.
Não é muito empolgante, mas eu precisava fazer essa referência para assimilações nos próximos artigos relacionados a data logger etc.
O código de teste:
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_spiffs.h"
#include "driver/uart.h"
#include "freertos/task.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true
};
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
#define EX_UART_NUM UART_NUM_0
#define BUF_SIZE (70)
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
char *data = (char *) malloc(BUF_SIZE);
static void fileHandler(void *pvParameters){
writeFile("teste.txt","Manual do Maker");
renameFile("teste.txt","dobitaobyte.txt");
readFile("teste.txt");
vTaskDelete(NULL);
}
void drawText(const void *text,byte posX, byte posY){
char *text2display = (char*) text;
//display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(posX,posY);
display.println((char*)text);
//display.setCursor(0,10);
display.display();
}
void clearData(){
for (byte i=0;i<BUF_SIZE;i++){
data[i] = 0;
}
}
void removeFile(const char *filename){
struct stat st;
clearData();
strcpy(data,"/spiffs/");
strcat(data,filename);
if (stat(data,&st) == 0){
unlink(data);
}
else{
uart_write_bytes(EX_UART_NUM,(const char*)"Arquivo nao encontrado.\n",24);
}
}
void renameFile(const char *sourceFile, const char *targetFile){
char src[sizeof(sourceFile)+8];
strcpy(src,"/spiffs/");
strcat(src,sourceFile);
char target[sizeof(targetFile)+8];
strcpy(target,"/spiffs/");
strcat(target,(char*)targetFile);
uart_write_bytes(EX_UART_NUM,(const char*)"Renomeando...\n",14);
//if (rename(src,target) != 0){
// uart_write_bytes(EX_UART_NUM,(const char*)"Falha ao renomear.\n",19);
// }
}
void readFile(const char *filename){
uart_write_bytes(EX_UART_NUM,(const char*)"Lendo...\n",19);
char val[30];
strcpy(val,"/spiffs/");
strcat(val,filename);
FILE *f = fopen(data,"r");
// if (f == NULL) {
// uart_write_bytes(EX_UART_NUM,(const char*)"Falha na leitura.\n",18);
// return;
// }
char line[21];
fgets(line,21,f);
fclose(f);
char *p =strchr(line,'\n');
if (p){
*p = '\0';
}
uart_write_bytes(EX_UART_NUM,(const char*)">>> ",4);
uart_write_bytes(EX_UART_NUM,(char*)line,sizeof(line));
uart_write_bytes(EX_UART_NUM,(char*)"\n",1);
drawText(line,0,0);
}
void writeFile(char *filename,const char *content){
uart_write_bytes(EX_UART_NUM,(const char*)"Escrevendo...\n",14);
clearData();
strcpy(data,"/spiffs/");
strcat(data,(const char*)filename);
strcat(data,(const char*)"\n");
uart_write_bytes(EX_UART_NUM,(const char*)data,sizeof(data));
FILE* f = fopen(data, "w");
if (f == NULL) {
uart_write_bytes(EX_UART_NUM,(const char*)"Falhou.\n",8);
}
else{
fprintf(f,content);
fclose(f);
uart_write_bytes(EX_UART_NUM,(const char*)"Feito.\n",7);
}
}
void setup() {
delay(3000);
//display
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
//serial
uart_param_config(EX_UART_NUM, &uart_config);
uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);
uart_write_bytes(EX_UART_NUM,(const char*)"iniciando...\n",13);
//verificação do sistema de arquivos (montagem, partição existente, formatação)
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
uart_write_bytes(EX_UART_NUM,(const char*)"Falha ao montar ou formatar o sistema de arquivos\n", 66);
} else if (ret == ESP_ERR_NOT_FOUND) {
uart_write_bytes(EX_UART_NUM,(const char*)"Nao pude encontrar a particao\n",41);
} else {
uart_write_bytes(EX_UART_NUM,(const char*)"Falha ao iniciar o SPIFFS\n", 38);
}
while (true);
}
xTaskCreatePinnedToCore(fileHandler,"fileHandler",10000,NULL,1,NULL,0);
}
void loop() {
// put your main code here, to run repeatedly:
}
Nos próximos artigos relacionados será mais empolgante, só assimile a ideia de que você pode guardar código para reuso, assim não precisamos reescrever as estruturas sempre que formos programar. Com isso, em 5 passos temos esse processo todo pronto. As funções de escrita, leitura, exclusão de arquivos e renomeação sempre podem ser reaproveitados também, inclusive podemos criar uma classe para isso. Mas se for para criar uma classe, acredito ainda que a melhor opção é utilizar o SPIFFS através do artigo citado logo no início, com os recursos da API do Arduino mesmo.
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.