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芯片通信。
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等属性 |
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; /* 转换为有符号数 */
}
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总线读取芯片寄存器。
3.5 HAL层:用户态与内核的桥梁
在Android或嵌入式Linux系统中,HAL层负责将IIO内核接口封装为上层框架可调用的API。典型的HAL层实现包含以下步骤:
- 设备发现:扫描
/sys/bus/iio/devices/,匹配Sensor名称或ID。 - 属性映射:将
in_accel_x_raw、in_accel_scale等sysfs文件映射为HAL属性。 - 数据转换:将原始值乘以scale因子,转换为标准单位(如m/s²)。
- 事件轮询:通过
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;
}
3.6 中断与触发模式:事件驱动架构
加速度计支持中断触发模式,当检测到运动、自由落体或阈值超限时,芯片通过INT引脚通知SoC。IIO子系统通过iio_trigger机制处理这类事件:
| 触发类型 | 描述 | 典型应用 |
|---|---|---|
| 硬件触发 | Sensor芯片INT引脚产生中断 | 运动检测、敲击识别 |
| 软件触发 | 内核定时器周期性触发 | 连续数据采样(如100Hz) |
| sysfs触发 | 用户态写入触发文件 | 调试、单次读取 |
在ARM平台,中断处理函数中通常调用iio_trigger_poll()来通知IIO核心进行数据读取。这部分代码涉及GPIO中断注册、中断号映射等平台相关操作,是移植到RISC-V时需要重点修改的部分。
3.7 本章小结:ARM架构下的可移植性分析
通过以上分析,可以总结出ARM平台Sensor驱动中各层的可移植性特征:
- IIO核心框架:完全平台无关,RISC-V内核同样支持IIO子系统。
- Sensor芯片驱动(寄存器读写逻辑):90%以上可复用,仅I2C/SPI传输函数需要适配。
- 中断处理:逻辑可复用,但中断注册、GPIO控制需平台适配。
- 设备树:需要根据RISC-V SoC重新编写节点。
- HAL层:完全可复用,不依赖具体平台。