Pular para o conteúdo principal

Laboratório 03

TarefaTemplateInícioFimConteúdo
LAB0310-09-202617-09-2026Sensores e Atuadores em IoT;

Checklist — PlatformIO IDE e Logging no ESP32​

  • Verificar git --version
  • Abrir o VS Code
  • Abrir a extensão PlatformIO IDE
  • Criar um projeto ESP32
  • Selecionar o framework Arduino
  • Executar o primeiro Build
  • Criar wokwi.toml
  • Criar diagram.json
  • Executar a simulação
  • Verificar o Serial Monitor
  • Atualizar o repositório no GitHub
  • Executar git push
  • Confirmar que o projeto está disponível no GitHub
  • Logout

Antes de começar

Confira o ambiente (faça a configuração uma vez por máquina; a verificação, a cada aula):

  1. Instalar as ferramentas — git, GitHub CLI e VS Code
  2. Configurar o git — nome, e-mail e editor padrão (uma vez)
  3. Verificar git, gh e VS Code — deve mostrar as versões e Logged in to github.com

Verificação rápida de versão instalada:

git -v && gh --version && code -v && pio --version && wokwi-cli -V && wsl --version && docker --version

Verificação rápida de autenticação:

git config --get-regexp ^user\. && gh auth status && code --list-extensions --profile "ESP32IO"

Ao terminar — máquina de laboratório

Antes de sair, faça logout para não deixar sua conta do GitHub ativa na máquina (limpa a credencial salva do git e encerra a sessão do gh).


Clone o repositório inicial do LAB03​

Escolha o Grupo e entre com o comando abaixo para criar o repositório no GitHub:

1. Crie e entre na pasta-mãe da disciplina:

mkdir "%USERPROFILE%\ELT85BN21" & cd /d "%USERPROFILE%\ELT85BN21"

2. Clone e entre no repositório do laboratório:

git clone https://github.com/ELT85B-N21-2026-2/lab03-grupo-a.git && cd lab03-grupo-a

3. Verifique status e abra o conteúdo do repositório no perfil ESP32IO do VS Code :

git status && code . --profile "ESP32IO"
Estrutura organizacional dos repositórios
ELT73A-S22-2026-2
├── Times
│ ├── Grupo-A
│ ├── ...
│ ├── Grupo-P # (Professor)
│ └── Grupo-N # (Notas)
└── Repositórios
├── lab03-template
├── lab03-grupo-a
├── ...
├── lab03-grupo-p # (Professor)
└── lab03-grupo-n # (Notas)

Repositório do Grupo A para Laboratório 03:

  1. Copie o link do repositório acima.
  2. Abra a página de envio da atividade no Moodle (botão abaixo).
  3. Clique em Adicionar envio.
  4. Cole o link no campo Texto online.
  5. Clique em Salvar mudanças.
  6. Confira se o status mudou para Enviado para avaliação.
Abrir envio no Moodle
Avaliação por commits

Cada cartão Ponto de commit é uma tarefa: faça-a, copie o comando do cartão e rode. A mensagem começa com o código (T1:, T2:…) — é como a correção identifica sua entrega. Um commit por tarefa; pode refazer (vale o mais recente); e não esqueça o git push. Detalhes em Como funciona a avaliação.

Avaliação do Laboratório — como é corrigido

A avaliação é automática e roda no GitHub a cada push. A ideia é simples:

Cada tarefa é entregue por um commit.

A mensagem do commit começa com o > código da tarefa (T1, T2, ...). O robô de correção encontra o commit de cada tarefa, volta o repositório para aquele ponto exato da história e testa se os requisitos daquela tarefa foram cumpridos.

Você acompanha a nota na aba Actions do seu repositório (resumo do job) e no arquivo GRADE.md, disponível em Artifacts de cada execução.


Convenção de commits (obrigatória)​

Ao terminar uma tarefa, faça um commit cuja mensagem comece com o código dela:

git add .
git commit -m "T1: implementa os cinco niveis de log"
git push

Regras:

  • O código vai no início da mensagem: T1, T2, ... seguido de : e uma descrição.
  • Pode refazer uma tarefa: basta um novo commit T1: .... O corretor usa sempre o commit mais recente de cada código.
  • T1 e T10 são tarefas diferentes — o corretor não confunde uma com a outra.
  • Uma tarefa sem commit correspondente vale zero.

Cada commit funciona como um carimbo com data/hora da entrega: serve de prova do seu progresso e permite corrigir tarefas que editam o mesmo arquivo (por exemplo, mudar CORE_DEBUG_LEVEL de 5 para 2 e depois voltar para 5).


O que é verificado em cada tarefa​

O corretor faz, para cada tarefa, no estado do repositório naquele commit:

  1. Compilação — o projeto precisa compilar com pio run (código quebrado não pontua nessa verificação).
  2. Requisitos específicos — presença das macros e configurações certas (ex.: log_e/log_w/log_i/log_d/log_v, CORE_DEBUG_LEVEL no valor pedido, formatação printf, etc.).

A nota de cada tarefa é proporcional ao número de verificações que passam. A rubrica completa (tarefas, pontos e checagens) está em .github/autograde/grade.py — o professor pode ajustá-la.

Crie um novo projeto no PlatformIo​

pio project init -b esp32dev -O "framework=arduino" -O "monitor_speed=115200" --sample-code
Ponto de commit · T1 (5 pts) #

pio project init

git add . && git commit -m "T1: pio project init" && git push

ESP32 Arduino Hal Logging​

Aqui está um exemplo completo para o arquivo src/main.cpp.

Este código demonstra a diferença entre as macros nativas do ESP32 Arduino Hal Logging (log_i, log_w, log_e, etc.) e o método tradicional Serial.println(), além de realizar o piscar de LED (blink) assíncrono utilizando millis().


Código: src/main.cpp​

#include <Arduino.h>

// Definição do pino do LED (GPIO 2 é o LED embutido na maioria das placas ESP32 DevKit)
#define LED_PIN 2

// Intervalo de tempo para o Blink (em milissegundos)
const unsigned long BLINK_INTERVAL = 500;

// Variáveis de controle de tempo
unsigned long previousMillis = 0;
bool ledState = LOW;
uint32_t counter = 0;

void setup() {
// Configura o pino do LED como saída
pinMode(LED_PIN, OUTPUT);

// Inicializa a comunicação serial a 115200 baud
Serial.begin(115200);

// Aguarda um instante para a estabilização da conexão Serial
delay(1000);

Serial.println("\n==============================================");
Serial.println(" ESP32 Lab: Teste de Logs e Blink de LED ");
Serial.println("==============================================\n");

// Mensagens de Log do ESP32 (requer CORE_DEBUG_LEVEL > 0 no platformio.ini)
log_v("Log de Verbose: Informação detalhada/baixa relevância.");
log_d("Log de Debug: Informação para depuração do sistema.");
log_i("Log de Info: Sistema inicializado com sucesso no pino GPIO %d.", LED_PIN);
log_w("Log de Warning: Este é um exemplo de aviso!");
log_e("Log de Error: Este é um exemplo de erro simulação!");
}

void loop() {
unsigned long currentMillis = millis();

// Lógica de tempo não-bloqueante (evita o uso de delay)
if (currentMillis - previousMillis >= BLINK_INTERVAL) {
previousMillis = currentMillis;

// Inverte o estado do LED
ledState = !ledState;
digitalWrite(LED_PIN, ledState);

counter++;

// Mensagem via Serial.println padrão do Arduino
Serial.print("[Arduino Serial] Ciclo do LED: ");
Serial.print(counter);
Serial.print(" | Estado atual: ");
Serial.println(ledState ? "LIGADO" : "DESLIGADO");

// Mensagem usando o Log nativo do ESP32 a cada 5 trocas de estado
if (counter % 5 == 0) {
log_i("Contador atingiu %u trocas de estado.", counter);
}
}
}

Ponto de commit · T2 (10 pts) #

Teste de Logs e Blink de LED

git add src/main.cpp && git commit -m "T2: Teste de Logs e Blink de LED" && git push

Destaques do Código​

  1. **log_i(), log_w(), log_e()**:
  • São macros de log nativas do core ESP32/Arduino.
  • Suportam formatação similar ao printf (ex: %d, %s, %u).
  • Adicionam automaticamente a tag do arquivo, linha da chamada e cor (se a flag -D CONFIG_ARDUHAL_LOG_COLORS=1 estiver no platformio.ini).
  1. **Uso de millis() em vez de delay()**:
  • O uso de delay() bloqueia o processamento do ESP32. Utilizar a verificação por millis() permite que o microcontrolador execute outras tarefas de segundo plano no loop() sem travar a CPU.

#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000);

#ifdef DEBUG_MODE
log_i("SISTEMA INICIADO EM MODO DE DEBUG");
log_d("Informações detalhadas de hardware e memória ativas.");
#else
// Código executado apenas no ambiente Release
Serial.println("Sistema iniciado (Modo Release).");
#endif
}

void loop() {
// Lógica do programa
}

Logging no ESP32​

Fundamentos: por que usar log em vez de Serial.print()?​

Muita gente depura o ESP32 assim:

Serial.print("valor = ");
Serial.println(valor);

Funciona, mas tem problemas quando o projeto cresce:

  • Não tem níveis. Um erro grave e uma mensagem de curiosidade têm a mesma aparência.
  • Fica no binário para sempre. Você precisa comentar/apagar os print na versão final, e isso é fácil de esquecer.
  • Não diz de onde veio. Em um projeto com 20 arquivos, "valor = 5" não ajuda a localizar a origem.

O sistema de log do Arduino-ESP32 resolve isso. Ele oferece cinco níveis de severidade e cada mensagem sai automaticamente com timestamp, nível, arquivo, linha e função de origem.

Os cinco níveis (do mais grave ao mais verboso)​

MacroNívelValorQuando usar
log_e()Error1Falhas que impedem a operação (sensor não responde).
log_w()Warning2Algo estranho, mas o programa continua (valor fora da faixa).
log_i()Info3Marcos normais de funcionamento ("WiFi conectado").
log_d()Debug4Detalhes úteis durante o desenvolvimento.
log_v()Verbose5Tudo, inclusive laços internos e valores brutos.

O valor 0 (NONE) desliga todos os logs.

A ideia-chave: o nível é decidido na compilação​

Existe uma macro chamada CORE_DEBUG_LEVEL. Cada macro de log só gera código se o seu nível for menor ou igual a CORE_DEBUG_LEVEL. Ou seja:

  • Se você compilar com CORE_DEBUG_LEVEL=3 (Info), então log_d() e log_v() desaparecem do binário — não ocupam memória nem gastam tempo de CPU.
  • Para gerar a versão "de produção", basta recompilar com CORE_DEBUG_LEVEL=1 (só erros) ou 0 (nenhum log). Você não apaga uma linha sequer do código.
A grande vantagem

As mensagens de debug ficam no código-fonte, documentando o programa, mas somem do produto final com uma única troca de configuração.


A API do esp32-hal-log.h​

Formatação estilo printf​

As macros aceitam os mesmos especificadores do printf do C:

int temp = 25;
float tensao = 3.31;
const char* estado = "OK";

log_i("Temperatura: %d C", temp);
log_i("Tensao: %.2f V", tensao);
log_i("Estado do sensor: %s", estado);
log_d("temp=%d tensao=%.2f estado=%s", temp, tensao, estado);

Principais especificadores: %d (inteiro), %u (inteiro sem sinal), %.2f (float com 2 casas), %s (string C), %c (caractere), %x (hexadecimal), %p (ponteiro), %% (o próprio símbolo de porcentagem).

Formato da saída no monitor serial​

Uma chamada como log_i("Sistema iniciado") dentro de setup() produz algo assim:

[342][I][main.cpp:15] setup(): Sistema iniciado

Lendo da esquerda para a direita:

  • [342] — timestamp em milissegundos desde o boot.
  • [I] — o nível (E, W, I, D ou V).
  • [main.cpp:15] — arquivo e linha exatos de onde a mensagem saiu.
  • setup(): — a função que a chamou.
  • Sistema iniciado — a sua mensagem.

Você não precisa escrever nada disso: o prefixo é montado automaticamente pela macro.

Dump de memória em hexadecimal​

Para inspecionar buffers (ex.: pacotes de rede, leitura de sensor I2C), existem as variantes log_buf_*:

uint8_t pacote[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02};
log_buf_i(pacote, sizeof(pacote));

Isso imprime os bytes formatados em hexadecimal, útil para depurar protocolos.


Configurar o platformio.ini​

Este é o passo mais importante da aula. Abra o platformio.ini na raiz do projeto e deixe-o assim:

platformio.ini
; CORE_DEBUG_LEVEL define ate que nivel de log sera COMPILADO:
; 0 = NONE | 1 = ERROR | 2 = WARN | 3 = INFO | 4 = DEBUG | 5 = VERBOSE
build_flags =
-DCORE_DEBUG_LEVEL=5
MacroCORE_DEBUG_LEVEL ≥Uso típico
log_e("...", ...)1Erro
log_w("...", ...)2Aviso
log_i("...", ...)3Informação
log_d("...", ...)4Debug
log_v("...", ...)5Verbose
log_buf_i(buf, tam)3Dump hexadecimal

Em platformio.ini: build_flags = -DCORE_DEBUG_LEVEL=<0..5>

Erro nº 1 dos iniciantes

Se você esquecer o -DCORE_DEBUG_LEVEL, o valor padrão costuma ser 0 (NONE) e nada aparece, mesmo com o código correto. Se seus logs sumiram, comece a investigação por aqui.

; =====================================================================
; Configurações Globais / Padrão (Herdadas por todos os ambientes)
; =====================================================================
[env]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

; Define o ambiente padrão caso nenhum seja especificado explicitamente
default_envs = debug

; =====================================================================
; Ambiente de Debug (Desenvolvimento e Testes)
; =====================================================================
[env:debug]
build_type = debug

; Filtros para o monitor serial em ambiente de desenvolvimento
monitor_filters =
esp32_exception_decoder
colorize
time

; Flags específicas para compilação em Debug
build_flags =
-D CORE_DEBUG_LEVEL=4 ; 4 = Debug, 5 = Verbose
-D CONFIG_ARDUHAL_LOG_COLORS=1 ; Habilita cores no console
-D DEBUG_MODE ; Macro customizada para usar no código (#ifdef DEBUG_MODE)

; =====================================================================
; Ambiente de Release (Produção e Implantação)
; =====================================================================
[env:release]
build_type = release

; Velocidade de upload maior para acelerar gravação em lote
upload_speed = 921600

; Flags de otimização de tamanho e desempenho para produção
build_flags =
-D CORE_DEBUG_LEVEL=0 ; 0 = Logs desativados (economiza memória Flash e RAM)
-Os ; Otimiza o tamanho do binário gerado

O código-base (src/main.cpp)​

src/main.cpp
#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000); // da tempo do monitor serial abrir e nao perder as 1as linhas

log_i("===== Laboratorio de Log ESP32 =====");
log_i("Sistema iniciado com sucesso");
}

void loop() {
// veremos o loop nos exercicios
}

Observe que usamos Serial.begin() normalmente. O sistema de log escreve no mesmo periférico serial; o Serial.begin continua sendo o que inicializa a UART e define a velocidade.

Ponto de commit · T3 (10 pts) #

Filtra logs com CORE_DEBUG_LEVEL

git add platformio.ini && git commit -m "T3: Filtra logs com CORE_DEBUG_LEVEL" && git push

Configurando o Wokwi​

O Wokwi simula o ESP32 usando o firmware já compilado pelo PlatformIO. Precisamos de dois arquivos na raiz do projeto.

O "circuito". Para começar, só a placa é suficiente:

diagram.json
{
"version": 1,
"author": "Uri Shaked",
"editor": "wokwi",
"parts": [
{
"type": "wokwi-esp32-devkit-v1",
"id": "esp",
"top": 0,
"left": 0,
"attrs": {}
},
{
"type": "wokwi-led",
"id": "led1",
"top": -3.33,
"left": 153.33,
"attrs": { "color": "red" }
},
{
"type": "wokwi-resistor",
"id": "r1",
"top": 64,
"left": 149.33,
"rotate": 90,
"attrs": {}
}
],
"connections": [
["esp:TX0", "$serialMonitor:RX", "", []],
["esp:RX0", "$serialMonitor:TX", "", []],
["esp:GND.1", "led1:C", "black", ["h0"]],
["led1:A", "r1:1", "green", ["v0"]],
["r1:2", "esp:D2", "green", ["h0", "v38"]]
]
}

As duas conexões ligam a UART do ESP32 ao monitor serial do simulador — é o que faz os logs aparecerem na tela do Wokwi.

Ponto de commit · T4 (15 pts) #

Configurando o Wokwi

git add diagram.json wokwi.toml && git commit -m "T4: Configurando o Wokwi" && git push

O fluxo de trabalho​

  1. Compile: ícone do PlatformIO na barra inferior (✓ "Build"), ou Ctrl+Alt+B. Isso gera o firmware.bin/.elf.
  2. Simule: abra a paleta de comandos (Ctrl+Shift+P) → Wokwi: Start Simulator.
  3. Uma aba com a placa e o monitor serial do Wokwi aparece. Seus logs surgem ali.
Regra de ouro

O Wokwi só roda o que já foi compilado. Se você mudar o código, compile de novo antes de reiniciar o simulador, senão vai ver o firmware antigo.


Roteiro de laboratório (prática guiada)​

Exercício 1 — Os cinco níveis de uma vez​

Substitua o setup() por:

src/main.cpp
void setup() {
Serial.begin(115200);
delay(1000);

log_e("Isto e um ERRO (log_e)");
log_w("Isto e um AVISO (log_w)");
log_i("Isto e uma INFO (log_i)");
log_d("Isto e DEBUG (log_d)");
log_v("Isto e VERBOSE (log_v)");
}

Compile com CORE_DEBUG_LEVEL=5 e simule. Anote: você deve ver as cinco linhas, cada uma com seu prefixo de nível.

Exercício 2 — Filtrando por nível de compilação​

Sem mudar o código do Exercício 1, edite o platformio.ini e troque para:

platformio.ini
build_flags =
-DCORE_DEBUG_LEVEL=2

Compile de novo e simule. Observe: agora só aparecem log_e e log_w. As linhas de Info, Debug e Verbose foram removidas do binário na compilação — não é um filtro em tempo de execução, elas literalmente não existem mais no firmware.

Para o relatório

Por que isso é melhor do que um if (nivel <= X) dentro do código?

Exercício 3 — Log com dados formatados​

Volte para CORE_DEBUG_LEVEL=5 e use um loop() que simula a leitura de um sensor:

src/main.cpp
void loop() {
static int contador = 0;
float temperatura = 20.0 + (contador % 15); // valor fake que varia

log_d("Ciclo #%d iniciado", contador);
log_i("Leitura de temperatura: %.1f C", temperatura);

if (temperatura > 30.0) {
log_w("Temperatura alta: %.1f C (limite 30)", temperatura);
}

contador++;
delay(1000);
}

Simule e acompanhe o timestamp crescendo de ~1000 ms em ~1000 ms, e o aviso aparecendo só quando o valor passa de 30.

Exercício 4 — Simulando um erro real​

Vamos fingir que um sensor falhou:

src/main.cpp
bool lerSensor(float* saida) {
static int tentativa = 0;
tentativa++;

if (tentativa % 4 == 0) { // a cada 4 leituras, "falha"
log_e("Falha ao ler sensor na tentativa %d", tentativa);
return false;
}

*saida = 25.5;
log_v("Sensor respondeu OK na tentativa %d", tentativa);
return true;
}

void loop() {
float t;
if (lerSensor(&t)) {
log_i("Temperatura valida: %.1f C", t);
} else {
log_w("Usando ultimo valor conhecido (leitura ignorada)");
}
delay(1000);
}

Objetivo pedagógico: perceber como os níveis contam uma história — o log_e marca a falha exata (com número da tentativa e linha do arquivo), o log_w mostra a recuperação, e o log_v só aparece se você quiser o detalhe fino.


Exercícios propostos (para entregar)​

  1. Contador de erros. Modifique o Exercício 4 para contar quantas falhas ocorreram e, a cada 10 ciclos, emitir um log_i com a taxa de falhas em porcentagem (%.1f%%).
  2. Nível de produção. Compile o projeto com CORE_DEBUG_LEVEL=1 e descreva no relatório o que muda na saída. Explique por que essa seria a configuração para um produto entregue ao cliente.
  3. Hexdump. Crie um uint8_t buffer[8] preenchido com valores à sua escolha e imprima-o com log_buf_i(). Cole a saída no relatório.
  4. Desafio. Ligue um LED no diagrama do Wokwi (GPIO 2) e faça-o piscar, emitindo um log_d a cada troca de estado. Anexe o diagram.json modificado.

Referência rápida​

MacroAparece quando CORE_DEBUG_LEVEL ≥Uso típico
log_e("...", ...)1Erro
log_w("...", ...)2Aviso
log_i("...", ...)3Informação
log_d("...", ...)4Debug
log_v("...", ...)5Verbose
log_buf_i(buf, tam)3Dump hexadecimal

Em platformio.ini: build_flags = -DCORE_DEBUG_LEVEL=<0..5>


Solução de problemas (troubleshooting)​

SintomaCausa provávelSolução
Nenhum log apareceCORE_DEBUG_LEVEL não definido ou 0Adicione -DCORE_DEBUG_LEVEL=5 em build_flags e recompile
Só aparecem erros e avisosNível de compilação baixo (1 ou 2)Suba o CORE_DEBUG_LEVEL
Caracteres estranhosVelocidade erradamonitor_speed deve bater com Serial.begin() (115200)
As primeiras linhas somemMonitor abriu depois do bootAdicione delay(1000) após Serial.begin()
Wokwi mostra código antigoSimulou sem recompilarFaça Build antes de Start Simulator
Wokwi não acha o firmwareCaminho errado no wokwi.tomlConfira o nome do env em .pio/build/<env>/
log_i "não existe"Faltou #include <Arduino.h>O header de log já vem junto do core Arduino; garanta o include

Documentação de Referência da Linguagem Arduino​

Sketch - setup()​

A função setup() é chamada quando um sketch inicia. Use-a para inicializar variáveis, configurar o modo dos pinos(INPUT ou OUTPUT), inicializar bibliotecas, etc. A função setup() será executada apenas uma vez, apoós a placa ser alimentada ou acontecer um reset.

int buttonPin = 3;

void setup() {
// Inicializa a porta serial
Serial.begin(9600);
// configura o pino 3 como INPUT
pinMode(buttonPin, INPUT);
}

void loop() {
// ...
}

Sketch - loop()​

Depois de criar uma função setup(), a qual inicializa e atribui os valores iniciais, a função loop() faz precisamente o que o seu nome sugere, e repete-se consecutivamente enquanto a placa estiver ligada, permitindo o seu programa mudar e responder a essas mudanças. Use-a para controlar ativamente uma placa Arduino.

int buttonPin = 3;

// setup inicializa a porta serial e o pino para o botão
void setup() {
Serial.begin(9600);
pinMode(buttonPin, INPUT);
}

// loop checa o estado do botão repetidamente, e envia
// pela serial um 'H' se este está sendo pressionado
void loop() {
if (digitalRead(buttonPin) == HIGH) {
Serial.write('H');
}
else {
Serial.write('L');
}

delay(1000);
}

Bits, Bytes e Operadores no ESP32​


Estruturas de Controle no ESP32​


CI workflow​

Example workflow (.github/workflows/grade.yml):

.github/workflows/grade.yml
name: Build & Test

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Cache PlatformIO
uses: actions/cache@v5
with:
path: |
~/.cache/pip
~/.platformio/.cache
~/.platformio/packages
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install PlatformIO
run: pip install platformio

- name: Build project
run: pio run

# Uncomment if you want native/unit tests in CI
# - name: Run tests
# run: pio test -e native
Ponto de commit · T5 (15 pts) #

GitHub action

git add .github/workflows/grade.yml && git commit -m "T5: GitHub action" && git push

Wokwi for CI and GitHub Actions (TODO)​

GitHub action for using Wokwi Embedded Systems Simulator in your CI workflow.

Logout do seu ambiente dev, git e gh​

Para o git "esquecer" suas informações salvas:

git credential-manager erase && cmdkey /list | findstr "github" && gh auth logout