12、传感器系统:车载传感器(IMU、轮速、摄像头)、Sensor HAL、传感器融合
传感器系统,说白了就是车的「五官」。没有它们,车就是个瞎子、聋子。我在做第一代车载信息娱乐系统时,对传感器的理解还很肤浅,觉得不就是读几个数据嘛。直到有一次,车辆在隧道里定位漂移了十几米,我才意识到——传感器水很深。
Android Automotive OS 的传感器架构,跟手机上的 Sensor Framework 很像,但多了很多车载专属的东西。比如轮速传感器、方向盘转角、甚至安全带状态。今天我们就来拆解这套系统。
12.1 车载传感器的分类与角色
车载传感器大致分三类:
- 惯性传感器(IMU):加速度计 + 陀螺仪。测量车辆的加速度和角速度。我习惯把 IMU 叫做「内感」,因为它不依赖外部信号。
- 轮速传感器:每个轮子一个,测量转速。用于车速推算、ABS、ESP。嗯,这里要注意——轮速传感器读的是脉冲数,不是直接的速度。
- 视觉传感器(摄像头):前视、环视、舱内摄像头。数据量最大,处理最复杂。
你想想看,如果只有 GPS,车进了隧道就废了。但加上 IMU 和轮速,就能在无 GPS 环境下继续推算位置。这就是传感器融合的价值。
核心观点:单一传感器都有短板。IMU 有漂移,轮速有打滑,摄像头怕逆光。融合才是王道。
12.2 Sensor HAL:硬件与框架的桥梁
Android 的传感器架构分三层:
- 应用层:通过 SensorManager 获取数据。
- 框架层:SensorService 管理所有传感器。
- 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;
}
这段代码里,Q 和 R 是两个关键参数。Q 代表你对模型的信任程度,R 代表你对传感器的信任程度。我曾经在调试时把 R 设得太小,结果卡尔曼滤波完全跟着传感器走,噪声一点没滤掉。后来把 R 调大,效果才出来。
避坑指南:我曾经在量产项目上遇到过一个问题——IMU 的零偏没有校准,导致融合后的位置误差每小时漂移 500 米。后来加了上电自校准流程,才解决。记住:IMU 必须做零偏校准,否则融合结果不可用。
12.4 车载传感器融合的典型架构
下面这张图展示了车载传感器融合的典型流程。我习惯把它画成三层:
这张图里,我最想强调的是「时间戳对齐」这一步。不同传感器的采样频率不同,IMU 可能是 200Hz,轮速只有 50Hz,摄像头只有 30fps。如果不做时间戳对齐,融合出来的数据就是乱的。
我在项目里用过两种方案:
- 硬件时间戳:传感器自己打时间戳,精度高,但需要硬件支持。
- 软件插值:在融合层做线性插值,把低频数据插到高频时间轴上。简单,但精度差一些。
我个人更推荐硬件时间戳。虽然实现复杂,但省去了很多后期调试的麻烦。
12.5 实战中的几个关键点
最后分享几个我在实战中踩过的坑:
- 传感器校准:IMU 必须做零偏校准,轮速传感器要做半径校准。不校准的话,融合结果会慢慢漂移。
- 数据同步:不同传感器的数据到达时间不一样。我建议在 HAL 层就打好时间戳,不要在应用层再补。
- 异常处理:传感器会失效。比如摄像头被泥巴糊住,轮速传感器在冰雪路面打滑。融合算法必须能检测到异常,并自动降低该传感器的权重。
- 性能优化:传感器融合的计算量不小。我建议把融合算法放在单独的线程里,不要跟 UI 线程混在一起。
总结一下:传感器系统是车载开发的硬骨头。IMU、轮速、摄像头各有各的脾气,Sensor HAL 是连接硬件和框架的桥梁,传感器融合则是把一堆「不完美」的数据变成「可用」的信息。做这行,耐心比技术更重要。
一个小技巧:调试传感器融合时,先把每个传感器的原始数据打印出来,画成曲线图。肉眼看一下,很多问题就一目了然了。我曾经靠这个办法,半小时就找到了一个时间戳偏移的 bug。