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的可靠性更强。

在这种引脚资源比较充足的情况下,选择SPI肯定是更好的

1.2 SPI接口

引脚名称功能描述
SCKSPI 时钟信号,由主控输出
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_controllerstruct i2c_adapter代表物理硬件接口(硬件控制器)
从设备对象struct spi_devicestruct i2c_client代表挂载在总线上的外设
驱动对象struct spi_driverstruct i2c_driver描述外设的驱动逻辑
传输描述struct spi_transferstruct i2c_msg定义单次读写操作的参数
传输集合struct spi_messageSPI 将多个 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驱动

        我们操作gyro陀螺仪的目的是读取其寄存器中的X,Y,Z方向的三个角速度,所以驱动中肯定包括此内容,除此之外吗,我们阅读数据手册得到其还需要初始化流程,并在数据手册中找到需要的寄存器。

在数据手册中我们可以看到,对于陀螺仪而言,通过PS引脚就可以直接选择是I2C还是SPI模式,也不需要什么额外的初始化,所以对于陀螺仪的初始化而言我们只需要进行一个复位即可,一般情况下虽然不必须,但我们还是会读取一下CHIP_ID来验证一下SPI通讯是否正确,然后就可以配置其量程与带宽/采样率。

根据数据手册我可以了解,我们需要的寄存器有:

驱动框架:

#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复杂。

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

#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驱动

对于加速度计而言,其初始化过程于陀螺仪相比更复杂:

1.I2C-SPI模式:

由数据手册可知,对于加速度计而言,上电默认是I2C模式,只有片选引脚经过一个上升沿才能转换为SPI模式,所以这就要求我们在初始化的时候读一下ACC_CHIP_ID寄存器的内容,因为 SPI 的片选 CS 一般是低电平有效,一次 SPI 读操作本身就会包含一次“拉低片选 → 传输数据 → 拉高片选”的过程。

CSB1:  ───────┐       ┌───────
              │       │
              └───────┘
              下降沿    上升沿

2.挂起-正常模式:

陀螺仪默认已经处于正常模式,而加速度计默认是挂起模式,不能直接采集数据。

所以驱动初始化时,还需要把加速度计从 suspend mode 切换到 normal mode。加速度计进入正常模式的步骤如下:

1. 给 BMI088 上电
2. 等待 1 ms
3. 向 ACC_PWR_CTRL 寄存器写入 0x04
4. 再等待 450 us

所以,我们需要对加速度计进行量程和采样率的配置,对各个方向加速度的读取,最终我们需要的寄存器如下:

并且,在读取寄存器数据时,如数据手册所示,读取数据时的第一个数据是"脏"数据,不需要读取,后续数据才是所需数据:

即,正常情况下SPI通讯应该是:

蛋accel要求SPI通讯是:

即需要多发一次垃圾数据把垃圾消息抵消掉。

完整代码:

#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>

#define DEVICE_NAME         "bmi088_accel"
#define CLASS_NAME          "bmi088"

/* BMI088 加速度计寄存器定义 */
#define ACC_CHIP_ID_REG     0x00
#define ACC_DATA_START_REG  0x12  /* 0x12~0x17 分别为 X_LSB, X_MSB, Y_LSB, Y_MSB, Z_LSB, Z_MSB */
#define ACC_CONF_REG        0x40  /* 带宽和 ODR 配置 */
#define ACC_RANGE_REG       0x41  /* 量程配置 */
#define ACC_PWR_CONF_REG    0x7C
#define ACC_PWR_CTRL_REG    0x7D

#define BMI088_ACC_CHIP_ID  0x1E  /* 加速度计正确 ID */

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

/**
 * BMI088 加速度计 SPI 读取核心逻辑:
 * 依据数据手册,Accel 部分进行 SPI 读取时,在发送完寄存器地址后,
 * 芯片会先返回一个【Dummy Byte(伪字节)】,之后才是真实数据。
 */
static int bmi088_accel_read_regs(struct spi_device *spi, u8 reg, u8 *buf, size_t len)
{
    struct spi_transfer t[2];
    struct spi_message m;
    u8 tx_buf[2];

    tx_buf[0] = reg | 0x80; /* 最高位置 1 表示读操作 */
    tx_buf[1] = 0x00;       /* 发送一个空字节,用于产生时钟消耗掉 Dummy Byte */

    memset(t, 0, sizeof(t));
    spi_message_init(&m);

    /* 阶段 1:发送寄存器地址 + 消耗 Dummy Byte */
    t[0].tx_buf = tx_buf;
    t[0].len = 2;
    spi_message_add_tail(&t[0], &m);

    /* 阶段 2:连续读取接收真实的数据字节 */
    t[1].rx_buf = buf;
    t[1].len = len;
    spi_message_add_tail(&t[1], &m);

    return spi_sync(spi, &m);
}

/* BMI088 加速度计 SPI 写入逻辑 */
static int bmi088_accel_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_accel_open(struct inode *inode, struct file *file)
{
    struct bmi088_accel_dev *dev = container_of(inode->i_cdev, struct bmi088_accel_dev, cdev);
    file->private_data = dev;
    return 0;
}

/* 用户层 read 接口:一次性抛出 6 字节的原始 XYZ 轴数据 */
static ssize_t bmi088_accel_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
{
    struct bmi088_accel_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_accel_read_regs(dev->spi, ACC_DATA_START_REG, raw_data, 6);
    mutex_unlock(&dev->lock);

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

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

    return 6;
}

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

static const struct file_operations bmi088_accel_fops = {
    .owner   = THIS_MODULE,
    .open    = bmi088_accel_open,
    .read    = bmi088_accel_read,
    .release = bmi088_accel_release,
};

/* 硬件配置与状态机唤醒 */
static int bmi088_hardware_init(struct spi_device *spi)
{
    u8 chip_id = 0;
    int ret;

    /* 1. 强制接口切换:芯片上电默认为 I2C 模式。
     * 发送一次 dummy 读取操作通过 CS 线的跳变强制使其切换至 SPI 模式。
     */
    bmi088_accel_read_regs(spi, ACC_CHIP_ID_REG, &chip_id, 1);
    msleep(1);

    /* 2. 配置电源模式:将 PWR_CONF 设为 0x00(Active mode 活跃状态) */
    ret = bmi088_accel_write_reg(spi, ACC_PWR_CONF_REG, 0x00);
    if (ret < 0) return ret;
    msleep(1);

    /* 3. 开启加速度计传感器:向 PWR_CTRL 写入 0x04 */
    ret = bmi088_accel_write_reg(spi, ACC_PWR_CTRL_REG, 0x04);
    if (ret < 0) return ret;
    usleep_range(450, 1000); /* 手册要求的唤醒延时 */

    /* 4. 读取真实的 CHIP_ID 验证通信是否正常 */
    ret = bmi088_accel_read_regs(spi, ACC_CHIP_ID_REG, &chip_id, 1);
    if (ret < 0) return ret;

    if (chip_id != BMI088_ACC_CHIP_ID) {
        dev_err(&spi->dev, "Chip ID mismatch! Got 0x%02X, expected 0x1E\n", chip_id);
        return -ENODEV;
    }

    /* 5. 配置量程 (Range)
     * 0x00: +-3g, 0x01: +-6g, 0x02: +-12g, 0x03: +-24g
     * 设为 +-12g,满足高动态场景
     */
    ret = bmi088_accel_write_reg(spi, ACC_RANGE_REG, 0x02);
    if (ret < 0) return ret;

    /* 6. 配置输出数据速率 (ODR) 和带宽 (Bandwidth)
     * 0xA8: 100Hz ODR, 0xA9: 200Hz ODR, 0xAA: 400Hz ODR (Normal filter)
     * 设为 200Hz,配合视觉处理算法的常规高频采集需求
     */
    ret = bmi088_accel_write_reg(spi, ACC_CONF_REG, 0xA9);
    if (ret < 0) return ret;

    msleep(2); /* 给予芯片内部滤波器极短的稳定时间 */

    dev_info(&spi->dev, "BMI088 Accel initialized! ID: 0x%02X, Range: +-12g, ODR: 200Hz\n", chip_id);
    return 0;
}

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

    /* 配置 SPI 模式与速率 (可根据设备树覆盖,但这里做个底层保底) */
    spi->mode = SPI_MODE_0;
    spi->bits_per_word = 8;
    spi_setup(spi);

    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);

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

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

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

    /* 创建设备类 */
    dev->class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev->class)) {
        ret = PTR_ERR(dev->class);
        goto del_cdev;
    }

    /* 创建设备文件 (/dev/bmi088_accel) */
    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_accel_remove(struct spi_device *spi)
{
    struct bmi088_accel_dev *dev = spi_get_drvdata(spi);

    /* 让传感器进入 Suspend 模式以省电 */
    bmi088_accel_write_reg(spi, ACC_PWR_CONF_REG, 0x03);

    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 Accelerometer driver removed\n");
    return 0;
}

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

static struct spi_driver bmi088_accel_driver = {
    .driver = {
        .name           = "bmi088-accel",
        .of_match_table = bmi088_accel_of_match,
    },
    .probe              = bmi088_accel_probe,
    .remove             = bmi088_accel_remove,
};

module_spi_driver(bmi088_accel_driver);

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

4.问题排查

1.导入驱动模块之后匹配不上设备树

原因:在 RK3588/RK3506 这种基于 ARM64 的平台上,有时候仅仅有 of_match_table 是不够的,特别是当驱动架构需要更明确的 ID 映射时。

解决方法:添加 id_table (最有效)

有些内核版本的 SPI 子系统在查找驱动时,如果同时存在 id_table,会优先匹配该表。请在你的驱动中添加这一段:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值