4. 加速度计HAL驱动封装

任务目标

本案例完成加速度计从Linux内核驱动到Android HAL层的完整封装链路:编写一个可加载的内核驱动模块,提供原始数据读取接口;实现HAL层模块,通过sysfs节点读取驱动输出的原始值,并转换为标准单位(m/s²)。最终通过HAL层测试程序验证数据读取与单位转换的正确性。

环境与依赖

  • Linux内核源码树(已配置交叉编译环境),目标平台为ARM64 Android设备
  • Android NDK(用于编译HAL测试程序)
  • 内核驱动依赖:linux/i2c.h、linux/miscdevice.h、linux/uaccess.h
  • HAL层依赖:hardware/libhardware/include/hardware/(Android系统源码环境)

内核驱动实现

驱动模拟I2C加速度计(如ADXL345),注册misc设备并提供sysfs属性节点。驱动内部维护一个可写的原始值寄存器,便于测试时注入数据。

// accel_drv.c — 加速度计内核驱动(模拟I2C设备)
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/sysfs.h>
#include <linux/platform_device.h>

#define ACCEL_DRV_NAME "accel_dev"
#define ACCEL_RANGE    (4 * 9.80665f)  /* ±4g 量程 */

static int raw_x = 100, raw_y = -50, raw_z = 900;  /* 可注入的原始值 */

static ssize_t raw_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    return sprintf(buf, "%d %d %d\n", raw_x, raw_y, raw_z);
}

static ssize_t raw_store(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count)
{
    sscanf(buf, "%d %d %d", &raw_x, &raw_y, &raw_z);
    return count;
}

static DEVICE_ATTR(raw, 0664, raw_show, raw_store);

static struct attribute *accel_attrs[] = {
    &dev_attr_raw.attr,
    NULL,
};
ATTRIBUTE_GROUPS(accel_attrs);

static struct miscdevice accel_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = ACCEL_DRV_NAME,
    .groups = accel_groups,
};

static int accel_probe(struct platform_device *pdev)
{
    return misc_register(&accel_miscdev);
}

static int accel_remove(struct platform_device *pdev)
{
    misc_deregister(&accel_miscdev);
    return 0;
}

static struct platform_driver accel_driver = {
    .probe = accel_probe,
    .remove = accel_remove,
    .driver = {
        .name = ACCEL_DRV_NAME,
    },
};

module_platform_driver(accel_driver);
MODULE_LICENSE("GPL");

驱动加载后生成设备节点/dev/accel_dev及sysfs属性/sys/devices/platform/accel_dev/raw。写入"x y z"可注入原始值,读取返回当前原始值。

HAL模块实现

HAL模块实现sensors_module_t标准接口,提供加速度计传感器句柄。核心逻辑为读取sysfs节点、解析原始值、按量程转换为m/s²。

// accel_hal.c — 加速度计HAL模块
#include <hardware/sensors.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define SYSFS_RAW_PATH "/sys/devices/platform/accel_dev/raw"
#define ACCEL_RANGE    (4 * 9.80665f)
#define ACCEL_RESOLUTION 1024.0f  /* 假设12位ADC,中心点512 */

static int read_raw_values(int *x, int *y, int *z)
{
    FILE *fp = fopen(SYSFS_RAW_PATH, "r");
    if (!fp) return -errno;
    int ret = fscanf(fp, "%d %d %d", x, y, z);
    fclose(fp);
    return (ret == 3) ? 0 : -EIO;
}

static float convert_to_si(int raw)
{
    /* 原始值偏移中心点,按比例映射到量程 */
    return ((raw - 512) / ACCEL_RESOLUTION) * ACCEL_RANGE;
}

static int accel_get_sensors_list(struct sensors_module_t *module,
                                  struct sensor_t const **list)
{
    static struct sensor_t accel_sensor = {
        .name = "MMA8452Q Accelerometer",
        .vendor = "Example Corp",
        .version = 1,
        .handle = SENSORS_HANDLE_BASE,
        .type = SENSOR_TYPE_ACCELEROMETER,
        .maxRange = ACCEL_RANGE,
        .resolution = ACCEL_RANGE / ACCEL_RESOLUTION,
        .power = 0.2f,
        .minDelay = 10000,
    };
    *list = &accel_sensor;
    return 1;
}

static int accel_open(const struct hw_module_t *module, const char *id,
                      struct hw_device_t **device)
{
    (void)module; (void)id;
    /* 实际工程中在此分配sensors_poll_device_t并实现data_open/data_close */
    return 0;
}

static struct hw_module_methods_t accel_methods = {
    .open = accel_open,
};

struct sensors_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = SENSORS_MODULE_API_VERSION_1_0,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "Accelerometer HAL Module",
        .author = "Example",
        .methods = &accel_methods,
    },
    .get_sensors_list = accel_get_sensors_list,
};

HAL测试程序

// test_accel_hal.c — 验证HAL层读取与转换
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define SYSFS_RAW_PATH "/sys/devices/platform/accel_dev/raw"

int main(void)
{
    int fd = open(SYSFS_RAW_PATH, O_WRONLY);
    if (fd < 0) { perror("open sysfs"); return 1; }
    /* 注入已知原始值:中心点512 + 1g对应102.4 LSB/g */
    write(fd, "614 512 512\n", 11);  /* X轴 +1g */
    close(fd);

    FILE *fp = fopen(SYSFS_RAW_PATH, "r");
    int x, y, z;
    fscanf(fp, "%d %d %d", &x, &y, &z);
    fclose(fp);

    float range = 4 * 9.80665f;
    float res = 1024.0f;
    float ax = (x - 512) / res * range;
    float ay = (y - 512) / res * range;
    float az = (z - 512) / res * range;

    printf("raw: %d %d %d\n", x, y, z);
    printf("accel (m/s^2): %.2f %.2f %.2f\n", ax, ay, az);
    return 0;
}

编译与运行

# 1. 编译内核驱动(在Android内核源码树中)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-android- M=$(pwd)/drivers/misc modules

# 2. 加载驱动并注入测试数据
adb root
adb push accel_drv.ko /data/local/tmp/
adb shell insmod /data/local/tmp/accel_drv.ko
adb shell "echo '614 512 512' > /sys/devices/platform/accel_dev/raw"

# 3. 编译HAL测试程序(使用NDK)
aarch64-linux-android-gcc -o test_accel_hal test_accel_hal.c
adb push test_accel_hal /data/local/tmp/
adb shell /data/local/tmp/test_accel_hal

验证方法

测试程序注入原始值614 512 512(X轴偏移102 LSB,对应+1g),预期输出:

raw: 614 512 512
accel (m/s^2): 9.81 0.00 0.00

判定通过条件:X轴加速度值在9.7~9.9 m/s²范围内(考虑浮点精度),Y/Z轴接近0。若输出与预期不符,检查sysfs路径是否正确、驱动是否加载成功。

常见问题

  • insmod失败:确认内核版本与驱动编译环境一致,检查dmesg查看具体错误。
  • sysfs节点不存在:确认驱动probe成功,执行ls /sys/devices/platform/accel_dev/查看属性。
  • 数值偏差:检查量程与分辨率宏定义是否与驱动实际配置一致。