BMI088 是一款六轴惯性测量单元内部集成三轴加速度计和三轴陀螺仪常用于机器人、无人机、运动控制、姿态检测等场景。相比普通低成本 IMUBMI088 更强调抗振动能力和温度稳定性因此比较适合安装在电机、机械结构、移动机器人等振动较强的系统中。MBI088既支持I2C总线也支持SPI总线。本文以嵌入式 Linux 平台为例说明如何基于 SPI 总线实现 BMI088 驱动。驱动目标包括完成 SPI 设备匹配、读取芯片 ID、初始化加速度计和陀螺仪、周期读取六轴原始数据并将原始寄存器值换算为实际物理量。需要注意的是BMI088 不能简单地当成一个普通 SPI 传感器处理。它虽然封装在同一个芯片中但内部加速度计和陀螺仪相对独立二者有各自的寄存器空间、数据输出寄存器、片选信号和初始化流程。因此驱动设计时最好把它理解为“同一个封装里的两个 SPI 从设备”。1.BMI088SPI接口特点1.1 为什么选择SPI而不是I2C1实时性I2C 通常工作在标准模式100kHz或快速模式400kHz频率较低SPI频率较高而且I2C通讯有繁琐的寻址、ACK/NACK 应答位等内容耗时更长故而SPI可以更好的满足实时性。2可靠性在电机运转产生强磁场和高频噪声的环境下I2C 的 SCL/SDA 线极易受到干扰导致波形畸变而SPI的可靠性更强。1.2 SPI接口引脚名称功能描述SCKSPI 时钟信号由主控输出SDI / MOSI主控向 BMI088 写数据SDO1 / MISO加速度计数据输出SDO2 / MISO陀螺仪数据输出CSB1加速度计片选信号CSB2陀螺仪片选信号INT1 / INT2加速度计中断引脚INT3 / INT4陀螺仪中断引脚PS协议选择引脚SPI 模式下通常接 GNDI2C下接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驱动驱动框架#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 时钟 /* 构造单次传输同时收发 len1 个字节 */ 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 时钟 /* 构造单次传输同时收发 len1 个字节 */ 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驱动