3. ARM平台Sensor驱动架构分析:以I2C/SPI接口加速度计为例

本章深入剖析ARM Linux平台下Sensor驱动的经典分层架构。以I2C/SPI接口的加速度计(如ADXL345、MMA8452)为具体案例,逐层拆解IIO子系统、HAL层与内核层的职责划分、数据流与接口契约。理解这套分层体系,是后续向RISC-V平台移植适配的基础。

3.1 驱动分层全景:从用户态到寄存器

ARM Linux Sensor驱动通常采用四层模型:

  • 应用层:通过HAL接口获取传感器数据,不关心底层硬件。
  • HAL层:封装内核接口,提供统一API给上层框架(如Android Sensors HAL)。
  • 内核IIO子系统:提供标准化的设备注册、数据读取、触发缓冲、事件上报框架。
  • 硬件层:通过I2C/SPI总线与物理Sensor芯片通信。
关键认知: IIO(Industrial I/O)子系统是Linux内核为传感器、ADC、DAC等工业I/O设备提供的统一驱动框架,替代了早期杂乱的miscdevice实现。
ARM Linux Sensor驱动四层架构 ARM Linux Sensor驱动四层架构(I2C/SPI加速度计) 应用层 App / SensorService / NDK HAL层 Android Sensors HAL / libsensor 内核IIO子系统 IIO Core / IIO Device Driver / Trigger / Buffer sysfs接口 / chrdev / 事件上报 硬件层 I2C/SPI总线控制器 + 加速度计芯片 数据流方向(用户态 → 内核态 → 硬件) HAL ↔ IIO 通过ioctl/read/sysfs IIO ↔ 硬件 通过I2C/SPI驱动

3.2 内核IIO子系统:驱动核心骨架

IIO子系统是ARM Linux Sensor驱动的核心骨架。它提供了一套标准化的驱动编写范式,开发者只需填充必要的回调函数即可。IIO驱动的主要组成部分包括:

组件 职责 关键数据结构/API
IIO设备 代表一个物理Sensor,注册到IIO总线 struct iio_dev / iio_device_register()
IIO通道 描述Sensor的每个数据轴(X/Y/Z) struct iio_chan_spec
触发缓冲 支持硬件触发或软件定时器批量读取 iio_triggered_buffer_setup()
IIO事件 阈值检测、自由落体等中断事件 iio_push_event()
sysfs接口 用户态通过/sys/bus/iio/访问 自动生成in_accel_x_raw等属性
实战提示: 在ARM平台编写加速度计IIO驱动时,最常实现的回调是read_raw()和write_raw()。前者负责从芯片寄存器读取原始数据并转换为标准单位(如m/s²),后者用于设置量程、采样率等参数。

3.3 I2C/SPI总线接入:以加速度计ADXL345为例

加速度计通过I2C或SPI总线与SoC连接。驱动中需要实现总线级的读写操作。以下以I2C接口的ADXL345为例,展示内核驱动中典型的寄存器读写模式:

/* ADXL345 I2C 读寄存器示例 */
static int adxl345_i2c_read(struct i2c_client *client, u8 reg, u8 *val)
{
    struct i2c_msg msg[2];
    int ret;

    /* 写寄存器地址 */
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].len = 1;
    msg[0].buf = ®

    /* 读数据 */
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].len = 1;
    msg[1].buf = val;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret == 2)
        return 0;
    else
        return -EIO;
}

/* 读取加速度X轴原始值(14位左对齐) */
static int adxl345_read_axis(struct iio_dev *indio_dev, int axis)
{
    struct adxl345_data *data = iio_priv(indio_dev);
    u8 reg_low, reg_high;
    u16 raw_val;

    reg_low = ADXL345_REG_DATAX0 + (axis * 2);
    reg_high = ADXL345_REG_DATAX0 + (axis * 2) + 1;

    adxl345_i2c_read(data->client, reg_low, &data->buf[0]);
    adxl345_i2c_read(data->client, reg_high, &data->buf[1]);

    raw_val = (data->buf[1] << 8) | data->buf[0];
    return (s16)raw_val;  /* 转换为有符号数 */
}
移植注意: I2C/SPI控制器驱动在ARM和RISC-V平台上是平台相关的。虽然Sensor芯片驱动(如ADXL345)的寄存器读写逻辑可以复用,但总线控制器初始化、时钟配置、DMA支持等代码需要针对RISC-V SoC重新适配。

3.4 IIO通道定义与数据上报

IIO通道(iio_chan_spec)定义了Sensor对外暴露的数据维度。对于三轴加速度计,典型定义如下:

static const struct iio_chan_spec adxl345_channels[] = {
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_X,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
        .address = ADXL345_AXIS_X,
    },
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_Y,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
        .address = ADXL345_AXIS_Y,
    },
    {
        .type = IIO_ACCEL,
        .modified = 1,
        .channel2 = IIO_MOD_Z,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
        .address = ADXL345_AXIS_Z,
    },
};

当用户态读取/sys/bus/iio/devices/iio:deviceX/in_accel_x_raw时,IIO核心自动调用驱动注册的read_raw()回调,最终触发I2C/SPI总线读取芯片寄存器。

IIO通道数据读取流程 IIO通道数据读取流程(用户态 → 寄存器) 用户态 cat in_accel_x_raw IIO核心 iio_read_channel_raw() 驱动read_raw() adxl345_read_axis() I2C 传输 返回原始数据(raw value) 关键点:IIO核心通过info_mask_separate自动匹配通道与回调 驱动只需实现寄存器读写,无需关心sysfs属性生成 移植到RISC-V时,read_raw()逻辑可完全复用

3.5 HAL层:用户态与内核的桥梁

在Android或嵌入式Linux系统中,HAL层负责将IIO内核接口封装为上层框架可调用的API。典型的HAL层实现包含以下步骤:

  1. 设备发现:扫描/sys/bus/iio/devices/,匹配Sensor名称或ID。
  2. 属性映射:将in_accel_x_raw、in_accel_scale等sysfs文件映射为HAL属性。
  3. 数据转换:将原始值乘以scale因子,转换为标准单位(如m/s²)。
  4. 事件轮询:通过poll()或IIO事件fd监听数据就绪。
/* HAL层伪代码:读取加速度计数据 */
float read_accel_x() {
    int raw_fd, scale_fd;
    char raw_str[16], scale_str[16];
    int raw_val;
    float scale_val;

    /* 打开sysfs属性文件 */
    raw_fd = open("/sys/bus/iio/devices/iio:device0/in_accel_x_raw", O_RDONLY);
    scale_fd = open("/sys/bus/iio/devices/iio:device0/in_accel_scale", O_RDONLY);

    /* 读取原始值和缩放因子 */
    read(raw_fd, raw_str, sizeof(raw_str));
    read(scale_fd, scale_str, sizeof(scale_str));

    raw_val = atoi(raw_str);
    scale_val = atof(scale_str);

    /* 转换为标准单位(m/s²) */
    return (float)raw_val * scale_val;
}
移植要点: HAL层代码通常与内核接口解耦,只要RISC-V平台上的IIO设备名称和属性路径不变,HAL层可以零修改直接复用。这是IIO子系统带来的最大移植优势。

3.6 中断与触发模式:事件驱动架构

加速度计支持中断触发模式,当检测到运动、自由落体或阈值超限时,芯片通过INT引脚通知SoC。IIO子系统通过iio_trigger机制处理这类事件:

触发类型 描述 典型应用
硬件触发 Sensor芯片INT引脚产生中断 运动检测、敲击识别
软件触发 内核定时器周期性触发 连续数据采样(如100Hz)
sysfs触发 用户态写入触发文件 调试、单次读取

在ARM平台,中断处理函数中通常调用iio_trigger_poll()来通知IIO核心进行数据读取。这部分代码涉及GPIO中断注册、中断号映射等平台相关操作,是移植到RISC-V时需要重点修改的部分。

IIO中断触发数据流 IIO中断触发数据流(硬件中断模式) 加速度计 ADXL345 INT引脚 GPIO中断 控制器 IIO Trigger iio_trigger_poll() 驱动读取 read_all_channels() 移植关键:中断号(IRQ)和GPIO引脚映射在ARM和RISC-V上不同 需要修改设备树(Device Tree)中的中断控制器和引脚配置 中断处理函数逻辑(读取FIFO、清除中断标志)可复用

3.7 本章小结:ARM架构下的可移植性分析

通过以上分析,可以总结出ARM平台Sensor驱动中各层的可移植性特征:

  • IIO核心框架:完全平台无关,RISC-V内核同样支持IIO子系统。
  • Sensor芯片驱动(寄存器读写逻辑):90%以上可复用,仅I2C/SPI传输函数需要适配。
  • 中断处理:逻辑可复用,但中断注册、GPIO控制需平台适配。
  • 设备树:需要根据RISC-V SoC重新编写节点。
  • HAL层:完全可复用,不依赖具体平台。