Attempt to add jerk correction
This commit is contained in:
@@ -59,7 +59,8 @@ File cfgFile(InternalFS);
|
||||
Config cfg;
|
||||
const Config CFG_DEFAULTS = {
|
||||
CONFIG_MAGIC, 600.0f, 0.060f, 0.08f, CURVE_LINEAR, 0x00, CHARGE_SLOW,
|
||||
/*tapThreshold=*/12, /*tapAction=*/TAP_ACTION_LEFT, /*tapKey=*/0, /*tapMod=*/0
|
||||
/*tapThreshold=*/12, /*tapAction=*/TAP_ACTION_LEFT, /*tapKey=*/0, /*tapMod=*/0,
|
||||
/*jerkThreshold=*/2000.0f
|
||||
};
|
||||
|
||||
// ─── Telemetry definition ─────────────────────────────────────────────────────
|
||||
@@ -128,6 +129,14 @@ uint32_t loopStalls = 0; // loop iterations where dt > 20ms (behind sch
|
||||
bool pendingCal = false;
|
||||
bool pendingReset = false;
|
||||
|
||||
// ── Jerk-based shock detection — freeze cursor during tap impacts ────────────
|
||||
// Jerk = da/dt (rate of change of acceleration). Normal mouse rotation produces
|
||||
// smooth accel changes (low jerk); a tap is a sharp impulse (very high jerk).
|
||||
// This cleanly separates taps from any intentional motion regardless of speed.
|
||||
unsigned long shockFreezeUntil = 0;
|
||||
float prevAx = 0, prevAy = 0, prevAz = 0; // previous frame's accel for Δa
|
||||
const unsigned long SHOCK_FREEZE_MS = 80; // hold freeze after last spike
|
||||
|
||||
ChargeStatus lastChargeStatus = CHGSTAT_DISCHARGING;
|
||||
|
||||
int idleFrames = 0;
|
||||
@@ -223,6 +232,8 @@ void setup() {
|
||||
#endif
|
||||
|
||||
calibrateGyroBias();
|
||||
// Seed previous-accel for jerk detection so first frame doesn't spike
|
||||
prevAx = imu.readFloatAccelX(); prevAy = imu.readFloatAccelY(); prevAz = imu.readFloatAccelZ();
|
||||
|
||||
bledis.setManufacturer("Seeed Studio");
|
||||
bledis.setModel("XIAO nRF52840 Sense");
|
||||
@@ -294,7 +305,7 @@ void loop() {
|
||||
if (cmd == 'r') { Serial.println("[SERIAL] Reset"); pendingReset = true; }
|
||||
}
|
||||
|
||||
if (pendingCal) { pendingCal = false; calibrateGyroBias(); }
|
||||
if (pendingCal) { pendingCal = false; calibrateGyroBias(); prevAx = imu.readFloatAccelX(); prevAy = imu.readFloatAccelY(); prevAz = imu.readFloatAccelZ(); }
|
||||
if (pendingReset) { pendingReset = false; factoryReset(); }
|
||||
|
||||
// Heartbeat LED
|
||||
@@ -345,19 +356,37 @@ void loop() {
|
||||
float ay = imu.readFloatAccelY();
|
||||
float az = imu.readFloatAccelZ();
|
||||
|
||||
// ── Jerk-based shock detection — freeze cursor during tap impacts ────────
|
||||
// Jerk = da/dt. Normal rotation = smooth accel changes (low jerk);
|
||||
// a tap is a sharp impulse (very high jerk).
|
||||
float jx = (ax - prevAx) / dt, jy = (ay - prevAy) / dt, jz = (az - prevAz) / dt;
|
||||
float jerkSq = jx*jx + jy*jy + jz*jz;
|
||||
prevAx = ax; prevAy = ay; prevAz = az;
|
||||
bool shocked = (jerkSq > cfg.jerkThreshold) || (now < shockFreezeUntil);
|
||||
if (jerkSq > cfg.jerkThreshold) shockFreezeUntil = now + SHOCK_FREEZE_MS;
|
||||
|
||||
// Complementary filter — gx=pitch axis, gz=yaw axis on this board layout
|
||||
angleX = ALPHA*(angleX + gx*dt) + (1.0f - ALPHA)*atan2f(ax, sqrtf(ay*ay + az*az));
|
||||
angleY = ALPHA*(angleY + gz*dt) + (1.0f - ALPHA)*atan2f(ay, sqrtf(ax*ax + az*az));
|
||||
// During shock: gyro-only integration to avoid accel spike corrupting angles
|
||||
if (shocked) {
|
||||
angleX += gx * dt;
|
||||
angleY += gz * dt;
|
||||
} else {
|
||||
angleX = ALPHA*(angleX + gx*dt) + (1.0f - ALPHA)*atan2f(ax, sqrtf(ay*ay + az*az));
|
||||
angleY = ALPHA*(angleY + gz*dt) + (1.0f - ALPHA)*atan2f(ay, sqrtf(ax*ax + az*az));
|
||||
}
|
||||
|
||||
// ── Gravity-based axis decomposition ──────────────────────────────────────
|
||||
// Low-pass filter accel to get a stable gravity estimate in device frame.
|
||||
// This lets us project angular velocity onto world-aligned axes regardless
|
||||
// of how the device is rolled. Device forward (pointing) axis = X.
|
||||
// Confirmed by diagnostics: GX=roll, GY=nod, GZ=pan in user's hold.
|
||||
// Skip update during shock to protect the gravity estimate from tap spikes.
|
||||
const float GRAV_LP = 0.05f;
|
||||
gravX += GRAV_LP * (ax - gravX);
|
||||
gravY += GRAV_LP * (ay - gravY);
|
||||
gravZ += GRAV_LP * (az - gravZ);
|
||||
if (!shocked) {
|
||||
gravX += GRAV_LP * (ax - gravX);
|
||||
gravY += GRAV_LP * (ay - gravY);
|
||||
gravZ += GRAV_LP * (az - gravZ);
|
||||
}
|
||||
|
||||
float gN = sqrtf(gravX*gravX + gravY*gravY + gravZ*gravZ);
|
||||
if (gN < 0.3f) gN = 1.0f;
|
||||
@@ -400,14 +429,18 @@ void loop() {
|
||||
#ifdef FEATURE_AUTO_RECAL
|
||||
if (idle && idleStartMs != 0 && (now - idleStartMs >= AUTO_RECAL_MS)) {
|
||||
Serial.println("[AUTO-CAL] Long idle — recalibrating...");
|
||||
idleStartMs = 0; calibrateGyroBias(); return;
|
||||
idleStartMs = 0; calibrateGyroBias(); prevAx = imu.readFloatAccelX(); prevAy = imu.readFloatAccelY(); prevAz = imu.readFloatAccelZ(); return;
|
||||
}
|
||||
#endif
|
||||
|
||||
int8_t moveX = 0, moveY = 0;
|
||||
uint8_t flags = 0;
|
||||
|
||||
if (idle) {
|
||||
if (shocked) {
|
||||
// Shock freeze — discard accumulated sub-pixel motion and suppress output
|
||||
accumX = accumY = 0.0f;
|
||||
flags |= 0x08; // bit3 = shock freeze active
|
||||
} else if (idle) {
|
||||
accumX = accumY = 0.0f;
|
||||
flags |= 0x01;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user