简介:面向嵌入式开发、机器人、无人机及物联网运动感知场景的MPU6500六轴IMU驱动算法资料包,适合具备一定C语言与传感器调试基础、希望打通从寄存器配置到姿态解算链路的开发者。压缩包共157个文件、约730KB,以C源文件、头文件、工程配置与编译中间文件为主,并含少量Python脚本、PDF文档和IDE工程文件,覆盖驱动移植、DMP调用与测试工程等环节,便于在本地工程中对照理解模块划分。已有1043人学习下载。内容围绕初始化配置、原始数据采集与校准、加速度计与陀螺仪数据融合,以及欧拉角和四元数姿态解算展开,涉及互补滤波、卡尔曼滤波和Madgwick等思路;结合motion_driver-5.1.2相关实现,读者可参考其API组织、滤波器代码与测试入口,梳理传感器数据读取、融合和连续旋转表示的工程做法,为运动控制与姿态跟踪项目提供可复用的驱动与算法参考。
1. 从一颗六轴传感器说起:MPU6500 驱动算法到底解决什么问题
某开发者把 MPU6500 焊到板子上,用现成例程读加速度传感器,串口里却是一串上下跳动的原始码值;静止时模长不是 1g,稍微动一下就漂。MPU6500驱动算法要做的不是“读寄存器”这么简单,而是把 SPI/I2C 通信、量程配置、零偏校准、温度补偿和姿态融合串成一条可复现的链路。这份资源适合正在做云台、跌倒检测、姿态记录、振动监测的从业者,也适合刚接触六轴传感器、想把原始数据变成可用角度的新手。下面从寄存器地图开始,把每一步拆到能抄作业。
2. 寄存器地图与通信链路:SPI/I2C 读写时序怎么落到代码
2.1 先看 MPU6500 的寄存器分区与关键地址
MPU6500 的寄存器可以粗分成四块:身份与电源、采样与滤波、量程配置、数据与中断。调试时不要一上来就配量程,先读身份寄存器,再确认电源和通信链路,否则后面所有数据都不可信。下面这张表是驱动里最常打交道的地址,实际写代码时仍要以手头器件的数据手册为准,不同封装或兼容型号在个别位上会有差异。
| 地址 | 名称 | 作用 | 常用值 |
|---|---|---|---|
| 0x75 | WHO_AM_I | 身份自检 | 0x70 |
| 0x6B | PWR_MGMT_1 | 复位与时钟选择 | 0x80 复位,0x01 自动时钟 |
| 0x6C | PWR_MGMT_2 | 轴使能 | 0x00 六轴全开 |
| 0x19 | SMPLRT_DIV | 采样分频 | 按目标速率计算 |
| 0x1A | CONFIG | 陀螺低通滤波 | 0x03 左右 |
| 0x1B | GYRO_CONFIG | 陀螺量程 | 0x00 ±250 dps |
| 0x1C | ACCEL_CONFIG | 加速度量程 | 0x00 ±2g |
| 0x1D | ACCEL_CONFIG2 | 加速度低通滤波 | 0x03 左右 |
| 0x3B | ACCEL_XOUT_H | 数据起始地址 | 连续 14 字节 |
| 0x38 | INT_ENABLE | 中断使能 | 0x01 数据就绪 |
| 0x3A | INT_STATUS | 中断状态 | 读清 |
| 0x23 | FIFO_EN | FIFO 使能 | 按需 |
| 0x72 | FIFO_COUNTH | FIFO 计数 | 读前先看 |
| 0x74 | FIFO_R_W | FIFO 读写 | 按计数读 |
SPI 读地址时最高位要置 1,写地址最高位清 0,这是很多驱动第一行就写错的地方。I2C 则用 7 位地址 0x68 或 0x69,AD0 接地是 0x68,接高是 0x69。通信选型上,I2C 两根线,布线简单,适合低速采样和多传感器共享总线;SPI 四根线加片选,速率高、抗干扰相对好,适合高速采样和较长线缆。常见做法是调试阶段先用 I2C 把寄存器跑通,量产需要高速率时再切 SPI。SPI 模式优先固定为模式 0 或模式 3,不要中途切换,否则容易出现“大部分数据对、偶尔跳一个离谱值”的玄学问题。
提示:WHO_AM_I 读到 0x71 不一定是假货,可能是兼容型号。先核对数据手册的寄存器差异,再决定要不要继续用同一套配置。
2.2 SPI 四线模式下的读写函数与 CRC/时序注意
SPI 是全双工,读寄存器时先发地址字节,再发 dummy 字节把数据从 MISO 移回来。片选 CS 必须在整个事务期间保持低电平,不能每发一个字节就拉高,否则 MPU6500 会认为一次事务已经结束。下面这段代码把读写拆成两个函数,平台相关的 CS 和单字节收发用 extern 声明,方便移植到不同主控。
#include <stdint.h> #include <stddef.h> /* 平台相关:CS 拉低/拉高、单字节收发 */ extern void spi_cs_low(void); extern void spi_cs_high(void); extern uint8_t spi_transfer(uint8_t tx); /* 写一个寄存器:地址最高位清 0 */ void mpu6500_spi_write_reg(uint8_t reg, uint8_t val) { spi_cs_low(); (void)spi_transfer(reg & 0x7F); /* 写地址:bit7=0 */ (void)spi_transfer(val); spi_cs_high(); } /* 连续读:地址最高位置 1,读回 N 字节 */ void mpu6500_spi_read_regs(uint8_t reg, uint8_t *buf, size_t len) { if (buf == NULL || len == 0) return; spi_cs_low(); (void)spi_transfer(reg | 0x80); /* 读地址:bit7=1 */ for (size_t i = 0; i < len; ++i) { buf[i] = spi_transfer(0x00); /* dummy 字节产生时钟 */ } spi_cs_high(); }这段代码的逻辑是:写寄存器时先发 7 位地址,再发数据;读寄存器时先发“地址或 0x80”,然后连续发 dummy 字节,MISO 上返回的就是寄存器内容。参数说明:reg 是 7 位寄存器地址,不要传 8 位读写地址;len 是一次连续读的字节数,读 0x3B 起始的 14 字节时 len 取 14;spi_transfer 的返回值是 MISO 上的字节。注意:如果主控 SPI 外设支持硬件片选,仍建议用软件控制 CS,便于在 CS 拉低后插入一小段建立时间。SPI 时钟先降到 1 MHz,确认 WHO_AM_I 正确后再逐步升,升速后要连续读几千帧看有没有偶发错帧。
2.3 I2C 备选链路与地址选择
I2C 读寄存器不是简单的“读一个字节”,而是“写寄存器地址 + 重复起始 + 读数据”的组合事务。很多 HAL 库把这一步拆成两个函数,中间如果插入了其他 I2C 设备事务,可能丢总线。下面这段代码把写寄存器和连续读分开,读函数里先写地址再读数据。
#include <stdint.h> #include <stddef.h> #define MPU6500_I2C_ADDR_7BIT 0x68u /* AD0 接地;接高则为 0x69 */ extern int i2c_write(uint8_t addr7, const uint8_t *data, size_t len); extern int i2c_read(uint8_t addr7, uint8_t *data, size_t len); int mpu6500_i2c_write_reg(uint8_t reg, uint8_t val) { uint8_t frame[2] = {reg, val}; return i2c_write(MPU6500_I2C_ADDR_7BIT, frame, 2); } int mpu6500_i2c_read_regs(uint8_t reg, uint8_t *buf, size_t len) { if (buf == NULL || len == 0) return -1; if (i2c_write(MPU6500_I2C_ADDR_7BIT, ®, 1) != 0) return -1; return i2c_read(MPU6500_I2C_ADDR_7BIT, buf, len); }这段代码的逻辑是:写寄存器时把寄存器地址和数据拼成一帧发出去;读寄存器时先发寄存器地址,再发起读事务。参数说明:addr7 是 7 位地址,不要传 8 位地址;有些驱动写 0xD0,那是 8 位写地址,容易和 7 位地址混。注意:I2C 速率在 400 kHz 以内比较稳,长线或上拉电阻偏大时降到 100 kHz;上拉电阻一般 2.2k 到 4.7k,太小会增加功耗,太大边沿会变缓。
| 维度 | SPI | I2C |
|---|---|---|
| 线数 | 4 根加片选 | 2 根 |
| 速率 | 高 | 中低 |
| 多设备 | 每设备一根片选 | 共享总线,靠地址 |
| 抗干扰 | 较好,但时钟线长易振铃 | 依赖上拉和布线 |
| 调试难度 | 中 | 低 |
3. 初始化与量程配置:把原始码值变成有物理意义的加速度
3.1 上电时序、复位与 WHO_AM_I 自检
MPU6500 上电后内部稳压和振荡器需要时间稳定,常见做法是上电延时 100 ms,再写复位寄存器,复位后再等 100 ms。复位后所有配置寄存器回到默认值,必须重新配置时钟、量程、滤波和采样率。WHO_AM_I 是第一步自检,读不到就不要继续配量程,否则后面所有数据都不可信。下面这段初始化代码把顺序写死,避免“先配量程再复位”这种把配置冲掉的低级错误。
extern void delay_ms(uint32_t ms); int mpu6500_init(void) { uint8_t who = 0; delay_ms(100); /* 等电源稳定 */ mpu6500_spi_write_reg(0x6B, 0x80); /* 软复位 */ delay_ms(100); mpu6500_spi_write_reg(0x6B, 0x01); /* 自动选择时钟 */ delay_ms(10); mpu6500_spi_read_regs(0x75, &who, 1); if (who != 0x70 && who != 0x71) { return -1; /* 身份不对,直接退出 */ } mpu6500_spi_write_reg(0x6C, 0x00); /* 六轴全开 */ return 0; }这段代码的逻辑是:先等电源稳定,再软复位,再选时钟,再读身份,最后开轴。参数说明:0x6B 的 bit7 是 DEVICE_RESET,写 1 复位;0x01 是 CLKSEL=1,选择内部 PLL;0x6C 写 0x00 表示六个轴都不进入待机。注意:复位后不要立刻读数据,等 100 ms;有些板子电源爬升慢,延时可以加到 200 ms;如果 WHO_AM_I 读到 0x00 或 0xFF,先查通信,不要怀疑芯片坏了。
3.2 加速度计量程、带宽与低通滤波配置
加速度量程决定灵敏度和最大可测加速度。量程选大了,小信号分辨率下降;选小了,冲击会削顶。静态倾角测量常用 ±2g,一般运动用 ±4g 或 ±8g,高冲击场景才用 ±16g。灵敏度换算必须和配置一致,改了量程不改换算系数,静止模长就会不是 1g。
| AFS_SEL | 量程 | 灵敏度 LSB/g | 适用场景 |
|---|---|---|---|
| 0 | ±2g | 16384 | 静态倾角、低振动 |
| 1 | ±4g | 8192 | 一般运动 |
| 2 | ±8g | 4096 | 冲击 |
| 3 | ±16g | 2048 | 高冲击 |
低通滤波决定噪声带宽和延时。带宽越高,动态
本文还有配套的精品资源,点击获取