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.
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
Passo 1: as ligações
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.
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
Passo 4: desenhar os estados
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
- monte o circuito na protoboard seguindo a imagem e teste com o programa do passo 2;
- grave o programa completo como
main.py; - na placa de verdade, espere o PIR aquecer: a lição avisa que, logo depois de ligado, ele pode disparar sozinho por um tempo;
- 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;
- 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
- Canais do ADC da ESP32-C3 no MicroPython v1.29.0:
ports/esp32/machine_adc.c, como registrado no perfil do chip do simulador; documentação do MicroPython:machine.ADC. - Pinagem da ESP32-C3 Super Mini: espboards.dev e Last Minute Engineers.
- Funcionamento do PIR HC-SR501 e do LDR: as lições e as fichas dos componentes, com as fontes citadas nelas.
Para praticar no PyPlaca
Trilhas
- Trilha 4: Analógico: ADC, potenciômetro e LDR
- Trilha 5: Sensores: temperatura, distância e presença
- Trilha 3: PWM: brilho, cor, som e movimento
- Trilha 7: Tempo sem travar
Lições
- LDR: medindo a luz
- PIR: alguém passou?
- Fade: acender e apagar devagar
- Máquina de estados
- Média móvel: acalmando a leitura