Introduction
In this article, we'll walk through the steps to create a Bluetooth-enabled air mouse using an Arduino. An air mouse uses gyroscope data to control the cursor on a screen by moving it through the air. This project is perfect for anyone interested in exploring Bluetooth, Arduino programming, and IMU (Inertial Measurement Unit) sensors.
By the end of this guide, you'll have a fully functional air mouse that you can use to control your computer wirelessly.
Parts and Materials Required
You'll need the following components to build the air mouse:
Arduino Board (such as Arduino Nano 33 IoT or ESP32 with built-in Bluetooth capabilities)
IMU Sensor (MPU6050 or similar gyroscope and accelerometer module)
Bluetooth Module (BLE-capable or built into your microcontroller)
Tactile Push Buttons (for left and right mouse clicks)
Breadboard and Jumper Wires
Battery or USB Power Source.
Wiring Diagram
Attach a circuit diagram showing the connections between your Arduino, IMU sensor, and buttons. Label each connection clearly for easier assembly. Describe the connections in text format for added clarity:
Connect VCC of the IMU sensor to 3.3V or 5V (depending on your microcontroller).
Connect GND of the IMU sensor to GND on the microcontroller.
Connect SCL and SDA pins of the IMU sensor to the respective I2C pins on the microcontroller.
Connect each button between a GPIO pin (for instance, GPIO 18 and 19) and GND.
Circuit Diagram
Coding the Air Mouse
Explain the code functionality in sections to help readers understand each part:
- Include Necessary Libraries
#include <Wire.h>
#include <SPI.h>
#include <BleMouse.h>
Wire and SPI libraries for I2C communication with the IMU.
BleMouse library to handle Bluetooth mouse functionality.
2. Define Variables and Constants
Explain the variables, such as Sensitivity to control cursor speed, and debounce settings for buttons. These variables help make cursor movement smoother and handle button presses effectively.
3. Initialize the BLE Mouse and IMU Sensor
The setup() function initializes BLE and I2C, configuring the IMU sensor to read gyroscope data.
void setup() {
Wire.begin();
Serial.begin(115200);
bleMouse.begin();
// Configure IMU settings
}
- Loop to Read and Process Gyroscope Data
In the loop(), the program reads data from the IMU, applies smoothing to reduce jitter, and then moves the mouse cursor based on the processed gyro data.
void loop() {
while (i2cRead(0x3B, i2cData, 14)); // Read IMU data
// Process and smooth gyro data
// Move the mouse cursor
}
- Button Handling for Left and Right Clicks
This section handles button press events to simulate left and right mouse clicks. It includes debounce logic to prevent accidental double-clicks.
if (bleMouse.isConnected()) {
// Read button states and manage click actions
}
Full code
#include <Wire.h>
#include <SPI.h>
#include <BleMouse.h>
uint8_t data[6];
int16_t gyroX, gyroZ;
int Sensitivity = 200; // Increase sensitivity to reduce cursor speed
int delayi = 5; // Slightly increased delay for smoother movement
BleMouse bleMouse;
uint32_t timer;
uint8_t i2cData[14];
// Button states and debounce settings
bool leftButtonPressed = false;
bool rightButtonPressed = false;
bool dragging = false;
const unsigned long debounceDelay = 50; // Debounce delay for button presses
unsigned long lastLeftDebounceTime = 0;
unsigned long lastRightDebounceTime = 0;
int lastLeftButtonState = HIGH;
int lastRightButtonState = HIGH;
const uint8_t IMUAddress = 0x68;
const uint16_t I2C_TIMEOUT = 1000;
// Thresholds for gyro movement to avoid unintended mouse movement
const int gyroThresholdX = 3; // Reduced for finer control
const int gyroThresholdZ = 3;
// Smoothing variables
float smoothedGyroX = 0;
float smoothedGyroZ = 0;
float alpha = 0.2; // Smoothing factor between 0 and 1
// Initialization delay settings
unsigned long movementStartDelay = 1000; // 1-second delay before cursor starts moving
unsigned long startTime;
bool movementEnabled = false;
// Double-click and long press timing
unsigned long lastClickTime = 0;
const unsigned long doubleClickDelay = 400;
const unsigned long longPressDelay = 500;
uint8_t i2cWrite(uint8_t registerAddress, uint8_t* data, uint8_t length, bool sendStop) {
Wire.beginTransmission(IMUAddress);
Wire.write(registerAddress);
Wire.write(data, length);
return Wire.endTransmission(sendStop);
}
uint8_t i2cWrite2(uint8_t registerAddress, uint8_t data, bool sendStop) {
return i2cWrite(registerAddress, &data, 1, sendStop);
}
uint8_t i2cRead(uint8_t registerAddress, uint8_t* data, uint8_t nbytes) {
uint32_t timeOutTimer;
Wire.beginTransmission(IMUAddress);
Wire.write(registerAddress);
if (Wire.endTransmission(false))
return 1;
Wire.requestFrom(IMUAddress, nbytes, (uint8_t)true);
for (uint8_t i = 0; i < nbytes; i++) {
if (Wire.available())
data[i] = Wire.read();
else {
timeOutTimer = micros();
while (((micros() - timeOutTimer) < I2C_TIMEOUT) && !Wire.available());
if (Wire.available())
data[i] = Wire.read();
else
return 2;
}
}
return 0;
}
void setup() {
Wire.begin();
Serial.begin(115200);
bleMouse.begin();
// Initialize IMU settings
i2cData[0] = 7;
i2cData[1] = 0x00;
i2cData[3] = 0x00;
while (i2cWrite(0x19, i2cData, 4, false));
while (i2cWrite2(0x6B, 0x01, true));
while (i2cRead(0x75, i2cData, 1));
delay(100);
while (i2cRead(0x3B, i2cData, 6));
// Initialize button pins for mouse clicks
pinMode(18, INPUT_PULLUP); // Left click
pinMode(19, INPUT_PULLUP); // Right click
startTime = millis(); // Record the start time
delay(100);
}
void loop() {
while (i2cRead(0x3B, i2cData, 14));
// Check if the initialization delay has passed
if (millis() - startTime >= movementStartDelay) {
movementEnabled = true; // Enable movement after delay
}
// Read the gyro data
gyroX = ((i2cData[8] << 8) | i2cData[9]);
gyroZ = ((i2cData[12] << 8) | i2cData[13]);
// Normalize gyroscope data with Sensitivity adjustment
gyroX = gyroX / Sensitivity / 1.1 * -1;
gyroZ = gyroZ / Sensitivity * -1;
// Exponential smoothing
smoothedGyroX = alpha * gyroX + (1 - alpha) * smoothedGyroX;
smoothedGyroZ = alpha * gyroZ + (1 - alpha) * smoothedGyroZ;
// Only process movements and clicks if BLE mouse is connected and movement is enabled
if (bleMouse.isConnected() && movementEnabled) {
Serial.print("Smoothed Gyro X: ");
Serial.print(smoothedGyroX);
Serial.print(" Smoothed Gyro Z: ");
Serial.print(smoothedGyroZ);
Serial.print("\r\n");
// Non-linear scaling and move the mouse based on gyro data if above threshold
int adjustedGyroX = (abs(smoothedGyroX) > gyroThresholdX) ? smoothedGyroX * 0.5 : smoothedGyroX;
int adjustedGyroZ = (abs(smoothedGyroZ) > gyroThresholdZ) ? smoothedGyroZ * 0.5 : smoothedGyroZ;
bleMouse.move(adjustedGyroZ, -adjustedGyroX);
// Debouncing for left button (GPIO 18)
int leftButtonState = digitalRead(18);
unsigned long currentTime = millis();
if (leftButtonState != lastLeftButtonState) {
lastLeftDebounceTime = currentTime;
}
if ((currentTime - lastLeftDebounceTime) > debounceDelay) {
if (leftButtonState == LOW && !leftButtonPressed) {
// Double-click detection
if (currentTime - lastClickTime < doubleClickDelay) {
bleMouse.click(MOUSE_LEFT); // Double-click
Serial.println("Double-click");
} else {
lastClickTime = currentTime;
leftButtonPressed = true;
Serial.println("Left Click Pressed");
bleMouse.press(MOUSE_LEFT);
}
} else if (leftButtonState == HIGH && leftButtonPressed) {
// Long press for drag
if (currentTime - lastClickTime >= longPressDelay && !dragging) {
dragging = true;
Serial.println("Dragging Enabled");
} else if (!dragging) {
bleMouse.release(MOUSE_LEFT);
Serial.println("Left Click Released");
}
leftButtonPressed = false;
} else if (dragging && leftButtonState == HIGH) {
// Stop dragging when button is released
bleMouse.release(MOUSE_LEFT);
dragging = false;
Serial.println("Dragging Disabled");
}
}
lastLeftButtonState = leftButtonState;
// Debouncing for right button (GPIO 19)
int rightButtonState = digitalRead(19);
if (rightButtonState != lastRightButtonState) {
lastRightDebounceTime = millis();
}
if ((millis() - lastRightDebounceTime) > debounceDelay) {
if (rightButtonState == LOW && !rightButtonPressed) {
bleMouse.press(MOUSE_RIGHT);
rightButtonPressed = true;
Serial.println("Right Click Pressed");
} else if (rightButtonState == HIGH && rightButtonPressed) {
bleMouse.release(MOUSE_RIGHT);
rightButtonPressed = false;
Serial.println("Right Click Released");
}
}
lastRightButtonState = rightButtonState;
}
// Delay to control the refresh rate of the loop
delay(delayi);
}
Setting Up the IMU Sensor and Calibrating Sensitivity
Explain how to set up the IMU sensor, including sensitivity adjustments and thresholds for smoother control. You could suggest initial values for gyroThresholdX and gyroThresholdZ to avoid accidental movement.
Assembly Instructions
- Connect the Components: Use the wiring diagram to connect your components. Attach the IMU sensor to the microcontroller and set up the buttons for left and right click actions.
- Upload the Code: Open the Arduino IDE, paste the code, select the appropriate board and port, then upload.
- Power On: Power on your Arduino with a battery or USB cable.
Testing and Calibration
After setting everything up:
- Pair the air mouse with a Bluetooth-compatible device.
- Hold the air mouse as if using a standard computer mouse.
- Adjust the sensitivity if the cursor moves too fast or too slow by changing the Sensitivity variable in the code.
Troubleshooting Tips
Cursor Drifting: Increase the gyro threshold values.
No Cursor Movement: Check Bluetooth connection and ensure IMU is functioning correctly.
Conclusion
Building your own Bluetooth air mouse is a fun and challenging project that combines microcontroller programming with motion control. This air mouse can be customized further for added functionality, such as scroll actions or additional mouse buttons.
With this guide, you now have a solid foundation to explore similar projects involving gyroscopes and Bluetooth controls!
Follow me on
Twitter
x.com
Github
Gopinath-08 (Gopinath Majhi) · GitHub