15、传感器系统移植:传感器HAL架构、加速度计/陀螺仪驱动、磁力计与GPS融合、传感器校准与测试

传感器系统,说白了就是Android Automotive的“感官神经”。车要感知自己在加速、转弯、朝向哪里,全靠这一套东西。我刚开始做i.MX平台移植时,觉得传感器不就是读个数据嘛,结果被HAL层的兼容性问题折腾了好几个通宵。今天咱们就把这块彻底讲透。

15.1 传感器HAL架构:Android的传感器抽象层

Android的传感器框架分三层:应用层、框架层、HAL层。我们做移植,核心战场就在HAL层。它位于Linux驱动和Android框架之间,负责把底层硬件数据包装成标准格式。

HAL层的核心接口定义在hardware/libhardware/include/hardware/sensors.h中。你需要实现的结构体是sensors_module_tsensors_poll_device_t。我个人习惯先把这两个结构体填好,再回头写具体逻辑。

// 传感器模块结构体示例
static struct hw_module_methods_t sensors_module_methods = {
    .open = sensors_open,
};

struct sensors_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .version_major = 1,
        .version_minor = 0,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "i.MX Sensors Module",
        .author = "Your Team",
        .methods = &sensors_module_methods,
    },
};

这里有个坑:HAL_MODULE_INFO_SYM这个宏名不能改,它是Android HAL加载器查找模块入口的固定符号。我曾经因为拼写错误,导致传感器服务死活加载不上,查了两天才发现是少了个下划线。

核心要点:传感器HAL需要实现至少以下函数:

  • get_sensors_list() – 返回支持的传感器列表
  • activate() – 启用/禁用传感器
  • batch() – 设置采样率和最大报告延迟
  • poll() – 阻塞读取传感器事件
  • flush() – 刷新当前数据

15.2 加速度计与陀螺仪驱动:从设备树到数据流

加速度计和陀螺仪通常是组合芯片,比如BMI160或LSM6DS3。在i.MX平台上,驱动开发的第一步是配置设备树。

// 设备树节点示例(i.MX8M Plus)
&i2c3 {
    status = "okay";
    clock-frequency = <400000>;

    bmi160: bmi160@68 {
        compatible = "bosch,bmi160";
        reg = <0x68>;
        interrupt-parent = <&gpio1>;
        interrupts = <15 IRQ_TYPE_EDGE_RISING>;
        vdd-supply = <®_3v3>;
        vddio-supply = <®_1v8>;
    };
};

驱动里最关键的其实是中断处理。加速度计和陀螺仪的数据更新频率很高,用轮询会吃掉大量CPU。我建议用FIFO模式加硬件中断,这样CPU只在数据满时被唤醒一次。

嗯,这里要注意:i.MX的I2C控制器有时序要求,400kHz是上限。如果你发现传感器读数不稳定,先查I2C波形,别急着改驱动逻辑。

实战技巧:调试传感器驱动时,先用i2c-tools直接读取寄存器,确认硬件通信正常。命令示例:

i2cdetect -y 3        # 扫描I2C总线3上的设备
i2cget -y 3 0x68 0x0f # 读取BMI160芯片ID寄存器

15.3 磁力计与GPS融合:组合导航的基石

磁力计(电子罗盘)和GPS的融合,说白了就是解决“GPS更新慢、磁力计易受干扰”这对矛盾。Android框架里提供了Sensor.TYPE_ROTATION_VECTOR,它已经帮你做了融合,但前提是HAL层要正确上报原始数据。

融合的核心算法是卡尔曼滤波或互补滤波。我项目中用过互补滤波,实现简单,效果也够用。公式大致是这样:

// 互补滤波伪代码
angle = 0.98 * (angle + gyro_data * dt) + 0.02 * accel_angle;

为什么是0.98和0.02?说白了就是信任陀螺仪的短期变化,信任加速度计的长期稳定。这个系数你可以根据实际传感器噪声调整。

GPS数据通过android.hardware.gnss HAL接口上报,和传感器HAL是两条独立的路径。但在应用层,LocationManager会同时监听GPS和传感器数据,自动做融合。你只需要确保传感器HAL正确上报TYPE_ROTATION_VECTORTYPE_MAGNETIC_FIELD即可。

注意:磁力计非常容易受车内电磁干扰。我曾经在实车测试时发现,开启空调后磁力计读数偏了30度。解决方案是在HAL层加一个简单的硬铁校准,或者引导用户做“8字校准”。

15.4 传感器校准与测试:让数据可信

校准是传感器系统移植中最容易被忽视的环节。不校准的数据,就是垃圾数据。

Android提供了SensorManager.getDefaultSensor().getCalibration()接口,但底层需要HAL支持。校准参数通常存储在/persist/sensors/分区,每次启动时加载。

我总结了一套校准流程:

  1. 加速度计校准:六面静止法。把设备平放、侧放、倒放,记录六个面的重力读数,算出偏移和比例因子。
  2. 陀螺仪校准:静止时读数应为0。取1000个样本的平均值作为零偏。
  3. 磁力计校准:做“8字”运动,采集球面上的点,拟合出中心偏移和半径。
  4. GPS校准:冷启动后等待至少5分钟,让星历下载完整。

测试命令:使用Android自带的sensorservice工具可以快速验证:

adb shell dumpsys sensorservice
adb shell dumpsys sensorservice --sensor 1  # 查看加速度计数据

我个人习惯在HAL层加一个调试节点,比如/sys/class/sensors/calib,可以直接写入校准参数。这样在产线校准时就方便多了。

15.5 知识体系总览

下面这张图总结了传感器系统移植的核心逻辑,从硬件到应用层的数据流向一目了然。

i.MX Android Automotive 传感器系统移植架构 硬件层 加速度计/陀螺仪 (I2C/SPI) | 磁力计 (I2C) | GPS (UART/USB) 内核驱动层 设备树配置 | I2C/SPI驱动 | 中断处理 | FIFO管理 | 输入子系统 HAL层 sensors_module_t | sensors_poll_device_t | 校准参数管理 | 数据融合 Android框架层 SensorManager | LocationManager | 旋转向量 | 组合导航 数据流向:硬件 → 驱动 → HAL → 框架

这张图把整个传感器系统移植的脉络理清楚了。你从硬件层开始,一步步往上走,每一层都有明确的接口和职责。做移植时,我建议先打通驱动到HAL的通道,再处理校准和融合,最后才去调应用层效果。

我的经验:第一次移植时,别追求完美。先让加速度计和陀螺仪能出数据,哪怕有噪声。数据通了,后面校准和滤波都好办。最怕的是驱动没通,你在HAL层瞎调半天,全是白费功夫。


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