
上述我们的驱动对用户接口的实现使用的是字符设备驱动对于 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 设备可以包含一个或多个 channelchannel 用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?sharetypeblogdetailsharerId162460210sharereferPCsharesourcemyingridspm1011.2480.3001.81181.3 iio_info:user读取时调用的函数iio_info类似于字符设备中的file_operationsIIO 驱动中还需要定义一个struct iio_infostatic 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_id0x%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);