Skip to content

Commit caf5da8

Browse files
committed
Fix janky and drifty movement by switching to angle-based control
Major improvements to mouse movement quality: 1. **Fixed drift issue**: Changed from acceleration-based to angle-based control - Old: integrated acceleration → velocity → position (double integration = rapid drift) - New: use orientation angles (pitch/roll) directly for velocity - No more continuous drift when holding device still 2. **Fixed jankiness**: Added exponential smoothing filter - Smooths raw angle inputs with alpha=0.3 (30% new, 70% old) - Reduces jitter while maintaining responsiveness 3. **Better dead zone**: Applied to tilt angles instead of acceleration - Ignores tilts below 3 degrees (was 0.05g acceleration) - More effective at filtering unintentional movements 4. **Improved sensitivity**: Updated to 8.0 pixels/degree - Clear units: tilt angle → cursor speed - More intuitive tuning (was using acceleration units) 5. **Optimized AHRS fusion parameters**: - Reduced gain to 0.5 for better stability - Increased acceleration rejection to 20.0 - Better suited for slow, deliberate tilt movements Technical details: - driver/src/uinput.c: Rewrite process_sensor_data() to use Euler angles - driver/src/main.c: Update config defaults (sensitivity, dead_zone units) - Movement now smooth, predictable, and drift-free
1 parent b6d75ca commit caf5da8

2 files changed

Lines changed: 43 additions & 47 deletions

File tree

driver/src/main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
#include "uinput.h"
1313

1414
MouseConfig config = {
15-
.movement_sensitivity = 2.0f, // Default: pixels per degree/second
15+
.movement_sensitivity = 8.0f, // Pixels per degree of tilt (higher = faster cursor)
1616
.scroll_sensitivity = 1.0f,
17-
.dead_zone = 0.05f, // Default: degrees/second threshold for angular velocity
17+
.dead_zone = 3.0f, // Ignore tilts below 3 degrees (reduces jitter)
1818
.scroll_threshold = 0.3f,
1919
.invert_x = false,
2020
.invert_y = false,

driver/src/uinput.c

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ typedef struct {
3232
FusionAhrs ahrs; // Fusion AHRS algorithm
3333
double t_last;
3434
float cursor_x, cursor_y; // Virtual cursor position (accumulated)
35+
float smoothed_vel_x, smoothed_vel_y; // Smoothed cursor velocity
3536
int initialized;
3637
} FusionFilterState;
3738

@@ -138,14 +139,14 @@ void process_sensor_data(UInputDevice* device, const SensorPacket* packet) {
138139
// Initialize Fusion AHRS
139140
FusionAhrsInitialise(&fusion_state.ahrs);
140141

141-
// Set AHRS settings optimized for fast, accurate mouse control
142+
// Set AHRS settings optimized for tilt-based mouse control
142143
FusionAhrsSettings settings = {
143144
.convention = FusionConventionNwu, // North-West-Up coordinate system
144-
.gain = 1.0f, // Higher gain for faster convergence
145+
.gain = 0.5f, // Moderate gain (balance speed vs stability)
145146
.gyroscopeRange = 2000.0f, // ±2000 degrees/s range
146-
.accelerationRejection = 10.0f, // Lower rejection for mouse movements
147+
.accelerationRejection = 20.0f, // Moderate rejection (filter fast movements)
147148
.magneticRejection = 0.0f, // No magnetometer
148-
.recoveryTriggerPeriod = 2 * 200 // 2 seconds at 200Hz (faster recovery)
149+
.recoveryTriggerPeriod = 5 * 50 // 5 seconds at 50Hz (don't recover too fast)
149150
};
150151
FusionAhrsSetSettings(&fusion_state.ahrs, &settings);
151152

@@ -158,48 +159,43 @@ void process_sensor_data(UInputDevice* device, const SensorPacket* packet) {
158159
// Update AHRS with sensor data (no magnetometer)
159160
FusionAhrsUpdateNoMagnetometer(&fusion_state.ahrs, gyroscope, accelerometer, dt);
160161

161-
// Get current quaternion
162+
// Get current orientation as Euler angles (roll, pitch, yaw)
162163
FusionQuaternion quaternion = FusionAhrsGetQuaternion(&fusion_state.ahrs);
163-
164-
// Get linear acceleration (with gravity removed by Fusion)
165-
FusionVector linear_acceleration = FusionAhrsGetLinearAcceleration(&fusion_state.ahrs);
166-
167-
// Transform linear acceleration from device frame to world frame using current orientation
168-
// This makes movement independent of device rotation - move device left = cursor left
169-
FusionMatrix rotation_matrix = FusionQuaternionToMatrix(quaternion);
170-
FusionVector world_acceleration = FusionMatrixMultiplyVector(rotation_matrix, linear_acceleration);
171-
172-
// Debug: log sensor fusion values
164+
FusionEuler euler = FusionQuaternionToEuler(quaternion);
165+
166+
// Use orientation angles directly for cursor control
167+
// Roll (tilting left/right) → X movement
168+
// Pitch (tilting forward/back) → Y movement
169+
// Yaw ignored (no magnetometer, drifts anyway)
170+
float roll = euler.angle.roll; // degrees
171+
float pitch = euler.angle.pitch; // degrees
172+
173+
// Apply dead zone to angles (filter small unintentional tilts)
174+
float dead_zone_deg = config.dead_zone; // e.g., 3.0 degrees
175+
if (fabsf(roll) < dead_zone_deg) roll = 0.0f;
176+
if (fabsf(pitch) < dead_zone_deg) pitch = 0.0f;
177+
178+
// Map tilt angles to cursor velocity (degrees → pixels/second)
179+
// Larger tilt = faster cursor movement
180+
float raw_vel_x = roll * config.movement_sensitivity; // Roll → horizontal velocity
181+
float raw_vel_y = pitch * config.movement_sensitivity; // Pitch → vertical velocity
182+
183+
// Apply exponential smoothing to reduce jitter (alpha = 0.3 = 30% new, 70% old)
184+
// Lower alpha = smoother but more lag, higher alpha = more responsive but jittery
185+
float alpha = 0.3f;
186+
fusion_state.smoothed_vel_x = alpha * raw_vel_x + (1.0f - alpha) * fusion_state.smoothed_vel_x;
187+
fusion_state.smoothed_vel_y = alpha * raw_vel_y + (1.0f - alpha) * fusion_state.smoothed_vel_y;
188+
189+
// Integrate smoothed velocity to cursor position
190+
fusion_state.cursor_x += fusion_state.smoothed_vel_x * dt;
191+
fusion_state.cursor_y += fusion_state.smoothed_vel_y * dt;
192+
193+
// Debug: log orientation and velocity
173194
static int debug_count = 0;
174-
if (++debug_count % 10 == 0) { // Every 10 frames (~200ms)
175-
FusionEuler euler = FusionQuaternionToEuler(quaternion);
176-
syslog(LOG_INFO, "FUSION: Roll:%.1f° Pitch:%.1f° Yaw:%.1f° | WorldAccel(%.3f, %.3f, %.3f) dt:%.4f",
195+
if (++debug_count % 10 == 0) { // Every 10 frames (~200ms at 50Hz)
196+
syslog(LOG_INFO, "Angles: Roll:%.1f° Pitch:%.1f° Yaw:%.1f° | Vel(%.1f, %.1f) px/s | dt:%.4f",
177197
euler.angle.roll, euler.angle.pitch, euler.angle.yaw,
178-
world_acceleration.axis.x, world_acceleration.axis.y, world_acceleration.axis.z, dt);
179-
}
180-
181-
// Apply dead zone to filter small movements (in g units)
182-
float dead_zone_g = config.dead_zone; // e.g., 0.03 g
183-
if (fabsf(world_acceleration.axis.x) < dead_zone_g) world_acceleration.axis.x = 0.0f;
184-
if (fabsf(world_acceleration.axis.y) < dead_zone_g) world_acceleration.axis.y = 0.0f;
185-
186-
// Map world-space acceleration to cursor velocity
187-
// World X acceleration → horizontal cursor movement
188-
// World Y acceleration → vertical cursor movement
189-
// Z acceleration ignored (vertical in world frame)
190-
float cursor_vel_x = world_acceleration.axis.x * config.movement_sensitivity; // World X → Screen X
191-
float cursor_vel_y = -world_acceleration.axis.y * config.movement_sensitivity; // World Y → Screen Y (inverted)
192-
193-
// Integrate velocity to position
194-
fusion_state.cursor_x += cursor_vel_x * dt;
195-
fusion_state.cursor_y += cursor_vel_y * dt;
196-
197-
// Debug velocity and accumulation
198-
if (debug_count % 10 == 0) {
199-
syslog(LOG_INFO, "VEL: (%.2f, %.2f) px/s | cursor_accum: (%.2f, %.2f) | sens:%.1f deadzone:%.3f g",
200-
cursor_vel_x, cursor_vel_y,
201-
fusion_state.cursor_x, fusion_state.cursor_y,
202-
config.movement_sensitivity, dead_zone_g);
198+
fusion_state.smoothed_vel_x, fusion_state.smoothed_vel_y, dt);
203199
}
204200

205201
// Extract integer deltas for mouse movement
@@ -223,8 +219,8 @@ void process_sensor_data(UInputDevice* device, const SensorPacket* packet) {
223219
// Log periodically (every 50 packets ~1 second)
224220
static int log_count = 0;
225221
if (++log_count % 50 == 0) {
226-
syslog(LOG_INFO, "FUSION Angular Vel: (%.2f, %.2f) | Cursor: (%.2f, %.2f) -> dx:%d dy:%d",
227-
cursor_vel_x, cursor_vel_y,
222+
syslog(LOG_INFO, "Movement: Vel(%.1f, %.1f) px/s | Accum(%.2f, %.2f) | Delta dx:%d dy:%d",
223+
fusion_state.smoothed_vel_x, fusion_state.smoothed_vel_y,
228224
fusion_state.cursor_x, fusion_state.cursor_y,
229225
dx, dy);
230226
}

0 commit comments

Comments
 (0)