Pular para o conteúdo
IDE livre

Projeto

Projeto: luz noturna que acende com movimento no escuro, com ESP32-C3 Super Mini

Passo a passo de uma luz de corredor com sensor PIR, LDR e LED na ESP32-C3 Super Mini, que acende suave quando alguém passa no escuro e apaga aos poucos.

Por Prof. Rodrigo Faustino · Atualizado em · 11 min de leitura

Métricas deste artigo

Palavras
2.015
Caracteres
10.855
Tempo de leitura
~11 min

Quem já levantou de madrugada para beber água conhece o problema: acender a luz do corredor ofusca, e andar no escuro dá topada. Este projeto resolve isso com uma luz noturna inteligente: ela só acende quando está escuro e alguém passa, acende suave, fica acesa enquanto houver movimento e depois apaga aos poucos. De dia, não acende nunca.

É um projeto pequeno, com peças da bancada do simulador — um sensor de presença PIR, um LDR e um LED —, mas ele junta três jeitos diferentes de a placa conversar com o mundo: uma entrada digital, uma entrada analógica e uma saída PWM. E o programa é o exemplo perfeito de máquina de estados, o assunto que separa programas que "quase funcionam" de programas que funcionam sempre.

A placa é a ESP32-C3 Super Mini, que cabe numa caixinha atrás de um rodapé ou de um móvel.

O que você vai precisar

  • uma ESP32-C3 Super Mini (ficha);
  • um sensor de presença PIR HC-SR501 (ficha);
  • um LDR e um resistor de 10 kΩ (ficha do LDR);
  • um LED (o branco quente fica bonito) e um resistor de 220 Ω (ficha do LED);
  • protoboard, fios e, para a versão final, uma caixinha e um carregador USB de celular.
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

Passo 1: as ligações

Ligações da ESP32-C3 Super Mini: 5V ao VCC do PIR HC-SR501, GPIO1 à saída OUT do PIR, que sai com 3,3 V, e GND ao GND; 3V3 ao LDR, GPIO0 ao ponto do meio entre o LDR e o resistor de 10 kΩ, que vai ao GND; GPIO6 ao ânodo do LED por um resistor de 220 Ω e GND ao cátodo.
Três peças, três jeitos de conversar com a placa: uma entrada digital (PIR), uma analógica (LDR) e uma saída PWM (LED).

O PIR é alimentado com 5 V, do pino 5V da Super Mini, mas a saída OUT já sai com 3,3 V, como explica a lição PIR — por isso ela vai direto ao GPIO1, sem divisor.

O LDR forma um divisor de tensão com o resistor de 10 kΩ: o LDR vai do 3V3 ao ponto do meio, o resistor vai do ponto do meio ao GND, e o ponto do meio vai ao GPIO0. No claro, a resistência do LDR cai e a tensão do meio sobe; no escuro, ela desce. É o circuito da lição LDR.

Aqui aparece uma diferença importante entre a ESP32 da DevKit V1 e a ESP32-C3. Na C3, só os GPIO0 a GPIO4 leem tensões pelo ADC1. O GPIO5 pertence ao ADC2, que o MicroPython não consegue abrir nesse chip. Por isso o LDR vai no GPIO0. E, entre o GPIO0 e o GPIO4, o GPIO2 é melhor evitar, por ser um pino de configuração do chip na partida.

O LED, com o resistor de 220 Ω, vai no GPIO6. Ele recebe PWM, para poder acender e apagar suave.

Um detalhe de energia: o PIR fica no 5 V e o LDR no 3,3 V. Na protoboard do circuito de exemplo, cada um está ligado a um barramento diferente — o 5 V no de cima e o 3,3 V no de baixo. Ligar o LDR no 5 V faria o ponto do meio passar dos 3,6 V que o pino aguenta.

Passo 2: ler os sensores

O primeiro programa só lê e mostra. Abra-o com o circuito: o LDR do exemplo está num ambiente de 5 lux, como um quarto à noite com alguma luz da rua, e o PIR está parado.

from machine import Pin, ADC
import time

ldr = ADC(Pin(0), atten=ADC.ATTN_11DB)   # até uns 3,1 V na C3
pir = Pin(1, Pin.IN)

for i in range(4):
    mv = ldr.read_uv() // 1000
    print('LDR %4d mV   PIR %d' % (mv, pir.value()))
    time.sleep(1)

No simulador, clique no LDR e mude a luz para 300 lux (uma sala acesa): a leitura sobe para perto de 2,9 V. Clique no PIR e marque "alguém passando": a saída vai a 1 e fica assim pelo tempo de retenção. Anote as leituras do LDR em algumas situações — elas vão definir o limite de "escuro" do projeto.

Passo 3: calibrar o "escuro"

O que é escuro? Depende do lugar. O jeito honesto é medir: com a luz do corredor como você quer que ela seja à noite e de dia, anote as leituras. No simulador, 5 lux deram cerca de 960 mV e 300 lux, cerca de 2900 mV. Um limite entre os dois — 1200 mV — separa bem as situações.

Mas um limite só cria um problema no fim da tarde: quando a luz está bem perto dele, a leitura oscila e o projeto fica "em dúvida", acendendo e apagando. Duas técnicas resolvem isso, as mesmas do monitor de bateria do artigo baterias na ESP32:

  • média: ler várias vezes e tirar a média, como na lição média móvel;
  • histerese: um limite para virar "escuro" (1200 mV) e outro, mais alto, para voltar a "claro" (1500 mV).

O exemplo testa a histerese com uma lista de leituras de um fim de tarde.

ESCURECEU = 1200     # mV: abaixo disto, virou noite
CLAREOU = 1500       # mV: só volta a ser dia acima disto

leituras = [2900, 2400, 1700, 1300, 1190, 1230, 1180, 1250, 1210, 900, 600, 1400, 1450, 1520, 2100]
escuro = False
for mv in leituras:
    antes = escuro
    if not escuro and mv < ESCURECEU:
        escuro = True
    elif escuro and mv > CLAREOU:
        escuro = False
    print('%4d mV -> %s%s' % (mv, 'escuro' if escuro else 'claro', '  <- mudou' if escuro != antes else ''))

Sem a histerese, as leituras de 1190, 1230, 1180 e 1250 fariam o estado trocar quatro vezes em quatro leituras. Com ela, troca uma vez ao escurecer e outra ao amanhecer.

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

Passo 4: desenhar os estados

Diagrama com três estados: apagada, acesa e apagando. De apagada para acesa: escuro e movimento. De acesa para apagando: sem movimento por 20 segundos. De apagando para acesa: movimento de novo. De apagando para apagada: o brilho chegou a zero. De acesa para apagada, tracejado: amanheceu, apaga na hora.
A luz noturna como máquina de estados. Desenhar o diagrama antes de programar evita os casos esquecidos.

Antes de escrever o programa principal, vale desenhar o que a luz pode estar fazendo e o que a faz mudar. São três estados:

  • APAGADA: LED desligado; espera escuro e movimento;
  • ACESA: LED no brilho escolhido; cada movimento renova o tempo; sem movimento por 20 s, passa a apagar;
  • APAGANDO: o brilho cai um pouco a cada passo; se alguém passar, volta a ACESA; se o brilho chegar a zero, vira APAGADA.

E uma transição especial: se clarear com a luz acesa (alguém acendeu a lâmpada do corredor, ou amanheceu), ela apaga na hora. O diagrama é o mesmo tipo de desenho da lição máquina de estados, e é ele que mostra os casos que um programa feito "de cabeça" esqueceria — como o que fazer se alguém passa enquanto a luz está apagando.

Passo 5: o programa completo

O programa segue o diagrama ao pé da letra. Uma variável guarda o estado; a cada passo de 50 ms, ele lê os sensores e decide. O brilho usa PWM com duty_u16, como na lição fade: subir e descer o brilho aos poucos é só mudar o duty um pouco a cada passo.

Para o exemplo caber no simulador, as entradas vêm de uma lista de acontecimentos (segundo, escuro, movimento) em vez dos sensores, e os tempos são curtos: 2 s de espera sem movimento em vez de 20 s. O LED do circuito é de verdade: veja ele acender e apagar na vista 2D enquanto o programa roda.

from machine import Pin, PWM
from time import ticks_ms, ticks_diff, sleep_ms

led = PWM(Pin(6), freq=1000, duty_u16=0)
BRILHO = 40000          # de 0 a 65535: não precisa ser o máximo à noite
SEM_MOVIMENTO_MS = 2000 # no projeto, 20 s
PASSO = 1000            # quanto o brilho muda a cada 50 ms: 2 s para subir ou descer

# (segundo, está escuro?, há movimento?) — a "história" de uma noite curta
historia = [(0, False, True), (1, True, False), (2, True, True), (3, True, False),
            (5.6, True, True), (5.8, True, False), (8.8, False, False)]

estado, brilho, ultimo_mov = 'APAGADA', 0, 0
escuro = movimento = False
inicio = ticks_ms()
while ticks_diff(ticks_ms(), inicio) < 11000:
    t = ticks_diff(ticks_ms(), inicio)
    while historia and t >= historia[0][0] * 1000:
        _, escuro, movimento = historia.pop(0)
    anterior = estado
    if movimento:
        ultimo_mov = t
    if estado == 'APAGADA' and escuro and movimento:
        estado = 'ACESA'
    elif estado == 'ACESA' and t - ultimo_mov >= SEM_MOVIMENTO_MS:
        estado = 'APAGANDO'
    elif estado == 'APAGANDO' and movimento:
        estado = 'ACESA'
    elif estado == 'APAGANDO' and brilho == 0:
        estado = 'APAGADA'
    if not escuro and estado != 'APAGADA':
        estado, brilho = 'APAGADA', 0          # clareou: apaga na hora
    alvo = BRILHO if estado == 'ACESA' else 0
    if brilho < alvo:
        brilho = min(alvo, brilho + PASSO)
    elif brilho > alvo:
        brilho = max(alvo, brilho - PASSO)
    led.duty_u16(brilho)
    if estado != anterior:
        print('%5.1f s  %-8s -> %s' % (t / 1000, anterior, estado))
    sleep_ms(50)

Leia a saída junto com a história: no segundo 0 há movimento, mas está claro — nada acontece. No segundo 2, escuro e movimento: a luz acende. Ela fica acesa até 2 s depois do último movimento e começa a apagar; pouco depois, alguém passa de novo durante a descida, e a luz volta a acender. No fim, ela começa a apagar outra vez, mas clareia antes de terminar — e apaga na hora.

Para a versão com sensores de verdade, troque a lista pelas leituras: movimento = pir.value() == 1 e o escuro da histerese do passo 3, com a média de algumas leituras do LDR. Ajuste também o SEM_MOVIMENTO_MS para uns 20 segundos.

Um ponto fino sobre o PIR: o próprio módulo já tem um tempo de retenção, ajustado num trimpot, durante o qual a saída continua em 1 depois que o movimento acaba. O tempo do programa soma com ele. Deixe o trimpot no mínimo e controle o tempo pelo programa: é mais fácil de mudar e de explicar.

Por que acender devagar

Acender de uma vez funcionaria, mas uma luz que cresce em um ou dois segundos incomoda muito menos os olhos acostumados ao escuro — e parece mais "caprichada". Há um detalhe curioso aqui: o olho não percebe o brilho de forma proporcional à energia. A diferença entre um duty de 1 % e de 5 % parece enorme; entre 80 % e 100 %, quase não se nota. Por isso, em muitos projetos, o brilho sobe por uma curva em vez de uma reta: passos pequenos no começo e maiores no fim. Um jeito simples de testar é elevar ao quadrado a fração do brilho: led.duty_u16(int(65535 * (fracao ** 2))). Experimente as duas versões no simulador e na bancada e peça à turma para dizer qual parece mais "linear" — é um bom experimento sobre percepção.

O BRILHO do exemplo também não é o máximo. À noite, com os olhos adaptados, pouca luz basta para andar com segurança, e menos brilho é menos ofuscamento. Ajuste com o LED já no lugar onde ele vai ficar.

Passo 6: montar e instalar

  1. monte o circuito na protoboard seguindo a imagem e teste com o programa do passo 2;
  2. grave o programa completo como main.py;
  3. na placa de verdade, espere o PIR aquecer: a lição avisa que, logo depois de ligado, ele pode disparar sozinho por um tempo;
  4. escolha o lugar: o PIR precisa "ver" a passagem das pessoas, e o LDR precisa ver a luz do ambiente — mas não a luz do próprio LED. Se o LDR enxergar o LED, a luz noturna acende, o LDR acha que clareou e ela apaga, num ciclo sem fim. Aponte o LED para o chão e o LDR para o teto, ou separe os dois com um anteparo;
  5. alimente com um carregador de celular pela USB-C.

Passo 7: testar uma noite inteira

Um projeto que funciona por dez segundos na mesa pode se comportar de outro jeito ao longo de uma noite. Um teste simples: deixe o programa imprimir no Serial (ou gravar num arquivo) cada mudança de estado, com o tempo desde que a placa ligou, e deixe a luz funcionando no corredor por uma noite. De manhã, olhe o registro:

  • quantas vezes ela acendeu? Faz sentido com o número de vezes que alguém passou?
  • houve acendimentos de dia? Se sim, o limite de escuro está alto;
  • houve acendimentos com ninguém passando? Pode ser o PIR vendo uma janela, um aquecedor ou um animal;
  • quanto tempo ela ficou acesa no total? Esse número, multiplicado pela corrente do LED, diz quanta energia a luz gastou.

Esse registro transforma "acho que está funcionando" em "sei como está funcionando" — e é a base para ajustar os números do programa com argumentos, não no chute.

Problemas comuns

  • Acende de dia: o limite de escuro está alto demais para o lugar. Meça de novo com o programa do passo 2 e ajuste.
  • Pisca no fim da tarde: falta histerese, ou a diferença entre os dois limites é pequena demais.
  • Acende sozinha de madrugada: correntes de ar quente e animais de estimação também movem o PIR. Ajuste a sensibilidade no outro trimpot do módulo e a posição.
  • Liga e desliga sem parar: o LDR está vendo o LED (passo 6, item 4).

Ideias para ir além

  • Brilho pela hora: com o relógio da placa acertado pela internet, a luz pode ser mais fraca de madrugada e mais forte no começo da noite.
  • Registro de passagens: contar quantas vezes alguém passou à noite e guardar num arquivo, como no artigo guardar dados na ESP32, pode virar um gráfico interessante para uma aula de estatística.
  • Anel de LEDs: trocar o LED por um anel NeoPixel, da trilha de displays, permite escolher a cor da luz.

O que este projeto ensina

Por fora, é uma luzinha. Por dentro, há calibração (o "escuro" se mede, não se adivinha), filtragem (média e histerese contra leituras que oscilam), saída analógica com PWM e, principalmente, a disciplina de desenhar os estados antes de programar. Quase todo aparelho da casa — da máquina de lavar ao micro-ondas — funciona com uma máquina de estados parecida com esta. Um bom exercício para a turma é escolher um desses aparelhos e desenhar o diagrama dele.

Também vale notar o que o projeto não precisa: internet, aplicativo, conta em serviço nenhum. Tudo acontece dentro da placa, com três sensores baratos e algumas dezenas de linhas de Python. Muita "casa inteligente" de verdade poderia ser assim — mais simples, mais barata e funcionando mesmo quando o Wi-Fi cai.

Para continuar

As lições usadas estão nas trilhas Analógico (LDR e média móvel), Sensores (PIR), PWM (fade) e Tempo sem travar (máquina de estados). O artigo O ADC da ESP32 explica a atenuação e a curva do ADC, e o mapa de pinos ajuda nas variações.

Fontes

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