21、传感器系统:Sensor HAL、物理传感器驱动、虚拟传感器融合

传感器系统,说白了就是Android设备的“五官”。没有它,手机就是个瞎子、聋子。我这些年调过不少传感器,从最基础的加速度计,到复杂的陀螺仪+磁力计融合,踩过的坑能写满一页纸。今天咱们就把Sensor HAL、物理驱动和虚拟传感器融合这三块彻底讲透。

21.1 Sensor HAL:应用与硬件的桥梁

Sensor HAL(硬件抽象层)是Android传感器架构中最关键的一环。它位于Framework和Kernel之间,负责把底层传感器的原始数据,包装成上层能理解的格式。

我个人习惯把Sensor HAL看作一个“翻译官”。它要做的三件事:

  • 枚举传感器:告诉系统“我有哪些传感器,各自什么参数”
  • 数据上报:以固定频率把物理数据推上去
  • 功耗管理:该睡的时候睡,该醒的时候醒

来看一个典型的Sensor HAL实现片段:

// sensors.cpp - 核心实现
static int sensor_get_sensors_list(struct sensors_module_t* module,
                                   struct sensor_t const** list) {
    *list = sSensorList;  // 传感器列表
    return ARRAY_SIZE(sSensorList);
}

static int sensor_activate(struct sensors_poll_device_t* dev,
                           int handle, int enabled) {
    // 打开或关闭指定传感器
    // 我在项目中遇到过,这里忘记关中断导致功耗飙升
    if (enabled) {
        enable_sensor_irq(handle);
    } else {
        disable_sensor_irq(handle);
    }
    return 0;
}

static int sensor_set_delay(struct sensors_poll_device_t* dev,
                            int handle, int64_t ns) {
    // 设置采样周期,单位纳秒
    // 嗯,这里要注意:不同传感器支持的频率范围不一样
    return set_sensor_rate(handle, ns);
}

核心要点:Sensor HAL必须实现sensors_module_tsensors_poll_device_t两个结构体。前者负责发现传感器,后者负责数据流控制。

21.2 物理传感器驱动:从寄存器到数据

物理传感器驱动,就是直接跟硬件寄存器打交道的代码。我刚开始做驱动时,总觉得这步很简单——不就是读寄存器吗?后来发现,坑全在细节里。

一个典型的加速度计驱动流程:

  1. 初始化:配置I2C/SPI总线,设置量程和采样率
  2. 数据读取:轮询或中断方式读取X/Y/Z轴原始值
  3. 数据转换:把原始值换算成物理单位(m/s²)
  4. 上报:通过Input子系统或IIO框架上报给上层

我曾经在一个项目里,因为I2C时钟频率没配好,导致传感器数据偶尔跳变。排查了整整两天才发现是时序问题。所以驱动层面的稳定性,直接决定了上层数据质量。

// 加速度计驱动 - 读取数据示例
static int accel_read_data(struct i2c_client *client, 
                           struct accel_data *data) {
    int ret;
    u8 buf[6];
    
    // 读取6字节原始数据(X/Y/Z各2字节)
    ret = i2c_smbus_read_i2c_block_data(client, 
                                        ACCEL_OUT_X_L, 6, buf);
    if (ret < 0) {
        dev_err(&client->dev, "read accel data failed\n");
        return ret;
    }
    
    // 转换为16位有符号数
    data->x = (s16)(buf[0] | (buf[1] << 8));
    data->y = (s16)(buf[2] | (buf[3] << 8));
    data->z = (s16)(buf[4] | (buf[5] << 8));
    
    return 0;
}

经验之谈:物理传感器驱动建议使用Linux内核的IIO(Industrial I/O)框架。它提供了统一的缓冲区管理和触发机制,比你自己写Input驱动省事多了。

21.3 虚拟传感器融合:1+1 > 2

虚拟传感器,说白了就是拿多个物理传感器的数据,通过算法算出新的传感器。最典型的就是“旋转矢量传感器”——它融合了加速度计、陀螺仪和磁力计的数据。

为什么会需要虚拟传感器?你想想看,单个传感器都有缺陷:

  • 加速度计:低频响应好,但高频噪声大
  • 陀螺仪:高频响应好,但会漂移
  • 磁力计:绝对方向准,但容易受干扰

融合算法就是取长补短。我常用的方法是互补滤波:

// 互补滤波 - 姿态融合简化版
void complementary_filter(imu_data_t *imu, float dt) {
    // 陀螺仪积分得到角度
    float gyro_angle = imu->gyro_z * dt;
    
    // 加速度计计算倾角
    float accel_angle = atan2(imu->accel_y, imu->accel_z);
    
    // 互补融合:陀螺仪占主导,加速度计修正漂移
    float alpha = 0.98;  // 权重系数
    imu->filtered_angle = alpha * (imu->filtered_angle + gyro_angle) 
                         + (1 - alpha) * accel_angle;
}

注意:虚拟传感器的实现位置很关键。可以在Sensor HAL层做,也可以在Framework层的SensorService里做。我个人建议放在HAL层,因为这样延迟更低,而且不依赖上层Java代码。

21.4 传感器系统的整体架构

下面这张图,是我画的一个传感器系统整体架构。它展示了从物理硬件到上层应用的数据流向:

Android传感器系统架构图 物理传感器硬件层 加速度计 | 陀螺仪 | 磁力计 | 光感 | 接近传感器 | 气压计 内核驱动层 (Kernel Driver) I2C/SPI总线驱动 | IIO框架 | Input子系统 | 中断处理 Sensor HAL层 传感器枚举 | 数据上报 | 虚拟传感器融合 | 功耗管理 Framework层 (SensorService) 传感器权限管理 | 数据分发 | 应用回调 | 唤醒锁管理

从这张图可以清楚看到,数据从底向上逐层传递。每一层都有自己负责的事情。我在做系统优化时,经常需要跨层排查问题——比如上层数据抖动,可能是驱动中断没处理好,也可能是HAL层滤波参数不对。

21.5 虚拟传感器的实现策略

虚拟传感器融合,我把它分为三种策略:

策略 实现位置 优点 缺点
HAL层融合 sensors.cpp 延迟低,C/C++性能好 调试困难,更新需OTA
Framework层融合 SensorService.java 易于调试,可动态更新 延迟高,受GC影响
硬件融合 传感器内部MCU 功耗最低,精度最高 依赖硬件厂商,灵活性差

我个人更倾向于HAL层融合。为什么?因为我在一个项目里试过Framework层做融合,结果GC pause导致数据出现周期性抖动。后来改到HAL层,用环形缓冲区+无锁队列,问题就解决了。

避坑指南:我曾经在虚拟传感器里直接用了malloc分配内存,结果在中断上下文里崩溃了。记住:HAL层的数据处理路径上,不要做任何可能阻塞或分配内存的操作。预分配好缓冲区,用完了复用。

21.6 传感器校准与数据质量

传感器校准是个容易被忽视的环节。物理传感器出厂时都有偏差,比如加速度计的零偏、陀螺仪的温漂。如果不校准,虚拟传感器融合出来的数据就是错的。

我常用的校准方法:

  • 静态校准:设备静止时,采集多组数据求平均,得到零偏值
  • 动态校准:设备旋转时,利用已知运动模型反推误差
  • 温度补偿:建立温度-偏差曲线,运行时查表修正

校准数据一般存储在/persist/sensors/分区,这样重启后还能保留。我曾经见过一个产品,每次开机都要重新校准,用户体验极差——这就是没处理好持久化存储。

21.7 性能优化与功耗平衡

传感器系统有个永恒的矛盾:数据越精准,功耗越高。怎么平衡?

我的做法是:

  1. 按需采样:应用不监听时,驱动自动进入低功耗模式
  2. 批量上报:多个传感器数据打包一起上报,减少唤醒次数
  3. 硬件FIFO:利用传感器内部的FIFO缓存数据,CPU不用频繁读取

嗯,这里要特别提一下硬件FIFO。很多现代传感器都内置了FIFO,可以缓存几十到几百组数据。你想想看,如果不用FIFO,每10ms就要唤醒CPU一次;用了FIFO,可以100ms才唤醒一次,功耗直接降一个数量级。

小技巧:调试传感器功耗时,用systrace抓取sensorspower标签,可以清楚看到每个传感器的唤醒频率。我一般先看这个,再决定要不要优化。

好了,传感器系统这块就讲到这里。从HAL到驱动,从物理传感器到虚拟融合,每一步都有它的门道。实际做项目时,多留个心眼——数据不对先查驱动,功耗高了先看FIFO,融合不准先做校准。这些经验,都是我用加班换来的。