上述我们的驱动对用户接口的实现使用的是字符设备驱动,对于 BMI088 陀螺仪,我们可以注册一个 /dev/bmi088_gyro 节点,然后用户层通过 open()、read() 读取 6 字节原始角速度数据。这种方式简单直接,非常适合入门 SPI 通信、寄存器读写和字符设备驱动框架。
但是,当设备逐渐复杂之后,字符设备驱动的问题就会暴露出来:
1. 用户层不知道 read 出来的 6 个字节分别代表什么;
2. x/y/z 三轴数据格式需要自己约定;
3. 原始值到物理单位的转换比例需要用户层自己维护;
4. 采样率、量程、滤波带宽等参数缺少标准接口;
5. 后续如果要支持 FIFO、中断、buffer、trigger,需要自己重新造轮子。
BMI088 属于典型 IMU 传感器,包含加速度计和陀螺仪。对于这类传感器,Linux 内核中已经提供了更合适的框架 ------ IIO 子系统。
IIO,全称 Industrial I/O ,主要用于 ADC、DAC、加速度计、陀螺仪、磁力计、气压计等采集类设备。Linux 官方文档中说明,IIO 设备会通过 /sys/bus/iio/devices/iio:deviceX/ 目录向用户空间暴露标准属性,例如 name、sampling_frequency_available 等;每个 IIO 设备可以包含一个或多个 channel,channel 用 struct iio_chan_spec 描述。
1.IIO子系统框架
一个基础 IIO 驱动通常包含以下几部分:
1. 驱动私有数据结构体;
2. SPI/I2C 寄存器读写函数;
3. 硬件初始化函数;
4. IIO channel 描述;
5. IIO read_raw() 回调函数;
6. iio_info 操作函数表;
7. probe() 中分配并注册 iio_dev;
8. 设备树匹配表和 spi_driver 结构体。
1.1驱动私有数据
在IIO子系统框架下和不在IIO子系统框架下对驱动私有数据的处理是不同的(构造方式不同)。
私有数据是什么?
在Linux内核中,probe函数执行完毕后,内核并不会帮你记住某个设备的具体状态(比如当前打开了没有、SPI指针是多少、互斥锁初始化了没)。因此,驱动必须自己搞一块内存,把所有该设备独有的、运行时需要用的变量全部塞进去。
非IIO子系统下
在之前的非IIO子系统中,我们对驱动私有数据的处理是,在probe中:
cpp
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
给你的私有数据结构体分配内存。
cpp
spi_set_drvdata(spi, dev);
这是把你的私有数据挂到 spi_device 上。
在IIO子系统情况下,我们对私有数据的处理是这样的:
IIO 版驱动中,可以定义如下私有数据:
cpp
struct bmi088_gyro_data {
struct spi_device *spi;
struct mutex lock;
};
在 IIO 驱动中,私有数据一般通过下面方式分配:
cpp
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);
其中:
cpp
devm_iio_device_alloc()
函数作用(极其关键):给iio设备分配一块内容,并将iio设备绑定的spi设备上,即iio设备的生命周期随spi设备的生命周期的变化,除此之外在iio设备后面分配一块内存用于存放驱动私有数据。
然后:
cpp
data = iio_priv(indio_dev);
就是从 indio_dev 中取出驱动私有数据。
1.2 IIO Channel:描述 BMI088 有哪些数据通道
IIO 驱动里最重要的结构之一是:
cpp
struct iio_chan_spec
它用来描述一个传感器通道。
BMI088 陀螺仪有 3 个角速度通道:
x 轴角速度
y 轴角速度
z 轴角速度
所以可以定义如下 channel 数组:
cpp
static const struct iio_chan_spec bmi088_gyro_channels[] = {
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_X,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_Y,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_Z,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
};
参数详解:
1.3 iio_info:user读取时调用的函数
iio_info类似于字符设备中的file_operations;
IIO 驱动中还需要定义一个 struct iio_info:
cpp
static const struct iio_info bmi088_gyro_info = {
.read_raw = bmi088_gyro_read_raw,
};
它类似字符设备驱动中的 file_operations。
字符设备中:
cpp
static const struct file_operations fops = {
.open = xxx_open,
.read = xxx_read,
};
IIO 驱动中:
cpp
static const struct iio_info bmi088_gyro_info = {
.read_raw = bmi088_gyro_read_raw,
};
也就是说:
cpp
用户读取 /dev/bmi088_gyro
|
v
调用 file_operations.read
用户读取 in_anglvel_x_raw
|
v
调用 iio_info.read_raw
1.4 read_raw():IIO读取数据的核心
read_raw() 是 IIO direct mode 中最核心的函数。
示例代码如下:
cpp
static int bmi088_gyro_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct bmi088_gyro_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = bmi088_gyro_read_axis(data, chan->channel2, val);
if (ret < 0)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = BMI088_GYRO_SCALE_2000DPS_NANO;
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
}
这个函数里面有两个关键参数:
chan:表示用户正在访问哪个通道;
mask:表示用户正在访问这个通道的哪个属性。
读取raw数据:
当用户执行:
cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw
IIO 框架会调用:
cpp
bmi088_gyro_read_raw(indio_dev, chan, val, val2, IIO_CHAN_INFO_RAW);
此时:
cpp
mask = IIO_CHAN_INFO_RAW
chan->channel2 = IIO_MOD_X
所以代码进入:
cpp
case IIO_CHAN_INFO_RAW:
ret = bmi088_gyro_read_axis(data, chan->channel2, val);
然后驱动通过 SPI 读取 BMI088 数据寄存器,并返回 x 轴原始值。
如果用户读取:
cat in_anglvel_y_raw
那么:
chan->channel2 = IIO_MOD_Y
如果用户读取:
cat in_anglvel_z_raw
那么:
chan->channel2 = IIO_MOD_Z
所以可以总结为:
chan 决定读哪个轴;
mask 决定读 raw 还是 scale。
1.5 注册IIO设备步骤
probe函数如下:
cpp
static int bmi088_gyro_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct bmi088_gyro_data *data;
int ret;
/*
* 1. 配置 SPI 基本参数
*
* 如果你的设备树里已经配置了 spi-cpol / spi-cpha,
* 这里可以根据实际情况删除 spi->mode 这一行。
*
* BMI088 常见使用 SPI_MODE_0。
*/
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
if (!spi->max_speed_hz)
spi->max_speed_hz = 10000000;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
/*
* 2. 分配 IIO 设备
*
* indio_dev 是 IIO 核心设备;
* data 是驱动私有数据,通过 iio_priv(indio_dev) 获取。
*/
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);
spi_set_drvdata(spi, indio_dev);
/*
* 3. 初始化 BMI088 gyroscope 硬件
*/
ret = bmi088_gyro_hardware_init(spi);
if (ret < 0)
return ret;
/*
* 4. 填充 IIO 设备信息
*/
indio_dev->name = BMI088_GYRO_DRV_NAME;
indio_dev->info = &bmi088_gyro_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = bmi088_gyro_channels;
indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);
/*
* 5. 注册 IIO 设备
*
* 注册成功后,会在 /sys/bus/iio/devices/ 下生成 iio:deviceX。
*/
ret = devm_iio_device_register(&spi->dev, indio_dev);
if (ret < 0) {
dev_err(&spi->dev, "Failed to register IIO device: %d\n", ret);
return ret;
}
dev_info(&spi->dev, "BMI088 gyroscope IIO driver probed\n");
return 0;
}
步骤如下:
1. 配置 SPI 参数
cpp
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
if (!spi->max_speed_hz)
spi->max_speed_hz = 10000000;
ret = spi_setup(spi);
这里配置 SPI 模式、每字位数和最大频率。
如果设备树中已经配置了 spi-max-frequency、spi-cpol、spi-cpha 等属性,则这里可以根据实际情况简化。
2. 分配 IIO 设备
cpp
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
这一步分配 IIO 设备对象,同时分配驱动私有数据。
3. 初始化私有数据
cpp
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);
这里把 SPI 设备指针保存到私有数据中,后面 read_raw() 读寄存器时会用到。
4. 初始化 BMI088 硬件
cpp
ret = bmi088_gyro_hardware_init(spi);
if (ret < 0)
return ret;
硬件初始化主要包括:
1. 软件复位;
2. 读取 CHIP_ID;
3. 设置 normal mode;
4. 配置量程;
5. 配置带宽和采样率。
5. 填充 IIO 设备信息
cpp
indio_dev->name = BMI088_GYRO_DRV_NAME;
indio_dev->info = &bmi088_gyro_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = bmi088_gyro_channels;
indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);
含义如下:
name :IIO 设备名称;
info :IIO 操作函数表;
modes :当前使用 direct mode;
channels :通道数组;
num_channels :通道数量。
6. 注册 IIO 设备
cpp
ret = devm_iio_device_register(&spi->dev, indio_dev);
注册成功后,IIO 框架会自动生成:
/sys/bus/iio/devices/iio:deviceX/
并根据 channels 和 info_mask 自动生成:
in_anglvel_x_raw
in_anglvel_y_raw
in_anglvel_z_raw
in_anglvel_scale
2. 完整代码示例
cpp
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/iio/iio.h>
/*
* BMI088 Gyroscope register definitions
*/
#define BMI088_GYRO_DRV_NAME "bmi088_gyro_iio"
#define GYRO_CHIP_ID_REG 0x00
#define GYRO_DATA_START_REG 0x02 /* 0x02~0x07: X_LSB X_MSB Y_LSB Y_MSB Z_LSB Z_MSB */
#define GYRO_RANGE_REG 0x0F
#define GYRO_BANDWIDTH_REG 0x10
#define GYRO_LPM1_REG 0x11
#define GYRO_SOFTRESET_REG 0x14
#define BMI088_GYRO_CHIP_ID 0x0F
/*
* 当前驱动里配置的量程:
* GYRO_RANGE_REG = 0x00 -> ±2000 dps
*
* IIO 中 angular velocity 的标准单位通常按 rad/s 表示。
*
* ±2000 dps = ±2000 degree/s
* = ±34.906585 rad/s
*
* raw 范围近似为 -32768 ~ 32767
*
* scale = 34.906585 / 32768
* ≈ 0.001065264 rad/s per LSB
*
* IIO_VAL_INT_PLUS_NANO 表示:
* val + val2 / 1e9
*
* 所以这里返回:
* 0 + 1065264 / 1e9
*/
#define BMI088_GYRO_SCALE_2000DPS_NANO 1065264
struct bmi088_gyro_data {
struct spi_device *spi;
struct mutex lock;
};
/*
* BMI088 gyroscope SPI read
*
* SPI 是边发边收:
* 第 1 字节发送寄存器地址,收到的数据无效;
* 后续发送 dummy byte,用来产生 SCLK,从机返回真正数据。
*/
static int bmi088_gyro_read_regs(struct spi_device *spi, u8 reg,
u8 *buf, size_t len)
{
struct spi_transfer t = {0};
struct spi_message m;
u8 *tx_buf;
u8 *rx_buf;
int ret;
tx_buf = kzalloc(len + 1, GFP_KERNEL);
rx_buf = kzalloc(len + 1, GFP_KERNEL);
if (!tx_buf || !rx_buf) {
ret = -ENOMEM;
goto free_bufs;
}
tx_buf[0] = reg | 0x80; /* bit7 = 1 表示读 */
memset(tx_buf + 1, 0xFF, len); /* dummy bytes,用来产生时钟 */
t.tx_buf = tx_buf;
t.rx_buf = rx_buf;
t.len = len + 1;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
if (ret == 0) {
/*
* rx_buf[0] 是发送寄存器地址时收到的无效数据;
* 真正有效数据从 rx_buf[1] 开始。
*/
memcpy(buf, rx_buf + 1, len);
} else {
dev_err(&spi->dev, "SPI read failed: %d\n", ret);
}
free_bufs:
kfree(rx_buf);
kfree(tx_buf);
return ret;
}
/*
* BMI088 gyroscope SPI write
*/
static int bmi088_gyro_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
u8 buf[2];
buf[0] = reg & 0x7F; /* bit7 = 0 表示写 */
buf[1] = val;
return spi_write(spi, buf, sizeof(buf));
}
/*
* 读取 6 字节原始陀螺仪数据,并根据轴选择返回 x/y/z
*/
static int bmi088_gyro_read_axis(struct bmi088_gyro_data *data,
int axis, int *val)
{
u8 raw[6];
s16 x, y, z;
int ret;
mutex_lock(&data->lock);
ret = bmi088_gyro_read_regs(data->spi, GYRO_DATA_START_REG, raw, 6);
mutex_unlock(&data->lock);
if (ret < 0)
return ret;
x = (s16)((raw[1] << 8) | raw[0]);
y = (s16)((raw[3] << 8) | raw[2]);
z = (s16)((raw[5] << 8) | raw[4]);
switch (axis) {
case IIO_MOD_X:
*val = x;
break;
case IIO_MOD_Y:
*val = y;
break;
case IIO_MOD_Z:
*val = z;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* IIO read_raw 回调函数
*
* 用户读取下面这些 sysfs 节点时,会进入这个函数:
*
* in_anglvel_x_raw
* in_anglvel_y_raw
* in_anglvel_z_raw
* in_anglvel_scale
*/
static int bmi088_gyro_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct bmi088_gyro_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = bmi088_gyro_read_axis(data, chan->channel2, val);
if (ret < 0)
return ret;
/*
* 返回整数 raw 值。
*/
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/*
* 当前配置是 ±2000 dps。
*
* scale ≈ 0.001065264 rad/s per LSB
*/
*val = 0;
*val2 = BMI088_GYRO_SCALE_2000DPS_NANO;
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
}
static const struct iio_info bmi088_gyro_info = {
.read_raw = bmi088_gyro_read_raw,
};
/*
* 定义 IIO 通道
*
* type = IIO_ANGL_VEL 表示角速度
*
* modified = 1 表示这是带方向修饰的通道
*
* channel2 = IIO_MOD_X/Y/Z 表示 X/Y/Z 轴
*
* info_mask_separate:
* 每个轴都有自己的 raw 节点
*
* info_mask_shared_by_type:
* 三个角速度轴共享同一个 scale
*/
static const struct iio_chan_spec bmi088_gyro_channels[] = {
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_X,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_Y,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_ANGL_VEL,
.modified = 1,
.channel2 = IIO_MOD_Z,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
},
};
/*
* BMI088 gyroscope hardware initialization
*/
static int bmi088_gyro_hardware_init(struct spi_device *spi)
{
u8 chip_id = 0;
int ret;
/*
* 1. 软件复位
*
* 这一步不是绝对必须,但工程上建议保留。
* 作用是让陀螺仪从一个干净状态重新开始。
*/
ret = bmi088_gyro_write_reg(spi, GYRO_SOFTRESET_REG, 0xB6);
if (ret < 0)
return ret;
msleep(30);
/*
* 2. 读取 CHIP_ID,确认 SPI 通信和器件身份
*/
ret = bmi088_gyro_read_regs(spi, GYRO_CHIP_ID_REG, &chip_id, 1);
if (ret < 0)
return ret;
if (chip_id != BMI088_GYRO_CHIP_ID) {
dev_err(&spi->dev,
"Gyro Chip ID mismatch! Got 0x%02X, expected 0x%02X\n",
chip_id, BMI088_GYRO_CHIP_ID);
return -ENODEV;
}
/*
* 3. 设置电源模式为 Normal Mode
*
* BMI088 gyro 上电后默认就是 normal mode。
* 这里再次写入,是为了防御式初始化。
*/
ret = bmi088_gyro_write_reg(spi, GYRO_LPM1_REG, 0x00);
if (ret < 0)
return ret;
msleep(1);
/*
* 4. 配置量程:±2000 dps
*/
ret = bmi088_gyro_write_reg(spi, GYRO_RANGE_REG, 0x00);
if (ret < 0)
return ret;
/*
* 5. 配置 ODR 和带宽
*
* 0x00: ODR 2000 Hz, filter bandwidth 532 Hz
*/
ret = bmi088_gyro_write_reg(spi, GYRO_BANDWIDTH_REG, 0x00);
if (ret < 0)
return ret;
dev_info(&spi->dev,
"BMI088 gyroscope initialized successfully, chip_id=0x%02X\n",
chip_id);
return 0;
}
static int bmi088_gyro_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct bmi088_gyro_data *data;
int ret;
/*
* 1. 配置 SPI 基本参数
*
* 如果你的设备树里已经配置了 spi-cpol / spi-cpha,
* 这里可以根据实际情况删除 spi->mode 这一行。
*
* BMI088 常见使用 SPI_MODE_0。
*/
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
if (!spi->max_speed_hz)
spi->max_speed_hz = 10000000;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
/*
* 2. 分配 IIO 设备
*
* indio_dev 是 IIO 核心设备;
* data 是驱动私有数据,通过 iio_priv(indio_dev) 获取。
*/
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);
spi_set_drvdata(spi, indio_dev);
/*
* 3. 初始化 BMI088 gyroscope 硬件
*/
ret = bmi088_gyro_hardware_init(spi);
if (ret < 0)
return ret;
/*
* 4. 填充 IIO 设备信息
*/
indio_dev->name = BMI088_GYRO_DRV_NAME;
indio_dev->info = &bmi088_gyro_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = bmi088_gyro_channels;
indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);
/*
* 5. 注册 IIO 设备
*
* 注册成功后,会在 /sys/bus/iio/devices/ 下生成 iio:deviceX。
*/
ret = devm_iio_device_register(&spi->dev, indio_dev);
if (ret < 0) {
dev_err(&spi->dev, "Failed to register IIO device: %d\n", ret);
return ret;
}
dev_info(&spi->dev, "BMI088 gyroscope IIO driver probed\n");
return 0;
}
/*
* 如果你使用的是较新的内核,remove 可以省略;
* 因为这里使用了 devm_iio_device_alloc 和 devm_iio_device_register,
* 资源会自动释放。
*
* 如果你的内核要求 .remove 必须存在,可以加上下面这个函数。
*/
static void bmi088_gyro_remove(struct spi_device *spi)
{
dev_info(&spi->dev, "BMI088 gyroscope IIO driver removed\n");
}
/*
* 设备树匹配表
*
* 正式写法建议使用 "bosch,bmi088-gyro"。
* 为了兼容你原来的设备树,这里也保留 "my-bmi088-gyro"。
*/
static const struct of_device_id bmi088_gyro_of_match[] = {
{ .compatible = "bosch,bmi088-gyro" },
{ .compatible = "my-bmi088-gyro" },
{ }
};
MODULE_DEVICE_TABLE(of, bmi088_gyro_of_match);
static const struct spi_device_id bmi088_gyro_id[] = {
{ "bmi088-gyro", 0 },
{ "my-bmi088-gyro", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, bmi088_gyro_id);
static struct spi_driver bmi088_gyro_driver = {
.driver = {
.name = BMI088_GYRO_DRV_NAME,
.of_match_table = bmi088_gyro_of_match,
},
.probe = bmi088_gyro_probe,
.remove = bmi088_gyro_remove,
.id_table = bmi088_gyro_id,
};
module_spi_driver(bmi088_gyro_driver);
MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("BMI088 Gyroscope SPI IIO Driver");
MODULE_LICENSE("GPL");