跳到主要内容
FallenSigh
返回

摆脱桌面的束缚:基于 IMU 的空中飞鼠算法与实现原理解析

编辑此页

在现代人机交互设备中,空中飞鼠(Air Mouse)允许用户摆脱桌面的物理限制,通过在空中挥动设备来控制屏幕上的光标。它通常被应用于幻灯片演示、智能电视遥控以及 VR 交互中。

本文将深入解析一套在嵌入式设备上运行的空中飞鼠算法的完整实现。该实现硬件上基于 BMI270(陀螺仪 + 加速度计),在算法上巧妙融合了 Mahony 姿态解算滤波器 和 1 Euro 自适应光标滤波器。

空中飞鼠系统整体架构:从 IMU 姿态解算到屏幕坐标的数据流
图 1:从物理运动到像素位移的数据流

整体架构:从物理运动到像素位移

这套空中飞鼠系统的核心思想是:角速度积分计算姿态 →\rightarrow 加速度计修正重力方向 →\rightarrow 姿态欧拉角映射为屏幕坐标 →\rightarrow 动态滤波与交互防抖。

当用户同时长按设备的侧边按键(Side 1 + Side 2)2秒时,系统会挂起底部的 PAW3395 光学追踪传感器,唤醒 BMI270 IMU 模块,正式进入空中飞鼠模式。整个数据流分为两个核心模块:attitude.c(姿态解算) 和 pointer.c(光标映射)。


第一部分:姿态解算(Attitude Estimation)

要获取鼠标在空中的指向,我们需要计算设备的 3D 姿态。这里使用了经典的 Mahony 互补滤波算法。

1. 基础数学:四元数积分

系统使用四元数 q=[q0,q1,q2,q3]Tq = [q_0, q_1, q_2, q_3]^T 来表示设备在空间中的旋转。 陀螺仪测量得到三轴角速度 ω⃗=[ωx,ωy,ωz]T\vec{\omega} = [\omega_x, \omega_y, \omega_z]^T。根据刚体运动学,四元数的微分方程为:

q˙=12q⊗[0ωxωyωz]\dot{q} = \frac{1}{2} q \otimes \begin{bmatrix} 0 \\ \omega_x \\ \omega_y \\ \omega_z \end{bmatrix}

在代码实现中,离散化的四元数积分更新公式表达如下:

q0(t)=q0(t−1)+Δt2(−q1ωx−q2ωy−q3ωz)q_0(t) = q_0(t-1) + \frac{\Delta t}{2}(-q_1\omega_x - q_2\omega_y - q_3\omega_z)

q1(t)=q1(t−1)+Δt2(q0ωx+q2ωz−q3ωy)q_1(t) = q_1(t-1) + \frac{\Delta t}{2}(q_0\omega_x + q_2\omega_z - q_3\omega_y)

依此类推计算 q2,q3q_2, q_3。更新后,必须对四元数进行归一化以保证其表示有效旋转。

2. Mahony 加速度计重力修正

陀螺仪积分不可避免地会产生累积误差(积分漂移)。为了消除漂移,我们利用加速度计测量的重力向量作为绝对参考点。 Mahony 滤波的核心在于叉乘误差反馈。

首先,根据当前四元数推算出理论上的重力向量 v⃗\vec{v}。因为在导航系中重力垂直向下,将其通过逆向旋转映射到机体系,提取出一半的重力向量参数:

const float half_vx = a->q1 * a->q3 - a->q0 * a->q2;
const float half_vy = a->q0 * a->q1 + a->q2 * a->q3;
const float half_vz = a->q0 * a->q0 - 0.5f + a->q3 * a->q3;

(注:代码为了计算效率,提取了 12v⃗\frac{1}{2}\vec{v})

然后,将加速度计测得的向量进行归一化,得到实际的重力向量 a⃗=[nx,ny,nz]T\vec{a} = [n_x, n_y, n_z]^T。 计算理论重力与实际重力的误差,这里使用向量外积(叉乘):e⃗=a⃗×v⃗\vec{e} = \vec{a} \times \vec{v}。

const float half_ex = ny * half_vz - nz * half_vy;
// ...计算半误差向量

这个误差向量 e⃗\vec{e} 代表了当前姿态估计与实际重力方向的偏差角度。 最后,使用比例增益 mahony_kp(在此系统中默认为 1.0f)将这个误差反馈补偿到陀螺仪的角速度读取中:

ω⃗corrected=ω⃗raw+Kp⋅e⃗\vec{\omega}_{corrected} = \vec{\omega}_{raw} + K_p \cdot \vec{e}

Mahony 互补滤波:加速度计重力向量与叉乘误差反馈框图
图 2:Mahony 滤波的叉乘误差反馈

3. ZARU(零偏更新):对抗温漂的终极武器

尽管有加速度计修正,但加速度计只能修正 Roll(横滚)和 Pitch(俯仰),对绕重力轴的 Yaw(偏航)无能为力。如果陀螺仪存在静态零偏(Zero Bias),指针会在屏幕上发生横向匀速漂移。

为此,系统引入了 ZARU (Zero Angular Rate Update) 算法。 ZARU 通过统计学特征判断设备是否处于绝对静止状态:

  1. 陀螺仪角速度的平方和 ≤\le zaru_gyro_dps2^2 (默认 0.5 dps)。

  2. 加速度计的模长在 1g±1g \pm zaru_accel_tol_g (默认 0.08g)范围内。

当检测到静止(still == true)且持续超过确认帧数(zaru_confirm = 100帧)时,系统会将此时陀螺仪的微小输出认定为静态漂移,将其累加求均值(或通过时间常数 zaru_tau_s 进行动态逼近),并从后续的测量中扣除(gx - a->bias_x 等)。


第二部分:光标映射与 One Euro 滤波

在 pointer.c 中,系统需要把欧拉角(Yaw, Pitch)转换为屏幕上的 DX 和 DY(相对坐标)。

1. 坐标转换基础

系统设定了灵敏度参数(例如 36 count/度)。欧拉角的增量乘以灵敏度即为像素偏移。

float x = yaw_deg - p->neutral_yaw;
float y = pitch_deg - p->neutral_pitch;

为了避免由于取整造成的亚像素精度丢失,系统维护了 pend_x 和 pend_y,只输出整数部分 ox / oy,小数部分留到下一帧累加。

2. 核心挑战:生理震颤与 One Euro 滤波

人手悬空持握设备时,必然产生高频的生理震颤(Jitter)。如果直接进行线性映射,屏幕上的光标会像筛糠一样抖动。 如果使用简单的低通滤波(LPF),虽然能滤除抖动,但在用户快速挥动设备时会产生无法忍受的“拖拽感”和延迟(Lag)。

这套代码使用了 HCI(人机交互)领域极其著名的 1 Euro Filter (一欧元滤波器)。 1 Euro 滤波器本质上是一个一阶低通滤波器,其核心公式为:

Xi=αXraw+(1−α)Xi−1X_i = \alpha X_{raw} + (1 - \alpha) X_{i-1}

平滑系数 α\alpha 取决于时间步长 Δt\Delta t 和截止频率 fcf_c:

α=Δtτ+Δt其中τ=12πfc\alpha = \frac{\Delta t}{\tau + \Delta t} \quad \text{其中} \quad \tau = \frac{1}{2\pi f_c}

1 Euro 的魔法在于自适应的截止频率 fcf_c:

fc=fmin+β∣X˙∣f_c = f_{min} + \beta \vert{}\dot{X}\vert{}

其中 ∣X˙∣\vert{}\dot{X}\vert{} 是当前手部运动的导数(速度)。


第三部分:极致的交互体验细节

除了纯粹的数学算法,要让空中飞鼠“好用”,pointer.c 中还包含了非常精妙的交互控制逻辑。

  1. 按键防抖冻结 (Click Guard) 当你按下悬空鼠标的左键或右键时,肌肉发力必然会导致设备产生不自觉的偏转。系统侦测到鼠标按键按下(Press Edge)时,会调用 pointer_freeze() 强制将状态置为 frozen = true,停止坐标输出。 何时解冻?
  1. 空中离合 (Clutch) 普通鼠标移动到鼠标垫边缘时,我们会把它“抬起来”移回中心。空中飞鼠怎么“抬”? 系统设定中键(Middle Button)为离合器(Clutch)。按下中键时,光标被完全冻结;松手时,触发 pointer_rebase,将当前的欧拉角设定为新的中心零点(Neutral Pose)。

结语

一个看似简单的空中飞鼠,其背后是深厚的信号处理与 HCI 功底。从硬件传感器的调度切换、对抗传感器物理特性的 Mahony 与 ZARU 算法,再到针对人体工学优化的 1 Euro 滤波与防抖状态机。这些优美的 C 语言数学实现,最终呈现给了用户一个如丝般顺滑的魔法指针。

实机演示


编辑此页
分享这篇文章:

下一篇
从裸机到上位机:WCH CH585 无线鼠标的固件架构与三条链路