

Nesse post vamos aprender sobre a ESP-IDF, a framework nativa para desenvolvimento no ESP32, explicando seu uso, as diferenças entre a IDF e a Arduino, instalação no Visual Studio Code e a criação de projetos, antes de iniciar a leitura é importante ressaltar que a IDF é uma framework pensada no uso profissional, sendo recomendado ter conhecimento nos seguintes tópicos:
Nesse post vamos usar a placa DOIT ESP32 DevKit V1 e componentes discretos como LEDs e resistores, além de um display I2C. Vale lembrar que é possível usar qualquer placa baseada nos SoCs da família ESP32, ou seja, as linhas ESP32, ESP32-S, ESP32-C, ESP32-H e ESP32-P.
Apenas por curiosidade, as placas baseadas no ESP8266, como o ESP-01 e ESP-12 (NodeMCU) usam uma SDK diferente da ESP-IDF, a ESP8266 Non-OS SDK
Abaixo, a lista completa de materiais utilizados:
A Espressif IoT Development Framework ou simplesmente ESP-IDF, é a SDK nativa para a família ESP32, criada pela Espressif. Ela é um conjunto completo de ferramentas para todo o ciclo de desenvolvimento de uma Aplicação IoT, desde o protótipo até o produto final, contendo:
Agora que aprendemos o que é a ESP-IDF, vamos para a instalação.

Feito isso, feche o programa e abra novamente.
Seguindo, vamos ser direcionados a outro menu, aonde vamos apenas clicar em "Find ESP-IDF in your system" e selecionar a última versão de release - caso exista uma nova versão no momento em que você lê este post, pode instalar sem problemas - clicando em "install", na sequência.
Agora é só aguarda a instalação, isso pode demorar dependendo da sua velocidade de internet.Com a IDF já instalada podemos ir para a próxima etapa: a criação de um projeto!
Esse outro menu é bem intuitivo, não tem segredo. A primeira opção é o nome do projeto (vamos colocar "primeiro-projeto"), a segunda é a pasta em que vai ficar esse projeto, que vamos deixar na opção padrão mesmo. Na opção "Choose ESP-IDF Board", vamos selecionar nossa placa, a usada nesse projeto é a primeira opção, ESP-WROVER-KT 3.3V. Logo abaixo selecionamos a porta que nossa placa está, você pode selecionar a porta agora ou configurar após criar o projeto.
Feito isso, vamos clicar na opção "Choose Template" para abrir o menu de Templates. A primeira caixa nos permite alterar a Opção "Extension" para "ESP-IDF", para selecionar a lista de templates, "Extension" para templates-base e "ESP-IDF" para criar o projeto com um exemplo. Para TODOS os projetos demonstrados neste post vamos deixar selecionada a opção padrão e usar o template "template-app". Agora basta clicar em "Create Project" e clicar em "sim"/"yes" na caixinha de diálogo seguinte, que pergunta se queremos abrir o projeto, e pronto! Já podemos dar início aos primeiros passos com a IDF!
Com o projeto criado, vamos nos deparar com vários arquivos, no momento vamos alterar apenas o arquivo principal, localizado no diretório main/main.c.
Antes de escrevemos nosso primeiro código, vamos fazer uma pequena lista dos comandos básicos:

Essas informações são chamadas de Logs, e é sobre isso que vamos falar agora!Como já comentamos, a ESP-IDF tem como foco o uso profissional, e por isso uma das características mais importantes dela é o seu sistema de geração de logs e tratamento de erros, uma característica crucial na etapa de prototipação. Não precisamos dominar esse assunto por agora, mas é bom entender a base desse conceito, pois toda IDF faz uso desse sistema.
#include <stdio.h>
void app_main(void)
{
printf("hello world\n\r");
}
<esp_log.h>. essa biblioteca vai disponibilizar 5 macros, cada uma com nível de verbosidade:ESP_LOGE para erros.ESP_LOGW para avisos.ESP_LOGI para informações gerais.ESP_LOGD para informações de Debug.ESP_LOGV para detalhes adicionais.ESP_LOGx("Taks1", "texto: %d %d", var1, var2.....);, onde "x" é o nível de verbosidade (E/W/I/D/V).A essa altura você já aprendeu a como gerar logs, mas quais as vantagens de se usar logs ao invés de um simples print? Logs são uma forma mais sofisticada de prints de debug, além de ter os mesmos mecanismos de formatação do printf. Eles mostram o nome do evento e o momento que o log foi gerado (muito útil quando lidamos com um sistema multitarefa, como a ESP-IDF), e tem a maior vantagem de todas: podem ser desativadas no arquivo de configuração. Quando estamos na etapa de prototipação, queremos receber o máximo de informação possíveis sobre o funcionamento, mas quando o produto está finalizado, não queremos que o usuário veja essas informações. Se optarmos por usar prints no processo de prototipação, teremos que, manualmente, removê-los antes de lançar o produto, mas se estivermos usando Logs, podemos deixá-los no código e controlar seu funcionamento pelo menuconfig, da seguinte forma:
<esp_err.h>.Assim como no FreeRTOS temos funções que retornam: pdPASS e pdFAIL. Na ESP-IDF temos ESP_OK, ESP_FAIL e ESP_ERR_x (onde X é o nome do erro). A grande maioria das funções da IDF retornam o tipo esp_err_t, que representa um dos valores citados acima, além disso, também temos algumas macros para nos ajudar a lidar com erros:ESP_ERROR_CHECK(x); caso X seja um erro, a execução do código é interrompida.ESP_ERROR_CHECK_WITHOUT_ABORT(x); caso X seja um erro, gera um log de erro, mas continua a execução.ESP_RETURN_ON_ERROR(x, TAG, "fail reason"); caso X seja um erro, exibe uma mensagem personalizada e chama: return x;.ESP_GOTO_ON_ERROR(x, label, TAG, "fail reason"); caso X seja um erro, exibe uma mensagem personalizada e chama: goto label;.Esse projeto é apenas um exercício de fixação dos tópicos recém-abordados. Nesse código vamos criar duas task que exibem mensagens de logs a cada 5 segundos e configurar a exibição deles.
//libC includes
#include <stdio.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
//ESP-IDF includes
#include <esp_log.h>
//task para teste
void task1(void*);
void task2(void*);
void app_main(void){
static const char *TAG = "app_main"; //tag da main
if(xTaskCreatePinnedToCore(task1,"task1",2048,NULL, 1,NULL, 0) != pdPASS){
ESP_LOGE(TAG, "ERRO A iniciar a task1");
}else{
ESP_LOGI(TAG, "task1 criada com sucesso");
}
if(xTaskCreatePinnedToCore(task2,"task2",2048,NULL, 1,NULL, 0) != pdPASS){
ESP_LOGE(TAG, "ERRO A iniciar a task2");
}else{
ESP_LOGI(TAG, "task2 criada com sucesso");
}
while(true){
vTaskDelay(pdMS_TO_TICKS(10000)); //10Seg de delay
printf("hello world\n\r");
}
}
void task1(void *args){
static const char *TAG = "Task1"; //tag da task1
while(true){
vTaskDelay(pdMS_TO_TICKS(5000)); //5Seg de delay
ESP_LOGE(TAG,"Logs de ERRO tem a cor vermelha!");
ESP_LOGW(TAG,"Logs de AVISO tem a cor amarela!");
ESP_LOGI(TAG,"Logs de INFO tem a cor verde!");
}
}
void task2(void *args){
static const char *TAG = "Task2";//tag da task2
while(true){
vTaskDelay(pdMS_TO_TICKS(5000)); //5Seg de delay
ESP_LOGD(TAG,"Logs de DEBUG tem a cor Branca e alta verbosidade");
ESP_LOGV(TAG,"Logs VERBOSOS tem a cor Branca e a maior verbosidade, são usadas para mensagens grandes e detalhadas sobre o funcionamento do aplicação \
assim como essa mensagem, por padrão Logs verbosos e debug estão desabilitados");
}
}Veja no vídeo abaixo o funcionamento:Agora que já temos uma noção básica de como a ESP-IDF funciona, vamos para próxima etapa: controlar os periféricos da placa!
<driver/gpio.h>Na GPIO Driver há duas maneiras de configurar as GPIOS: individualmente por meio de funções livre ou múltiplos GPIOs por meio da "gpio_config_t" struct (GPIO PAD). Vamos mostrar, na sequência, como utilizar as duas formas:gpio_set_direction(gpio_num_t, gpio_mode_t)". Essa função recebe 2 parâmetros:gpio_num_t é o número da GPIO que queremos configurar. Recebe um valor no formato GPIO_NUM_x, onde "x" é o número da GPIO (Ex: num. GPIO10 = GPIO_NUM_10).gpio_mode_t é a direção que queremos configurar a GPIO. Esta pode receber 6 valores:gpio_set_pull_mode(gpio_num_t, gpio_pull_mode_t)", onde o primeiro argumento é o número da GPIO e o segundo é o modo de PULL, que na ESP-IDF pode receber 4 valores:gpio_pullup_en(gpio_num_t) e gpio_pulldown_en(gpio_num_t)) gpio_config_t, essas struct possui os seguintes campos:uint64_t pin_bit_mask : A posição de cada bit desse valor representa uma GPIO, ou seja 0b...001 = GPIO1, 0b...010 = GPIO2, 0b....111 = GPIO1, GPIO2 e GPIO3. Todas as GPIOS selecionadas nessa bitmask vão ser configurados da mesma forma.gpio_mode_t mode Seleciona o modo, igual já comentado na função anterior.gpio_pullup_t pull_up_en: Configura o pull-up, recebendo:gpio_pulldown_t pull_down_en: Configura o pull-down, recebendo:gpio_int_type_t intr_type Configura modo de interrupção (vamos manter em GPIO_INTR_DISABLE por enquanto)gpio_config(gpio_config_t*). Isso vai configurar o hardware conforme as informações na struct.gpio_set_level(gpio_num_t gpio_num, uint32_t level), que é semelhante ao "digitalWrite()" e recebe o número da GPIO juntamente com o nível lógico da porta (1 = HIGH e 0 = LOW).int gpio_get_level(gpio_num_t gpio_num), semelhante ao "digitalRead()" e recebe o número da GPIO, retornando o nível lógico da porta (1 = HIGH e 0 = LOW).Nesse projeto, vamos configurar as GPIOs das duas formas que aprendemos para implementar um botão que seleciona qual LED irá piscar.

// C includes
#include <stdio.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//esp-idf includes
#include <esp_log.h>
#include <esp_err.h>
#include <driver/gpio.h>
//defines
#define BTN GPIO_NUM_21
#define LED_V GPIO_NUM_22
#define LED_A GPIO_NUM_23
//função que configura os GPIOs
void GPIO_init();
void app_main(void){
const static char TAG[] = "main task"; //tag main
GPIO_init(); //inicia as GPIOS
gpio_num_t leds[] = {LED_A, LED_V};
int led_atual = 0;
int input = 0;
int input_anterior = 1;
while(true){
input = gpio_get_level(BTN);
if(input == 0 && input != input_anterior){
led_atual = (led_atual + 1)%2;// muda o led atual
ESP_LOGI(TAG, "botão pressionado!");
}
input_anterior = input;
//pisca led atual
gpio_set_level(leds[led_atual], 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(leds[led_atual], 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void GPIO_init(){
const static char TAG[] = "GPIO_init";
//configura GPIOS com a struct gpio_config
gpio_config_t leds = {
.pin_bit_mask = (1<<LED_A) | (1<<LED_V), //BITMASK das portas 23 e 22
.mode = GPIO_MODE_DEF_OUTPUT, //modo output only
.pull_up_en = GPIO_PULLUP_DISABLE, //sem pullup
.pull_down_en = GPIO_PULLDOWN_DISABLE,// sem pulldown
.intr_type = GPIO_INTR_DISABLE, //interrupções desligadas
};
ESP_ERROR_CHECK(gpio_config(&leds)); //inicia a configuração e verifica erros
//configura GPIO com funções
gpio_set_direction(BTN, GPIO_MODE_DEF_INPUT); // configura a porta 21 como input
gpio_set_pull_mode(BTN, GPIO_PULLUP_ONLY); //configura pull_up na porta 21
ESP_LOGI(TAG, "GPIOs configuradas!!!");
}
Agora vamos aprender a configurar a UART na ESP-IDF. Assim como as GPIOS, a UART também tem seu driver, sendo incluída pelo header: <driver/uart.h>.O processo para usar a UART na ESP-IDF é dividido em 3 etapas:
uart_config_t, essa struct tem os seguintes campos: int baud_rate: Velocidade da comunicação uart_word_length_t data_bits: Tamanho dos pacotes, que recebe um dos seguintes valores:UART_DATA_5_BITSUART_DATA_6_BITSUART_DATA_7_BITSUART_DATA_8_BITSUART_DATA_BITS_MAX uart_parity_t parity: Habilita e configura a quantidade de Bits de paridade. Recebe um dos seguintes valores:UART_PARITY_DISABLEUART_PARITY_EVENUART_PARITY_ODD uart_stop_bits_t stop_bits: Habilita e configura a quantidade de Stop Bits. Recebe um dos seguintes valores: UART_STOP_BITS_ UART_STOP_BITS_1_5 UART_STOP_BITS_2 UART_STOP_BITS_MAX uart_hw_flowcontrol_t flow_ctrl: Configura o controle de fluxo (nesse post, esse campo ficará desabilitado); uint8_t rx_flow_ctrl_thresh: Configura o fluxo do pino RTS (nesse post, esse campo ficará desabilitado);uart_sclk_t source_clk: Seleciona a fonte de clock (nesse post, esse campo ficará no valor padrão);uart_param_config(uart_port_t, const uart_config_t*), onde o primeiro parâmetro é o barramento UART que vamos usar. O ESP32 conta com 3 barramentos UART:UART_NUM_0UART_NUM_1UART_NUM_2uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num). O primeiro parâmetro é o barramento UART que vamos configurar, e os 4 últimos são o número de cada pino que vamos usar, nessa ordem: TX, RX, RTS e CTS, caso você não queira utilizar algum pino, como nesse caso os pinos RTS e CTS, basta utilizar o valor: UART_PIN_NO_CHANGE em vez de usar o número da porta.uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int event_queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags), sendo seus parâmetros:uart_write_bytes(uart_port_t uart_num, const void *src, size_t size), selecionando a UART que queremos enviar a mensagem, um ponteiro para a informação que queremos enviar, e o tamanho em bytes da informação.uart_get_buffered_data_len(uart_port_t uart_num, size_t *size). Nesse ponto já sabemos o que é o primeiro parâmetro, o segundo parâmetro é um ponteiro para a variável que queremos salvar a informação (o retorno por referência nessa função é usado, pois ela retorna esp_err_t para tratamento de erros).Caso haja informações a serem lidas, usamos a função: uart_read_bytes(uart_port_t uart_num, void *buf, uint32_t length, TickType_t ticks_to_wait), que recebe os parâmetros:Nesse projeto vamos fazer um simples ECHO via UART (echo significa que vamos apenas repetir a informação recebida), passando por todas as etapas de configuração da UART, leitura e escrita de informação.Nesse código vamos usar a UART0, então é recomendado desligar todos os Logs, como já vimos início desse post.
//C includes
#include <stdio.h>
//freeRTOS includes
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
//esp-idf includes
#include <esp_log.h>
#include <esp_err.h>
#include <driver/uart.h>
//UART defines
#define UART UART_NUM_0
#define BAUDRATE 115200
#define UART_BUF_LEN 2048
#define UART_EVENT_BUF_LEN 10
//UART tipes
QueueHandle_t uart_event_queue;
//função que inicia o driver de UART
void UART_init();
void app_main(void){
size_t RX_len = 0; //contador de Bytes no RX buffer
char Recive_buf[UART_BUF_LEN] = {0};//inicializa o array com 0
//inicializa a UART
UART_init();
while(true){
//verifica se há informações para ler
uart_get_buffered_data_len(UART,&RX_len);
if(RX_len != 0){
uart_read_bytes(UART,Recive_buf,RX_len, pdMS_TO_TICKS(1000)); //le as informações do RX
uart_write_bytes(UART, Recive_buf, RX_len); // envia as informações para TX
RX_len = 0; // reseta o contadotor de Bytes
}
vTaskDelay(pdMS_TO_TICKS(100)); //espera 100MS a cada leitura
}
}
void UART_init() {
//configuração basica de UART
uart_config_t uart_params = {
.baud_rate = BAUDRATE,
.data_bits = UART_DATA_8_BITS, //pacotes de 8bits
.parity = UART_PARITY_DISABLE, //sem paridade
.stop_bits = UART_STOP_BITS_1, //apenas 1Bit de stop
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, //sem pino de CTS, função desabilitada
.rx_flow_ctrl_thresh = 0,// sem pino RTS, função desabilitada
.source_clk = UART_SCLK_DEFAULT, //clock padrão da UART
};
//aplicando as configuraçõe
ESP_ERROR_CHECK(uart_param_config(UART, &uart_params));
//configuração dos pinos
ESP_ERROR_CHECK(uart_set_pin(UART, 1, 3, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
//----------------TXD, RXD, CTS(sem pino),RTS(sem pino)
//inicilizando Driver de UART
ESP_ERROR_CHECK(uart_driver_install(UART, UART_BUF_LEN, UART_BUF_LEN, UART_EVENT_BUF_LEN, &uart_event_queue, 0));
}Para o último tópico desse post vamos falar como configurar e usar o driver I₂C. Vamos começar incluindo o header <driver/i2c.h>, que tem as etapas bem parecidas com a configuração do driver da UART (até um pouco mais fácil, eu diria). Assim como na UART, esse processo segue algumas etapas, nesse caso são apenas duas, sendo a primeira a configuração dos parâmetros, tanto I₂C master quanto slave são configurados pela struct: i2c_config_t. Essa struct tem os seguintes campos:
i2c_mode_t mode: É aqui que vamos definir se nosso I₂C vai estar no modo master ou slave, podendo receber os seguintes valores:int sda_io_num: Número da GPIO que será usada para SDAint scl_io_num: Número da GPIO vamos usar para SCLbool sda_pullup_en: Habilita o pull-up para o pino SDA (true = habilitado, false = desabilitado)bool scl_pullup_en: Habilita o pull-up para o pino SCL (true = habilitado, false = desabilitado)clk_flags: Seleciona a fonte de clock do I₂C (nesse post vamos manter na opção padrão)master.clk_speed clock da comunicação i2c, não pode ser maior que 1Mhz.slave.addr_10bit_en: Habilita endereços de 10Bits para i2c (0 = desabilitado, 1 habilitado)slave.uint16_t slave_addr: Endereço desse dispositivo no barramento I₂Cslave.uint32_t maximum_speed: Clock máximo que esse dispositivo pode trabalhar (não pode ser maior que 1Mhz)i2c_param_config(i2c_port_t, const i2c_config_t*). Os argumentos dessa função significam:i2c_driver_install(i2c_port_t, i2c_mode_t, size_t, size_t, int), onde cada parâmetro dessa função significa:i2c_master_write_to_device(i2c_port_t, uint8_t*, const uint8_t, size_t TickType_t), onde cada argumento significa:i2c_master_read_from_device(i2c_port_t, uint8_t, uint8_t*, size_t TickType_t), onde cada argumento significa:i2c_slave_read_buffer(i2c_port_t, uint8_t*, size_t, TickType_t), onde cada argumento significam:i2c_slave_write_buffer(i2c_port_t, const uint8_t*, int, TickType_t), onde cada argumento significa:Nesse projeto vamos fazer algo um pouco diferente. Como comentamos no início do artigo, a ESP-IDF também suporta o uso da linguagem C++, então vamos fazer uso disso: nesse projeto vamos aprender como instalar bibliotecas de terceiros (fora da lista de componentes).
Feito isso, vamos extrair o arquivo na pasta "main/" do nosso projeto:
Após, vamos renomear o arquivo main.c para main.cpp e adicionar o seguinte código no arquivo CMakeLists.txt:idf_component_register(SRCS "main.cpp" "./UniversalLCD-main/universallcd.cpp" INCLUDE_DIRS "." "UniversalLCD-main")Após isso, tudo está pronto para começar o projeto!

//Lib C includes
#include <stdio.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//ESP-IDF includes
#include <driver/i2c.h>
#include <driver/gptimer.h>
#include <esp_err.h>
#include <esp_log.h>
//extra includes
#include <UniversalLCD-main/universallcd.hpp>
//DEFINES
#define LCD_ADDR 0x27
#define I2C_MASTER I2C_NUM_0
#define TIMER_HZ (1000*1000)
//lcd types -------------------------------------------------------------------------------------------------
struct LcdDrive : public universalLCD::BusInterface{
void send(uint8_t config, uint8_t data){
uint8_t package = (config & 0b00000111) | (data & 0xF0) | 0x08;
i2c_master_write_to_device(I2C_MASTER, LCD_ADDR,&package,1,pdMS_TO_TICKS(100));
}
};
//lcd driver delay (This code runs at a frequency of 1Khz, so it is not necessary to block the task for a microsecond delay)
static bool timer_delay_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
void delay(uint32_t us);
void lcd_timer_init(void);
//lcd drive handler
gptimer_handle_t lcd_driver_delay;
gptimer_config_t timer_config;
gptimer_alarm_config_t delay_config;
gptimer_event_callbacks_t delay_callback;
volatile bool timer_flag;
//--------------------------------------------------------------------------------------------------------------------------------------
void I2C_init();
extern "C" void app_main(){
I2C_init();
lcd_timer_init();
LcdDrive drive;
universalLCD::UniversalLCD lcd(drive,universalLCD::Bus4Bits,delay);
lcd.begin()
.write("Hello world")
.cursorOn()
.cursorBlinkOn();
while(true) vTaskDelay(portMAX_DELAY);
}
void I2C_init(){
//configura I2C
i2c_config_t conf = {
.mode = I2C_MODE_MASTER, //modo master
.sda_io_num = GPIO_NUM_21,
.scl_io_num = GPIO_NUM_22,
.sda_pullup_en = GPIO_PULLUP_ENABLE, //pull-ups habilitados
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master = 100000, //100Khz de clock
.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL, //clock padrão
};
//aplica as configuraçoes
ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER, &conf));
//inicia o driver I2C
ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER, conf.mode, 0, 0, 0));
}
//LCD Functions
void lcd_timer_init(void){
timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = TIMER_HZ,
.intr_priority = 0,
.flags = 0,
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &lcd_driver_delay));
delay_callback = {
.on_alarm = timer_delay_callback,
};
delay_config = {
.alarm_count = 0,
.reload_count = 0,
.flags = 0,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(lcd_driver_delay,&delay_callback,NULL));
ESP_ERROR_CHECK(gptimer_enable(lcd_driver_delay));
ESP_ERROR_CHECK(gptimer_start(lcd_driver_delay));
}
static bool timer_delay_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx){
timer_flag = true;
return true;
}
void delay(uint32_t us){
uint64_t count;
gptimer_stop(lcd_driver_delay);
gptimer_get_raw_count(lcd_driver_delay, &count);
delay_config = {
.alarm_count = (uint64_t)(us+count),
.reload_count = 0,
.flags = 0,
};
timer_flag = false;
ESP_ERROR_CHECK(gptimer_set_alarm_action(lcd_driver_delay, &delay_config));
gptimer_start(lcd_driver_delay);
while(!timer_flag);
}
Finalizamos aqui essa introdução. Esperamos que esse post tenha contribuído para seu aprendizado e desenvolvimento de novas habilidades, é sempre bom aprender novas ferramentas e funcionalidades para implementar em nossos projetos.Sinta-se a vontade para nos enviar dúvidas e/ou sugestões nos comentários e fique atento às novidades, traremos outros artigos sobre a ESP-IDF no futuro.Obrigado por ler até aqui, e até mais!
|
Aprenda a usar a Espressif IoT Development Framework e crie projetos profissionais com placas da família ESP32!
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200