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.
Para esse projeto você irá precisar dos seguintes materiais:
Esta biblioteca permite a leitura e interpretação dos dados do sensor MPU6050. Para instalá-la:
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:
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.
Para carregar o código:
ESP32 Dev Module).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
}#include "AirMouse.h"
#include <MPU6050_tockn.h>
#include <Wire.h>#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#define TOUCH_LEFT T8 // GPIO33 (botão esquerdo)
#define TOUCH_RIGHT T9 // GPIO32 (botão direito)
MPU6050 mpu6050(Wire);
AirMouse airMouse;
int moveX, moveY;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);
}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.
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
}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.
Observe que quando reiniciamos o ESP, a conexão se encerra, para reconectar, devemos remover o dispositivo e o adicionar novamente. Para isso:
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.
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.
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; }
Essa abordagem minimiza a sensibilidade a variações rápidas, tornando o sistema mais estável em ambientes ruidosos.
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.
O filtro RC consiste em:
Há variadas possibilidades de montagem de um filtro, aqui temos as principais:




O filtro garante que o sinal recebido pelo microcontrolador já esteja filtrado, reduzindo a necessidade de processamento adicional no software.
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:
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!
|
Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!