9、硬件抽象层(HAL)移植(下):GPS/GNSS HAL实现、Vehicle HAL(车辆 HAL)核心接口、Light HAL与背光控制、Vibrator HAL移植

好,咱们接着往下聊。上一节我们把HAL移植的基础框架和几个核心模块讲完了,这一节我挑几个在车载Android里特别关键、但又容易踩坑的模块来深入说说。GPS、Vehicle、Light、Vibrator——这四个HAL,说白了就是车机的“感官”和“执行器”。你想想看,车不知道自己在哪、不知道车速、没法控制屏幕亮度、连个震动反馈都没有,那用户体验基本就崩了。

9.1 GPS/GNSS HAL:让车知道自己在哪

GPS HAL在Android里其实是个老模块了,但车载场景下要求更高。我个人的习惯是,先别急着写代码,先搞清楚硬件接口。你是用UART还是SPI?NMEA数据是直接透传还是需要协议转换?这些决定了你的HAL实现策略。

GPS HAL的核心接口是GnssHal,你需要实现的主要方法包括:

  • init() —— 初始化GNSS芯片,配置波特率、更新频率
  • start() —— 开始定位,通常对应硬件上开启连续定位模式
  • stop() —— 停止定位,进入低功耗待机
  • injectTime() —— 注入网络时间,辅助快速定位
  • setPositionMode() —— 设置定位模式(MS-Based、Standalone等)

这里我重点说一下NMEA解析。Android框架层期望你通过回调上报GnssLocation对象,但底层硬件吐出来的通常是$GPGGA、$GPRMC这样的NMEA语句。你需要自己写一个解析器,或者用现成的库(比如libnmea)。

核心流程:

硬件NMEA数据 → HAL层解析 → 填充GnssLocation → 回调上报给Framework

我在项目中遇到过一个问题:某款GNSS芯片输出的NMEA语句里,经纬度是DDMM.MMMM格式(度分格式),而Android框架要求的是DD.DDDDDD(度格式)。如果不做转换,定位会直接漂到海里。嗯,这个坑我替你们踩过了。

// NMEA经纬度转换示例(DDMM.MMMM → DD.DDDDDD)
double convertNmeaToDecimal(double nmeaCoord) {
    int degrees = (int)(nmeaCoord / 100);
    double minutes = nmeaCoord - degrees * 100;
    return degrees + minutes / 60.0;
}

调试技巧: 在init()阶段先打印原始NMEA数据到logcat,确认硬件链路通了再写解析逻辑。我曾经花了两天查一个“定位不上”的问题,最后发现是UART的TX/RX接反了。

9.2 Vehicle HAL:车机的“中枢神经”

Vehicle HAL(VHAL)是Android Automotive里最核心、也最复杂的HAL。它负责把车辆信号(车速、车门状态、空调温度、续航里程等)抽象成Android能理解的属性(VehicleProperty)。

VHAL的核心接口是IVehicle,你需要实现:

  • getAllProperties() —— 返回车辆支持的所有属性列表
  • get() —— 读取某个属性的当前值
  • set() —— 设置某个属性(比如关窗、调空调)
  • subscribe() —— 订阅属性变化事件(比如车速变化时通知上层)

这里有个关键点:属性ID的定义。Android定义了一套标准的VehicleProperty(比如VEHICLE_PROPERTY_CAR_SPEED、VEHICLE_PROPERTY_DOOR_POS),但车厂往往有私有属性。我建议你把这些私有属性定义在vendor/your_company/vehicle/目录下,不要和AOSP的混在一起,否则升级时容易冲突。

属性类别 典型属性 数据格式
动力系统 车速、续航、电池电量 Float、Int32
车身控制 车门锁、车窗位置、天窗状态 Int32(枚举值)
空调系统 目标温度、风量、AC开关 Float、Boolean
安全系统 安全带状态、胎压、故障码 Int32、VehiclePropertyGroup

VHAL的另一个难点是事件上报机制。车辆信号是实时变化的,你不能让上层轮询。正确的做法是:在subscribe()里注册回调,当硬件检测到信号变化时,通过回调把数据推上去。我曾经见过一个方案,用定时器每100ms去读一次CAN总线——这其实是在浪费CPU,而且延迟不可控。

注意: VHAL的set()操作可能涉及安全风险。比如远程解锁车门,必须做权限校验。Android Automotive有VehiclePropertyAccess机制,记得给敏感属性加上ACCESS_PRIVILEGED标记。

9.3 Light HAL与背光控制

Light HAL在手机上主要是控制LED通知灯,但在车机上,它控制的是屏幕背光氛围灯。车载屏幕的亮度调节和手机不一样——手机根据环境光传感器自动调,车机还要考虑白天/黑夜模式、驾驶模式等因素。

Light HAL的核心接口是setLight(),你需要实现:

  • LIGHT_ID_BACKLIGHT —— 主屏幕背光,亮度范围0-255
  • LIGHT_ID_KEYBOARD —— 物理按键背光(如果有)
  • LIGHT_ID_BUTTONS —— 方向盘按键背光
  • LIGHT_ID_ATTENTION —— 呼吸灯/提示灯

背光控制的底层通常是通过PWM调节。你需要知道硬件上PWM的通道号和频率范围。我个人的做法是:在HAL层封装一个pwm_set_duty()函数,然后根据传入的亮度值映射到PWM占空比。

// 背光亮度到PWM占空比的映射
int brightness_to_pwm(int brightness) {
    // 0-255 → 0-1000 (占空比千分比)
    return (brightness * 1000) / 255;
}

这里有个坑:有些屏幕的背光PWM频率太低,低亮度下会闪烁。我遇到过一款屏幕,PWM频率只有200Hz,亮度调到10%以下时肉眼可见闪烁。解决方案是提高PWM频率(比如改到1kHz以上),或者改用DC调光。嗯,这个得和硬件工程师一起调。

经验之谈: 车机的背光调节最好做“渐变”效果。从暗到亮直接跳变,用户会感觉刺眼。在HAL层实现一个简单的渐变算法,每次调节时分成10步,每步间隔20ms,体验会好很多。

9.4 Vibrator HAL:给用户一个“触感”

Vibrator HAL看起来简单,不就是控制一个马达震动吗?但在车机上,震动反馈的用途比手机多得多:触摸屏按键反馈、方向盘震动提醒、安全带未系警告……不同的场景需要不同的震动强度和时长。

Vibrator HAL的核心接口是vibrator_on()vibrator_off(),但Android 10之后引入了IVibrator HIDL接口,支持更精细的控制:

  • on() —— 启动震动,指定时长(毫秒)
  • off() —— 停止震动
  • setAmplitude() —— 设置震动幅度(0.0 - 1.0)
  • perform() —— 执行预定义的震动效果(比如点击、长按)

底层驱动通常通过PWM或GPIO控制马达。如果是线性马达(LRA),还需要考虑谐振频率。我建议你在HAL层做一层抽象:

// 震动效果定义
typedef struct {
    int duration_ms;    // 震动时长
    float amplitude;    // 幅度 0.0-1.0
    int effect_type;    // 0=连续, 1=脉冲, 2=渐强
} vibrator_effect_t;

我在项目中遇到过一个问题:某款车机的震动马达启动延迟很大,从调用on()到马达真正转起来,有将近50ms的延迟。这对于触摸反馈来说太慢了,用户会感觉“按键按下去没反应”。后来发现是驱动里有个不必要的延时,去掉后延迟降到了5ms以内。

注意: 车规级的震动马达和手机马达不一样,长时间连续震动可能会过热。建议在HAL层加一个“最大连续震动时长”的保护,比如超过5秒自动停止。别问我怎么知道的……

9.5 本章知识体系总览

为了让你更直观地理解这四个HAL在系统架构中的位置和关系,我画了一张图:

HAL移植知识体系(下) Android Application Framework GPS/GNSS HAL GnssHal接口 NMEA解析 定位模式控制 Vehicle HAL IVehicle接口 属性订阅/设置 CAN总线抽象 Light HAL 背光控制 PWM调光 氛围灯管理 Vibrator HAL IVibrator接口 震动效果控制 马达驱动抽象 Linux Kernel / Hardware Drivers GNSS芯片 / CAN控制器 / PWM背光 / 震动马达

从这张图你可以看到,这四个HAL模块处于应用框架和内核驱动之间,各自负责不同的硬件抽象。移植的关键在于:理解硬件特性,封装成Android期望的接口。说白了,就是给Android框架一个它认识的“脸”,背后驱动什么硬件它不管。

好了,这一节的内容就到这里。四个HAL各有各的脾气,但核心思路是一致的:先打通硬件链路,再封装标准接口,最后别忘了做异常处理和调试日志。下一节我们会进入更底层的BSP移植,到时候再聊。


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