Projetos · Mecatrônica Jovem nº 16

Caça-Fantasmas com Efeito Hall: Arduino e Sensor KY-024

Um Arduino, um sensor Hall e um ímã para enxergar o que os olhos não veem: o campo magnético. Tem simulador para testar no navegador, código pronto, ligações e gráfico em tempo real no computador.

Capa da Revista Mecatrônica Jovem edição 16

Publicado na Mecatrônica Jovem nº 16

Este projeto saiu na edição 16 da revista Mecatrônica Jovem, como uma versão atualizada do projeto original do professor Newton C. Braga: o Caça-Fantasmas. A ideia clássica ganhou Arduino, sensor Hall e um gráfico em Python, aproximando a proposta original das ferramentas de hoje.

Abrir edição oficial

Visão geral

O módulo KY-024 reúne um sensor Hall linear 49E e um comparador LM393. A saída A0 varia com a intensidade e a polaridade do campo magnético; a saída D0 liga quando o campo passa de um limite ajustado no trimpot do módulo.

O Arduino lê as duas saídas. Quando o D0 dispara, acende um LED e toca um buzzer. Ao mesmo tempo, o brilho de um segundo LED acompanha a intensidade do campo, e os valores seguem pela serial para o Plotter Serial da Arduino IDE ou para o programa em Python.

Aviso de professor: este sensor não prova que fantasmas existem. Ele mede campo magnético (ímãs, alto-falantes, motores), e isso já é ciência de verdade e muito divertido.

Como funciona o efeito Hall

Dentro de uma plaquinha por onde passa corrente, o campo magnético empurra os elétrons para um dos lados. Aparece uma tensão perpendicular à corrente e ao campo, proporcional à corrente e à intensidade do campo. Quanto mais forte o campo, maior a tensão. O efeito foi descoberto por Edwin Hall em 1879.

Simulador: mexa no campo e veja o Arduino reagir

Mesma conta do Hall.ino: o constrain() segura o valor entre 390 e 820, o map() estica essa faixa para 0 a 1023, e o LED de brilho recebe esse valor dividido por 4.

Perto de 512 é o repouso. Para um lado é um polo do ímã; para o outro lado, o outro polo.

Simplificação: aqui o D0 dispara quando o valor bruto passa deste limite. No módulo real, o nível do D0 pode ser alto ou baixo.

512bruto (A0)
0mapeado (0 a 1023)
0PWM do LED (0 a 255)
desligadoD0 (alerta)
LED de alerta
LED de brilho
Buzzer: silêncio
Valor bruto (A0) Valor mapeado (tracejada)

Simulação feita para entender a lógica. Não é a leitura de um sensor de verdade.

Lista de peças

Peças comuns de bancada, fáceis de achar em lojas de eletrônica.

  • 1 Arduino UNO R3
  • 1 módulo KY-024 (sensor Hall linear 49E + comparador LM393)
  • 2 LEDs, um para o alerta e outro para o brilho
  • 2 resistores de 220 a 470 ohms, um para cada LED
  • 1 buzzer ativo
  • Protoboard e fios jumper
  • 1 ímã (os de neodímio são os mais sensíveis)

Hardware e pinagem

Pinagem usada no Hall.ino, escrito para o Arduino UNO R3.

PeçaPino do Arduino
KY-024 A0A0
KY-024 D0D9
KY-024 +5V
KY-024 GGND
LED de alerta (com resistor)D13
LED de brilho (com resistor)D6, pino PWM
Buzzer ativoD7

O catodo dos LEDs e o outro terminal do buzzer vão ao GND, junto com o GND do módulo.

Por que o LED de brilho está no D6? Para variar o brilho usamos PWM, e no UNO só os pinos 3, 5, 6, 9, 10 e 11 têm PWM. O pino 13 não tem, e nele o LED só liga ou desliga.

Código do Arduino: Hall.ino

Abra na Arduino IDE, escolha a placa Arduino UNO, selecione a porta e faça o upload.

// ======================================================== // Projeto : Caça-Fantasmas com Efeito Hall (Fantasma 5) // Autor : Tio Rafa - Revista Mecatrônica Jovem nº 16 // Origem : versão atualizada do projeto original do prof. Newton C. Braga // Placa : Arduino UNO R3 (ATmega328P) // IDE : Arduino IDE // Sensor : módulo KY-024 (sensor Hall linear 49E + comparador LM393) // ======================================================== // // O que este programa faz: // 1. Lê a saída analógica (A0) do KY-024: quanto mais forte o campo // magnético, mais o valor se afasta do "repouso". // 2. Lê a saída digital (D0): vira "ativa" quando o campo passa do // limite ajustado no trimpot do módulo. // 3. Liga o LED e o buzzer quando o D0 dispara. // 4. Faz o brilho de um segundo LED acompanhar a intensidade do campo. // 5. Envia "bruto,mapeado" pela Serial (uma linha a cada 100 ms) para o // Plotter Serial da Arduino IDE ou para o hall_plotter.py. // // Ligações (confira com o esquema do artigo): // KY-024 A0 -> A0 KY-024 D0 -> D9 // KY-024 + -> 5V KY-024 G -> GND // LED digital (com resistor) -> D13 (é também o LED "L" da placa) // LED de brilho (com resistor) -> D6 (PWM) // Buzzer ativo -> D7 // -------------------- Pinos -------------------- const uint8_t PINO_KY024_A0 = A0; // saída analógica do módulo const uint8_t PINO_KY024_D0 = 9; // saída digital do módulo const uint8_t PINO_LED_ALERTA = 13; // acende quando o D0 dispara const uint8_t PINO_LED_BRILHO = 6; // precisa ser pino PWM (3, 5, 6, 9, 10 ou 11 no UNO) const uint8_t PINO_BUZZER = 7; // -------------------- Ajustes -------------------- // Em alguns módulos o D0 fica em nível ALTO quando detecta; em outros, em BAIXO. // Se o LED e o buzzer ficarem ligados SEM ímã por perto, troque HIGH por LOW. const uint8_t NIVEL_D0_DETECTANDO = HIGH; // Faixa observada no A0 durante os testes do artigo (repouso até ímã bem perto). // Cada sensor é um pouco diferente: abra o Plotter Serial, aproxime e afaste // o ímã e ajuste estes dois números para o SEU módulo. const int LEITURA_MINIMA = 390; const int LEITURA_MAXIMA = 820; const unsigned long INTERVALO_MS = 100; // tempo entre uma leitura e outra const long VELOCIDADE_SERIAL = 9600; // precisa ser igual no hall_plotter.py // -------------------- Variáveis -------------------- unsigned long ultimoEnvio = 0; void setup() { Serial.begin(VELOCIDADE_SERIAL); pinMode(PINO_KY024_D0, INPUT); pinMode(PINO_LED_ALERTA, OUTPUT); pinMode(PINO_LED_BRILHO, OUTPUT); pinMode(PINO_BUZZER, OUTPUT); // Começa com tudo desligado. digitalWrite(PINO_LED_ALERTA, LOW); digitalWrite(PINO_BUZZER, LOW); analogWrite(PINO_LED_BRILHO, 0); } void loop() { // millis() em vez de delay(): o Arduino não "dorme" entre as leituras. unsigned long agora = millis(); if (agora - ultimoEnvio < INTERVALO_MS) { return; } ultimoEnvio = agora; int leituraBruta = analogRead(PINO_KY024_A0); // 0 a 1023 bool detectando = (digitalRead(PINO_KY024_D0) == NIVEL_D0_DETECTANDO); // Alerta digital: LED + buzzer. digitalWrite(PINO_LED_ALERTA, detectando ? HIGH : LOW); digitalWrite(PINO_BUZZER, detectando ? HIGH : LOW); // constrain() segura o valor dentro da faixa; sem ele o map() devolveria // números negativos ou maiores que 1023 fora da faixa de calibração. int leituraLimitada = constrain(leituraBruta, LEITURA_MINIMA, LEITURA_MAXIMA); int leituraMapeada = map(leituraLimitada, LEITURA_MINIMA, LEITURA_MAXIMA, 0, 1023); // analogWrite() aceita 0 a 255, então dividimos por 4 (1023 / 4 = 255). analogWrite(PINO_LED_BRILHO, leituraMapeada / 4); // Formato "bruto,mapeado": o Plotter Serial desenha duas linhas. Serial.print(leituraBruta); Serial.print(","); Serial.println(leituraMapeada); }

Baixar Hall.ino

Calibre para o seu módulo

Cada sensor é um pouco diferente. Abra o Plotter Serial (Ferramentas, Plotter Serial, em 9600), aproxime e afaste o ímã e ajuste LEITURA_MINIMA e LEITURA_MAXIMA no código. Como o sensor responde também à polaridade, inverter o ímã leva o valor para o outro lado do repouso.

Se o LED e o buzzer ficarem ligados sem ímã por perto, troque NIVEL_D0_DETECTANDO para LOW (alguns módulos usam nível baixo ao detectar) ou gire o trimpot do módulo.

Gráfico em Python: hall_plotter.py

O Python lê o que o Arduino manda pela USB e desenha as duas curvas em tempo real, com Tkinter e Matplotlib.

  • Instale as bibliotecas: pip install -r requirements.txt (pyserial, matplotlib e pillow). No Linux, o Tkinter pode vir separado: sudo apt install python3-tk.
  • Feche o Monitor Serial e o Plotter Serial da Arduino IDE: só um programa usa a porta por vez.
  • Rode python hall_plotter.py. Se ele não achar o Arduino sozinho, informe a porta: python hall_plotter.py --porta COM3 (no Linux, algo como /dev/ttyACM0).
  • Para ter um banner no topo da janela, coloque uma imagem chamada cabecalho.png ao lado do script. Sem ela, tudo funciona do mesmo jeito.
""" Caça-Fantasmas com Efeito Hall (Fantasma 5) - Plotter em Python Tio Rafa - Revista Mecatrônica Jovem nº 16 Versão atualizada do projeto original do professor Newton C. Braga. Lê as linhas "bruto,mapeado" enviadas pelo Hall.ino e desenha as duas curvas em tempo real com Tkinter + Matplotlib. Como usar: pip install -r requirements.txt python hall_plotter.py # procura o Arduino sozinho python hall_plotter.py --porta COM3 # Windows, porta escolhida python hall_plotter.py --porta /dev/ttyACM0 # Linux Feche o Monitor Serial e o Plotter Serial da Arduino IDE antes de rodar: só um programa por vez pode usar a porta. """ import argparse import sys import time import tkinter as tk from collections import deque from pathlib import Path import serial import serial.tools.list_ports from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure # Pillow é opcional: só serve para mostrar o banner "cabecalho.png". try: from PIL import Image, ImageTk except ImportError: Image = ImageTk = None TITULO_JANELA = "Caça-Fantasmas - Mecatrônica Jovem" VELOCIDADE_PADRAO = 9600 # igual ao Hall.ino PONTOS_NA_TELA = 50 # quantas leituras aparecem no gráfico INTERVALO_TELA_MS = 50 # de quanto em quanto tempo a tela é atualizada BANNER = Path(__file__).with_name("cabecalho.png") # opcional, ao lado do script COR_FUNDO = "#0d0d14" COR_TEXTO = "#e6e6f0" COR_BRUTO = "lime" COR_MAPEADO = "fuchsia" def interpretar_linha(texto): """Transforma 'bruto,mapeado' em (int, int). Devolve None se a linha for inválida.""" partes = texto.strip().split(",") if len(partes) != 2: return None try: return int(partes[0]), int(partes[1]) except ValueError: return None def encontrar_porta(): """Escolhe automaticamente a porta de um Arduino, se houver só uma candidata.""" portas = list(serial.tools.list_ports.comports()) palavras = ("arduino", "ch340", "usb serial", "usb-serial", "acm") candidatas = [ p for p in portas if any(w in f"{p.description} {p.device}".lower() for w in palavras) ] if len(candidatas) == 1: return candidatas[0].device if portas: print("Não consegui escolher a porta sozinho. Portas encontradas:") for p in portas: print(f" {p.device} - {p.description}") else: print("Nenhuma porta serial encontrada. O Arduino está conectado?") print("Rode de novo com: python hall_plotter.py --porta NOME_DA_PORTA") return None class Plotter: def __init__(self, raiz, conexao, pontos): self.raiz = raiz self.conexao = conexao self.contador = 0 self.eixo_x = deque(maxlen=pontos) self.bruto = deque(maxlen=pontos) self.mapeado = deque(maxlen=pontos) raiz.title(TITULO_JANELA) raiz.configure(bg=COR_FUNDO) self._montar_banner() self._montar_grafico() raiz.protocol("WM_DELETE_WINDOW", self.fechar) raiz.after(INTERVALO_TELA_MS, self.atualizar) def _montar_banner(self): if Image is None or not BANNER.exists(): return imagem = Image.open(BANNER) imagem.thumbnail((900, 120)) self._banner_tk = ImageTk.PhotoImage(imagem) # guarda a referência, senão some tk.Label(self.raiz, image=self._banner_tk, bg=COR_FUNDO).pack(side=tk.TOP) def _montar_grafico(self): figura = Figure(figsize=(10, 4), dpi=100, facecolor=COR_FUNDO) self.eixo = figura.add_subplot(111, facecolor=COR_FUNDO) self.eixo.tick_params(colors=COR_TEXTO) for borda in self.eixo.spines.values(): borda.set_color(COR_TEXTO) self.eixo.set_xlabel("Leituras", color=COR_TEXTO) self.eixo.set_ylabel("Valor (0 a 1023)", color=COR_TEXTO) self.eixo.grid(alpha=0.2) self.curva_bruto, = self.eixo.plot([], [], color=COR_BRUTO, label="Valor analógico (A0)") self.curva_mapeado, = self.eixo.plot([], [], color=COR_MAPEADO, label="Valor mapeado") self.eixo.legend(facecolor=COR_FUNDO, labelcolor=COR_TEXTO) self.tela = FigureCanvasTkAgg(figura, master=self.raiz) self.tela.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) def ler_pendentes(self): """Lê TODAS as linhas que chegaram, para o gráfico não ficar atrasado.""" novas = [] while self.conexao.in_waiting > 0: linha = self.conexao.readline().decode("utf-8", errors="ignore") valores = interpretar_linha(linha) if valores is not None: novas.append(valores) return novas def atualizar(self): try: novas = self.ler_pendentes() except (serial.SerialException, OSError) as erro: print(f"Conexão perdida: {erro}") self.fechar() return for valor_bruto, valor_mapeado in novas: self.contador += 1 self.eixo_x.append(self.contador) self.bruto.append(valor_bruto) self.mapeado.append(valor_mapeado) if novas: self.curva_bruto.set_data(self.eixo_x, self.bruto) self.curva_mapeado.set_data(self.eixo_x, self.mapeado) inicio, fim = self.eixo_x[0], self.eixo_x[-1] self.eixo.set_xlim(inicio, max(fim, inicio + 1)) todos = list(self.bruto) + list(self.mapeado) self.eixo.set_ylim(min(todos) - 20, max(todos) + 20) self.tela.draw_idle() self.raiz.after(INTERVALO_TELA_MS, self.atualizar) def fechar(self): if self.conexao.is_open: self.conexao.close() self.raiz.destroy() def main(): leitor = argparse.ArgumentParser(description="Plotter do Caça-Fantasmas com Efeito Hall") leitor.add_argument("--porta", help="porta serial (ex.: COM3 ou /dev/ttyACM0)") leitor.add_argument("--velocidade", type=int, default=VELOCIDADE_PADRAO, help=f"baud rate (padrão {VELOCIDADE_PADRAO}, igual ao Hall.ino)") leitor.add_argument("--pontos", type=int, default=PONTOS_NA_TELA, help=f"leituras visíveis no gráfico (padrão {PONTOS_NA_TELA})") args = leitor.parse_args() porta = args.porta or encontrar_porta() if porta is None: sys.exit(1) try: conexao = serial.Serial(porta, args.velocidade, timeout=1) except serial.SerialException as erro: print(f"Não consegui abrir {porta}: {erro}") print("Dica: feche o Monitor Serial e o Plotter Serial da Arduino IDE.") sys.exit(1) time.sleep(2) # o UNO reinicia ao abrir a porta; espera ele acordar conexao.reset_input_buffer() raiz = tk.Tk() Plotter(raiz, conexao, args.pontos) raiz.mainloop() if __name__ == "__main__": main()

Baixar hall_plotter.py · Baixar requirements.txt

Resultado esperado

  • Sem ímã por perto, o LED de alerta e o buzzer ficam desligados.
  • Ao aproximar o ímã, o valor bruto sai do valor de repouso, a curva sobe e o LED de brilho acende mais forte.
  • Quando o campo passa do limite do trimpot, o LED de alerta acende e o buzzer toca.
  • Virando o ímã, a curva vai para o outro lado do repouso.

Pontos de atenção

  • Cuidado com ímãs fortes: os de neodímio beliscam dedos, danificam cartões magnéticos e devem ficar longe de pessoas que usam marcapasso.
  • Nível do D0: alguns módulos KY-024 ativam o D0 em nível baixo. O código tem a constante NIVEL_D0_DETECTANDO para isso.
  • Porta serial ocupada: se o Python não abrir a porta, feche o Monitor Serial e o Plotter Serial da Arduino IDE.

Créditos

O projeto original do detector magnético é do professor Newton C. Braga. Esta é uma versão atualizada, publicada na revista Mecatrônica Jovem nº 16, com artigo e código do Tio Rafa. Para conhecer o trabalho do professor Newton, visite o Instituto Newton C. Braga.

Quer aprender mais sobre o sensor Hall? Leia o artigo sobre Magnetômetro do professor Léo Corradini em potassio-40.blogspot.com.

O projeto no GitHub

O código completo, as ligações e o passo a passo estão no repositório do projeto.

github.com/Tio-Rafa-SE/efeito-hall-caca-fantasmas

Quer montar o seu Caça-Fantasmas?

Baixe o projeto completo, teste o simulador e leve a ideia para a sua turma. Se quiser adaptar para uma aula ou oficina, fale com o Tio Rafa.