1. 基本原理
在这篇【四轴】软件IIC通信的实现 – Duki's Blog博客中,我介绍了软件IIC的实现方式。而MPU6050,正是一种通过IIC进行通信的传感器外设。
1.1 什么是MPU6050
MPU6050 是 InvenSense 公司推出的一款6 轴惯性传感器模块,广泛应用于姿态检测、运动追踪、导航等领域。它集成了3 轴陀螺仪和3 轴加速度计。
简单来说,MPU6050是一个外设,要想操作这个外设,我们可以使用IIC通信协议。
1.2 如何与MPU6050通信
在之前的博客中,我们介绍实现了软件模拟IIC,其中包含两个重要的应用层函数:读取寄存器数据和写入寄存器数据。通过查阅MPU6050的相关手册,是可以得到该设备的地址以及里面各个寄存器的地址的,因此,我们便可以轻松的读写MPU6050的寄存器。
2. 实现代码
2.1 源文件
#include "mpu6050.h"
#include "MyIIC.h" // 引入软件I2C库
/**
* @brief 初始化MPU6050
* @retval 0: 成功, 1: 失败
*/
uint8_t MPU6050_Init(void)
{
I2C_Init(); // 初始化I2C
if (MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00) != 0) // 解除休眠模式
return 1;
if (MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07) != 0) // 设置采样率
return 1;
if (MPU6050_WriteReg(MPU6050_CONFIG, 0x06) != 0) // 配置低通滤波器
return 1;
if (MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18) != 0) // 配置陀螺仪量程 ±2000°/s
return 1;
if (MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x01) != 0) // 配置加速度计量程 ±4g
return 1;
return 0;
}
/**
* @brief 向MPU6050寄存器写数据
* @param reg 寄存器地址
* @param data 写入的数据
* @retval 0: 成功, 1: 失败
*/
uint8_t MPU6050_WriteReg(uint8_t reg, uint8_t data)
{
return I2C_WriteReg(MPU6050_I2C_ADDR, reg, data);
}
/**
* @brief 从MPU6050寄存器读取数据
* @param reg 寄存器地址
* @param data 读取到的数据
* @retval 0: 成功, 1: 失败
*/
uint8_t MPU6050_ReadReg(uint8_t reg, uint8_t *data)
{
return I2C_ReadReg(MPU6050_I2C_ADDR, reg, data);
}
/**
* @brief 从MPU6050批量读取数据
* @param reg 起始寄存器地址
* @param buf 存储读取数据的缓冲区
* @param len 读取数据的长度
* @retval 0: 成功, 1: 失败
*/
uint8_t MPU6050_ReadData(uint8_t reg, uint8_t *buf, uint16_t len)
{
I2C_Start();
I2C_SendByte((MPU6050_I2C_ADDR << 1) | 0); // 发送设备地址+写指令
if (I2C_WaitAck() != 0)
{
I2C_Stop();
return 1;
}
I2C_SendByte(reg); // 发送起始寄存器地址
if (I2C_WaitAck() != 0)
{
I2C_Stop();
return 1;
}
I2C_Start();
I2C_SendByte((MPU6050_I2C_ADDR << 1) | 1); // 发送设备地址+读指令
if (I2C_WaitAck() != 0)
{
I2C_Stop();
return 1;
}
for (uint16_t i = 0; i < len; i++)
{
buf[i] = I2C_ReceiveByte();
if (i == (len - 1))
I2C_SendNotAck(); // 最后一个字节发送NACK
else
I2C_SendAck();
}
I2C_Stop();
return 0;
}
/**
* @brief 获取并处理加速度数据,将原始ADC值转换为工程单位(g)
* @param ax 指向X轴加速度数据的指针(浮点型,单位g)
* @param ay 指向Y轴加速度数据的指针(浮点型,单位g)
* @param az 指向Z轴加速度数据的指针(浮点型,单位g)
*/
void MPU6050_GetAccelData(float *ax, float *ay, float *az)
{
uint8_t buf[6];
int16_t raw_ax, raw_ay, raw_az;
// 读取6字节的原始加速度数据
MPU6050_ReadData(MPU6050_ACCEL_XOUT_H, buf, 6);
// 合并高低字节为int16_t类型的原始数据
raw_ax = (int16_t)((buf[0] << 8) | buf[1]);
raw_ay = (int16_t)((buf[2] << 8) | buf[3]);
raw_az = (int16_t)((buf[4] << 8) | buf[5]);
// 转换为浮点数并存储到输出变量
*ax = (float)raw_ax * ACC_Range / ADC_16;
*ay = (float)raw_ay * ACC_Range / ADC_16;
*az = (float)raw_az * ACC_Range / ADC_16;
// 使用高斯牛顿计算得到的校准参数
*ax = (*ax - P3) * P0; // X轴校准:减去零偏误差,乘以比例误差
*ay = (*ay - P4) * P1; // Y轴校准
*az = (*az - P5) * P2; // Z轴校准
}
/**
* @brief 获取并处理陀螺仪数据,将原始ADC值转换为工程单位(°/s)
* @param gx 指向X轴角速度数据的指针(浮点型,单位°/s)
* @param gy 指向Y轴角速度数据的指针(浮点型,单位°/s)
* @param gz 指向Z轴角速度数据的指针(浮点型,单位°/s)
*/
void MPU6050_GetGyroData(float *gx, float *gy, float *gz)
{
uint8_t buf[6];
int16_t raw_gx, raw_gy, raw_gz;
// 读取6字节的原始陀螺仪数据
MPU6050_ReadData(MPU6050_GYRO_XOUT_H, buf, 6);
// 合并高低字节为int16_t类型的原始数据
raw_gx = (int16_t)((buf[0] << 8) | buf[1]);
raw_gy = (int16_t)((buf[2] << 8) | buf[3]);
raw_gz = (int16_t)((buf[4] << 8) | buf[5]);
// 转换为浮点数并存储到输出变量
*gx = (float)raw_gx * GRO_Range / ADC_16 * DEG_TO_RAD;
*gy = (float)raw_gy * GRO_Range / ADC_16 * DEG_TO_RAD;
*gz = (float)raw_gz * GRO_Range / ADC_16 * DEG_TO_RAD;
// 消去零偏误差
*gx = (*gx) - GX_OFFSET;
*gy = (*gy) - GY_OFFSET;
*gz = (*gz) - GZ_OFFSET;
}
void MPU6050_GetGyroAveData(float *gx, float *gy, float *gz) {
float tmp_gx, tmp_gy, tmp_gz;
for(int i = 0;i < CYCLE_COUNT;i++) {
MPU6050_GetGyroData(&tmp_gx, &tmp_gy, &tmp_gz);
*gx += tmp_gx;
*gy += tmp_gy;
*gz += tmp_gz;
}
*gx = (*gx) / CYCLE_COUNT;
*gy = (*gy) / CYCLE_COUNT;
*gz = (*gz) / CYCLE_COUNT;
}
2.2 头文件
#ifndef __MPU6050_H
#define __MPU6050_H
#include "main.h"
//倍率说明
#define DEG_TO_RAD (3.14159265358979323846 / 180.0)
#define ADC_16 65536
#define ACC_Range 4
#define GRO_Range 4000
//高斯牛顿迭代矫正参数
#define P0 1.001004
#define P1 1.006896
#define P2 0.987984
#define P3 0.038779
#define P4 -0.012867
#define P5 0.509085
//陀螺仪偏置矫正
#define GX_OFFSET -0.05
#define GY_OFFSET -0.01
#define GZ_OFFSET 0.03
//陀螺仪采样循环次数
#define CYCLE_COUNT 10
// MPU6050 I2C地址
#define MPU6050_I2C_ADDR 0x68 // 默认地址(7位地址),实际写操作时需要左移1位
// MPU6050 寄存器地址
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
// MPU6050功能函数声明
uint8_t MPU6050_Init(void); // 初始化MPU6050
uint8_t MPU6050_WriteReg(uint8_t reg, uint8_t data); // 写寄存器
uint8_t MPU6050_ReadReg(uint8_t reg, uint8_t *data); // 读寄存器
uint8_t MPU6050_ReadData(uint8_t reg, uint8_t *buf, uint16_t len); // 批量读取数据
void MPU6050_GetAccelData(float *ax, float *ay, float *az); // 读取加速度
void MPU6050_GetGyroData(float *gx, float *gy, float *gz); // 读取陀螺仪
void MPU6050_GetGyroAveData(float *gx, float *gy, float *gz); //读取取均值的陀螺仪示数
void MPU6050_GetTemp(float *temp); // 读取温度
#endif
2.3 一些解释
2.3.1 MPU6050_Init
关于MPU6050的初始化,有很多文章可以参考:
stm32的陀螺仪芯片MPU6050的初始化寄存器配置 - 爱吃炸鸡的小猪 - 博客园
简单来说,需要配置的有以下几个核心寄存器:
1.MPU6050_PWR_MGMT_1:电源寄存器
2. MPU6050_SMPLRT_DIV:分频寄存器
3.MPU6050_CONFIG:低通滤波器配置
4.MPU6050_GYRO_CONFIG:陀螺仪配置
5.MPU6050_ACCEL_CONFIG:加速度计配置
uint8_t MPU6050_Init(void)
{
I2C_Init(); // 初始化I2C
if (MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00) != 0) // 解除休眠模式
return 1;
if (MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07) != 0) // 设置采样率
return 1;
if (MPU6050_WriteReg(MPU6050_CONFIG, 0x06) != 0) // 配置低通滤波器
return 1;
if (MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18) != 0) // 配置陀螺仪量程 ±2000°/s
return 1;
if (MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x01) != 0) // 配置加速度计量程 ±4g
return 1;
return 0;
}
对于我的初始化取值,解释如下:
- 电源寄存器取1,以x轴PLL做时钟源,更精准
- 采样分屏器取7,实际为8分频,所以陀螺仪的采样频率为1000 / 8 = 125hz,加速度计采样率为1000hz。
- 低通滤波器取6,选择的5hz频率,这里本想按说明那样设定为陀螺仪频率的一半,即该寄存器取3,但实际发现这样设定以后陀螺仪取值不够准确。
- 陀螺仪配置寄存器取0x18,量程取最大
- 加速度计配置寄存器取0x01,量程最小
2.3.2 读取值的处理
1.首先要知道,不论是加速度计和陀螺仪,其读出的原始值都是16位ADC读出的值,所以,需要根据其量程进行相应的转换。
// 转换为浮点数并存储到输出变量
*ax = (float)raw_ax * ACC_Range / ADC_16;
*ay = (float)raw_ay * ACC_Range / ADC_16;
*az = (float)raw_az * ACC_Range / ADC_16;
// 转换为浮点数并存储到输出变量
*gx = (float)raw_gx * GRO_Range / ADC_16 * DEG_TO_RAD;
*gy = (float)raw_gy * GRO_Range / ADC_16 * DEG_TO_RAD;
*gz = (float)raw_gz * GRO_Range / ADC_16 * DEG_TO_RAD;
2.对于陀螺仪数据,需要进行零偏误差矫正;对于加速度计,需要进行高斯牛顿迭代矫正。这样处理之后,观察到的效果是在正放状态下,加速度计只有z轴值为1,其余全为0;陀螺仪三个轴都为0。
如此之后的数据才可进一步拿去做姿态解算。当然,其实本阶段只要能读出和写入MPU6050寄存器数据即可,数据处理这一部分的内容不应放在这一部分讲,而是应放在第三阶段的姿态计算部分,详情可见下面这篇博客:
【四轴】MPU6050数据滤波处理记录 – Duki's Blog
参考链接
关于MPU6050学习的一些总结之一MPU6050芯片手册的整理_mpu6050中文数据手册-CSDN博客
stm32的陀螺仪芯片MPU6050的初始化寄存器配置 - 爱吃炸鸡的小猪 - 博客园