【四轴】基于IIC通信读写MPU6050寄存器

news2024/12/23 13:49:21

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. 电源寄存器取1,以x轴PLL做时钟源,更精准
  2. 采样分屏器取7,实际为8分频,所以陀螺仪的采样频率为1000 / 8 = 125hz,加速度计采样率为1000hz。
  3. 低通滤波器取6,选择的5hz频率,这里本想按说明那样设定为陀螺仪频率的一半,即该寄存器取3,但实际发现这样设定以后陀螺仪取值不够准确。
  4. 陀螺仪配置寄存器取0x18,量程取最大
  5. 加速度计配置寄存器取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的初始化寄存器配置 - 爱吃炸鸡的小猪 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252482.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

arkTS:使用ArkUI实现用户信息的持久化管理与自动填充(PersistentStorage)

arkUI&#xff1a;使用ArkUI实现用户信息的持久化管理与自动填充&#xff08;PersistentStorage&#xff09; 1 主要内容说明2 例子2.1 登录页2.1.1登陆页的相关说明2.1.1.1 持久化存储的初始化2.1.1.2 输入框2.1.1.3 记住密码选项2.1.1.4 登录按钮的逻辑2.1.1.5 注册跳转 2.1.…

基于SpringBoot+Vue的美妆购物网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

【SpringBoot+Vue】x-admin管理系统跟做

技术栈 前端技术说明Vue前端框架Vuex全局状态管理框架ElementUI前端UI框架Axios前端HTTP框架vue-element-admin项目脚手架 后端技术说明SpringBoot容器MVC框架MyBatisORM框架MyBatis-plusMyBatis增强工具Redis非关系型数据库 数据库准备 SET NAMES utf8mb4; SET FOREIGN_KE…

【Docker】Docker配置远程访问

配置Docker的远程访问&#xff0c;你需要按照以下步骤进行操作&#xff1a; 1. 在Docker宿主机上配置Docker守护进程监听TCP端口 Docker守护进程默认只监听UNIX套接字&#xff0c;要实现远程访问&#xff0c;需要修改配置以监听TCP端口。 ‌方法一&#xff1a;修改Docker服务…

LeetCode hot100(自用背诵、部分题目、非最优解)

点击题目可以跳转到LeetCode 哈希 两数之和 public int[] twoSum(int[] nums, int target) {int lengthnums.length;int[] ans new int[2];for (int i 0; i <length-1 ; i) {for (int j i1; j < length; j) {if(nums[i]nums[j]target){ans[0]i;ans[1]j;}}}return an…

Android -- 简易音乐播放器

Android – 简易音乐播放器 播放器功能&#xff1a;* 1. 播放模式&#xff1a;单曲、列表循环、列表随机&#xff1b;* 2. 后台播放&#xff08;单例模式&#xff09;&#xff1b;* 3. 多位置同步状态回调&#xff1b;处理模块&#xff1a;* 1. 提取文件信息&#xff1a;音频文…

基础Web安全|SQL注入

基础Web安全 URI Uniform Resource Identifier&#xff0c;统一资源标识符&#xff0c;用来唯一的标识一个资源。 URL Uniform Resource Locator&#xff0c;统一资源定位器&#xff0c;一种具体的URI&#xff0c;可以标识一个资源&#xff0c;并且指明了如何定位这个资源…

ESG研究报告白皮书与ESG治理报告合集(2020-2023年)

一.资料范围&#xff1a;&#xff08;1&#xff09;ESG白皮书及指南;&#xff08;2&#xff09;ESG研究报告,&#xff08;3&#xff09;ESG治理报告分析&#xff08;4&#xff09;上市公司ESG报告&#xff08;知名企业&#xff09; 二、资料用途&#xff1a;可以分析研究企业E…

WPF指示灯的实现方式

WPF指示灯的实现方式 样式 XAML <Button x:Name"Btn1" Width"25" Height"25" Grid.Row"0" Grid.Column"1" Margin"10 5 5 5 "><Button.Template><ControlTemplate TargetType"Button"…

初识QT第一天

思维导图 利用Qt尝试做出原神登陆界面 import sys from PyQt6.QtGui import QIcon, QPixmap, QMovie from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QLineEdit# 封装原神窗口类 class Genshin(QWidget):# 构造函数def __init__(self):# 初始化父类…

【Linux】线程池设计 + 策略模式

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 线程池 1-1 ⽇志与策略模式1-2 线程池设计1-3 线程安全的单例模式1-3-1 什么是单例模式1-3-2 单例模式的特点1-3-3 饿汉实现⽅式和懒汉实现⽅式1-3-4 饿汉…

vim插件管理器vim-plug替代vim-bundle

文章目录 vim-plug与vim-bundle对比vim-plug安装vim-plug管理安装插件相关文章 vim-plug与vim-bundle对比 vim-plug 和 vim-bundle 都是 Vim 的插件管理器&#xff0c;但它们有一些关键的区别。以下是两者的主要对比&#xff1a; 易用性和简洁性 vim-plug: 易用性: vim-plug …

107.【C语言】数据结构之二叉树求总节点和第K层节点的个数

目录 1.求二叉树总的节点的个数 1.容易想到的方法 代码 缺陷 思考:能否在TreeSize函数内定义静态变量解决size的问题呢? 其他写法 运行结果 2.最好的方法:分而治之 代码 运行结果 2.求二叉树第K层节点的个数 错误代码 运行结果 修正 运行结果 其他写法 1.求二…

【代码随想录day48】【C++复健】739. 每日温度;496.下一个更大元素 I;503.下一个更大元素II

739. 每日温度 一顿操作猛如虎&#xff0c;一看击败5%。一眼顶针&#xff0c;鉴定为在存栈的时候把值和下标一起存了&#xff0c;所以导致了问题。 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<vector<…

vscode + conda + qt联合开发

安装vscode 安装conda 清华大学开源软件镜像(Anaconda下载)_清华大学镜像-CSDN博客 conda create新建一个环境&#xff0c;激活这个环境&#xff0c;然后安装pyside6 pip install pyside6 -i https://pypi.tuna.tsinghua.edu.cn/simple 安装成功后输入 pip list查看是否安装…

第十一篇 绘图matplotlib.pyplot的使用

文章目录 摘要安装方法入门案例使用plt绘图使用ax绘图plt.figure参数plot参数案例一 绘制红色实心的点状图案例二 绘制红色的破折线图案例三 绘制两条线颜色总结设置标题、轴名称、图例使用plt实现绘图使用ax实现绘图legend()中loc设置刻度plt自定义刻度ax自定义刻度plt.title …

Unity-Particle System属性介绍(一)基本属性

什么是ParticleSystem 粒子系统是Unity中用于模拟大量粒子的行为的组件。每个粒子都有一个生命周期&#xff0c;包括出生、运动、颜色变化、大小变化和死亡等。粒子系统可以用来创建烟雾、火焰、水、雨、雪、尘埃、闪电和其他各种视觉效果。 开始 在项目文件下创建一个Vfx文件…

计算机的错误计算(一百七十二)

摘要 探讨 MATLAB 对于算式 的计算误差。 例1. 在 MATLAB 中计算 的值。 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB 的输出中只有3位正确数字&#xff0c;有效数字的错误率为 (16-3)/16 81.25% . 因为16位的正确输出为 0.2971242332737277e-18&#xff08;ISReals…

Flink四大基石之CheckPoint(检查点) 的使用详解

目录 一、Checkpoint 剖析 State 与 Checkpoint 概念区分 设置 Checkpoint 实战 执行代码所需的服务与遇到的问题 二、重启策略解读 重启策略意义 代码示例与效果展示 三、SavePoint 与 Checkpoint 异同 操作步骤详解 四、总结 在大数据流式处理领域&#xff0c;Ap…

S4 UPA of AA :新资产会计概览

通用并行会计&#xff08;Universal Parallel Accounting&#xff09;可以支持每个独立的分类账与其他模块集成&#xff0c;UPA主要是为了支持平行评估、多货币类型、财务合并、多准则财务报告的复杂业务需求 在ML层面UPA允许根据不同的分类账规则对物料进行评估&#xff0c;并…