2026-03-24 23:11:17 +01:00
2026-03-19 22:11:45 +01:00
2026-03-24 23:11:17 +01:00
2026-03-19 22:38:04 +01:00
2026-03-19 22:11:45 +01:00
2026-03-19 22:11:45 +01:00

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 (VS Code extension or CLI)
  • adafruit-nrfutil for flashing: pip install adafruit-nrfutil

Flash

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:

#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:

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";
Description
No description provided
Readme 217 KiB
Languages
C++ 33.2%
JavaScript 30.1%
CSS 15.3%
HTML 12.3%
C 6.8%
Other 2.3%