# IMU Air Mouse A BLE HID mouse that uses the onboard IMU of a **Seeed XIAO nRF52840 Sense** to move the cursor by tilting and rotating the device in the air. Intended as an open-source replacement for presentation remotes like the Logitech Spotlight, with additional features. ## Features - **6-DoF gyro + accelerometer** via LSM6DS3 with complementary filter - **Hardware tap detection** — single tap = left click, double tap = right click - **BLE HID mouse** — works natively on Windows, macOS, Linux, Android, iOS - **BLE Battery Service** — charge level visible in OS Bluetooth settings - **Web config UI** (`web/index.html`) — configure over BLE from any Chrome/Edge browser, no app install - **Flash persistence** — config survives power cycles (LittleFS) - **Live IMU stream** — 20 Hz gyro/accel data streamed to the web UI visualiser - **Live telemetry** — temperature, uptime, click counts, gyro bias RMS, recal count - **Temperature compensation** — gyro drift correction by Δ temperature since last calibration - **Auto-recalibration** — recalibrates automatically after 5 minutes of idle - **Configurable charge rate** — OFF / 50 mA slow / 100 mA fast via BQ25100 HICHG pin - **Boot-loop detection** — 3 rapid reboots trigger safe mode (config service disabled, flash wiped) ## Hardware | Part | Notes | |---|---| | Seeed XIAO nRF52840 Sense | nRF52840 + LSM6DS3 IMU onboard | | Li-ion battery (10440 / LiPo) | Connected to VBAT + GND pads | ## LED Status The XIAO has three user LEDs (active LOW — HIGH = off, LOW = on): | LED | Pattern | Meaning | |---|---|---| | Blue | Single pulse every 10 s | BLE connected (heartbeat) | | Green | Single pulse every 10 s | Advertising / not connected (heartbeat) | | Green | Rapid flutter (~10 Hz) | Gyro calibration in progress | | Red | Fast blink (continuous) | IMU init failed — hardware fault | | Red | 3 slow blinks on boot | Boot-loop detected — entered safe mode | | Red | 6 rapid blinks | Battery critically low (< 3.10 V) | > **Blue** = BLE-related state. **Green** = device activity. **Red** = fault only. ## Web Config UI Open `web/index.html` in Chrome or Edge (desktop). Requires Web Bluetooth — enable it at `chrome://flags/#enable-web-bluetooth` on Linux. **Configurable parameters:** | Parameter | Range | Description | |---|---|---| | Sensitivity | 100 – 1500 | Cursor speed multiplier | | Dead zone | 0.005 – 0.2 rad/s | Noise floor; raise to reduce drift | | Accel strength | 0 – 0.5 | Pointer acceleration multiplier | | Curve | Linear / Square / √Sqrt | Response shape for input magnitude | | Flip X / Y | on/off | Invert horizontal or vertical axis | | Charge mode | Off / 50 mA / 100 mA | BQ25100 charge current | **Commands:** - **Calibrate Gyro** — recalculates bias offset; hold the device still on a flat surface for ~1 s - **Factory Reset** — wipes flash config, restores defaults ## Building ### Requirements - [PlatformIO](https://platformio.org/) (VS Code extension or CLI) - `adafruit-nrfutil` for flashing: `pip install adafruit-nrfutil` ### Flash ```sh pio run -t upload ``` Double-tap the reset button to enter the UF2 bootloader (red LED pulses) if the board doesn't auto-reset. ### Feature flags All optional features are enabled by `#define` in `source/config.h`. Comment out any line to disable that feature and reduce firmware size / RAM: ```c #define FEATURE_CONFIG_SERVICE // BLE config GATT service #define FEATURE_TELEMETRY // 1 Hz telemetry notifications #define FEATURE_IMU_STREAM // 20 Hz raw IMU stream #define FEATURE_TAP_DETECTION // Hardware tap → click engine #define FEATURE_TEMP_COMPENSATION // Gyro drift correction by temperature #define FEATURE_AUTO_RECAL // Auto-recalibrate after 5 min idle #define FEATURE_BATTERY_MONITOR // ADC battery level + BLE Battery Service #define FEATURE_BOOT_LOOP_DETECT // Crash counter → safe mode ``` ## BSP memory layout fix The XIAO nRF52840 Sense requires a modified linker script to work with SoftDevice S140 7.3.0. Edit `cores/linker/nrf52840_s140_v7.ld` in the Adafruit nRF52 Arduino core: ```c MEMORY { FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 RAM (rwx) : ORIGIN = 0x2000E000, LENGTH = 0x20040000 - 0x2000E000 } SECTIONS { . = ALIGN(4); .svc_data : { PROVIDE(__start_svc_data = .); KEEP(*(.svc_data)) PROVIDE(__stop_svc_data = .); } > RAM .fs_data : { PROVIDE(__start_fs_data = .); KEEP(*(.fs_data)) PROVIDE(__stop_fs_data = .); } > RAM } INSERT AFTER .data; INCLUDE "nrf52_common.ld"; ```