Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Timothy Lovett
Published © CC BY-NC-SA

Finger Freeflow Mouse

A tethered mouse with smart touch recognition for comfortable, drift-free, and intuitive control.

IntermediateFull instructions providedOver 2 days72

Things used in this project

Hardware components

PMW3360DM optical mouse sensor
×1
Eye Screws
×1
Large Masonite Board
×1
Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
Sans Sense, Xiao ESP32S3
×1
Mouse Buddy PCB
×1
Freeflow Mouse PCB
×1

Story

Read more

Custom parts and enclosures

Mouse Body.stl

Sketchfab still processing.

Mouse Touch.stl

Sketchfab still processing.

Mouse Cover.stl

Sketchfab still processing.

Schematics

Freeflow Mouse Schematic

Freeflow Mouse Control Board Schematic

Code

FreeflowMouse.ino

Arduino
Arduino Code for the Freeflow Mouse
#include <Wire.h>
#include "Adafruit_MPR121.h"
#include <PMW3360.h>
#include <Adafruit_NeoPixel.h>
#include "USB.h"
#include "USBHIDMouse.h"

#define SS A0
#define NEOPIXEL_PIN A3
#define NUMPIXELS 5

// Initialize sensors and LEDs
Adafruit_MPR121 cap = Adafruit_MPR121();
PMW3360 sensor;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
USBHIDMouse Mouse;

uint16_t lasttouched = 0;
uint16_t currtouched = 0;

bool mouseEnabled = false;
bool neopixelsEnabled = false;
int mouseSpeed = 0;  // 0 to 4, corresponds to different speed levels
unsigned long lastPressTime[9] = {0};
unsigned long lastRainbowTime = 0;
int rainbowWait = 10;
long firstPixelHue = 0;

void setMovementLevelColor(int level) {
  uint32_t color;
  uint8_t brightness = 50;  // Adjust brightness to be less intense
  switch (level) {
    case 0: color = strip.Color(brightness, 0, 0); break; // Red
    case 1: color = strip.Color(brightness, brightness, 0); break; // Yellow
    case 2: color = strip.Color(0, 0, brightness); break; // Blue
    case 3: color = strip.Color(0, brightness, 0); break; // Green
    case 4: color = strip.Color(brightness / 2, 0, brightness / 2); break; // Purple
  }
  strip.setPixelColor(0, color);
  strip.show();
}

void clearStrip() {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, 0);
  }
  strip.show();
}

void nonBlockingRainbow() {
  if (millis() - lastRainbowTime > rainbowWait) {
    for (int i = 1; i < strip.numPixels(); i++) {
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show();
    firstPixelHue += 256;  // Update the hue for the next frame
    if (firstPixelHue >= 5 * 65536) {
      firstPixelHue = 0;
    }
    lastRainbowTime = millis();  // Update the time
  }
}

void setup() {
  // Setup Touch Sensor
  if (!cap.begin(0x5A, &Wire, 3)) {
    while (1);
  }

  // Setup Mouse Sensor
  sensor.begin(SS);
  
  // Setup Neopixels
  strip.begin();
  strip.show();  // Initialize all pixels to 'off'

  // Initialize LED for mouse scroll speed indicator
  setMovementLevelColor(mouseSpeed);

  // Setup USB HID Mouse
  Mouse.begin();
  USB.begin();
}

void loop() {
  // Poll the touch sensor for button presses
  currtouched = cap.touched();

  for (uint8_t i = 0; i < 9; i++) {
    unsigned long currentMillis = millis();

    if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) && (currentMillis - lastPressTime[i] > 500)) {
      lastPressTime[i] = currentMillis;

      // Handle touch press actions
      switch (i) {
        case 0:  // Increase speed
          if (mouseSpeed < 4) mouseSpeed++;
          setMovementLevelColor(mouseSpeed);
          break;
        case 1:  // Mouse Forward
          Mouse.press(MOUSE_FORWARD);
          break;
        case 2:  // Enable mouse control (hold down to enable)
          mouseEnabled = true;
          break;
        case 3:  // Middle button
          Mouse.press(MOUSE_MIDDLE);
          break;
        case 4:  // Left click
          Mouse.press(MOUSE_LEFT);
          break;
        case 5:  // Right click
          Mouse.press(MOUSE_RIGHT);
          break;
        case 6:  // Toggle Neopixels
          neopixelsEnabled = !neopixelsEnabled;
          if (!neopixelsEnabled) {
            clearStrip(); 
          }
          break;
        case 7:  // Mouse Backward
          Mouse.press(MOUSE_BACKWARD);
          break;
        case 8:  // Decrease speed
          if (mouseSpeed > 0) mouseSpeed--;
          setMovementLevelColor(mouseSpeed);
          break;
      }
    }

    if (!(currtouched & _BV(i)) && (lasttouched & _BV(i))) {
      // Handle touch release actions
      switch (i) {
        case 1:  // Mouse Forward release
          Mouse.release(MOUSE_FORWARD);
          break;
        case 2:  // Disable mouse control
          mouseEnabled = false;
          break;
        case 3:  // Middle button release
          Mouse.release(MOUSE_MIDDLE);
          break;
        case 4:  // Left click release
          Mouse.release(MOUSE_LEFT);
          break;
        case 5:  // Right click release
          Mouse.release(MOUSE_RIGHT);
          break;
        case 7:  // Mouse Backward release
          Mouse.release(MOUSE_BACKWARD);
          break;
      }
    }
  }

  lasttouched = currtouched;

  // Poll the sensor for movement data
  PMW3360_DATA data = sensor.readBurst();
  
  // Handle mouse movement if enabled
  if (mouseEnabled && data.isOnSurface && data.isMotion) {
    Mouse.move(-data.dx * (mouseSpeed + 1), -data.dy * (mouseSpeed + 1), 0);
  }

  // Run Neopixel rainbow animation if enabled
  if (neopixelsEnabled) {
    nonBlockingRainbow();
  }

  delay(10);
}

Credits

Timothy Lovett

Timothy Lovett

16 projects β€’ 16 followers
Maker. I spent over a decade working on backend systems in various languages.

Comments