上述我们的驱动对用户接口的实现使用的是字符设备驱动,对于 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中:
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);给你的私有数据结构体分配内存。
spi_set_drvdata(spi, dev);这是把你的私有数据挂到spi_device上。
在IIO子系统情况下,我们对私有数据的处理是这样的:
IIO 版驱动中,可以定义如下私有数据:
struct bmi088_gyro_data { struct spi_device *spi; struct mutex lock; };在 IIO 驱动中,私有数据一般通过下面方式分配:
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev);>devm_iio_device_alloc()函数作用(极其关键):给iio设备分配一块内容,并将iio设备绑定的spi设备上,即iio设备的生命周期随spi设备的生命周期的变化,除此之外在iio设备后面分配一块内存用于存放驱动私有数据。
然后:
data = iio_priv(indio_dev);就是从indio_dev中取出驱动私有数据。
1.2 IIO Channel:描述 BMI088 有哪些数据通道
IIO 驱动里最重要的结构之一是:
struct iio_chan_spec它用来描述一个传感器通道。
BMI088 陀螺仪有 3 个角速度通道:
x 轴角速度 y 轴角速度 z 轴角速度所以可以定义如下 channel 数组:
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), }, };参数详解:
https://blog.csdn.net/myingrid/article/details/162460210?sharetype=blogdetail&sharerId=162460210&sharerefer=PC&sharesource=myingrid&spm=1011.2480.3001.8118
1.3 iio_info:user读取时调用的函数
iio_info类似于字符设备中的file_operations;
IIO 驱动中还需要定义一个struct iio_info:
static const struct iio_info bmi088_gyro_info = { .read_raw = bmi088_gyro_read_raw, };它类似字符设备驱动中的file_operations。
字符设备中:
static const struct file_operations fops = { .open = xxx_open, .read = xxx_read, };IIO 驱动中:
static const struct iio_info bmi088_gyro_info = { .read_raw = bmi088_gyro_read_raw, };也就是说:
用户读取 /dev/bmi088_gyro | v 调用 file_operations.read 用户读取 in_anglvel_x_raw | v 调用 iio_info.read_raw1.4 read_raw():IIO读取数据的核心
read_raw()是 IIO direct mode 中最核心的函数。
示例代码如下:
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_rawIIO 框架会调用:
bmi088_gyro_read_raw(indio_dev, chan, val, val2, IIO_CHAN_INFO_RAW);此时:
mask = IIO_CHAN_INFO_RAW chan->channel2 = IIO_MOD_X所以代码进入:
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函数如下:
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); >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 设备
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM;这一步分配 IIO 设备对象,同时分配驱动私有数据。
3. 初始化私有数据
data = iio_priv(indio_dev);>ret = bmi088_gyro_hardware_init(spi); if (ret < 0) return ret;硬件初始化主要包括:
1. 软件复位; 2. 读取 CHIP_ID; 3. 设置 normal mode; 4. 配置量程; 5. 配置带宽和采样率。5. 填充 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);含义如下:
name :IIO 设备名称; info :IIO 操作函数表; modes :当前使用 direct mode; channels :通道数组; num_channels :通道数量。6. 注册 IIO 设备
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_scale2. 完整代码示例
#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");