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 层暴露给上层的操作接口。它包含了一组函数指针,比如 activatesetDelaypoll 等。

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,并设置好 activatesetDelaypoll 等回调函数。

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 模块,包括数据采集、事件上报、以及性能优化。到时候,咱们会手写代码,一步步把理论变成实践。