4. Sensor HAL 接口定义:sensors.h 文件解析

做 Sensor 适配,绕不开一个核心文件——sensors.h。它位于 hardware/libhardware/include/hardware/ 目录下。说白了,这就是 Sensor HAL 层的“宪法”。所有 Sensor 驱动和上层框架之间的通信,都得按它定义的规矩来。

我第一次看这个文件时,说实话有点懵。结构体嵌套、回调函数指针、各种 flag……但后来我发现,只要抓住两个核心结构体,整个文件就清晰了。哪两个?sensor_t 和 sensors_poll_device_t。

4.1 sensor_t:传感器的“身份证”

sensor_t 结构体描述了一个传感器的所有静态信息。你可以把它想象成传感器的“身份证”。上层框架通过它,知道这个传感器是什么类型、是谁生产的、精度如何。

来看一下它的定义(我简化了部分字段,保留最核心的):

typedef struct {
    const char     *name;         // 传感器名字,比如 "BMI160 Accelerometer"
    const char     *vendor;       // 厂商,比如 "Bosch"
    int             version;      // 版本号
    int             handle;       // 传感器句柄,唯一标识
    int             type;         // 传感器类型,比如 SENSOR_TYPE_ACCELEROMETER
    float           maxRange;     // 最大量程
    float           resolution;   // 分辨率
    float           power;        // 功耗,单位 mA
    int32_t         minDelay;     // 最小采样周期,单位微秒
    uint32_t        fifoReservedEventCount; // FIFO 保留事件数
    uint32_t        fifoMaxEventCount;      // FIFO 最大事件数
    const char     *stringType;   // 字符串类型,用于扩展
    const char     *requiredPermission; // 需要的权限
    int32_t         maxDelay;     // 最大采样周期
    uint32_t        flags;        // 标志位
    void           *reserved[2];  // 保留字段
} sensor_t;

嗯,这里要注意 handle 字段。它是个整型值,用来唯一标识一个传感器。我在项目中遇到过一个问题:两个 Sensor 的 handle 重复了,结果上层框架只识别到了其中一个。排查了半天,最后发现是 HAL 层代码里写死了 handle 值,没有做唯一性检查。所以我的建议是:handle 最好用枚举或者宏定义,别用魔数。

我的小技巧: 定义 handle 时,可以用传感器类型加上序号。比如加速度计用 0x1001,陀螺仪用 0x2001。这样一看就知道是哪个传感器,也方便排错。

再说 flags 字段。它用来标识传感器的一些特殊能力。比如 SENSOR_FLAG_CONTINUOUS_MODE 表示连续采样模式,SENSOR_FLAG_ON_CHANGE_MODE 表示值变化时才上报。我刚开始做适配时,经常搞混这些 flag。后来我养成了一个习惯:每次定义 sensor_t 时,先把 flags 写清楚,再填其他字段。

4.2 sensors_poll_device_t:操作传感器的“遥控器”

如果说 sensor_t 是身份证,那 sensors_poll_device_t 就是遥控器。上层框架通过它来操作传感器——打开、关闭、设置采样率、读取数据。

它的定义是这样的:

typedef struct sensors_poll_device_t {
    struct hw_device_t common;  // 继承自 hw_device_t

    // 打开传感器
    int (*activate)(struct sensors_poll_device_t *dev,
                    int handle, int enabled);

    // 设置延迟/采样率
    int (*setDelay)(struct sensors_poll_device_t *dev,
                    int handle, int64_t ns);

    // 轮询读取传感器事件
    int (*poll)(struct sensors_poll_device_t *dev,
                sensors_event_t* data, int count);
} sensors_poll_device_t;

你看,就三个函数指针。但就是这三个函数,撑起了整个 Sensor 数据流。

activate:用来使能或禁用某个传感器。传入 handle 和 enabled 参数。我曾经踩过一个坑:在 activate 里忘记做引用计数。结果上层反复调用 enable/disable,底层驱动就被反复打开关闭,最后系统直接卡死。嗯,从那以后,我每次写 activate 都会加一个引用计数变量。

setDelay:设置采样周期。单位是纳秒。比如你想让加速度计以 100Hz 采样,就传入 10,000,000(10毫秒)。这里有个细节:实际采样率可能和请求的不完全一致。因为硬件有它自己的支持频率列表。我一般会在 setDelay 里做一次“频率对齐”,选一个最接近的硬件支持频率。

poll:这是最关键的函数。上层框架会循环调用它,来获取传感器数据。它返回 sensors_event_t 结构体数组。注意,poll 是阻塞的——如果没有新数据,它会一直等。我在项目中遇到过一个问题:poll 函数返回太快,导致 CPU 占用率飙升。后来发现是驱动层没有做好中断触发,一直在轮询。所以我的建议是:poll 一定要配合硬件中断来实现,别用纯软件轮询。

4.3 两个结构体的关系

我画了一张图,帮你理清它们的关系:

sensor_t • name: "BMI160 Accel" • type: SENSOR_TYPE_ACCELEROMETER • handle: 0x1001 • maxRange: 16.0, resolution: 0.001 sensors_poll_device_t • activate(handle, enabled) • setDelay(handle, ns) • poll(&events, count) • common: hw_device_t 通过 handle 关联 核心逻辑: 1. 上层框架通过 sensor_t 获取传感器信息 2. 通过 sensors_poll_device_t 的 activate/setDelay/poll 操作传感器 3. handle 是连接两者的“钥匙”

你看,sensor_t 描述了“有什么传感器”,sensors_poll_device_t 定义了“怎么操作传感器”。两者通过 handle 关联起来。上层框架先通过 sensor_t 拿到传感器列表,然后拿着 handle 去调用 sensors_poll_device_t 里的函数。

4.4 避坑指南

我做了这么多年 Sensor 适配,总结了几条血的教训:

  • handle 必须唯一:重复 handle 会导致上层框架只识别到第一个传感器。我曾经因为这个 bug 被客户追着问“为什么陀螺仪读不到数据”。
  • poll 函数不要返回太快:如果没有新数据,就让 poll 阻塞等待。否则 CPU 占用率会飙升,手机发烫。
  • setDelay 要做频率对齐:硬件不一定支持你请求的精确频率。选一个最接近的,然后返回实际设置的频率。
  • activate 要做好引用计数:防止上层反复开关导致驱动异常。
特别注意: sensors_poll_device_t 里的 common 字段是 hw_device_t 类型。这个结构体必须正确初始化,特别是 tag 和 module 字段。否则 HAL 层加载时会直接失败。我见过太多新手在这里栽跟头了。

4.5 小结

好了,这一章的内容就这些。总结一下:

  • sensor_t 是传感器的静态描述,相当于身份证。
  • sensors_poll_device_t 是操作接口,相当于遥控器。
  • 两者通过 handle 关联。
  • 实现时要注意引用计数、频率对齐、poll 阻塞等细节。

下一章我们会深入 sensors_event_t 结构体,看看传感器数据是怎么封装和上报的。到时候我会分享一个我踩过的关于时间戳的坑,保证让你印象深刻。

核心要点: 理解 sensor_t 和 sensors_poll_device_t 的关系,是掌握 Sensor HAL 的第一步。把这两个结构体吃透了,后面的代码实现就会顺畅很多。