Make tap freezing configurable, add toggles to other functions, minor UI changes

This commit is contained in:
Nik Rozman
2026-03-03 08:49:22 +01:00
parent 8f63d7c0b5
commit dcc50150b8
6 changed files with 97 additions and 41 deletions
+7 -4
View File
@@ -72,10 +72,11 @@ void pushConfigBlob() {
b.chargeMode = (uint8_t)cfg.chargeMode;
b.tapThreshold = cfg.tapThreshold;
b.tapAction = (uint8_t)cfg.tapAction;
b.tapKey = cfg.tapKey;
b.tapMod = cfg.tapMod;
b._pad = 0;
b.jerkThreshold = cfg.jerkThreshold;
b.tapKey = cfg.tapKey;
b.tapMod = cfg.tapMod;
b.tapFreezeEnabled = cfg.tapFreezeEnabled;
b.jerkThreshold = cfg.jerkThreshold;
b.featureFlags = cfg.featureFlags;
cfgBlob.write((uint8_t*)&b, sizeof(b));
}
#endif
@@ -118,7 +119,9 @@ void onConfigBlobWrite(uint16_t h, BLECharacteristic* c, uint8_t* d, uint16_t l)
cfg.tapKey = b->tapKey;
cfg.tapMod = b->tapMod;
#endif
cfg.tapFreezeEnabled = b->tapFreezeEnabled ? 1 : 0;
if (b->jerkThreshold >= 100.0f && b->jerkThreshold <= 50000.0f) cfg.jerkThreshold = b->jerkThreshold;
cfg.featureFlags = b->featureFlags & (FLAG_TAP_ENABLED | FLAG_TEMP_COMP_ENABLED | FLAG_AUTO_RECAL_ENABLED);
saveConfig();
Serial.print("[CFG] Written — sens="); Serial.print(cfg.sensitivity,0);
Serial.print(" dz="); Serial.print(cfg.deadZone,3);
+27 -16
View File
@@ -53,7 +53,15 @@
// ─── Persistence ──────────────────────────────────────────────────────────────
#define CONFIG_FILENAME "/imu_mouse_cfg.bin"
#define CONFIG_MAGIC 0xDEAD123AUL
#define CONFIG_MAGIC 0xDEAD123CUL
// ─── Runtime feature-override flags (cfg.featureFlags bitmask) ───────────────
// These mirror the compile-time FEATURE_* defines but can be toggled at runtime
// via the web UI and persisted in flash. Bits not listed here are reserved = 0.
#define FLAG_TAP_ENABLED 0x01 // Tap detection active (requires restart)
#define FLAG_TEMP_COMP_ENABLED 0x02 // Temperature gyro-drift compensation
#define FLAG_AUTO_RECAL_ENABLED 0x04 // Auto-recalibrate after long idle
#define FLAG_ALL_DEFAULT (FLAG_TAP_ENABLED | FLAG_TEMP_COMP_ENABLED | FLAG_AUTO_RECAL_ENABLED)
// ─── Enums ────────────────────────────────────────────────────────────────────
enum CurveType : uint8_t { CURVE_LINEAR=0, CURVE_SQUARE=1, CURVE_SQRT=2 };
@@ -83,27 +91,30 @@ struct Config {
TapAction tapAction; // what a double-tap does
uint8_t tapKey; // HID keycode (used when tapAction == TAP_ACTION_KEY)
uint8_t tapMod; // HID modifier byte (used when tapAction == TAP_ACTION_KEY)
float jerkThreshold; // jerk² threshold for tap-freeze detection
float jerkThreshold; // jerk² threshold for tap-freeze detection
uint8_t tapFreezeEnabled; // 1 = enable jerk-based cursor freeze during taps
uint8_t featureFlags; // bitmask of FLAG_* — runtime feature overrides
};
extern Config cfg;
extern const Config CFG_DEFAULTS;
// ─── ConfigBlob (over BLE, 20 bytes) ─────────────────────────────────────────
// ─── ConfigBlob (over BLE, 25 bytes) ─────────────────────────────────────────
struct __attribute__((packed)) ConfigBlob {
float sensitivity; // [0]
float deadZone; // [4]
float accelStrength; // [8]
uint8_t curve; // [12]
uint8_t axisFlip; // [13]
uint8_t chargeMode; // [14]
uint8_t tapThreshold; // [15] 131
uint8_t tapAction; // [16] TapAction enum
uint8_t tapKey; // [17] HID keycode
uint8_t tapMod; // [18] HID modifier
uint8_t _pad; // [19]
float jerkThreshold; // [20] jerk² tap-freeze threshold
float sensitivity; // [0]
float deadZone; // [4]
float accelStrength; // [8]
uint8_t curve; // [12]
uint8_t axisFlip; // [13]
uint8_t chargeMode; // [14]
uint8_t tapThreshold; // [15] 131
uint8_t tapAction; // [16] TapAction enum
uint8_t tapKey; // [17] HID keycode
uint8_t tapMod; // [18] HID modifier
uint8_t tapFreezeEnabled; // [19] 1 = enable jerk-based cursor freeze during taps
float jerkThreshold; // [20] jerk² tap-freeze threshold
uint8_t featureFlags; // [24] FLAG_* bitmask — runtime feature overrides
};
static_assert(sizeof(ConfigBlob) == 24, "ConfigBlob must be 24 bytes");
static_assert(sizeof(ConfigBlob) == 25, "ConfigBlob must be 25 bytes");
// ─── TelemetryPacket (24 bytes) ───────────────────────────────────────────────
#ifdef FEATURE_TELEMETRY
+12 -15
View File
@@ -60,7 +60,7 @@ 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,
/*jerkThreshold=*/2000.0f
/*jerkThreshold=*/2000.0f, /*tapFreezeEnabled=*/1, /*featureFlags=*/FLAG_ALL_DEFAULT
};
// ─── Telemetry definition ─────────────────────────────────────────────────────
@@ -221,7 +221,7 @@ void setup() {
Serial.println("[OK] IMU ready");
#ifdef FEATURE_TAP_DETECTION
setupTapDetection();
if (cfg.featureFlags & FLAG_TAP_ENABLED) setupTapDetection();
#endif
cachedTempC = readIMUTemp();
@@ -320,7 +320,7 @@ void loop() {
#endif
#ifdef FEATURE_TAP_DETECTION
processTaps(now);
if (cfg.featureFlags & FLAG_TAP_ENABLED) processTaps(now);
#endif
if (now - lastTime < (unsigned long)LOOP_RATE_MS) return;
@@ -340,17 +340,14 @@ void loop() {
#endif
// Gyro reads with optional temperature compensation
float gx, gy, gz;
float correction = 0.0f;
#ifdef FEATURE_TEMP_COMPENSATION
float correction = TEMP_COMP_COEFF_DPS_C * (cachedTempC - calTempC);
gx = (imu.readFloatGyroX() - biasGX - correction) * (PI/180.0f);
gy = (imu.readFloatGyroY() - biasGY - correction) * (PI/180.0f);
gz = (imu.readFloatGyroZ() - biasGZ - correction) * (PI/180.0f);
#else
gx = (imu.readFloatGyroX() - biasGX) * (PI/180.0f);
gy = (imu.readFloatGyroY() - biasGY) * (PI/180.0f);
gz = (imu.readFloatGyroZ() - biasGZ) * (PI/180.0f);
if (cfg.featureFlags & FLAG_TEMP_COMP_ENABLED)
correction = TEMP_COMP_COEFF_DPS_C * (cachedTempC - calTempC);
#endif
float gx = (imu.readFloatGyroX() - biasGX - correction) * (PI/180.0f);
float gy = (imu.readFloatGyroY() - biasGY - correction) * (PI/180.0f);
float gz = (imu.readFloatGyroZ() - biasGZ - correction) * (PI/180.0f);
float ax = imu.readFloatAccelX();
float ay = imu.readFloatAccelY();
@@ -362,8 +359,8 @@ void loop() {
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;
bool shocked = cfg.tapFreezeEnabled && ((jerkSq > cfg.jerkThreshold) || (now < shockFreezeUntil));
if (cfg.tapFreezeEnabled && jerkSq > cfg.jerkThreshold) shockFreezeUntil = now + SHOCK_FREEZE_MS;
// Complementary filter — gx=pitch axis, gz=yaw axis on this board layout
// During shock: gyro-only integration to avoid accel spike corrupting angles
@@ -427,7 +424,7 @@ void loop() {
bool idle = (idleFrames >= IDLE_FRAMES);
#ifdef FEATURE_AUTO_RECAL
if (idle && idleStartMs != 0 && (now - idleStartMs >= AUTO_RECAL_MS)) {
if ((cfg.featureFlags & FLAG_AUTO_RECAL_ENABLED) && idle && idleStartMs != 0 && (now - idleStartMs >= AUTO_RECAL_MS)) {
Serial.println("[AUTO-CAL] Long idle — recalibrating...");
idleStartMs = 0; calibrateGyroBias(); prevAx = imu.readFloatAccelX(); prevAy = imu.readFloatAccelY(); prevAz = imu.readFloatAccelZ(); return;
}