STM32通过I2C软件读写MPU6050

目录

1.运动学概念

2.MPU6050工作原理

3.MPU6050参数

4.MPU6050量程选择

5.MPU6050从机地址配置

6.硬件电路

7.MPU6050框图

8.MPU60X0寄存器映射(常用)

寄存器映射框图各部分作用介绍

常用寄存器简介

9.软件I2C和硬件I2C的区别

10.软件I2C读写MPU6050编写步骤

10.1程序整体构架

10.2需要注意的点

10.3软件I2C编写步骤

10.4软件I2C读写MPU6050编写步骤

11.代码编写:

程序文件简要说明:

MyI2C.c

MyI2C.h

MPU6050.c

MPU6050.h

main.c

12.结果

13.数据验证



I2C通信部分的介绍可以看我的这篇博客:STM32软件I2C通信详解

MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景

3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度

3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

1.运动学概念

  • 欧拉角:

    • 欧拉角是用来描述三维空间中刚体旋转的三个角度:俯仰角(Pitch)、滚转角(Roll)和偏航角(Yaw)。
    • 俯仰角(Pitch):飞机机头上下倾斜的角度。
    • 滚转角(Roll):飞机左右倾斜的角度。
    • 偏航角(Yaw):飞机左右转向的角度。

    任何一种传感器,都不能获得精确且稳定的欧拉角。要想获得,就需要进行数据融合,综合几种传感器的数据,取长补短。通常有互补滤波、卡尔曼滤波等

2.MPU6050工作原理

  1. 加速度计: 加速度计的内部可以想象为这个样子。是一个滑变电阻+两个弹簧

    1. 测量在X、Y、Z轴方向上测量加速度。通过检测重力加速度,可以推断出设备的倾斜角度。例如,在一个静止状态下,水平放置。 X和Y就是0。Z轴会受到一个向下重力所引的加速度;再例如,现在在自由落体。则X,Y,Z都为0。

    2. 在使用加速度计求角度的时候,只能是静态时才行。否则不行, 例如一个水平的小车,水平加速。如图,小人(芯片)收到的是向后和向下的力。 这时用三角函数求小车姿态,得到的就是小车停在一个斜坡上受到的力。

      总结就是:加速度具有静态稳定性,不具有动态稳定性

  2. 陀螺仪:

    1. 测量设备在X、Y、Z轴上的角速度。陀螺仪可以用于检测设备的旋转运动,比如快速转动或者缓慢旋转等。
    2. 想要通过角速度的到角度,只需要对角速度进行积分即可。但是陀螺仪测角速度也有局限性。 当物体静止时,角速度的值会因为噪声无法完全归零。经过积分的不断累积,角速度的值就会产生缓慢的漂移。

    总结就是:陀螺仪具有动态稳定性,但不具备静态稳定性。

所以,在使用时,进行互补滤波,就能得到静态和动态都稳定的都稳定的姿态角了

3.MPU6050参数

ADC和数据存储

  • 16位ADC:MPU6050集成了16位的ADC,用于将模拟信号转换为数字信号,量化范围为-32768到32767。
  • 量化过程:ADC将模拟信号转换为数字信号,并以两个字节进行存储。

**加速度计满量程选择:**±2、±4、±8、±16(g)

如果测量的物体的运动非常剧烈,就可以选择量程大一些。分辨率会更加粗糙

如果测量的物体的运动比较平缓,就可以选择量程小一些。分辨率会更加细腻

仪满量程选择: ±250、±500、±1000、±2000(°/sec)

陀螺仪的选择也是同上,满量程越大,测量范围越广、满量程越小,测量分辨率就越高

可配置的数字低通滤波器

  • 允许用户根据应用需求通报配置寄存器,来配置滤波器,滤除噪声和干扰。

可配置的时钟源、采样分频

  • 支持多种时钟源,包括内部振荡器、外部参考时钟。经过采样分频之后为AD和其他内部电路提供时钟。控制了分频系数,就控制了AD转换的快慢了。

I2C从机地址:

当AD0引脚接低电平(AD0=0):地址为1101 000 (0x68)。 当AD0引脚接高电平(AD0=1): 地址为1101 001 (0x69)。 具体地址配置决定了在I2C总线上的唯一性,避免地址冲突。

4.MPU6050量程选择

满量程选择

  • **剧烈运动:**选择较大的满量程,确保测量范围足够大。
  • **平缓运动:**选择较小的满量程,提升测量分辨率。

加速度计满量程示例:

  • ±16g:
    • 读取的ADC值为最大值32768时,对应实际加速度为16g。
    • ADC值为32768的一半(16384)时,对应加速度为8g。
  • ±2g:
    • 读取的ADC值为最大值32768时,对应实际加速度为2g。
    • ADC值为32768的一半(16384)时,对应加速度为1g。

测量分辨率:

  • 满量程越小,测量分辨率越高,测量越精细。
  • 满量程越大,测量范围越广。
  • ADC值与加速度值呈线性关系,可以通过乘一个系数从ADC值计算出实际加速度。

5.MPU6050从机地址配置

二进制地址转换为十六进制:

  • 以从机地址110 1000为例。
  • 把7位二进制数1101000转换为十六进制,即分割低4位和高3位:0110 1000,转换后为0x68。

I2C时序中的地址格式:

  • 在I2C通信时,需要发送7位从机地址110 1000加上1位读写位。共八位
  • 使用0x68作为从机地址,需要将0x68左移1位,再加上读写位(0或1)。
  • 转换步骤:
    • 将0x68左移1位:1101 0000(即0xD0)。
    • 再与读写位(0或1)进行或操作:(0x68 << 1) | 0 或 (0x68 << 1) | 1
    • 再转换成二进制其实就是0xD0或0xD1
    • 这样其实就是融入了读写位的从机地址

实际应用:

写操作时 直接发送一个字节。从机地址为0xD0。 读操作时 直接发送一个字节。从机地址为0xD1。

6.硬件电路

引脚功能
VCC、GND电源3.3v
SCL、SDAI2C通信引脚
XCL、XDA主机I2C通信引脚
AD0从机地址最低位
INT中断信号输出

7.MPU6050框图

左边一列是自测响应。用来验证芯片是否正常

左下角的Charge Pump 是电荷泵,也叫做充电泵。是外接电容的。是用来升压的

有各种传感器。甚至还有个温度传感器。

这些东西测出来的值经过ADC进行模数转换。

这些数据统一放到数据寄存器中。

我们读取就OK了。其他的都是全自动执行的

右边部分

  • Interrupt Status Register 中断控制寄存器,可以控制内部的事件到中断引脚的输出
  • FIFO 先入先出寄存器,可以对数据流进行缓存
  • Config Registors 配置寄存器,对内部的各个电路进行配置
  • Sensor Registers 传感器寄存器(数据寄存器),存储了各个传感器的数据
  • Factory Calibratilor 工厂校准,内部的传感器都进行了校准。
  • Digital Motion Processor(DMP) 数字运动处理器,芯片内部自带的姿态解算的硬件算法。需要配合官方的DMP库,进行姿态解算。
  • FSYNC 帧同步,
  • 剩下的这些这些(上边仨)就是通信接口的部分了。 上边是MPU6050与STM32进行通信的接口。 下边是MPU6050作为主机的扩展接口
    • 其中SerialInterface Bypass Mux 是用来进行旁路控制的, 他可以控制 下边的接口是直接接到STM32的总线上(黄线所连接),作为STM32的直接大哥 或者是MPU6050自己去控制这些外设(蓝色线所连接)。使STM32是MPU6050的大哥。MPU6050是这些外设的大哥

8.MPU60X0寄存器映射(常用)

寄存器映射框图各部分作用介绍

每个寄存器都是八位的,他们有唯一的地址。

常用寄存器简介

按照标黄部分从上往下介绍

第一部分

SMPLRT_DIV 采样频率分频器

  • 陀螺仪刷新率(采样频率) = 时钟频率(内部晶振、陀螺仪晶振输出和或者外部时钟引脚方波) / (1+分频值)
  • 使用了滤波器,陀螺仪时钟就是1KHZ。 不使用滤波器,陀螺仪时钟是8KHZ

CONFIG 配置寄存器

  • (外部同步这里不用)

  • 低通滤波器可以让输出数据更加平滑,参数越大,输出都懂就越小 配置的参数可以是如下的0-7 (最后一位是保留位没用到)

GYRO_CONFIG 陀螺仪配置寄存器

  • 高三位是XYZ轴的自测使能位 自测响应 = 自测使能时数据 - 自测失能时数据 得到的值在范围内就算通过自测。

  • 中间两位是满量程选择位 量程选择,则需要根据实际情况来。量程越大,量程越广。量程越小,分辨率越高

ACCEL_CONFIG 加速度计配置寄存器

  • 同上,高三位为自测使能位

  • 中间两位是满量程选择位

  • 不过后面还多了三位,这三位是配置 高通滤波器的 在这里暂时用不到。(是内置小功能需要用到的。比如自由落体检测,运动检测、零运动检测等)对我们的输入输出没有影响。

第二部分

这一大块,是数据寄存器

其中_H 表示高八位 _L表示第八位

加速度计XYZ轴

ACCEL_XOUT_H

ACCEL_XOUT_L

ACCEL_YOUT_H

ACCEL_YOUT_L

ACCEL_ZOUT_H

ACCEL_ZOUT_L

  • 这是加速度计的数据寄存器 在读取数据时直接读取数据寄存器就OK了。 是一个16位的有符号数 以二进制补码的方式存储


温度传感器

TEMP_OUT_H

TEMP_OUT_L

  • 同上


陀螺仪XYZ轴

GYRO_XOUT_H

GYRO_XOUT_L

GYRO_YOUT_H

GYRO_YOUT_L

GYRO_ZOUT_H

GYRO_ZOUT_L

  • 同上



第三部分

PWR_MGMT_1 电源管理寄存器 1

  • 第一位,设备复位 :写1 时 所有设备恢复默认值
  • 第二位,睡眠模式:写1 时 芯片睡眠,进入低功耗。芯片不工作。
  • 第三位,循环模式:写1时 设备进入低功耗,过一段时间启动一次。 并且唤醒的频率由电源管理寄存器2决定。
  • 第四位,温度传感器失能:写1时 禁用内部温度传感器
  • 最后三位,用来选择系统时钟来源


PWR_MGMT_2 电源管理寄存器 2

  • 这两位决定了唤醒的频率
  • 后面6位可以分别控制6个轴进入待机模式 可以省电用。如果只需要部分数据的话


WHO_AM_I 我是谁**(器件ID号)**

  • 这个寄存器是只读的。
  • 中间六位固定为 110100 所以读出这个寄存器的值就为0x68
  • 实际上ID号就是这个芯片的I2C地址。
  • 并且通过外部引脚AD0 修改设备号最低位的时候 是不反映在寄存器中的。寄存器并不会根据AD0的引脚的电平来变化。


第四部分

芯片上电之后,默认的是所有寄存器为0x00。

除了两个:

  • Register 107: 0x40 也就是电源管理寄存器1。0x40即0100 0000 对应寄存器也就是睡眠模式为1 也就是上电默认随眠
  • Register 117: 0x68 这个就是ID号的寄存器了。

9.软件I2C和硬件I2C的区别

  • 硬件电路件I2C的波形更加规整,时钟周期和占空比非常一致。

    每个时钟周期后都有严格的延时,保证每个周期的时间相同。

    硬件电路需要专门的某些GPIO口才能实现

  • 软件I2C的波形较为不规整,每个时钟周期和空闲时间都不一致。

    软件I2C时的引脚操作会有一定的延时,因此各个时钟周期的间隔和占空比都不均匀。

    但是软件实现I2C更加的方便,随便那个GPIO口都可以实现。

这里我们使用的是软件I2C

10.软件I2C读写MPU6050编写步骤

10.1程序整体构架

首先建立I2C通信层的.c和.h模块,再建立MPU6050.c, 最后是main.c

  • Main.c
    1. 调用MPU6050初始化函数。
    2. 循环读取数据并进行显示。
  • MPU6050.c
    1. 基于I2C通信协议,实现指定地址读,指定地址写
    2. 实现写寄存器来配置芯片,读寄存器得到传感器数据。
  • I2C.c
    1. 初始化GPIO。
    2. 编写基本的I2C操作函数,包括起始条件、终止条件、发送/接收一个字节、发送/接收应答等。

10.2需要注意的点

  1. 开漏输出模式

    • 开漏输出模式下GPIO有强下拉,弱上拉的特性,所以需要借助外部上拉电阻来输出高电平。
    • 当Out输出为1时,晶体管截止。此时可以直接读取输入数据寄存器,就可以得到开漏模式下输出的值。
  2. 写起始条件时

    在SCL高电平期间,SDA由高电平变为低电平,产生起始条件(Start Condition)。这表示一次I2C通信的开始。 (先拉低SDA再拉低SDA)

    • 起始条件不仅要能在SDA和SCL都为高电平时按照先SDA再SCL顺序拉低。他们谁先拉高都是无所谓的。

    • 也要兼容在重复起始条件。在重复起始条件时。 刚刚完成了应答的操作, SCL将要到拉低为低电平,而SDA的电平不敢确定。所以为了保险起见,需要趁SCL为低电平时 抓紧上拉SDA,再 上拉SCL才能保证不触发终止。举个栗子如下:

    所以综上来说,在写起始条件时。顺序应该为SDA 1、SCL 1、SDA 0、 SCL 0

  3. 写终止条件时

    • 同上, 在上次应答完成之后。**SCL下一个周期一定为下降沿。**而SDA的状态不确定。 所以为了确保在终止条件时,SDA一定能在SCL为高时产生上升沿, 需要先趁着SCL为低电平把SDA拉低,再把SCL拉高。 再拉高SDA。 这样就能产生在SCl为高电平时的上升沿了
  4. 写发送字节时

    • 发送字节是高位先行
    • 起始条件之后,SCL和SDA一定为低电平
    • 除了起始条件和终止条件,其他时候都是SDA再SCL低电平时变化
    • 取出一个字节(8bit)的任一位。可以使用 & 操作,比如 1011 0000 & 0x40 = 1011 0000 & 0100 0000 = 0000 0000(0) 1111 0000 & 0x40 = 1011 0000 & 0100 0000 = 0100 0000(非0即1) 所以发送的电平一定是0 或 1
    • 循环8次 发送一个字节
  5. 写接收字节时

    • 首先主机要释放SDA的控制权。也就是输出1,使mos管截止
    • 从机在SCL低电平期间 控制SDA 拉高或拉低
    • 主机控制SCL回到高电平。主机读取SDA
    • 拉低SCL,从机控制SDA。
    • 循环八次..接收一个字节

10.3软件I2C编写步骤

  1. 将打算用作SCL和SDA的GPIO引脚都初始化为开漏输出模式

  2. 将SCL和SDA都置高电平

  3. 写 写SCL函数(可选择加延时);

  4. 写 写SDA函数(可选择加延时);

  5. 写 读SDA函数(可选择加延时);

  6. 写起始条件函数: 拉高SDA、拉高SCL、拉低SDA、拉低SCL

  7. 写终止条件函数: 拉低SDA、拉高SCL、拉高SDA、

  8. 发送的一个字节函数 发送一个字节的最高位; 拉高SCL(从机读取) 拉低SCL(准备下次写入) 发送一个字节的次高位; 拉高SCL(从机读取) 拉低SCL(准备下次写入)

    …循环八次。可使用for循环

  9. 接收一个字节的函数 主机释放SDA(SDA输出1,SDA对 地 相当于断开)(这时候SCL为低,从机已经在SDA存放了数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) 主机拉低SCL(从机SDA存放数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) …..循环 返回循环后接收到的一个字节

  10. 发送应答的函数 (发送一个字节的简化版) 发送应答 SCL高电平(从机读取应答) SCL低电平(进入下一个时序单元)

  11. 接收应答的函数 主机释放SDA(SDA输出1,SDA对 地 相当于断开)(这时候SCL为低,从机已经在SDA存放了数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) 主机拉低SCL(从机SDA存放数据) 返回读出的值

10.4软件I2C读写MPU6050编写步骤

有了软件I2C的初始化,写MPU就很方便了。

只需要调用已经写好的起始、结束、发送字节、接受字节的时序就可以。

  1. 初始化MPU6050 包括调用I2C初始化函数。解除睡眠模式、设置采样频率、配置陀螺仪和加速度计等寄存器

  2. 编写“指定地址一个字节“的函数

    • 起始
    • 发送字节(设备地址+0)(我要写入)
    • 接收从机应答
    • 发送字节(指定要写入的地址)
    • 接收从机应答
    • 发送字节(发送要写入的数据)
    • 接收从机应答
    • 停止

    其中接受从机应答可以去判断,并输出对应的报错信息。这里没有添加。

  3. 编写“指定地址一个字节“的函数

    • 起始
    • 发送字节(设备地址+0)(我要写入)
    • 接收从机应答
    • 发送字节(指定要写入的地址)
    • 接收应答位
    • 重复起始
    • 发送字节(设备地址+1)我要读取
    • 接收应答位
    • 接收并保存从机发送的字节
    • 主机不应答(如果应答,从机会继续发送)
    • 停止
    • 返回读取到的值
  4. 编写“读取六轴传感器各个寄存器的值”

    可以直接传地址,也可以用结构体或者数组。方法很多。

11.代码编写:

程序文件简要说明:

  • MyI2C.c:初始化I2C所需要的引脚、编写时序的基本组成(起始、终止、发送/接收一个字节、应答、发送应答)
  • MyI2C.h:函数声明
  • MPU6050.c:初始化MPU6050的寄存器。在MyI2C的基础上,编写对指定地址发送指定值、读取指定地址的函数
  • MPU6050.h:函数声明、数据结构体声明
  • main.c:测试I2C通信结果。

MyI2C.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#define SCL_Clock   RCC_APB2Periph_GPIOA
#define SCL_PORT    GPIOA
#define SCL_Pin     GPIO_Pin_11

#define SDA_Clock   RCC_APB2Periph_GPIOA
#define SDA_PORT    GPIOA
#define SDA_Pin     GPIO_Pin_12

/**
  * 函    数:初始化I2C
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Init(void)
{
    Delay_Init();//初始化延时函数
    
    /*开启SCL和SDA时钟*/
    RCC_APB2PeriphClockCmd(SCL_Clock,ENABLE);
    RCC_APB2PeriphClockCmd(SDA_Clock,ENABLE);
    
    /*初始化SCL和SDA引脚*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_InitStructure.GPIO_Pin = SCL_Pin;
    GPIO_Init(SCL_PORT,&GPIO_InitStructure);   //SCL配置为开漏输出
    
    GPIO_InitStructure.GPIO_Pin = SDA_Pin;
    GPIO_Init(SDA_PORT,&GPIO_InitStructure);   //SDA配置为开漏输出
    
    GPIO_SetBits(SCL_PORT,SCL_Pin);//拉高SCL
    GPIO_SetBits(SDA_PORT,SDA_Pin);//拉高SDA
}

/**
  * 函    数:写一位SCL
  * 参    数:0或1
  * 返 回 值:无
  */
void MyI2C_W_SCL (uint8_t BitValue)
{
    GPIO_WriteBit(SCL_PORT,SCL_Pin,(BitAction)BitValue);//BitAction是一个typedef的枚举类型值。
    Delay_us(10);   //延时,给从机读取时间
}

/**
  * 函    数:写一位SDA
  * 参    数:0或1
  * 返 回 值:无
  */
void MyI2C_W_SDA (uint8_t BitValue)
{
    GPIO_WriteBit(SDA_PORT,SDA_Pin,(BitAction)BitValue);
    Delay_us(10);   //延时,给从机读取时间
}

/**
  * 函    数:读一位SDA
  * 参    数:无
  * 返 回 值:BitValue
  */
uint8_t MyI2C_R_SDA (void)
{
    uint8_t BitValue = 0x00;   
    BitValue = GPIO_ReadInputDataBit(SDA_PORT,SDA_Pin);
    Delay_us(10);
    return BitValue;
}

/**
  * 函    数:起始时序单元
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start (void)
{
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);//按照先SDA拉高再SCL拉高。防止在重复起始时触发终止条件
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);//先SDA拉低,再SCL拉低。触发起始条件
}

/**
  * 函    数:结束时序单元
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop (void)
{
    MyI2C_W_SDA(0);//在SCL为低电平时,先把SDA拉高,为后续做准备
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);//先SCL拉高,再SDA拉高。触发结束条件
}

/**
  * 函    数:发送一个字节
  * 参    数:Byte
  * 返 回 值:无
  */
void MyI2C_SendByte (uint8_t Byte)
{
    uint8_t i = 0;
    for(i = 0; i < 8; i++)
    {
        MyI2C_W_SDA( Byte & (0x80 >> i) );//按照最高位依次往右取出bit写入
        MyI2C_W_SCL(1);//从机读取
        MyI2C_W_SCL(0);//准备下次写入
    }
}

/**
  * 函    数:接收一个字节
  * 参    数:无
  * 返 回 值:Byte
  */
uint8_t MyI2C_ReceiveByte (void)
{
    MyI2C_W_SDA(1);//释放SDA控制
    uint8_t Byte = 0x00;//存放得到的bit
    
    uint8_t i = 0;
    for(i = 0; i < 8; i++)
    {
        MyI2C_W_SCL(1);//主机准备读取
        if (MyI2C_R_SDA() == 1)//如果读到的这一位为1
        {
            Byte |= (0x80 >> i);//就把这一位置1,否则默认为0
        }
        MyI2C_W_SCL(0);//从机放下一位bit的数据
    }
    return Byte;
}

/**
  * 函    数:发送应答
  * 参    数:AckBit
  * 返 回 值:无
  */
void MyI2C_SenndAck (uint8_t AckBit)
{
    
        MyI2C_W_SDA(AckBit);//应答
        MyI2C_W_SCL(1);//从机读取
        MyI2C_W_SCL(0);//准备下次动作
}

/**
  * 函    数:接收应答
  * 参    数:无
  * 返 回 值:reply
  */
uint8_t MyI2C_ReceiveAck (void)
{
    MyI2C_W_SDA(1);//释放SDA控制
    uint8_t AckBit = 0x00;//存放得到的bit

    MyI2C_W_SCL(1);//从机填写
    AckBit = MyI2C_R_SDA();//主机读取
    MyI2C_W_SCL(0);//准备下次动作
    return AckBit;
}

MyI2C.h

#ifndef __MYI2C_H
#define __MYI2C_H

//函    数:初始化I2C
void MyI2C_Init(void);

//函    数:起始时序单元
void MyI2C_Start (void);
    
//函    数:结束时序单元
void MyI2C_Stop (void);
    
//函    数:发送一个字节
void MyI2C_SendByte (uint8_t Byte);
    
//函    数:接收一个字节
uint8_t MyI2C_ReceiveByte (void);
    
//函    数:发送应答
void MyI2C_SenndAck (uint8_t AckBit);
    
//函    数:接收应答
uint8_t MyI2C_ReceiveAck (void);

#endif

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050.h"
#include "MyI2C.h"

#include "MPU6050_Reg.h"

#define MPU6050ADDRESS      0xD0

/**
  * 函    数:指定地址写一个字节
  * 参    数:RegAddress   指定的寄存器地址
              Data         指定写入的数据
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
    MyI2C_Start();//起始
    MyI2C_SendByte(MPU6050ADDRESS);//指定设备地址加写操作
    MyI2C_ReceiveAck();//接收应答位
    MyI2C_SendByte(RegAddress);//指定寄存器地址
    MyI2C_ReceiveAck();//接收应答位
    MyI2C_SendByte(Data);//指定写入指定的数据
    MyI2C_ReceiveAck();//接收应答位
    MyI2C_Stop();//停止
}

/**
  * 函    数:指定地址读一个字节
  * 参    数:RegAddress   指定要读的寄存器地址
  * 返 回 值:无
  */
uint8_t MPU6050_ReadeReg(uint8_t RegAddress)
{
    uint8_t Data = 0x00;
    
    MyI2C_Start();//起始
    MyI2C_SendByte(MPU6050ADDRESS);//指定设备地址加写操作
    MyI2C_ReceiveAck();//接收应答位
    MyI2C_SendByte(RegAddress);//指定寄存器地址
    MyI2C_ReceiveAck();//接收应答位
    
    MyI2C_Start();//重复起始
    MyI2C_SendByte(MPU6050ADDRESS | 0x01);//指定设备地址 加 读操作
    MyI2C_ReceiveAck();//接收应答位
    Data = MyI2C_ReceiveByte();//接收从机发送的字节
    MyI2C_SenndAck(1);//主机发送应答位(应答会继续读)
    MyI2C_Stop();//停止
    return Data;//返回读取到的值
}

/**
  * 函    数:初始化MPU6050
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
    /*初始化I2C*/
    MyI2C_Init();
    /*初始化MPU6050*/
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);  //电源管理1:不复位、解除睡眠、不循环、温度传感器不失能、选择X轴陀螺仪时钟
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);  //电源管理2:不需要循环模式唤醒频率、每个轴都不需要待机
    
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);  //采样率分频:数据输出的快慢,越小输出越快.这里给10分频
    MPU6050_WriteReg(MPU6050_CONFIG,0x06);  //配置寄存器:外部同步不需要、数字低通滤波器设置为110
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);   //陀螺仪配置寄存器:不自测、满量程选择:11最大量程
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);    //加速度计配置寄存器:不自测、满量程选择:11最大量程、不用高通滤波器
}

/**
  * 函    数:得到六轴传感器中的数据
  * 参    数:Str   MPU6050_Data的地址
  * 返 回 值:无
  */
SensorData MPU6050_Data;
void MPU6050_GetData(SensorData *Str)//存放这种结构体类型的地址
{
    Str->AccX = ( MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_H) <<8|
                  MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_L));
    Str->AccY = ( MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_H) <<8|
                  MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_L));
    Str->AccZ = ( MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_H) <<8|
                  MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_L));
    Str->Temp = ( MPU6050_ReadeReg(MPU6050_TEMP_OUT_H)   <<8|
                  MPU6050_ReadeReg(MPU6050_TEMP_OUT_L));
    Str->GyroX = ( MPU6050_ReadeReg(MPU6050_GYRO_XOUT_H) <<8 |
                  MPU6050_ReadeReg(MPU6050_GYRO_XOUT_L));
    Str->GyroY = ( MPU6050_ReadeReg(MPU6050_GYRO_YOUT_H) <<8 |
                  MPU6050_ReadeReg(MPU6050_GYRO_YOUT_L));
    Str->GyroZ = ( MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_H) <<8 |
                   MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_L));
}
//MPU6050_ACCEL_XOUT_H    0x3B
//MPU6050_ACCEL_XOUT_L    0x3C
//MPU6050_ACCEL_YOUT_H    0x3D
//MPU6050_ACCEL_YOUT_L    0x3E
//MPU6050_ACCEL_ZOUT_H    0x3F
//MPU6050_ACCEL_ZOUT_L    0x40
//MPU6050_TEMP_OUT_H      0x41
//MPU6050_TEMP_OUT_L      0x42
//MPU6050_GYRO_XOUT_H     0x43
//MPU6050_GYRO_XOUT_L     0x44
//MPU6050_GYRO_YOUT_H     0x45
//MPU6050_GYRO_YOUT_L     0x46
//MPU6050_GYRO_ZOUT_H     0x47
//MPU6050_GYRO_ZOUT_L     0x48

MPU6050.h

#ifndef __MPU6050_H
#define __MPU6050_H

/**
  * 函    数:初始化MPU6050
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void);

/**
  * 函    数:指定地址写一个字节
  * 参    数:RegAddress   指定的寄存器地址
              Data         指定写入的数据
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
/**
  * 函    数:指定地址读一个字节
  * 参    数:RegAddress   指定要读的寄存器地址
  * 返 回 值:无
  */
uint8_t MPU6050_ReadeReg(uint8_t RegAddress);

//传感器数据
typedef struct Data 
{
    int16_t AccX;
    int16_t AccY;
    int16_t AccZ;
    int16_t Temp;
    int16_t GyroX;
    int16_t GyroY;
    int16_t GyroZ;
}SensorData;
extern SensorData MPU6050_Data;

/**
  * 函    数:得到六轴传感器中的数据
  * 参    数:Str   MPU6050_Data的地址
  * 返 回 值:无
  */
void MPU6050_GetData(SensorData *Str);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Delay.h"
#include "key.h"
#include "MPU6050.h"
/*
    指定6050设备,读取117位寄存器(设备号)
    在OLED显示出来
*/

int main()
{
    OLED_Init();//初始化OLED;
    MPU6050_Init();//初始化MPU6050
    
    
    while(1)    
    {
         MPU6050_GetData(&MPU6050_Data);
        //显示加速度
        OLED_ShowSignedNum(1,1,(int16_t)MPU6050_Data.AccX,5);
        OLED_ShowSignedNum(2,1,(int16_t)MPU6050_Data.AccY,5);
        OLED_ShowSignedNum(3,1,(int16_t)MPU6050_Data.AccZ,5);
        //显示陀螺仪
        OLED_ShowSignedNum(1,8,MPU6050_Data.GyroX,5);
        OLED_ShowSignedNum(2,8,MPU6050_Data.GyroY,5);
        OLED_ShowSignedNum(3,8,MPU6050_Data.GyroZ,5);
        //显示温度
        OLED_ShowSignedNum(4,4,MPU6050_Data.Temp,5);
        
        
    }
    
}

/*测试读写寄存器*/
//#include "stm32f10x.h"                  // Device header
//#include "oled.h"
//#include "Delay.h"
//#include "key.h"
//#include "MPU6050.h"
///*
//    指定6050设备,读取117位寄存器(设备号)
//    写入寄存器,并读取出来。检查是否正确写入
//    在OLED显示出来
//*/

//int main()
//{
//    OLED_Init();//初始化OLED;
//    MPU6050_Init();//初始化MPU6050
//    /*读取0x75寄存器设备号并显示*/
//    uint8_t ID = MPU6050_ReadeReg(0x75);
//    OLED_ShowHexNum(1, 1, ID, 2);
//    /*解除芯片睡眠模式,并测试写入*/
//    MPU6050_WriteReg(0x6B,0x00);//解除
//    MPU6050_WriteReg(0x19,0xAA);//写入
//    uint8_t Data = MPU6050_ReadeReg(0x19);//读出
//    OLED_ShowHexNum(2, 1, Data, 2);//显示
//    MPU6050_WriteReg(0x19,0x66);//写入
//    Data = MPU6050_ReadeReg(0x19);//读出
//    OLED_ShowHexNum(3, 1, Data, 2);//显示
//}

/*测试I2C时序*/
//#include "stm32f10x.h"                  // Device header
//#include "oled.h"
//#include "Delay.h"
//#include "key.h"
//#include "MyI2C.h"
///*
//    查询所有7位I2C地址
//    找到总线上挂载的所有设备
//    在OLED显示出来
//*/

//int main()
//{
//    OLED_Init();//初始化OLED;
//    MyI2C_Init();//初始化I2C
//    
//    /*
//        从机地址为 1101 000 
//        左移后补0 = 1101 0001 = 0xD0(表示指定这个设备写)
//    */
//    uint8_t Row = 1;
//    uint8_t i = 0x00;
//    for (i = 0x00; i < 0x7F; i++)
//    {
//        MyI2C_Start();//起始时序
//        MyI2C_SendByte((i << 1) | 0);//发送字节。写模式
//        uint8_t Ack = MyI2C_ReceiveAck();//接收应答
//        MyI2C_Stop();//结束时序
//        if(Ack == 0)
//        {
//            OLED_ShowHexNum(Row,1,i,2);
//            OLED_ShowBinNum(Row,4,i,7);
//            Row++;
//        }
//    }
//}

12.结果

13.数据验证

  • 陀螺仪旋转检测
    • 陀螺仪绕Z轴旋转,陀螺仪Z轴会输出对应的角速度。
    • 图示中,三维空间的坐标轴X、Y、Z对应陀螺仪的三个方向。
    • 通过陀螺仪的测量,可以获得绕某一轴的旋转角速度信息,帮助理解物体的旋转状态。
  • 加速度计检测
    • 在正方体中放置一个小球,小球压在哪个面上就产生对应轴的输出。
    • 当前芯片水平放置,对应正方体的X轴、Y轴数据基本为0。
    • 小球在底面上,产生1个g的重力加速度,这里显示的数据是1943。
    • 1943代表Z轴方向的支持力,所以Z轴加速度为正。
  • 数据计算
    • 根据测量值1943和满量程32768(16位ADC),计算得出加速度的实际值。
    • 根据测量值1943和满量程32768(16位ADC),计算得出加速度的实际值。
    • 公式: 1943/32768 = Z/16g
    • 所以Z轴的加速度为0.95g。
  • 测量值比例公式
    • 读到的ADC值与满量程值之间的比例关系。
    • 公式: 读到的数据/32768 = X/满量程 (其中,满量程在16位系统中为-32768到32767)

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: STM32F407是一款强大的微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。首先,我们需要设置相关的GPIO引脚来模拟I2C的时钟(SCL)和数据线(SDA)。然后,我们可以通过相应的软件算法来模拟I2C的时序,并使用GPIO引脚来模拟信号的传输。 在进行软件模拟I2C读写MPU6050之前,我们需要先了解MPU6050的寄存器结构和通讯协议。MPU6050内部有多个寄存器,它们存储着各种传感器的原始数据和配置信息。通讯协议使用I2C,我们需要根据MPU6050的地址和寄存器地址来发送读写命令。 首先,发送开始信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平,SDA从高电平转为低电平时即可认为发送了开始信号。然后,发送MPU6050的设备地址,该地址为7位,由3位固定的值和一个可选择的读写位组成。接下来,发送要访问的寄存器地址,该地址也为7位。再发送读写命令位,0表示写入,1表示读取。 接下来,我们可以进行读写操作。对于写操作,我们发送要写入的数据到寄存器中。对于读操作,我们需要发送重复启动信号,然后再发送MPU6050的设备地址和寄存器地址,并设置读取命令位。此时,我们将把数据读取到SDA引脚上。 最后,发送停止信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平时将SDA从低电平转为高电平即可认为发送了停止信号。 以上是对于STM32F407软件模拟I2C读写MPU6050传感器的简要步骤介绍。具体实现过程中,需要根据实际需求进行相应的编码和调试。 ### 回答2: stm32f407是一款高性能的ARM Cortex-M4微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。 首先,我们需要了解MPU6050传感器的通信协议和寄存器地址。MPU6050使用I2C总线进行通信,需要通过开始信号、设备地址、寄存器地址、数据等步骤完成数据的读写。 在stm32f407上,我们可以使用GPIO引脚来模拟I2C总线的SCL(时钟线)和SDA(数据线)。我们需要配置SCL和SDA引脚为GPIO模式,并在代码中编写相应的功能实现软件模拟I2C总线的读写操作。 对于I2C总线的读操作,首先我们需要发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,再发送一个重新开始信号,然后发送设备地址加上读命令,最后读取接收到的数据。 对于I2C总线的写操作,首先也是发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,最后发送要写入的数据。 在代码中,我们可以按上述过程编写相应的函数来进行软件模拟的I2C读写操作,并将读取到的数据存储到相应的变量中。 需要注意的是,软件模拟I2C通信可能会导致一定的时序不准确。为了确保通信的稳定性和可靠性,我们可以使用延时函数来控制时序,并根据MPU6050传感器的参数手册调整延时时间。 总之,通过在stm32f407上进行软件模拟的I2C读写操作,我们可以实现对MPU6050传感器的数据读取和写入,从而实现与该传感器的通信和控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值