3、HAL层接口详解:sensors.h 核心结构体、回调函数、初始化流程
好,咱们直接进入正题。上一章我们把传感器 HAL 的整体架构捋了一遍,知道了它是个什么东西。这一章,咱们要深入到 sensors.h 这个头文件里,看看那些核心的结构体、回调函数到底长什么样,以及整个初始化流程是怎么跑起来的。
说实话,我当年第一次打开这个文件时,也被里面密密麻麻的结构体定义吓了一跳。但别怕,咱们一个一个拆解,你会发现其实就那么几个关键点。
3.1 核心结构体:传感器的“身份证”
在 sensors.h 里,有几个结构体是你必须搞清楚的。它们就像是传感器的“身份证”,描述了传感器的类型、能力、以及数据格式。
3.1.1 struct sensor_t:传感器的静态描述
这个结构体,说白了就是传感器的“户口本”。它包含了传感器的名字、厂商、类型、最大范围、精度、功耗等静态信息。系统在启动时,会通过 get_sensors_list 函数拿到一个 sensor_t 的数组,然后注册到 SensorService 里。
struct sensor_t {
const char* name; // 传感器名字,比如 "BMA250 Accelerometer"
const char* vendor; // 厂商,比如 "Bosch"
int version; // 版本号
int handle; // 句柄,唯一标识一个传感器
int type; // 类型,比如 SENSOR_TYPE_ACCELEROMETER
float maxRange; // 最大量程
float resolution; // 分辨率
float power; // 功耗,单位 mA
int32_t minDelay; // 最小采样周期,单位 us
uint32_t fifoReservedEventCount; // FIFO 预留事件数
uint32_t fifoMaxEventCount; // FIFO 最大事件数
const char* stringType; // 字符串类型,用于扩展
const char* requiredPermission; // 需要的权限
int32_t maxDelay; // 最大采样周期
uint32_t flags; // 标志位
void* reserved[2]; // 保留字段
};
嗯,这里要注意 handle 这个字段。我在项目中遇到过一个问题:两个传感器用了相同的 handle,结果上层应用死活分不清谁是谁。所以,handle 必须唯一,这是你的第一道防线。
3.1.2 struct sensors_event_t:传感器的“数据包”
传感器每采集一次数据,就会生成一个 sensors_event_t 事件。这个结构体是个联合体,里面包含了加速度、陀螺仪、磁力计等不同类型的数据。
typedef struct {
int32_t version; // 版本号
int32_t sensor; // 传感器 handle
int32_t type; // 事件类型
int32_t reserved0; // 保留
int64_t timestamp; // 时间戳,纳秒
union {
float data[16]; // 通用数据
sensors_vec_t acceleration; // 加速度
sensors_vec_t gyro; // 陀螺仪
sensors_vec_t magnetic; // 磁力计
sensors_vec_t orientation; // 方向
sensors_vec_t gravity; // 重力
sensors_vec_t linear_acceleration; // 线性加速度
sensors_vec_t rotation_vector; // 旋转矢量
float temperature; // 温度
float distance; // 距离
float light; // 光照
float pressure; // 气压
float relative_humidity; // 相对湿度
uncalibrated_event_t uncalibrated_accelerometer; // 未校准加速度
uncalibrated_event_t uncalibrated_gyro; // 未校准陀螺仪
uncalibrated_event_t uncalibrated_magnetic; // 未校准磁力计
meta_data_event_t meta_data; // 元数据
};
} sensors_event_t;
我个人习惯在填充 timestamp 时,使用 elapsedRealtimeNano() 或者硬件时间戳。千万别用 CLOCK_MONOTONIC 以外的时钟,否则时间同步会出问题。你想想看,上层应用拿到一个错乱的时间戳,那数据还怎么用?
3.1.3 struct sensors_poll_device_t:操作接口
这个结构体定义了 HAL 层暴露给上层的操作接口。它包含了一组函数指针,比如 activate、setDelay、poll 等。
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);
};
这里有个坑:activate 函数中,enabled 为 1 表示激活,0 表示去激活。我曾经见过有人把 1 和 0 搞反了,结果传感器死活关不掉,电池耗得飞快。嗯,这种低级错误,咱们得避免。
3.2 回调函数:系统与 HAL 的“握手协议”
回调函数是系统与 HAL 层通信的桥梁。系统通过回调函数,把控制权交给 HAL 层,让 HAL 层去执行具体的操作。
3.2.1 open_sensors:打开传感器设备
这个函数是 HAL 层的入口点。系统通过 hw_get_module 加载 HAL 模块后,会调用 open_sensors 来打开传感器设备,并返回一个 sensors_poll_device_t 的指针。
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_0;
dev->common.module = (struct hw_module_t*) module;
dev->common.close = (int (*)(struct hw_device_t*)) close_sensors;
dev->activate = poll__activate;
dev->setDelay = poll__setDelay;
dev->poll = poll__poll;
*device = &dev->common;
return 0;
}
这段代码看起来简单,但有个细节:common.tag 必须设置为 HARDWARE_DEVICE_TAG,否则系统会认为设备无效。我刚开始做的时候,忘了设置这个 tag,结果系统一直报 device not found,排查了半天才发现。
3.2.2 get_sensors_list:获取传感器列表
系统通过这个回调函数,获取 HAL 层支持的传感器列表。这个列表是一个 sensor_t 的数组,系统会遍历这个数组,为每个传感器创建对应的 Sensor 对象。
static int get_sensors_list(struct sensors_module_t* module,
struct sensor_t const** list) {
*list = sensor_list;
return ARRAY_SIZE(sensor_list);
}
这里要注意:sensor_list 必须是一个全局静态数组,而且它的生命周期要贯穿整个 HAL 模块的生命周期。我曾经见过有人把 sensor_list 定义在栈上,结果函数返回后,指针就变成了野指针,系统直接崩溃。
3.3 初始化流程:从加载到就绪
好了,现在咱们把核心结构体和回调函数都搞清楚了。接下来,咱们看看整个初始化流程是怎么跑的。
3.3.1 第一步:加载 HAL 模块
系统启动时,SensorService 会调用 hw_get_module 来加载传感器 HAL 模块。这个函数会根据硬件 ID 找到对应的 .so 文件,并调用它的 HMI 函数。
hw_get_module(SENSORS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
3.3.2 第二步:打开传感器设备
加载模块后,系统会调用 module->methods->open,也就是我们上面说的 open_sensors 函数。这个函数会初始化 sensors_poll_device_t,并设置好 activate、setDelay、poll 等回调函数。
3.3.3 第三步:获取传感器列表
系统通过 module->get_sensors_list 拿到传感器列表,然后遍历这个列表,为每个传感器创建 Sensor 对象,并注册到 SensorService 中。
3.3.4 第四步:激活传感器并开始轮询
当上层应用请求传感器数据时,系统会调用 activate 函数激活对应的传感器,然后通过 poll 函数不断轮询数据。轮询到的数据会以 sensors_event_t 的形式返回给系统。
核心要点:整个初始化流程,说白了就是“加载模块 -> 打开设备 -> 获取列表 -> 激活轮询”这四个步骤。每一步都有对应的回调函数,你只要把这些回调函数实现好,系统就能正常工作了。
3.4 避坑指南
最后,我分享几个我在项目中踩过的坑,希望能帮你少走弯路。
我曾经犯过的错:
- handle 重复:两个传感器用了相同的 handle,导致上层应用无法区分。解决方案:确保每个传感器的 handle 唯一,建议使用枚举或宏定义。
- 时间戳错误:使用了
CLOCK_MONOTONIC以外的时钟,导致时间同步问题。解决方案:统一使用elapsedRealtimeNano()或硬件时间戳。 - sensor_list 生命周期:把
sensor_list定义在栈上,导致野指针崩溃。解决方案:使用全局静态数组。 - activate 参数搞反:把 1 和 0 搞反,导致传感器无法关闭。解决方案:仔细阅读文档,确认参数含义。
个人小技巧:在实现 poll 函数时,我习惯使用一个环形缓冲区来缓存传感器数据。这样即使上层读取不及时,数据也不会丢失。你可以试试看。
好了,这一章的内容就到这里。下一章,咱们会深入讲解如何实现一个具体的传感器 HAL 模块,包括数据采集、事件上报、以及性能优化。到时候,咱们会手写代码,一步步把理论变成实践。