Entradas digitais
GPIO na ESP32: entradas, pull-up e o pino flutuante
Como a ESP32 lê um pino: níveis lógicos, pull-up e pull-down internos, o pino flutuante, os GPIOs 34 a 39, interrupções com Pin.irq e o debounce de botões.
Por Prof. Rodrigo Faustino · Atualizado em · 13 min de leitura
No artigo anterior o pino foi saída: o programa decidia o nível e o LED obedecia. Agora a direção se inverte. Um pino de entrada observa o que está ligado nele e devolve 0 ou 1 para o programa. É assim que a ESP32 percebe um botão apertado, um sensor de presença que disparou ou uma chave de fim de curso. Parece ainda mais simples que acender um LED, mas é aqui que aparece um dos erros mais traiçoeiros da eletrônica: o pino flutuante, que lê "qualquer coisa" e faz o programa parecer possuído.
Este artigo explica o que a ESP32 enxerga quando lê um pino, por que existem os resistores de pull-up e pull-down, quais pinos da DevKit V1 têm comportamentos especiais, e como reagir a um botão sem ficar perguntando por ele o tempo todo — com interrupções e com o debounce, a defesa contra a trepidação dos contatos.
Ler um pino: de tensão para 0 ou 1
Para o programa, uma entrada digital só tem dois valores: 0 ou 1. Para o pino, porém, o que existe é uma tensão, que pode ser qualquer valor entre 0 V e 3,3 V. Quem transforma a tensão em número é um circuito dentro do chip que compara a tensão do pino com dois limiares.
Na ESP32, com a alimentação dos pinos (VDD) em 3,3 V, os limiares típicos são:
- V_IL, o maior valor que ainda é lido como 0: 0,25 × VDD, cerca de 0,83 V;
- V_IH, o menor valor que já é lido como 1: 0,75 × VDD, cerca de 2,48 V.
Entre 0,83 V e 2,48 V fica uma zona proibida. Nela o fabricante não garante nada: a mesma tensão pode ser lida como 0 numa placa e 1 em outra, ou mudar com a temperatura. Um circuito bem projetado nunca deixa uma entrada parada nessa faixa. O simulador calcula a tensão de cada pino pelo circuito e, quando uma entrada cai na zona proibida, mostra o aviso zona-proibida na aba Avisos.
No MicroPython, uma entrada é criada com Pin.IN e lida com value():
from machine import Pin
botao = Pin(4, Pin.IN, Pin.PULL_UP)
print(botao.value()) # 0 ou 1O terceiro argumento é o pull — o assunto da próxima seção, e a parte mais importante deste artigo.
Da ementa ao semestre planejado
- Plano de ensino e calendário montados sozinhos
- Diário de aulas pronto para colar no SIGA
- Slides e listas de exercícios com gabarito
Versão Community grátis · Windows
O pino flutuante
Imagine um pino configurado como entrada e sem nada ligado. Qual é a tensão dele? A resposta honesta é: nenhuma tensão definida. Não há fonte puxando para 3,3 V nem caminho para o GND. A entrada de um GPIO é feita para quase não consumir corrente, o que significa que qualquer coisinha mexe na tensão dela: a carga acumulada no fio, ruído elétrico do ambiente, até a mão chegando perto. Dizemos que o pino está flutuando.
Na prática, um pino flutuante produz os bugs mais confusos da bancada. O programa funciona, depois para; o botão "aperta sozinho"; a leitura muda quando alguém encosta na mesa. Numa DevKit V1 do laboratório, um pino solto foi medido lendo 0 de forma estável — e por isso o simulador também lê 0 —, mas isso é sorte, não garantia. O simulador mostra o aviso pino-flutuante justamente para lembrar que esse valor não merece confiança.
O caso mais comum de pino flutuante é o botão ligado "do jeito ingênuo": uma perna no GPIO, a outra no GND. Apertado, o pino vai para 0 V e lê 0 — ótimo. Solto, o pino fica sem ligação nenhuma: flutua. O botão define o 0, mas ninguém define o 1.
O remédio: pull-up e pull-down
A solução é um resistor que puxa o pino para um nível conhecido enquanto ninguém mais está mandando nele:
- pull-up: resistor entre o pino e o 3,3 V. Sem nada mais ligado, o pino fica em 3,3 V e lê 1;
- pull-down: resistor entre o pino e o GND. Sem nada mais ligado, o pino fica em 0 V e lê 0.
O resistor é escolhido com valor alto o bastante para "perder a briga". Quando o botão é apertado e liga o pino direto ao GND, a corrente pelo pull-up é pequena e o pino vai para 0 V sem esforço. Por isso o pull não atrapalha a leitura: ele só decide o que acontece quando o resto do circuito fica em silêncio.
A ESP32 tem esses resistores dentro do chip, em quase todos os GPIOs, e o MicroPython os liga com uma palavra:
Pin(4, Pin.IN, Pin.PULL_UP)liga o pull-up interno;Pin(4, Pin.IN, Pin.PULL_DOWN)liga o pull-down interno;Pin(4, Pin.IN)deixa o pull como estava (o argumento vale-1, "não mexer"), epull=Nonedesliga os dois.
A resistência desses pulls internos é da ordem de 45 kΩ, segundo a ficha da ESP32 (é um valor que ainda vai ser confirmado numa placa do laboratório). É um pull fraco: ótimo para botões, insuficiente para barramentos rápidos como o I2C, que costumam pedir resistores externos menores.
Pin.PULL_UP, o resistor interno puxa o pino para 1 enquanto o botão está solto; apertado, o botão liga o pino ao GND e a leitura vira 0.Qual dos dois usar
A escolha segue o que o botão faz:
- botão ligado ao GND pede pull-up: solto lê 1, apertado lê 0;
- botão ligado ao 3,3 V pede pull-down: solto lê 0, apertado lê 1.
A primeira montagem é a mais comum, a ponto de ser quase um padrão. Com ela, 0 significa apertado — parece ao contrário, e muita gente estranha no começo. Uma dica é dar nome à lógica no código, para o resto do programa ler como se fala:
from machine import Pin
import time
botao = Pin(4, Pin.IN, Pin.PULL_UP)
def apertado():
return botao.value() == 0 # com pull-up, 0 = apertado
for i in range(40):
print('apertado' if apertado() else 'solto')
time.sleep_ms(250)Abra no simulador, rode e segure o botão com o mouse (na aba Circuito, na 2D ou na 3D). O Serial passa de "solto" para "apertado". A aba Pinos mostra o GPIO4 descendo para 0 enquanto o botão está pressionado.

Comparando: sem pull, pull-up e pull-down
O exemplo seguinte cria três entradas sem nada ligado — uma sem pull, uma com pull-up e uma com pull-down — e imprime o que cada uma lê. É o experimento que mostra, de uma vez, o que o pull muda.
from machine import Pin
solto = Pin(13, Pin.IN)
com_pull_up = Pin(14, Pin.IN, Pin.PULL_UP)
com_pull_down = Pin(27, Pin.IN, Pin.PULL_DOWN)
print('sem pull:', solto.value())
print('pull-up:', com_pull_up.value())
print('pull-down:', com_pull_down.value())O resultado é 0, 1 e 0, e a aba Avisos aponta o GPIO13 como flutuante. Os valores de pull-up e pull-down são garantidos; o do pino sem pull não. Troque o 13 pelo 0 e rode de novo: agora o pino sem pull lê 1. A próxima seção explica por quê.
Pinos com comportamento especial na DevKit V1
Nem todos os GPIOs da ESP32 são iguais. Três grupos merecem atenção quando o assunto é entrada.
GPIOs 34 a 39: só entrada e sem pull
Os GPIOs 34, 35, 36, 37, 38 e 39 são somente entrada. Na DevKit V1 de 30 pinos aparecem o 34, o 35, o VP (GPIO36) e o VN (GPIO39). Eles são ótimos para ler sensores, mas têm duas limitações:
- não podem ser saída:
Pin(34, Pin.OUT)dáValueError: pin can only be input; - não têm pull interno.
Pin(34, Pin.IN, Pin.PULL_UP)não dá erro — a placa simplesmente ignora o pedido, sem mensagem nenhuma. O pino continua sem pull e, se nada estiver ligado, flutua.
Essa segunda limitação é perigosa justamente por ser silenciosa: o código parece certo, roda sem erro e o botão não funciona direito. O simulador mostra o aviso sem-pull-interno para quebrar esse silêncio. Nesses pinos, o resistor de pull tem de ser externo: um resistor de verdade, na protoboard, entre o pino e o 3V3 (ou o GND).
Pinos de strapping: pull de fábrica
Os GPIOs 0, 2, 5, 12 e 15 são lidos pelo chip no instante em que ele liga, para decidir como dar a partida. Por isso alguns já nascem com um pull ligado de fábrica:
| GPIO | Pull de fábrica | Solto, lê |
|---|---|---|
| 0 | pull-up | 1 |
| 2 | pull-down | 0 |
| 5 | pull-up | 1 |
| 12 | pull-down | 0 |
| 15 | pull-up | 1 |
É por isso que Pin(0, Pin.IN).value() lê 1 com o pino solto: o pull-up de fábrica continua lá. (Numa DevKit V1 medida no laboratório, o GPIO14 também leu 1 solto.) O GPIO0 é o pino do botão BOOT — dá para ler o botão BOOT da própria placa como uma entrada, sem montar nada.
Esses pinos pedem cuidado na hora de montar: um componente que force nível num pino de strapping no momento em que a placa liga pode impedir a ESP32 de iniciar. O caso clássico é um pull-up externo no GPIO12. O simulador avisa quando um componente faz isso.
GPIOs 1 e 3: o terminal
Os GPIOs 1 (TX0) e 3 (RX0) são a porta serial que leva o REPL até o computador pela USB. Usá-los como entrada ou saída comum desliga o terminal da placa real. Deixe-os quietos.
A IA que lembra da sua vida e mora no seu computador
- Memória de longo prazo com a fonte de cada resposta
- Funciona offline: seus dados ficam no seu PC
- Lê PDFs, slides, fotos e gravações de reunião
Plano Essencial grátis para sempre
Saída que também lê
Um detalhe útil: no MicroPython da ESP32, Pin.OUT liga o pino como entrada e saída ao mesmo tempo. Por isso, ler um pino de saída devolve o nível real do pino, não só o que o programa mandou. É o que faz funcionar o truque led.value(not led.value()) para inverter um LED.
Existe ainda o modo Pin.OPEN_DRAIN (dreno aberto): o pino só sabe puxar para 0 ou soltar, nunca empurra o 1. Parece uma limitação, mas é o que permite vários dispositivos dividirem o mesmo fio sem curto-circuito: se um puxa para 0 e outro solta, ninguém briga. O 1 fica por conta de um pull-up. É assim que funcionam a linha de dados do sensor DHT22 e os dois fios do I2C, assuntos de artigos mais adiante.
A força de uma saída também é configurável, com o argumento drive: Pin.DRIVE_0 (5 mA), DRIVE_1 (10 mA), DRIVE_2 (20 mA, o padrão) e DRIVE_3 (40 mA). O simulador compara a corrente calculada no circuito com esse limite e avisa quando um pino está sendo exigido além da conta.
Perguntar ou ser avisado: polling e interrupções
O exemplo do botão lê o pino a cada 250 ms num laço. Esse jeito de trabalhar se chama polling (sondagem): o programa pergunta, pergunta, pergunta. Funciona, mas tem dois problemas. Primeiro, um toque mais rápido que o intervalo entre as perguntas passa despercebido. Segundo, o programa precisa ficar dedicado a isso, sem tempo para outras tarefas.
A alternativa é a interrupção: em vez de o programa perguntar, o pino avisa quando muda. No MicroPython, isso é o Pin.irq:
from machine import Pin
import time
botao = Pin(4, Pin.IN, Pin.PULL_UP)
apertos = 0
def ao_apertar(pino):
global apertos
apertos += 1
print('apertou! total:', apertos)
botao.irq(ao_apertar, Pin.IRQ_FALLING)
while True:
time.sleep(1)O segundo argumento diz qual mudança interessa:
Pin.IRQ_FALLING: a borda de descida, de 1 para 0 — com pull-up, o instante do aperto;Pin.IRQ_RISING: a borda de subida, de 0 para 1 — com pull-up, o instante em que o botão é solto;Pin.IRQ_FALLING | Pin.IRQ_RISING: as duas.
A função recebe o próprio pino como argumento (o pino do exemplo), o que permite usar a mesma função para vários botões e descobrir qual deles chamou. Como ela muda uma variável de fora, precisa da declaração global.
Como o aviso chega ao seu código
No MicroPython da ESP32, a função do irq não interrompe o programa no meio de uma linha. O aviso do pino é agendado e a função roda logo depois, num ponto seguro do programa principal: no fim de uma volta de laço, num if, ou durante um sleep. É por isso que o while True: time.sleep(1) do exemplo continua atendendo os apertos — dormir não bloqueia as interrupções.
Esse agendamento tem uma fila de 8 lugares. Se chegarem mais avisos do que isso antes de as funções rodarem, o excesso se perde. E, se a função der erro, a placa imprime o traceback e o programa principal segue. As duas regras levam ao mesmo conselho: a função de interrupção deve ser curta — somar um contador, guardar a hora, levantar uma bandeira — e deixar o trabalho pesado para o laço principal.
Trepidação e debounce
Um botão de verdade é uma pecinha de metal batendo em outra. No instante do aperto, o contato trepida: durante alguns milissegundos (de 1 a 5 ms é a faixa típica de ficha), o pino vai e volta entre 0 e 1 antes de firmar. Para um programa com irq, um único aperto pode virar três, cinco, dez bordas de descida. Com um botão trepidando, o contador do exemplo anterior pode avançar várias unidades num toque só.
O simulador usa um botão ideal, sem trepidação, para não confundir quem está começando. Mas os desafios das trilhas simulam a trepidação de propósito, com apertos e solturas muito rápidos, para conferir se o programa se defende.
A defesa mais simples é o debounce por tempo: guardar a hora do último aperto aceito e ignorar o que chegar logo depois.
from machine import Pin
import time
botao = Pin(4, Pin.IN, Pin.PULL_UP)
ultimo = 0
def ao_apertar(pino):
global ultimo
agora = time.ticks_ms()
if time.ticks_diff(agora, ultimo) > 200:
ultimo = agora
print('aperto aceito em', agora, 'ms')
botao.irq(ao_apertar, Pin.IRQ_FALLING)
while True:
time.sleep(1)A janela de 200 ms é generosa: muito maior que a trepidação e menor que o intervalo entre dois apertos de propósito. A conta usa ticks_diff, como todo cálculo de tempo na placa, para continuar certa quando o contador der a volta.
A trepidação também acontece ao soltar o botão. Com IRQ_FALLING e uma janela que cobre o aperto, o exemplo resolve o caso comum. Para ficar mais robusto, há duas melhorias clássicas: conferir pino.value() dentro da função (se o pino já voltou para 1, era ruído) ou fazer o debounce no laço principal, exigindo que a leitura fique igual por alguns milissegundos seguidos antes de aceitar a mudança.
Debounce no hardware
Também existe o debounce por componentes: um capacitor pequeno entre o pino e o GND, junto com o resistor de pull, "amacia" as transições e o pino só muda de verdade quando o contato firma. É comum em placas de produto. Para aprender, o debounce em software é mais instrutivo — e é o que os desafios cobram.
Checklist de uma entrada confiável
Quando uma entrada se comporta de modo estranho, este roteiro resolve quase todos os casos:
- Existe um pull? Interno (
Pin.PULL_UP/Pin.PULL_DOWN) ou externo. Sem pull, o pino flutua; - O pino tem pull interno? Nos GPIOs 34 a 39, não: o resistor precisa ser externo;
- A lógica está certa? Com pull-up, apertado é 0;
- O GND é comum? O botão, o sensor e a ESP32 precisam compartilhar o mesmo GND;
- A tensão está fora da zona proibida? Um divisor mal calculado pode deixar o pino entre 0,83 V e 2,48 V;
- Nada passa de 3,6 V? Um sinal de 5 V direto num GPIO danifica a ESP32 — é preciso um divisor de tensão;
- Há trepidação? Se um aperto conta várias vezes, falta debounce.
Na trilha de entradas digitais, cada item desse checklist vira uma lição com circuito pronto e um desafio com correção automática — inclusive um em que você monta o botão sozinho na protoboard. O próximo artigo da série, sobre PWM, volta ao pino de saída para fazer algo que um pino digital, em teoria, não sabe: ficar no meio do caminho entre o 0 e o 1.