Módulos Wifi

Air Mouse com ESP-32

Daniel Vasconcelos 14 de agosto de 202520 min

Air Mouse com ESP-32

Introdução

O Air Mouse com ESP-32 é um projeto que transforma seu ESP-32 em um mouse sem fio controlado por movimento. Com ele, você pode movimentar o cursor do seu computador apenas inclinando o dispositivo e, ainda, simular cliques utilizando sensores capacitivos. Para facilitar o desenvolvimento, desenvolvemos uma biblioteca personalizada (AirMouse) que abstrai as complexidades de comunicação e controle do cursor, permitindo que você se concentre na experimentação e na criação de novas aplicações.

Este projeto é ideal para quem quer aprender mais sobre sensores inerciais, comunicação I2C e interação com dispositivos de entrada, além de ser uma excelente porta de entrada para aplicações de controle gestual e interfaces inovadoras.

 


Materiais Necessários

Para esse projeto você irá precisar dos seguintes materiais:

Instalação das Bibliotecas

Biblioteca MPU6050_tockn

Esta biblioteca permite a leitura e interpretação dos dados do sensor MPU6050. Para instalá-la:

  1. Abra a Arduino IDE.
  2. No menu, vá em Sketch > Incluir Biblioteca > Gerenciar Bibliotecas…
  3. Na janela de bibliotecas, digite MPU6050_tockn na barra de pesquisa.
  4. Selecione a biblioteca e clique em Instalar.

Biblioteca AirMouse

A biblioteca AirMouse foi desenvolvida para facilitar a conversão dos dados do sensor em comandos de movimento do cursor e cliques. Para instalá-la via Git:

  1. Acesse o repositório oficial AQUI! (o mesmo processo é utilizado para outras bibliotecas hospedadas no Git).
  2. Clique em Code > Download ZIP.
  3. Na Arduino IDE, vá em Sketch > Incluir Biblioteca > Adicionar Biblioteca ZIP… e selecione o arquivo baixado.

Observação: Mesmo que você crie a página da biblioteca, o procedimento de instalação é padrão e o mesmo para qualquer biblioteca disponibilizada via Git.


Diagrama do Circuito

  • MPU6050
    • VCC → 3.3V do ESP-32
    • GND → GND do ESP-32
    • SCL → GPIO22 (I2C Clock)
    • SDA → GPIO21 (I2C Data)
  • Sensores Capacitivos para Cliques
    • Clique Esquerdo (Azul): Conecte o pino T8 (GPIO33)
    • Clique Direito (Vermelho): Conecte o pino T9 (GPIO32)

 

Código:

Para carregar o código:

  • Instale o ESP32 na Arduino IDE (se necessário).
  • Conecte o ESP32 via USB (use um cabo de dados).
  • Selecione a Placa e Porta (ESP32 Dev Module).
  • Copie e compile o código.
  • Carregue no ESP32 (pressione BOOT se necessário).
  • Monitor serial em 115200 bps
#include "AirMouse.h" 
#include 
#include 

// Definindo constantes
#define LIMITE_INCLINACAO 5.0   // Inclinação mínima em graus para iniciar o movimento
#define FATOR_ACELERACAO 0.09    // Reduzido para melhor controle do cursor
#define INTERVALO_ATUALIZACAO 5  // Tempo de atualização (ms)
#define TOUCH_THRESHOLD 80       // Threshold para detecção de toque

// Definindo os pinos dos sensores de toque
#define TOUCH_LEFT T8            // GPIO33 (botão esquerdo)
#define TOUCH_RIGHT T9           // GPIO32 (botão direito)

// Instanciando as classes do MPU6050 e do AirMouse
MPU6050 mpu6050(Wire);        // Cria uma instância do sensor MPU6050 usando a comunicação I2C
AirMouse airMouse;            // Cria uma instância para controlar o AirMouse

// Variáveis para controle do movimento do mouse
int moveX, moveY;

void setup() {
  // Inicializa a comunicação serial para depuração
  Serial.begin(115200);
  
  // Verifica se o AirMouse está conectado
  if (!airMouse.isConnected()) {
    Serial.println("--------------------- wasn't connected ---------------------");
    airMouse.begin("ESP32 Air Mouse"); // Tenta iniciar o AirMouse com o nome "ESP32 Air Mouse"
  } else {
    Serial.println("--------------------- is connected! ---------------------");
  }
  
  // Inicializa a comunicação I2C
  Wire.begin();
  
  // Inicializa o sensor MPU6050 e calcula os offsets do giroscópio
  mpu6050.begin();                
  mpu6050.calcGyroOffsets(true);  
  delay(1000);  // Atraso para garantir que o sensor esteja pronto
}

int getMedia(bool isLeft) {
    // Calcula a média das leituras dos sensores de toque (esquerdo ou direito)
    int soma = 0;
    int denom = 0;
    
    // Coleta 5 leituras para calcular a média
    for (int i = 0; i < 5; i++) { // Lê o valor do sensor de toque (esquerdo ou direito) int leitura = isLeft ? touchRead(TOUCH_LEFT) : touchRead(TOUCH_RIGHT); // Se a leitura for maior que 0 (indica toque), soma o valor e conta a leitura válida if (leitura > 0) { 
            soma += leitura;
            denom++; 
        }
    }
    
    // Retorna a média das leituras, evitando divisão por zero
    return (denom > 0) ? soma / denom : 0;
}

void updategiro() {
    // Atualiza as leituras do acelerômetro/giroscópio
    mpu6050.update();
    float ax = mpu6050.getAngleX(); // Obtém o ângulo no eixo X
    float ay = mpu6050.getAngleY(); // Obtém o ângulo no eixo Y

    // Inicializa o movimento do cursor como 0
    moveX = 0;
    moveY = 0;

    // Calcula o movimento do mouse baseado no valor de inclinação (ax) e na constante de aceleração
    if (abs(ax) > LIMITE_INCLINACAO) 
        moveX = int((ax > 0 ? 1 : -1) * (abs(ax) - LIMITE_INCLINACAO) * FATOR_ACELERACAO);

    // Calcula o movimento no eixo Y com um fator de aceleração ajustado
    if (abs(ay) > LIMITE_INCLINACAO) 
        moveY = int((ay > 0 ? -1 : 1) * (LIMITE_INCLINACAO - abs(ay)) * FATOR_ACELERACAO * 1.5);
}

void loop() {
    // Atualiza os valores de movimento baseados na inclinação
    updategiro();

    // Obtém as médias dos toques esquerdo e direito
    int leftTouch = getMedia(true);  // Média do toque esquerdo
    int rightTouch = getMedia(false); // Média do toque direito
    Serial.println(leftTouch); // Exibe a média do toque esquerdo no serial

    // Verifica se o botão esquerdo foi pressionado (valor abaixo do threshold)
    if (leftTouch < TOUCH_THRESHOLD) {
        airMouse.press(0x01);  // Pressiona o botão esquerdo do mouse
        Serial.println("Botão esquerdo pressionado!");
        Serial.println(getMedia(true)); // Exibe o valor de toque esquerdo

        // Enquanto o botão esquerdo estiver pressionado
        while (leftTouch < TOUCH_THRESHOLD) {
            updategiro();  // Atualiza o movimento baseado na inclinação
            airMouse.SendControl(0x01, moveX, moveY); // Move o cursor e envia o controle
            delay(INTERVALO_ATUALIZACAO); // Aguarda o tempo de atualização

            leftTouch = getMedia(true);  // Atualiza o valor do toque esquerdo durante o loop
        }
        airMouse.release();  // Libera o botão esquerdo
        Serial.println("Botão esquerdo solto!");
    } 
    // Verifica se o botão direito foi pressionado
    else if (rightTouch < TOUCH_THRESHOLD) {
        airMouse.press(0x02);  // Pressiona o botão direito do mouse
        Serial.println("Botão direito pressionado!");

        // Enquanto o botão direito estiver pressionado
        while (rightTouch < TOUCH_THRESHOLD) {
            updategiro();  // Atualiza o movimento baseado na inclinação
            airMouse.SendControl(0x02, moveX, moveY); // Move o cursor e envia o controle
            delay(INTERVALO_ATUALIZACAO); // Aguarda o tempo de atualização

            rightTouch = getMedia(false);  // Atualiza o valor do toque direito durante o loop
        }
        airMouse.release();  // Libera o botão direito
        Serial.println("Botão direito solto!");
    } 
    // Se nenhum botão estiver pressionado, apenas move o mouse
    else {
        airMouse.move(moveX, moveY);  // Move o mouse normalmente
    }

    delay(INTERVALO_ATUALIZACAO);  // Atraso para garantir que o movimento não seja excessivamente rápido
}


Entendendo o Código: 

1. Inclusões de Bibliotecas

#include "AirMouse.h" 
#include <MPU6050_tockn.h>
#include <Wire.h>
  • AirMouse.h: Biblioteca para controlar o mouse (aqui você usa o ESP32 para simular um mouse no computador).
  • MPU6050_tockn.h: Biblioteca para interagir com o sensor de movimento MPU6050, que é usado para medir a inclinação e aceleração.
  • Wire.h: Biblioteca necessária para comunicação I2C (usada para se comunicar com o MPU6050).

2. Definição de Constantes

#define LIMITE_INCLINACAO 5.0   // Inclinação mínima em graus para iniciar o movimento
#define FATOR_ACELERACAO 0.09    // Reduzido para melhor controle do cursor
#define INTERVALO_ATUALIZACAO 5  // Tempo de atualização (ms)
#define TOUCH_THRESHOLD 80       // Threshold para detecção de toque
  • LIMITE_INCLINACAO: Define a inclinação mínima do sensor para que o movimento do mouse seja detectado.
  • FATOR_ACELERACAO: Controla a sensibilidade do movimento do mouse em resposta à inclinação do sensor.
  • INTERVALO_ATUALIZACAO: Intervalo de tempo (em milissegundos) para atualizar a posição do mouse.
  • TOUCH_THRESHOLD: Valor limite para detectar quando o sensor de toque está pressionado. Se o valor do toque for menor que este valor, considera-se pressionado.

3. Definição dos Pinos e Variáveis Globais

#define TOUCH_LEFT T8            // GPIO33 (botão esquerdo)
#define TOUCH_RIGHT T9           // GPIO32 (botão direito)
MPU6050 mpu6050(Wire);
AirMouse airMouse;
int moveX, moveY;
  • TOUCH_LEFT e TOUCH_RIGHT: Definem os pinos para os sensores de toque (botões esquerdo e direito).
  • mpu6050: Instância do objeto MPU6050 para lidar com o sensor de movimento.
  • airMouse: Instância do objeto AirMouse para controlar o movimento do cursor.
  • moveX e moveY: Variáveis para armazenar a movimentação do mouse.

4. Função setup()

void setup() {
  Serial.begin(115200);
  if (!airMouse.isConnected()) {
    Serial.println("--------------------- wasn't connected ---------------------");
    airMouse.begin("ESP32 Air Mouse");
  } else {
    Serial.println("--------------------- is connected! ---------------------");
  }
  Wire.begin();
  mpu6050.begin();                
  mpu6050.calcGyroOffsets(true);  
  delay(1000);
}
  • Serial.begin(115200): Inicializa a comunicação serial para monitoramento.
  • airMouse.begin(“ESP32 Air Mouse”): Inicializa o controle do AirMouse com o nome “ESP32 Air Mouse”.
  • Wire.begin(): Inicia a comunicação I2C com o MPU6050.
  • mpu6050.begin(): Inicializa o sensor MPU6050.
  • mpu6050.calcGyroOffsets(true): Realiza o cálculo dos offsets do giroscópio para corrigir desvios iniciais.
  • delay(1000): Aguarda 1 segundo para garantir que o sensor esteja pronto.

5. Função getMedia(bool isLeft)

int getMedia(bool isLeft) {
// Calcula a média das leituras dos sensores de toque (esquerdo ou direito)
int soma = 0;
int denom = 0;

// Coleta 5 leituras para calcular a média
for (int i = 0; i < 5; i++) {
// Lê o valor do sensor de toque (esquerdo ou direito)
int leitura = isLeft ? touchRead(TOUCH_LEFT) : touchRead(TOUCH_RIGHT);

// Se a leitura for maior que 0 (indica toque), soma o valor e conta a leitura válida
if (leitura > 0) { 
soma += leitura;
denom++; 
}
}

// Retorna a média das leituras, evitando divisão por zero
return (denom > 0) ? soma / denom : 0;
}

getMedia(): Essa função calcula a média das leituras dos sensores de toque.

    • isLeft: Um parâmetro que indica se deve ler o sensor esquerdo (TOUCH_LEFT) ou direito (TOUCH_RIGHT).
    • touchRead(): Lê o valor do sensor de toque (quanto menor, mais “pressionado” é o botão).
    • A função calcula a média das leituras para evitar picos, retornando a média se houver leituras válidas, caso contrário, retorna 0.

6. Função updategiro()

void updategiro() {
// Atualiza as leituras do acelerômetro/giroscópio
mpu6050.update();
float ax = mpu6050.getAngleX(); // Obtém o ângulo no eixo X
float ay = mpu6050.getAngleY(); // Obtém o ângulo no eixo Y

// Inicializa o movimento do cursor como 0
moveX = 0;
moveY = 0;

// Calcula o movimento do mouse baseado no valor de inclinação (ax) e na constante de aceleração
if (abs(ax) > LIMITE_INCLINACAO)
moveX = int((ax > 0 ? 1 : -1) * (abs(ax) - LIMITE_INCLINACAO) * FATOR_ACELERACAO);

// Calcula o movimento no eixo Y com um fator de aceleração ajustado
if (abs(ay) > LIMITE_INCLINACAO)
moveY = int((ay > 0 ? -1 : 1) * (LIMITE_INCLINACAO - abs(ay)) * FATOR_ACELERACAO * 1.5);
}
  • updategiro(): Atualiza a posição do mouse com base na inclinação do sensor MPU6050.
    • mpu6050.update(): Atualiza os dados do sensor.
    • getAngleX() e getAngleY(): Obtêm a inclinação no eixo X e Y, respectivamente.
    • moveX e moveY: Calculam o movimento do mouse com base na inclinação, usando um fator de aceleração para controle mais suave.
    • abs(): Função que retorna o valor absoluto da inclinação.

7. Função loop()

void loop() {
// Atualiza os valores de movimento baseados na inclinação
updategiro();

// Obtém as médias dos toques esquerdo e direito
int leftTouch = getMedia(true); // Média do toque esquerdo
int rightTouch = getMedia(false); // Média do toque direito
Serial.println(leftTouch); // Exibe a média do toque esquerdo no serial

// Verifica se o botão esquerdo foi pressionado (valor abaixo do threshold)
if (leftTouch < TOUCH_THRESHOLD) {
airMouse.press(0x01); // Pressiona o botão esquerdo do mouse
Serial.println("Botão esquerdo pressionado!");
Serial.println(getMedia(true)); // Exibe o valor de toque esquerdo

// Enquanto o botão esquerdo estiver pressionado
while (leftTouch < TOUCH_THRESHOLD) {
updategiro(); // Atualiza o movimento baseado na inclinação
airMouse.SendControl(0x01, moveX, moveY); // Move o cursor e envia o controle
delay(INTERVALO_ATUALIZACAO); // Aguarda o tempo de atualização

leftTouch = getMedia(true); // Atualiza o valor do toque esquerdo durante o loop
}
airMouse.release(); // Libera o botão esquerdo
Serial.println("Botão esquerdo solto!");
}
// Verifica se o botão direito foi pressionado
else if (rightTouch < TOUCH_THRESHOLD) {
airMouse.press(0x02); // Pressiona o botão direito do mouse
Serial.println("Botão direito pressionado!");

// Enquanto o botão direito estiver pressionado
while (rightTouch < TOUCH_THRESHOLD) {
updategiro(); // Atualiza o movimento baseado na inclinação
airMouse.SendControl(0x02, moveX, moveY); // Move o cursor e envia o controle
delay(INTERVALO_ATUALIZACAO); // Aguarda o tempo de atualização

rightTouch = getMedia(false); // Atualiza o valor do toque direito durante o loop
}
airMouse.release(); // Libera o botão direito
Serial.println("Botão direito solto!");
}
// Se nenhum botão estiver pressionado, apenas move o mouse
else {
airMouse.move(moveX, moveY); // Move o mouse normalmente
}

delay(INTERVALO_ATUALIZACAO); // Atraso para garantir que o movimento não seja excessivamente rápido
}
  • loop(): Esta função é executada repetidamente. Aqui ocorre a atualização do movimento do mouse e a detecção de toques.
    • updategiro(): Atualiza a movimentação do mouse com base na inclinação do sensor.
    • getMedia(true) e getMedia(false): Obtemos a média das leituras do botão esquerdo e direito.
    • if (leftTouch < TOUCH_THRESHOLD): Se o botão esquerdo for pressionado, realiza o clique esquerdo e continua movendo o mouse até que o botão seja solto.
    • else if (rightTouch < TOUCH_THRESHOLD): Se o botão direito for pressionado, realiza o clique direito e continua movendo o mouse até que o botão seja solto.
    • airMouse.move(moveX, moveY): Se nenhum botão estiver pressionado, apenas move o mouse de acordo com a inclinação.

Imprecisão nas Leituras do Sensor de Toque

Quando um sensor capacitivo lê valores de toque, ele pode captar flutuações de ruído elétrico ou interferência, que geram resultados imprecisos, principalmente quando o valor de toque está perto do limiar de ativação. Isso pode causar leituras inconsistentes, onde o sistema pode registrar um toque que não existe ou não registrar um toque real.

Demonstração:

Observe que quando reiniciamos o ESP, a conexão se encerra, para reconectar, devemos remover o dispositivo e o adicionar novamente. Para isso:

  1. Vá para configurações e Selecione dispositivos.
  2. Selecione o dispositivo do ESP32 em “Bluetooth e outros dispositivos”.
  3. Selecione remover dispositivo.
  4. Selecione adicionar dispositivo.
  5. Selecione Bluetooth
  6. Espere alguns segundos.
  7. Selecione ESP-32 AirMouse.

 


Melhorias

Melhorando a Precisão das Leituras do Sensor de Toque com Filtro Passa-Baixa

Sensores capacitivos, como os utilizados para detectar toques, são suscetíveis a ruídos elétricos e interferências, o que pode resultar em leituras imprecisas. Essas flutuações são especialmente problemáticas quando o valor do toque está próximo do limiar de ativação, podendo gerar falsos positivos ou falhas na detecção de toques reais. Para mitigar esses problemas, uma das técnicas mais eficazes e simples é a aplicação de um filtro passa-baixa, que suaviza o sinal, removendo ruídos de alta frequência e preservando as variações mais lentas, como as causadas por um toque real.

Implementação do Filtro Passa-Baixa no Código

Embora o filtro passa-baixa seja tradicionalmente implementado como um circuito físico (composto por resistores e capacitores), sua lógica pode ser emulada no código por meio de técnicas de suavização de sinal. No projeto do Air Mouse com ESP-32, utilizamos a média das leituras do sensor de toque ao longo de várias amostras para simular o efeito de um filtro passa-baixa. Essa abordagem ajuda a reduzir a sensibilidade a flutuações rápidas, produzindo leituras mais estáveis e confiáveis.

Funcionamento do Filtro no Código

A função getMedia() é responsável por calcular a média das leituras do sensor de toque. Ela realiza cinco leituras consecutivas e descarta valores inválidos (como leituras nulas), garantindo que apenas dados consistentes sejam considerados. Esse processo de média atua como um filtro digital, suavizando o sinal e eliminando picos de ruído.

int getMedia(bool isLeft) {// Calcula a média das leituras dos sensores de toque (esquerdo ou direito)
int soma = 0;
int denom = 0;

// Coleta 5 leituras para calcular a média
for (int i = 0; i < 5; i++) {
// Lê o valor do sensor de toque (esquerdo ou direito)
int leitura = isLeft ? touchRead(TOUCH_LEFT) : touchRead(TOUCH_RIGHT);

// Se a leitura for maior que 0 (indica toque), soma o valor e conta a leitura válida
if (leitura > 0) { 
soma += leitura;
denom++; 
}
}

// Retorna a média das leituras, evitando divisão por zero
return (denom > 0) ? soma / denom : 0;
}

Análise do Código

  • touchRead(): Executa a leitura do sensor de toque (esquerdo ou direito).
  • soma: Acumula os valores válidos das leituras.
  • denom: Conta a quantidade de leituras válidas.
  • Média: Calculada somente quando há leituras válidas, evitando divisões por zero.

Essa abordagem minimiza a sensibilidade a variações rápidas, tornando o sistema mais estável em ambientes ruidosos.

Aprimorando a Precisão com Filtros de Hardware

Além da filtragem digital, um filtro passa-baixa RC (resistor-capacitor) pode ser adicionado ao hardware para estabilizar ainda mais as leituras. Esse filtro atenua ruídos diretamente no sinal analógico antes que ele chegue ao microcontrolador.

Funcionamento do Filtro

O filtro RC consiste em:

  • Resistor (R): Controla a variação da tensão, reduzindo interferências rápidas.
  • Capacitor (C): Armazena e libera carga gradualmente, suavizando o sinal.

Componentes Recomendados

    • Resistor (R): Escolha um valor entre 10kΩ e 100kΩ, dependendo da sensibilidade do seu sistema.
    • Capacitor (C): Escolha um capacitor (não use os eletrolíticos / polarizados) entre 0,1µF e 1µF, dependendo de quanto você quer suavizar as leituras. Um valor comum seria 0,1µF.

Montagens do Circuito

Há variadas possibilidades de montagem de um filtro, aqui temos as principais:

  • Filtro com capacitor: O capacitor atenua ruídos ao armazenar e liberar carga lentamente, suavizando variações rápidas do sinal.

  • Filtro RC: Um resistor limita variações rápidas e um capacitor suaviza o sinal, formando um filtro passa-baixa que reduz ruídos de alta frequência.

  • “Filtro” com resistor em pull-down: Mantém o sinal em nível baixo quando o sensor não está ativo, evitando leituras flutuantes.

  • “Filtro” com resistor em pull-down e corte de frequências: Além de estabilizar o sinal, pode atuar junto com um capacitor para reduzir ruídos de alta frequência.

O filtro garante que o sinal recebido pelo microcontrolador já esteja filtrado, reduzindo a necessidade de processamento adicional no software.

Conclusão

Se você chegou até aqui, parabéns! Você explorou a construção de um Air Mouse utilizando um ESP32 e sensores inerciais, criando uma forma inovadora de controlar o cursor sem a necessidade de um mouse convencional.

Durante o desenvolvimento, abordamos desde a leitura dos dados do MPU6050 até a conversão desses movimentos em comandos úteis. Também trabalhamos com filtros e sensores capacitivos para tornar a experiência de clique mais intuitiva e livre de botões físicos.

Com esse projeto, agora você tem um dispositivo funcional e personalizável, que pode ser aprimorado de diversas formas. Algumas ideias para evoluir o projeto incluem:

  • Melhorar a filtragem do sensor para um movimento mais suave(você já aprendeu em “Aprimorando a Precisão com Filtros de Hardware”);
  • Adicionar novos gestos para ampliar as funcionalidades.

A tecnologia está sempre evoluindo, e o mais importante é continuar testando, ajustando e inovando. Se tiver ideias ou melhorias, aqui você tem a base para experimentar e levar esse conceito ainda mais longe. Boa criação, e até mais!


Sobre o Autor


Daniel Vasconcelos

 


Daniel Vasconcelos

14 de agosto de 2025

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Assine nossa newsletter e
receba  10% OFF  na sua
primeira compra!