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 最好用枚举或者宏定义,别用魔数。
再说 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 描述了“有什么传感器”,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 的第一步。把这两个结构体吃透了,后面的代码实现就会顺畅很多。