Pular para o conteúdo

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.

Escala de tensão de 0 a 3,6 volts para uma entrada da ESP32: até cerca de 0,83 V a leitura é 0; de 0,83 V a 2,48 V é a zona proibida; acima de 2,48 V a leitura é 1; 3,6 V é o máximo que o pino aguenta.
Os limiares de uma entrada da ESP32 com VDD de 3,3 V: abaixo de 0,25 × VDD lê 0, acima de 0,75 × VDD lê 1. Entre os dois, a leitura não é garantida.

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 1

O terceiro argumento é o pull — o assunto da próxima seção, e a parte mais importante deste artigo.

FastTeacherPara professores

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
Conhecer o FastTeacher

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"), e pull=None desliga 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.

Dois esquemas lado a lado do botão no GPIO4 com o pull-up interno de cerca de 45 quilo-ohms ligado ao 3,3 V. Com o botão solto, o pino fica em 3,3 V e lê 1; com o botão apertado, o pino é ligado ao GND e lê 0.
Com 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.

Captura de tela do PyPlaca com a lição do botão: o código que lê o GPIO4 com pull-up no editor, o circuito com o botão na protoboard e a aba Pinos mostrando o GPIO4 mudando de 1 para 0.
A lição "Lendo um botão": a aba Pinos mostra o GPIO4 em 1 com o botão solto e em 0 enquanto ele está apertado.

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:

GPIOPull de fábricaSolto, lê
0pull-up1
2pull-down0
5pull-up1
12pull-down0
15pull-up1

É 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.

FastMindAssistente de IA local

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
Baixar grátis para Windows

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:

  1. Existe um pull? Interno (Pin.PULL_UP/Pin.PULL_DOWN) ou externo. Sem pull, o pino flutua;
  2. O pino tem pull interno? Nos GPIOs 34 a 39, não: o resistor precisa ser externo;
  3. A lógica está certa? Com pull-up, apertado é 0;
  4. O GND é comum? O botão, o sensor e a ESP32 precisam compartilhar o mesmo GND;
  5. A tensão está fora da zona proibida? Um divisor mal calculado pode deixar o pino entre 0,83 V e 2,48 V;
  6. Nada passa de 3,6 V? Um sinal de 5 V direto num GPIO danifica a ESP32 — é preciso um divisor de tensão;
  7. 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.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastMindAssistente de IA local

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
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

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
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

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
Baixar grátis para Windows

Plano Essencial grátis para sempre