SPI子系统:BMI088驱动

BMI088 是一款六轴惯性测量单元,内部集成三轴加速度计和三轴陀螺仪,常用于机器人、无人机、运动控制、姿态检测等场景。相比普通低成本 IMU,BMI088 更强调抗振动能力和温度稳定性,因此比较适合安装在电机、机械结构、移动机器人等振动较强的系统中。

MBI088既支持I2C总线也支持SPI总线。

本文以嵌入式 Linux 平台为例,说明如何基于 SPI 总线实现 BMI088 驱动。驱动目标包括:完成 SPI 设备匹配、读取芯片 ID、初始化加速度计和陀螺仪、周期读取六轴原始数据,并将原始寄存器值换算为实际物理量。

需要注意的是,BMI088 不能简单地当成一个普通 SPI 传感器处理。它虽然封装在同一个芯片中,但内部加速度计和陀螺仪相对独立:二者有各自的寄存器空间、数据输出寄存器、片选信号和初始化流程。因此驱动设计时,最好把它理解为"同一个封装里的两个 SPI 从设备"。

1.BMI088SPI接口特点

1.1 为什么选择SPI而不是I2C

**(1)实时性:**I2C 通常工作在标准模式(100kHz)或快速模式(400kHz),频率较低,SPI频率较高,而且I2C通讯有繁琐的寻址、ACK/NACK 应答位等内容,耗时更长,故而SPI可以更好的满足实时性。

**(2)可靠性:**在电机运转产生强磁场和高频噪声的环境下,I2C 的 SCL/SDA 线极易受到干扰,导致波形畸变;而SPI的可靠性更强。

1.2 SPI接口

引脚名称 功能描述
SCK SPI 时钟信号,由主控输出
SDI / MOSI 主控向 BMI088 写数据
SDO1 / MISO 加速度计数据输出
SDO2 / MISO 陀螺仪数据输出
CSB1 加速度计片选信号
CSB2 陀螺仪片选信号
INT1 / INT2 加速度计中断引脚
INT3 / INT4 陀螺仪中断引脚
PS 协议选择引脚(SPI 模式下通常接 GND,I2C下接VDD)

在硬件连接上,SCK 和 SDI 可以被加速度计和陀螺仪共用,但加速度计和陀螺仪需要独立片选。也就是说,主控访问加速度计时拉低 CSB1,访问陀螺仪时拉低 CSB2。

2.设备树撰写

根据RK3506原理图可知,我们选择将BMI088接到SPI0下;

由于我们要挂载到SPI下,我们先查看pinctrl相关内容,打开 rk3506-pinctrl.dtsi:

然后我们在板机设备树dts文件下撰写设备树,由于 BMI088 的加速度计和陀螺仪使用不同片选,设备树中可以把它们描述成同一 SPI 控制器下的两个设备

3.驱动编写

3.1SPI子系统常用数据结构与函数

数据结构与I2C对比:

功能层面 SPI 子系统 I2C 子系统 描述
总线控制器 struct spi_controller struct i2c_adapter 代表物理硬件接口(硬件控制器)
从设备对象 struct spi_device struct i2c_client 代表挂载在总线上的外设
驱动对象 struct spi_driver struct i2c_driver 描述外设的驱动逻辑
传输描述 struct spi_transfer struct i2c_msg 定义单次读写操作的参数
传输集合 struct spi_message struct i2c_msg SPI 将多个 transfer 组合成一个原子 message
关键差异点:
  • 传输模型 :SPI 使用 spi_message 将多个 spi_transfer 串联,保证传输的原子性;I2C 使用 i2c_msg 数组,直接通过 i2c_transfer 函数发送。

  • 地址处理 :I2C 结构中必须包含 addr(从机地址);SPI 结构中则主要关注 chip_select(片选引脚)。

常用核心函数对比

SPI:

  • spi_register_controller():注册 SPI 控制器。
  • spi_register_driver():注册 SPI 设备驱动。

I2C:

  • i2c_add_adapter():注册 I2C 适配器。
  • i2c_register_driver():注册 I2C 设备驱动。
B. 数据传输(同步方式)

SPI:

  • spi_sync(struct spi_device *spi, struct spi_message *message):这是最常用的同步传输接口,会阻塞直到传输完成。
  • spi_write() / spi_read() / spi_write_then_read():便捷函数,内部封装了 spi_message 的创建与提交。

I2C:

  • i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num):I2C 的核心传输函数,支持一次性发送多个 msg(例如"写寄存器地址 + 读数据"的组合操作)。
  • i2c_master_send() / i2c_master_recv():针对简单读写场景的快捷封装。

3.2驱动撰写

由于在BMI088内部是两个独立的芯片,三轴加速度计和三轴陀螺仪,所以我们的驱动程序也撰写两个是比较合适的。

3.2.1陀螺仪Gyro驱动

驱动框架:

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/delay.h>

#define DEVICE_NAME         "bmi088_gyro"
#define CLASS_NAME          "bmi088"

/* BMI088 陀螺仪寄存器定义 */
#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  /* 陀螺仪固定的 Chip ID */

/* 驱动设备私有结构体 */
struct bmi088_gyro_dev {
    struct spi_device *spi;
};

/* * BMI088 陀螺仪 SPI 读取逻辑:
 * 标准 SPI 读操作,发送 1 字节地址(最高位置 1 表示读),随后立即读取有效数据。
 */
//边发边收
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;
    /* 
     * 分配 len + 1 字节的 DMA 安全内存
     * 第 0 字节:读命令;第 1 ~ len 字节:Dummy 数据(产生时钟)
     */
    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;          // 读指令(最高位置 1)
    memset(tx_buf + 1, 0xFF, len);   // 填充 Dummy 字节,用于产生 SCLK 时钟

    /* 构造单次传输:同时收发 len+1 个字节 */
    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 陀螺仪 SPI 写入逻辑 */
static int bmi088_gyro_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
    u8 buf[2];
    buf[0] = reg & 0x7F; /* 最高位置 0 表示写操作 */
    buf[1] = val;
    return spi_write(spi, buf, 2);
}

/* SPI Probe 入口函数 */
static int bmi088_gyro_probe(struct spi_device *spi)
{
    struct bmi088_gyro_dev *dev;
    int ret;

    return 0;

destroy_class:
    class_destroy(dev->class);
del_cdev:
    cdev_del(&dev->cdev);
unregister_chrdev:
    unregister_chrdev_region(dev->dev_num, 1);
    return ret;
}

/* SPI Remove 卸载释放函数 */
static int bmi088_gyro_remove(struct spi_device *spi)
{
    struct bmi088_gyro_dev *dev = spi_get_drvdata(spi);

    dev_info(&spi->dev, "BMI088 Gyroscope driver removed\n");
    return 0;
}

/* 设备树匹配表 */
static const struct of_device_id bmi088_gyro_of_match[] = {
    { .compatible = "my-bmi088-gyro" },
    { }
};
MODULE_DEVICE_TABLE(of, bmi088_gyro_of_match);

/* SPI 驱动主体结构 */
static struct spi_driver bmi088_gyro_driver = {
    .driver = {
        .name           = "bmi088-gyro",
        .of_match_table = bmi088_gyro_of_match,
    },
    .probe              = bmi088_gyro_probe,
    .remove             = bmi088_gyro_remove,
};

module_spi_driver(bmi088_gyro_driver);

MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("Standard SPI Char Driver for Bosch BMI088 Gyroscope");
MODULE_LICENSE("GPL");

包括注册SPI设备,probe和SPI通讯逻辑,注册设备部分与I2C非常类似,关键区别就在SPI通讯部分:读寄存器和写寄存器。

可以发现写寄存器非常简单,因为SPI是全双工的,写的过程中也会把读的数据放到buf里,而我们写寄存器的话不需要等他把读的数据放到buf里,所以很简单;而读数据相对复杂,切SPI的通讯数据结构也相较于I2C复杂。

在上述基础上,我们需要继续补充设备初始化部分与字符设备部分,为用户空间开一个接口用于读取某些数据:

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/slab.h>

#define DEVICE_NAME         "bmi088_gyro"
#define CLASS_NAME          "bmi088"

/* BMI088 陀螺仪寄存器定义 */
#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  /* 陀螺仪固定的 Chip ID */

/* 驱动设备私有结构体 */
struct bmi088_gyro_dev {
    struct spi_device *spi;
    dev_t dev_num;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct mutex lock;
};

/* * BMI088 陀螺仪 SPI 读取逻辑:
 * 标准 SPI 读操作,发送 1 字节地址(最高位置 1 表示读),随后立即读取有效数据。
 */
//边发边收
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;
    /* 
     * 分配 len + 1 字节的 DMA 安全内存
     * 第 0 字节:读命令;第 1 ~ len 字节:Dummy 数据(产生时钟)
     */
    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;          // 读指令(最高位置 1)
    memset(tx_buf + 1, 0xFF, len);   // 填充 Dummy 字节,用于产生 SCLK 时钟

    /* 构造单次传输:同时收发 len+1 个字节 */
    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 陀螺仪 SPI 写入逻辑 */
static int bmi088_gyro_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
    u8 buf[2];
    buf[0] = reg & 0x7F; /* 最高位置 0 表示写操作 */
    buf[1] = val;
    return spi_write(spi, buf, 2);
}

/* 用户层 open 接口 */
static int bmi088_gyro_open(struct inode *inode, struct file *file)
{
    struct bmi088_gyro_dev *dev = container_of(inode->i_cdev, struct bmi088_gyro_dev, cdev);
    file->private_data = dev;
    return 0;
}

/* 用户层 read 接口:一次性抛出 6 字节的原始角速度 XYZ 轴数据 */
static ssize_t bmi088_gyro_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
{
    struct bmi088_gyro_dev *dev = file->private_data;
    u8 raw_data[6];
    int ret;

    if (count < 6)
        return -EINVAL;

    mutex_lock(&dev->lock);
    /* 采用 Burst Read(连续读)确保角速度 X,Y,Z 数据属于同一时间锁存点 */
    ret = bmi088_gyro_read_regs(dev->spi, GYRO_DATA_START_REG, raw_data, 6);
    mutex_unlock(&dev->lock);

    if (ret < 0) {
        dev_err(&dev->spi->dev, "Failed to read gyroscope data\n");
        return -EIO;
    }

    if (copy_to_user(user_buf, raw_data, 6))
        return -EFAULT;

    return 6;
}

static int bmi088_gyro_release(struct inode *inode, struct file *file)
{
    return 0;
}

/* 绑定文件操作结构体 */
static const struct file_operations bmi088_gyro_fops = {
    .owner   = THIS_MODULE,
    .open    = bmi088_gyro_open,
    .read    = bmi088_gyro_read,
    .release = bmi088_gyro_release,
};

/* 硬件配置与初始化 */
static int bmi088_gyro_hardware_init(struct spi_device *spi)
{
    u8 chip_id = 0;
    int ret;

    /* 1. 软件复位:向复位寄存器写入 0xB6 */
    ret = bmi088_gyro_write_reg(spi, GYRO_SOFTRESET_REG, 0xB6);
    if (ret < 0) return ret;
    msleep(30); /* 复位延时,给芯片重新加载内部配置留出足够时间 */

    /* 2. 读取 CHIP_ID 验证通信是否正常 */
    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 0x0F\n", chip_id);
        return -ENODEV;
    }

    /* 3. 配置量程 (GYRO_RANGE):0x00 代表 ±2000 °/s (DPS) */
    ret = bmi088_gyro_write_reg(spi, GYRO_RANGE_REG, 0x00);
    if (ret < 0) return ret;

    /* 4. 配置带宽与采样率 (GYRO_BANDWIDTH):0x00 代表 ODR 2000Hz, 滤波器截止频率 532Hz */
    ret = bmi088_gyro_write_reg(spi, GYRO_BANDWIDTH_REG, 0x00);
    if (ret < 0) return ret;

    /* 5. 设置电源模式:0x00 代表 Normal Mode(正常工作状态) */
    ret = bmi088_gyro_write_reg(spi, GYRO_LPM1_REG, 0x00);
    if (ret < 0) return ret;
    msleep(1);

    dev_info(&spi->dev, "BMI088 Gyroscope initialized successfully! ID: 0x%02X\n", chip_id);
    return 0;
}

/* SPI Probe 入口函数 */
static int bmi088_gyro_probe(struct spi_device *spi)
{
    struct bmi088_gyro_dev *dev;
    int ret;

    /* 分配设备内存 */
    dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    dev->spi = spi;
    spi_set_drvdata(spi, dev);
    mutex_init(&dev->lock);

    /* 1. 初始化物理硬件 */
    ret = bmi088_gyro_hardware_init(spi);
    if (ret)
        return ret;

    /* 2. 动态申请字符设备主从设备号 */
    ret = alloc_chrdev_region(&dev->dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0)
        return ret;

    /* 3. 初始化并添加 cdev */
    cdev_init(&dev->cdev, &bmi088_gyro_fops);
    dev->cdev.owner = THIS_MODULE;
    ret = cdev_add(&dev->cdev, dev->dev_num, 1);
    if (ret < 0)
        goto unregister_chrdev;

    /* 4. 创建设备类 (用于在 /dev 下自动生成节点) */
    dev->class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev->class)) {
        ret = PTR_ERR(dev->class);
        goto del_cdev;
    }

    /* 5. 创建设备文件 (/dev/bmi088_gyro) */
    dev->device = device_create(dev->class, NULL, dev->dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(dev->device)) {
        ret = PTR_ERR(dev->device);
        goto destroy_class;
    }

    return 0;

destroy_class:
    class_destroy(dev->class);
del_cdev:
    cdev_del(&dev->cdev);
unregister_chrdev:
    unregister_chrdev_region(dev->dev_num, 1);
    return ret;
}

/* SPI Remove 卸载释放函数 */
static int bmi088_gyro_remove(struct spi_device *spi)
{
    struct bmi088_gyro_dev *dev = spi_get_drvdata(spi);

    /* 逆序释放所有申请的空间与节点 */
    device_destroy(dev->class, dev->dev_num);
    class_destroy(dev->class);
    cdev_del(&dev->cdev);
    unregister_chrdev_region(dev->dev_num, 1);

    dev_info(&spi->dev, "BMI088 Gyroscope driver removed\n");
    return 0;
}

/* 设备树匹配表 */
static const struct of_device_id bmi088_gyro_of_match[] = {
    { .compatible = "my-bmi088-gyro" },
    { }
};
MODULE_DEVICE_TABLE(of, bmi088_gyro_of_match);

/* SPI 驱动主体结构 */
static struct spi_driver bmi088_gyro_driver = {
    .driver = {
        .name           = "bmi088-gyro",
        .of_match_table = bmi088_gyro_of_match,
    },
    .probe              = bmi088_gyro_probe,
    .remove             = bmi088_gyro_remove,
};

module_spi_driver(bmi088_gyro_driver);

MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("Standard SPI Char Driver for Bosch BMI088 Gyroscope");
MODULE_LICENSE("GPL");

3.2.1加速度计accel驱动