image 2 Saravati

Como Fazer um Medidor de Bateria Wi-Fi com ESP32 e Dashboard Animado

Neste projeto vamos transformar um ESP32 em um medidor inteligente de bateria, capaz de medir a tensão de uma pilha AAA de 1,5 V e mostrar o resultado diretamente no navegador do celular ou computador.

Além da tensão, o ESP32 calcula uma estimativa do nível de carga e apresenta tudo em uma página web com uma bateria animada, que vai enchendo e mudando de cor conforme o nível medido.

O projeto é relativamente simples, mas reúne vários conceitos interessantes:

  • conversão analógico-digital;
  • leitura de tensão;
  • ESP32;
  • Wi-Fi;
  • servidor web;
  • HTML;
  • CSS;
  • JavaScript;
  • IoT.

Ao final, teremos um pequeno instrumento conectado à rede sem necessidade de instalar aplicativo.

Objetivo do projeto

Nosso ESP32 fará três tarefas principais:

  1. medir a tensão da pilha AAA;
  2. estimar aproximadamente o nível de carga;
  3. disponibilizar essas informações em uma página web.

O funcionamento geral será:

Pilha AAA

    │

    ▼

GPIO34 / ADC

    │

    ▼

ESP32

    │

    ├── mede tensão

    │

    ├── estima porcentagem

    │

    └── cria servidor Web

             │

             ▼

       Wi-Fi da rede

             │

             ▼

      Celular / computador

Componentes necessários

Para montar o projeto você precisará de:

  • 1 ESP32 DevKit / ESP-WROOM-32
  • 1 pilha AAA alcalina de 1,5 V
  • suporte para pilha AAA ou fios
  • protoboard
  • jumpers
  • cabo USB
  • opcional: resistor de aproximadamente 10 kΩ para proteção da entrada
  • opcional: capacitor de 100 nF para estabilizar a leitura

Não é necessário utilizar display físico, porque nossa interface será exibida diretamente pelo navegador.

Qual pino vamos utilizar?

Utilizaremos:

GPIO34

para a leitura da bateria.

Esse pino pertence ao ADC1 do ESP32, o que é importante porque o projeto utiliza Wi-Fi.

Em muitos ESP32 clássicos, o ADC2 apresenta limitações enquanto o Wi-Fi está ativo. Por isso GPIO34 é uma escolha interessante para este tipo de aplicação.

Ligação da bateria

Como estamos utilizando somente uma pilha AAA, cuja tensão fica aproximadamente entre 1 V e 1,6 V, podemos medir diretamente com o ESP32.

A ligação básica é:

PILHA AAA

 (+) ───────────── GPIO34

 (-) ───────────── GND

Se quiser acrescentar uma proteção simples, podemos colocar um resistor em série:

AAA (+)

   │

 [10 kΩ]

   │

   └──────── GPIO34

AAA (-)

   │

  GND

Esse resistor não forma um divisor de tensão nesse caso. Ele apenas limita corrente em situações acidentais.

Atenção à tensão máxima

O GPIO do ESP32 trabalha em níveis de aproximadamente:

0 a 3,3 V

Neste projeto estamos medindo uma única célula AAA de aproximadamente 1,5 V.

Portanto, ela está dentro dessa faixa.

Não utilize este mesmo circuito para medir diretamente:

bateria de 9 V

12 V

24 V

fonte automotiva

bateria Li-Ion acima de 3,3 V

rede elétrica

Para tensões superiores seria necessário utilizar divisor resistivo e proteção apropriada.

Como o ESP32 mede a tensão?

O ESP32 possui internamente um conversor A/D.

A ideia básica é transformar uma tensão analógica em um número digital.

Entretanto, durante nossos testes percebemos que simplesmente utilizar:

analogRead()

e converter manualmente de 0–4095 para 0–3,3 V não fornecia a melhor precisão.

Por isso utilizamos:

analogReadMilliVolts()

Essa função retorna diretamente uma estimativa da tensão em milivolts.

Por exemplo:

1420 mV

equivale a:

1,420 V

Também fazemos diversas leituras e calculamos uma média para deixar o valor apresentado mais estável.

Porcentagem de uma pilha AAA

Existe um detalhe importante.

Não podemos afirmar que:

1,50 V = 100%

0,75 V = 50%

0 V = 0%

porque a descarga de uma pilha alcalina não é perfeitamente linear.

Além disso, a tensão depende de:

  • corrente consumida;
  • temperatura;
  • fabricante;
  • química da pilha;
  • idade;
  • resistência interna;
  • tempo desde a última utilização.

Por isso o percentual apresentado neste projeto é uma estimativa.

Usaremos aproximadamente:

TensãoCarga estimada
1,55 V ou mais100%
1,50 V95%
1,45 V85%
1,40 V75%
1,35 V65%
1,30 V50%
1,25 V40%
1,20 V30%
1,15 V20%
1,10 V10%
1,05 V5%
abaixo disso0%

Para um testador mais avançado, poderíamos medir a tensão sob carga, o que fornece uma indicação mais útil sobre o estado real da pilha.

A página web

O ESP32 vai hospedar uma página diretamente em sua memória.

Quando acessarmos seu endereço IP no navegador, veremos:

MEDIDOR DE BATERIA

ESP32

      ┌──────────────────┐

      │██████████████    │▐

      └──────────────────┘

             75%

            1,40 V

    Atualização em tempo real

A bateria muda de cor:

Verde   → carga alta

Amarelo → carga intermediária

Vermelho → carga baixa

Além disso, o preenchimento possui transição suave e um efeito de brilho.

Configurando a Arduino IDE

É necessário ter o suporte ao ESP32 instalado na Arduino IDE.

Depois selecione sua placa em:

Ferramentas → Placa → ESP32 Arduino

Por exemplo:

ESP32 Dev Module

Depois selecione a porta COM correta.

Configurando o Wi-Fi

No código você encontrará:

const char* ssid = “SEU_WIFI”;

const char* password = “SUA_SENHA”;

Substitua pelas informações da sua rede.

Por exemplo:

const char* ssid = “MinhaRede”;

const char* password = “12345678”;

O celular ou computador utilizado para visualizar o dashboard deverá estar na mesma rede local.

Código completo

#include <WiFi.h>

#include <WebServer.h>

// =====================================================

// WIFI

// =====================================================

const char* ssid = "SEU_WIFI";

const char* password = "SUA_SENHA";

// =====================================================

// ADC

// =====================================================

#define PINO_BATERIA 34

// =====================================================

// SERVIDOR

// =====================================================

WebServer server(80);

// =====================================================

// LEITURA DA TENSÃO

// =====================================================

float lerTensaoBateria() {

  const int AMOSTRAS = 40;

  uint32_t soma_mV = 0;

  for (int i = 0; i < AMOSTRAS; i++) {

    soma_mV += analogReadMilliVolts(PINO_BATERIA);

    delay(2);

  }

  float media_mV =

    soma_mV / (float)AMOSTRAS;

  return media_mV / 1000.0;

}

// =====================================================

// ESTIMATIVA DA PORCENTAGEM

// =====================================================

int calcularPorcentagem(float v) {

  if (v >= 1.55) return 100;

  if (v >= 1.50) return 95;

  if (v >= 1.45) return 85;

  if (v >= 1.40) return 75;

  if (v >= 1.35) return 65;

  if (v >= 1.30) return 50;

  if (v >= 1.25) return 40;

  if (v >= 1.20) return 30;

  if (v >= 1.15) return 20;

  if (v >= 1.10) return 10;

  if (v >= 1.05) return 5;

  return 0;

}

// =====================================================

// PÁGINA WEB

// =====================================================

const char pagina[] PROGMEM = R"rawliteral(

<!DOCTYPE html>

<html lang="pt-BR">

<head>

<meta charset="UTF-8">

<meta

name="viewport"

content="width=device-width, initial-scale=1.0">

<title>Medidor de Bateria ESP32</title>

<style>

* {

  box-sizing: border-box;

}

body {

  margin: 0;

  height: 100vh;

  display: flex;

  justify-content: center;

  align-items: center;

  font-family:

    Arial,

    Helvetica,

    sans-serif;

  background:

    radial-gradient(

      circle at top,

      #183153,

      #07111f

    );

  color: white;

}

.card {

  width: 360px;

  padding: 35px;

  border-radius: 25px;

  background:

    rgba(255,255,255,0.08);

  backdrop-filter:

    blur(10px);

  box-shadow:

    0 20px 60px

    rgba(0,0,0,0.45);

  text-align: center;

}

h1 {

  margin-top: 0;

  font-size: 25px;

  letter-spacing: 2px;

}

.subtitulo {

  opacity: 0.7;

  margin-bottom: 35px;

}

/* =======================================

   BATERIA

======================================= */

.bateria-container {

  display: flex;

  justify-content: center;

  align-items: center;

  margin: 30px 0;

}

.bateria {

  position: relative;

  width: 220px;

  height: 105px;

  border:

    6px solid white;

  border-radius: 15px;

  padding: 7px;

}

.bateria::after {

  content: "";

  position: absolute;

  right: -21px;

  top: 28px;

  width: 15px;

  height: 40px;

  background: white;

  border-radius:

    0 6px 6px 0;

}

.carga {

  width: 0%;

  height: 100%;

  border-radius: 7px;

  background: #00e676;

  transition:

    width 0.8s ease,

    background 0.8s ease;

  position: relative;

  overflow: hidden;

}

/* Efeito de brilho */

.carga::after {

  content: "";

  position: absolute;

  top: 0;

  left: -100%;

  width: 70%;

  height: 100%;

  background:

    linear-gradient(

      90deg,

      transparent,

      rgba(255,255,255,0.35),

      transparent

    );

  animation:

    brilho 2s infinite;

}

@keyframes brilho {

  0% {

    left: -100%;

  }

  100% {

    left: 150%;

  }

}

/* =======================================

   DADOS

======================================= */

.porcentagem {

  font-size: 55px;

  font-weight: bold;

  margin-top: 25px;

}

.tensao {

  font-size: 25px;

  margin-top: 10px;

  opacity: 0.85;

}

.status {

  margin-top: 20px;

  font-size: 14px;

  opacity: 0.6;

}

</style>

</head>

<body>

<div class="card">

<h1>

MEDIDOR DE BATERIA

</h1>

<div class="subtitulo">

ESP32 • ADC • Wi-Fi

</div>

<div class="bateria-container">

<div class="bateria">

<div

id="carga"

class="carga">

</div>

</div>

</div>

<div

id="porcentagem"

class="porcentagem">

--%

</div>

<div

id="tensao"

class="tensao">

-.-- V

</div>

<div

id="status"

class="status">

Atualização em tempo real

</div>

</div>

<script>

async function atualizar() {

  try {

    const resposta =

      await fetch("/dados");

    const dados =

      await resposta.json();

    const porcentagem =

      dados.porcentagem;

    document

      .getElementById(

        "porcentagem"

      )

      .innerHTML =

        porcentagem + "%";

    document

      .getElementById(

        "tensao"

      )

      .innerHTML =

        dados.tensao

        .toFixed(2) +

        " V";

    const carga =

      document

      .getElementById(

        "carga"

      );

    carga.style.width =

      porcentagem + "%";

    if (

      porcentagem > 60

    ) {

      carga.style.background =

        "#00e676";

    }

    else if (

      porcentagem > 25

    ) {

      carga.style.background =

        "#ffca28";

    }

    else {

      carga.style.background =

        "#ff5252";

    }

  }

  catch (erro) {

    console.log(

      erro

    );

  }

}

setInterval(

  atualizar,

  1000

);

atualizar();

</script>

</body>

</html>

)rawliteral";

// =====================================================

// ENDPOINT DE DADOS

// =====================================================

void enviarDados() {

  float tensao =

    lerTensaoBateria();

  int porcentagem =

    calcularPorcentagem(

      tensao

    );

  String json =

    "{";

  json +=

    "\"tensao\":" +

    String(

      tensao,

      3

    );

  json +=

    ",";

  json +=

    "\"porcentagem\":" +

    String(

      porcentagem

    );

  json +=

    "}";

  server.send(

    200,

    "application/json",

    json

  );

}

// =====================================================

// SETUP

// =====================================================

void setup() {

  Serial.begin(

    115200

  );

  delay(

    1000

  );

  pinMode(

    PINO_BATERIA,

    INPUT

  );

  analogReadResolution(

    12

  );

  analogSetPinAttenuation(

    PINO_BATERIA,

    ADC_11db

  );

  // ================================================

  // WIFI

  // ================================================

  WiFi.mode(

    WIFI_STA

  );

  WiFi.begin(

    ssid,

    password

  );

  Serial.println();

  Serial.print(

    "Conectando ao WiFi"

  );

  while (

    WiFi.status() !=

    WL_CONNECTED

  ) {

    delay(

      500

    );

    Serial.print(

      "."

    );

  }

  Serial.println();

  Serial.println(

    "WiFi conectado!"

  );

  Serial.print(

    "Acesse: http://"

  );

  Serial.println(

    WiFi.localIP()

  );

  // ================================================

  // SERVIDOR

  // ================================================

  server.on(

    "/",

    []() {

      server.send_P(

        200,

        "text/html",

        pagina

      );

    }

  );

  server.on(

    "/dados",

    enviarDados

  );

  server.begin();

  Serial.println(

    "Servidor iniciado!"

  );

}

// =====================================================

// LOOP

// =====================================================

void loop() {

  server.handleClient();

}

Gravando o código

Depois de colocar o nome e a senha da sua rede Wi-Fi:

  1. conecte o ESP32 ao computador;
  2. selecione a placa correta;
  3. selecione a porta COM;
  4. clique em Carregar.

Após terminar, abra o Monitor Serial em: 115200 baud

O ESP32 deverá mostrar algo semelhante a: Conectando ao WiFi…

WiFi conectado!

Acesse: http://192.168.0.105

Servidor iniciado!

O IP será diferente em cada rede.

Acessando pelo celular

Pegue o endereço mostrado no Monitor Serial.

Por exemplo: http://192.168.0.105

Abra esse endereço no navegador.

Você verá a página do medidor.

Não existe servidor externo e nenhum aplicativo precisa ser instalado.

A página está sendo enviada diretamente pelo próprio ESP32.

Testando a bateria

Conecte:

AAA (+) → GPIO34

AAA (-) → GND

O dashboard deverá começar a apresentar a tensão.

Por exemplo:

1,47 V

85%

A animação da bateria acompanha o percentual.

Atualização em tempo real

O navegador executa:

setInterval(

  atualizar,

  1000

);

Isso significa que, aproximadamente a cada segundo, o navegador pergunta ao ESP32:

/dados

O ESP32 responde algo parecido com:

{

  “tensao”: 1.472,

  “porcentagem”: 85

}

O JavaScript pega esses dados e atualiza a interface sem recarregar a página.

Esse é um conceito muito utilizado em sistemas IoT e dashboards embarcados.

Por que fazer várias leituras?

Em vez de utilizar apenas uma conversão: analogReadMilliVolts(PINO_BATERIA);

fazemos: 40 leituras

e calculamos a média.

Isso ajuda a reduzir:

  • ruído;
  • pequenas oscilações;
  • alterações rápidas no display;
  • instabilidade natural do ADC.

Melhorando ainda mais a estabilidade

Se a leitura oscilar muito, podemos colocar um capacitor de:

100 nF

entre:

GPIO34

e

GND

A montagem fica:

AAA (+)

   │

   ├──────── GPIO34

   │

 [100 nF]

   │

  GND

AAA (-) ─── GND

Esse capacitor ajuda a filtrar ruído de alta frequência.

Limitações da porcentagem

É importante entender que o ESP32 está medindo tensão, não energia armazenada diretamente.

Portanto: 1,30 V

não significa necessariamente que exista exatamente: 50% da energia original.

A porcentagem apresentada serve como referência visual. Para construir um testador mais preciso, poderíamos adicionar uma carga conhecida.

Por exemplo:

Pilha

  │

resistor de carga

  │

transistor / MOSFET

  │

ESP32

O ESP32 poderia:

  1. medir a tensão em vazio;
  2. ativar a carga;
  3. esperar alguns milissegundos;
  4. medir novamente;
  5. analisar a queda de tensão.

Isso permitiria avaliar melhor a resistência interna e o estado da pilha.

Possíveis evoluções

Depois dessa primeira versão podemos acrescentar:

  • teste automático sob carga;
  • medição de resistência interna;
  • histórico da bateria;
  • gráfico de tensão ao longo do tempo;
  • várias baterias simultaneamente;
  • armazenamento das medições;
  • MQTT;
  • integração com Home Assistant;
  • envio para banco de dados;
  • display OLED local;
  • alarme de bateria fraca;
  • medição de baterias Li-Ion com circuito adequado.

Aplicações

O conceito utilizado aqui pode ser aproveitado em diversos produtos:

  • equipamentos alimentados por bateria;
  • sistemas IoT;
  • sensores remotos;
  • nobreaks;
  • sistemas solares;
  • robôs;
  • dispositivos portáteis;
  • manutenção eletrônica;
  • monitoramento industrial.

A grande diferença é que nossa medição não fica limitada ao equipamento físico.

Ela passa a estar disponível pela rede.

Conclusão

Neste projeto construímos um medidor de bateria Wi-Fi utilizando ESP32, capaz de medir uma pilha AAA e apresentar a tensão e uma estimativa visual da carga diretamente em uma página web. Apesar de simples, o projeto reúne conceitos extremamente importantes de eletrônica e sistemas embarcados.

Utilizamos o ADC do ESP32 para transformar uma tensão analógica em informação digital, processamos essa informação no microcontrolador e disponibilizamos tudo através de um servidor web.

O resultado é um pequeno instrumento IoT que pode ser acessado pelo celular sem necessidade de instalar qualquer aplicativo. A partir dessa estrutura podemos evoluir facilmente para sistemas muito mais sofisticados de monitoramento de energia, baterias e equipamentos eletrônicos.

Saiba mais sobre a parceria Saravati e Sara Educação

Este “Guia de Montagem” é uma colaboração especial entre a Saravati e a Sara Educação, criado pelo Professor Felipe Rosa. Nosso objetivo é enriquecer a comunidade de entusiastas da eletrônica, IoT e automação com recursos educacionais de alta qualidade. Através dessa parceria, buscamos inspirar e capacitar criadores em seus projetos, disponibilizando esses guias em nosso blog e nas redes sociais.

@saravatirobotica no Instagram e Tiktok

@sara.educacao no Instagram e Tiktok