MEMS 加速度计耳机敲击算法
一、核心原理
敲击耳机外壳会产生一个持续 10-50ms、峰值 0.5-3g 的陡峭加速度脉冲,MEMS 加速度计通过检测这个脉冲的幅度、持续时间和波形特征来识别有效敲击事件。
左右区分最简单可靠的方法:左右耳机各安装一个独立的 MEMS 加速度计,各自检测自身的敲击事件,通过蓝牙分别上报给主控。
二、硬件选型与安装
推荐传感器
| 传感器 | 优势 | 功耗 | 内置功能 |
|---|---|---|---|
| BMI270 | 你正在使用的 IMU,集成加速度计 + 陀螺仪,有官方 API 支持 | 加速度计低功耗模式:1.5μA | 内置硬件敲击检测、运动检测 |
| LIS3DH | 专为可穿戴设计,低功耗标杆 | 低功耗模式:0.5μA | 内置单击 / 双击检测 |
| SC7A20H | 国产替代,成本低 | 低功耗模式:1μA | 基础敲击检测 |
强烈推荐使用内置硬件敲击检测功能的传感器,可以让 MCU 在 99% 的时间处于休眠状态,大幅降低功耗。
安装要点
- 将传感器紧贴耳机外壳内壁,最好用导热胶固定,确保振动能有效传递
- 优先让Z 轴垂直于敲击面(通常是耳机外侧),这样敲击时 Z 轴信号最强
- 避免安装在喇叭附近,防止扬声器振动产生干扰
三、核心检测算法(针对没有内置敲击算法的加速度计)
纯软件算法(灵活但功耗较高)
#define TAP_THRESHOLD 800 // 加速度变化阈值 (LSB) #define TAP_DURATION_MIN 3 // 最小持续时间 (采样点) #define TAP_DURATION_MAX 15 // 最大持续时间 (采样点) #define DOUBLE_TAP_INTERVAL 50 // 双击最大间隔 (ms) int16_t accel_prev[3] = {0}; uint32_t tap_time = 0; uint8_t tap_count = 0; void tap_detection_process(int16_t accel[3]) { // 计算三轴加速度变化量 int32_t delta_x = abs(accel[0] - accel_prev[0]); int32_t delta_y = abs(accel[1] - accel_prev[1]); int32_t delta_z = abs(accel[2] - accel_prev[2]); // 取最大变化量 int32_t delta_max = max(max(delta_x, delta_y), delta_z); static uint8_t tap_in_progress = 0; static uint8_t tap_duration = 0; if (delta_max > TAP_THRESHOLD && !tap_in_progress) { tap_in_progress = 1; tap_duration = 1; } else if (tap_in_progress) { tap_duration++; // 检查是否是有效敲击 if (delta_max < TAP_THRESHOLD / 2) { if (tap_duration >= TAP_DURATION_MIN && tap_duration <= TAP_DURATION_MAX) { // 有效敲击事件 uint32_t now = HAL_GetTick(); if (now - tap_time < DOUBLE_TAP_INTERVAL) { // 双击事件 handle_double_tap(); tap_count = 0; } else { tap_count = 1; tap_time = now; } } tap_in_progress = 0; tap_duration = 0; } } // 单击超时判断 if (tap_count == 1 && HAL_GetTick() - tap_time > DOUBLE_TAP_INTERVAL) { handle_single_tap(); tap_count = 0; } // 更新历史数据 memcpy(accel_prev, accel, sizeof(accel_prev)); }四、关键抗干扰技术(重中之重)
误触发是耳机敲击检测最大的痛点,必须采用多层防护:
1. 动态阈值调整
// 用IIR滤波器动态更新基线 #define BASELINE_ALPHA 64 int32_t baseline[3] = {0}; void update_baseline(int16_t accel[3]) { baseline[0] = baseline[0] + (accel[0] - baseline[0]) / BASELINE_ALPHA; baseline[1] = baseline[1] + (accel[1] - baseline[1]) / BASELINE_ALPHA; baseline[2] = baseline[2] + (accel[2] - baseline[2]) / BASELINE_ALPHA; } // 计算相对于基线的变化量 int32_t delta_z = abs(accel[2] - baseline[2]);2. 波形特征过滤
有效敲击波形有两个明显特征:
- 陡峭上升沿:加速度在 1-2 个采样点内达到峰值
- 快速衰减:峰值后迅速回落,整个脉冲持续时间 < 50ms
峰均比过滤:
// 计算窗口内的峰均比 float calculate_par(int16_t *data, uint8_t len) { int16_t max_val = 0; int32_t sum = 0; for (int i = 0; i < len; i++) { max_val = max(max_val, abs(data[i])); sum += abs(data[i]); } float avg_val = (float)sum / len; return (float)max_val / avg_val; } // 有效敲击的峰均比通常>3 if (par > 3.0) { // 可能是有效敲击 }3. 多轴数据融合
不要只看单一轴的信号,结合三轴数据可以过滤掉很多误触发:
// 敲击时应该只有一个轴的信号明显强于其他轴 int32_t delta_max = max(max(delta_x, delta_y), delta_z); int32_t delta_second = max(min(delta_x, delta_y), min(max(delta_x, delta_y), delta_z)); // 主副轴比值>2才认为是有效敲击 if (delta_max > delta_second * 2) { // 有效敲击 }4. 场景识别
结合其他传感器信息进一步过滤:
- 入耳检测:只有耳机佩戴在耳朵上时才启用敲击检测
- 姿态检测:走路、跑步时适当提高检测阈值
- 音频检测:如果同时检测到敲击声,可信度更高
五、调试与优化技巧
- 采样率选择:推荐 200-400Hz,太低会丢失敲击脉冲,太高增加功耗
- 阈值校准:不同耳机外壳材质和结构需要不同的阈值,建议预留校准接口
- 功耗优化:
- 只在入耳检测有效时启用敲击检测
- 使用传感器内置的 FIFO,减少 MCU 唤醒次数
- 敲击检测期间提高采样率,平时降低到 25Hz
- 用户体验:
- 单击响应延迟控制在 100ms 以内
- 双击间隔设置在 150-300ms 之间
- 提供敲击反馈(轻微震动或提示音)
六、常见问题解决
- 误触发太多:提高敲击阈值,增加峰均比过滤,启用多轴融合
- 敲击不灵敏:降低阈值,检查传感器安装是否牢固,确保 Z 轴垂直于敲击面
- 双击识别率低:调整双击间隔时间,增加静默时间参数
- 走路时误触发:结合陀螺仪检测运动状态,动态提高阈值
