12、传感器系统:车载传感器(IMU、轮速、摄像头)、Sensor HAL、传感器融合

传感器系统,说白了就是车的「五官」。没有它们,车就是个瞎子、聋子。我在做第一代车载信息娱乐系统时,对传感器的理解还很肤浅,觉得不就是读几个数据嘛。直到有一次,车辆在隧道里定位漂移了十几米,我才意识到——传感器水很深。

Android Automotive OS 的传感器架构,跟手机上的 Sensor Framework 很像,但多了很多车载专属的东西。比如轮速传感器、方向盘转角、甚至安全带状态。今天我们就来拆解这套系统。

12.1 车载传感器的分类与角色

车载传感器大致分三类:

  • 惯性传感器(IMU):加速度计 + 陀螺仪。测量车辆的加速度和角速度。我习惯把 IMU 叫做「内感」,因为它不依赖外部信号。
  • 轮速传感器:每个轮子一个,测量转速。用于车速推算、ABS、ESP。嗯,这里要注意——轮速传感器读的是脉冲数,不是直接的速度。
  • 视觉传感器(摄像头):前视、环视、舱内摄像头。数据量最大,处理最复杂。

你想想看,如果只有 GPS,车进了隧道就废了。但加上 IMU 和轮速,就能在无 GPS 环境下继续推算位置。这就是传感器融合的价值。

核心观点:单一传感器都有短板。IMU 有漂移,轮速有打滑,摄像头怕逆光。融合才是王道。

12.2 Sensor HAL:硬件与框架的桥梁

Android 的传感器架构分三层:

  1. 应用层:通过 SensorManager 获取数据。
  2. 框架层:SensorService 管理所有传感器。
  3. HAL 层:直接跟硬件打交道。

车载场景下,Sensor HAL 需要额外支持一些「非标准」传感器。比如轮速传感器,在手机上是没有的。我记得第一次移植轮速传感器时,发现 HAL 里根本没有对应的 type 定义。后来只能走 TYPE_DEVICE_PRIVATE_BASE 自定义通道。

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

// 传感器 HAL 的初始化流程
static int open_sensors(const struct hw_module_t* module,
                        const char* id,
                        struct hw_device_t** device) {
    struct sensors_poll_device_t* dev = malloc(sizeof(*dev));
    memset(dev, 0, sizeof(*dev));

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = SENSORS_DEVICE_API_VERSION_1_3;
    dev->common.module = (struct hw_module_t*)module;
    dev->common.close = close_sensors;

    dev->activate = sensor_activate;
    dev->setDelay = sensor_set_delay;
    dev->poll = sensor_poll;

    *device = &dev->common;
    return 0;
}

这段代码看着简单,但坑不少。比如 poll 函数的阻塞时间,我曾经设成 5ms,结果 CPU 占用率直接飙到 30%。后来改成 10ms,配合事件驱动,才压下来。

个人经验:车载 Sensor HAL 的 poll 频率不要超过 100Hz。IMU 可以到 200Hz,但轮速传感器 50Hz 就够用了。频率越高,CPU 开销越大,而且很多应用根本用不到那么高的频率。

12.3 传感器融合:1+1 > 2

传感器融合,说白了就是把多个传感器的数据揉在一起,得到更准确的结果。最常见的融合算法是卡尔曼滤波。

我举个例子。假设你要估算车速:

  • 轮速传感器告诉你:车速 60 km/h
  • IMU 加速度积分告诉你:车速 62 km/h
  • GPS 告诉你:车速 58 km/h

三个数据都不一样,信谁?卡尔曼滤波的做法是:给每个传感器一个「置信度」,然后加权平均。置信度高的传感器,权重就大。

来看一个简化的卡尔曼滤波实现:

// 一维卡尔曼滤波,用于车速融合
float kalman_filter(float z_measure, float x_prev, float p_prev) {
    // 预测步骤
    float x_pred = x_prev;          // 状态预测
    float p_pred = p_prev + Q;      // 协方差预测,Q为过程噪声

    // 更新步骤
    float K = p_pred / (p_pred + R); // 卡尔曼增益,R为测量噪声
    float x_est = x_pred + K * (z_measure - x_pred);
    float p_est = (1 - K) * p_pred;

    return x_est;
}

这段代码里,QR 是两个关键参数。Q 代表你对模型的信任程度,R 代表你对传感器的信任程度。我曾经在调试时把 R 设得太小,结果卡尔曼滤波完全跟着传感器走,噪声一点没滤掉。后来把 R 调大,效果才出来。

避坑指南:我曾经在量产项目上遇到过一个问题——IMU 的零偏没有校准,导致融合后的位置误差每小时漂移 500 米。后来加了上电自校准流程,才解决。记住:IMU 必须做零偏校准,否则融合结果不可用。

12.4 车载传感器融合的典型架构

下面这张图展示了车载传感器融合的典型流程。我习惯把它画成三层:

车载传感器融合架构 传感器层(Sensor HAL) IMU(加速度+陀螺仪) 轮速传感器 x4 摄像头(前视/环视) GPS/GNSS 传感器融合层(Sensor Fusion) 卡尔曼滤波(位置/速度) 姿态解算(航向/俯仰) 时间戳对齐 & 插值 应用层(Android Automotive OS) 导航 & 定位 ADAS 辅助驾驶 车辆动态控制

这张图里,我最想强调的是「时间戳对齐」这一步。不同传感器的采样频率不同,IMU 可能是 200Hz,轮速只有 50Hz,摄像头只有 30fps。如果不做时间戳对齐,融合出来的数据就是乱的。

我在项目里用过两种方案:

  • 硬件时间戳:传感器自己打时间戳,精度高,但需要硬件支持。
  • 软件插值:在融合层做线性插值,把低频数据插到高频时间轴上。简单,但精度差一些。

我个人更推荐硬件时间戳。虽然实现复杂,但省去了很多后期调试的麻烦。

12.5 实战中的几个关键点

最后分享几个我在实战中踩过的坑:

  1. 传感器校准:IMU 必须做零偏校准,轮速传感器要做半径校准。不校准的话,融合结果会慢慢漂移。
  2. 数据同步:不同传感器的数据到达时间不一样。我建议在 HAL 层就打好时间戳,不要在应用层再补。
  3. 异常处理:传感器会失效。比如摄像头被泥巴糊住,轮速传感器在冰雪路面打滑。融合算法必须能检测到异常,并自动降低该传感器的权重。
  4. 性能优化:传感器融合的计算量不小。我建议把融合算法放在单独的线程里,不要跟 UI 线程混在一起。

总结一下:传感器系统是车载开发的硬骨头。IMU、轮速、摄像头各有各的脾气,Sensor HAL 是连接硬件和框架的桥梁,传感器融合则是把一堆「不完美」的数据变成「可用」的信息。做这行,耐心比技术更重要。

一个小技巧:调试传感器融合时,先把每个传感器的原始数据打印出来,画成曲线图。肉眼看一下,很多问题就一目了然了。我曾经靠这个办法,半小时就找到了一个时间戳偏移的 bug。

公众号:蓝海资料掘金营,微信 deep3321